From e7ec34465141862bce9426c79513fadd236ba686 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 4 Jan 2021 22:33:51 +0100 Subject: [PATCH 001/145] Initial Python POC --- build.fsx | 2 +- src/Fable.Transforms/BabelPrinter.fs | 2 +- src/Fable.Transforms/FSharp2Fable.Util.fs | 4 +- src/Fable.Transforms/Fable2Babel.fs | 25 ++--- src/Fable.Transforms/Global/Babel.fs | 106 ++++++++++++++-------- src/Fable.Transforms/Global/Prelude.fs | 2 +- src/Fable.Transforms/Replacements.fs | 14 +-- src/Fable.Transforms/Transforms.Util.fs | 4 +- 8 files changed, 96 insertions(+), 63 deletions(-) diff --git a/build.fsx b/build.fsx index 234c88edcc..c51236d227 100644 --- a/build.fsx +++ b/build.fsx @@ -520,7 +520,7 @@ match argsLower with | "test-integration"::_ -> testIntegration() | "quicktest"::_ -> buildLibraryIfNotExists() - run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --forcePkgs --runScript" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --forcePkgs --extension .py" | "run"::_ -> buildLibraryIfNotExists() diff --git a/src/Fable.Transforms/BabelPrinter.fs b/src/Fable.Transforms/BabelPrinter.fs index 8fcf6d8d82..d72dba88b3 100644 --- a/src/Fable.Transforms/BabelPrinter.fs +++ b/src/Fable.Transforms/BabelPrinter.fs @@ -91,7 +91,7 @@ let run writer map (program: Program): Async = decl.Print(printer) if printer.Column > 0 then - printer.Print(";") + //printer.Print(";") printer.PrintNewLine() if extraLine then printer.PrintNewLine() diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index 94b15f8f7d..85feca523d 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -311,8 +311,8 @@ module Helpers = | _ -> fullName let cleanNameAsJsIdentifier (name: string) = - if name = ".ctor" then "$ctor" - else name.Replace('.','_').Replace('`','$') + if name = ".ctor" then "_ctor" + else name.Replace('.','_').Replace('`','_') let getEntityDeclarationName (com: Compiler) (ent: Fable.EntityRef) = let entityName = getEntityMangledName com true ent |> cleanNameAsJsIdentifier diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 0ae03bde8d..f74764b93d 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -745,7 +745,8 @@ module Util = ExpressionStatement(callSuper args) :> Statement let makeClassConstructor args body = - ClassMethod(ClassImplicitConstructor, Identifier "constructor", args, body) :> ClassMember + //let args = args |> Seq.toList |> (fun args -> (Identifier "self" :> Pattern)::args) |> Array.ofList + ClassMethod(ClassImplicitConstructor, Identifier "__init__", args, body) :> ClassMember let callFunction r funcExpr (args: Expression list) = CallExpression(funcExpr, List.toArray args, ?loc=r) :> Expression @@ -758,8 +759,8 @@ module Util = EmitExpression(txt, List.toArray args, ?loc=range) :> Expression let undefined range = -// Undefined(?loc=range) :> Expression - UnaryExpression(UnaryVoid, NumericLiteral(0.), ?loc=range) :> Expression + Undefined(?loc=range) :> Expression + //UnaryExpression(UnaryVoid, NumericLiteral(1.), ?loc=range) :> Expression let getGenericTypeParams (types: Fable.Type list) = let rec getGenParams = function @@ -789,7 +790,7 @@ module Util = let body = // TODO: If ident is not captured maybe we can just replace it with "this" if FableTransforms.isIdentUsed thisArg.Name body then - let thisKeyword = Fable.IdentExpr { thisArg with Name = "this" } + let thisKeyword = Fable.IdentExpr { thisArg with Name = "self" } Fable.Let(thisArg, thisKeyword, body) else body None, genTypeParams, args, body @@ -1733,12 +1734,9 @@ module Util = BlockStatement(Array.append [|varDeclStatement|] body.Body) args |> List.mapToArray (fun a -> a :> Pattern), body - let declareEntryPoint _com _ctx (funcExpr: Expression) = + let declareEntryPoint _com _ctx (name: string) (funcExpr: Expression) = let argv = emitExpression None "typeof process === 'object' ? process.argv.slice(2) : []" [] - let main = CallExpression (funcExpr, [|argv|]) :> Expression - // Don't exit the process after leaving main, as there may be a server running - // ExpressionStatement(emitExpression funcExpr.loc "process.exit($0)" [main], ?loc=funcExpr.loc) - PrivateModuleDeclaration(ExpressionStatement(main)) :> ModuleDeclaration + PrivateMainModuleDeclaration(ExpressionStatement(funcExpr)) :> ModuleDeclaration let declareModuleMember isPublic membName isMutable (expr: Expression) = let membName = Identifier membName @@ -1853,11 +1851,14 @@ module Util = let transformModuleFunction (com: IBabelCompiler) ctx (info: Fable.MemberInfo) (membName: string) args body = let args, body, returnType, typeParamDecl = getMemberArgsAndBody com ctx (NonAttached membName) info.HasSpread args body - let expr = FunctionExpression(args, body, ?returnType=returnType, ?typeParameters=typeParamDecl) :> Expression + + let id = (Identifier membName) |> Some + let expr = FunctionExpression(args, body, ?id=id, ?returnType=returnType, ?typeParameters=typeParamDecl) :> Expression info.Attributes |> Seq.exists (fun att -> att.Entity.FullName = Atts.entryPoint) |> function - | true -> declareEntryPoint com ctx expr + | true -> + declareEntryPoint com ctx membName expr | false -> declareModuleMember info.IsPublic membName false expr let transformAction (com: IBabelCompiler) ctx expr = @@ -1920,7 +1921,7 @@ module Util = |> ReturnStatement :> Statement |> Array.singleton |> BlockStatement - ClassMethod(ClassFunction, Identifier "cases", [||], body) :> ClassMember + ClassMethod(ClassFunction, Identifier "cases", [||], body, ``static``=true) :> ClassMember let baseExpr = libValue com ctx "Types" "Union" |> Some let classMembers = Array.append [|cases|] classMembers diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index 586ef57382..2ed305b2fb 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -21,20 +21,20 @@ module PrinterExtensions = member printer.PrintBlock(nodes: 'a array, printNode: Printer -> 'a -> unit, printSeparator: Printer -> unit, ?skipNewLineAtEnd) = let skipNewLineAtEnd = defaultArg skipNewLineAtEnd false - printer.Print("{") + printer.Print("") printer.PrintNewLine() printer.PushIndentation() for node in nodes do printNode printer node printSeparator printer printer.PopIndentation() - printer.Print("}") + printer.Print("") if not skipNewLineAtEnd then printer.PrintNewLine() member printer.PrintStatementSeparator() = if printer.Column > 0 then - printer.Print(";") + printer.Print("") printer.PrintNewLine() member _.IsProductiveStatement(s: Statement) = @@ -79,6 +79,14 @@ module PrinterExtensions = printer.Print(before) node.Print(printer) + member printer.PrintOptional(before: string, node: #Node option, after: string) = + match node with + | None -> () + | Some node -> + printer.Print(before) + node.Print(printer) + printer.Print(after) + member printer.PrintOptional(node: #Node option) = match node with | None -> () @@ -108,14 +116,14 @@ module PrinterExtensions = | Some (:? Identifier as id) when id.TypeAnnotation.IsSome -> printer.Print(" extends "); printer.Print(id.TypeAnnotation.Value.TypeAnnotation) - | _ -> printer.PrintOptional(" extends ", superClass) + | _ -> printer.PrintOptional("(", superClass, ")") // printer.PrintOptional(superTypeParameters) match implements with | Some implements when not (Array.isEmpty implements) -> printer.Print(" implements ") printer.PrintArray(implements, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) | _ -> () - printer.Print(" ") + printer.Print(":") printer.Print(body) member printer.PrintFunction(id: Identifier option, parameters: Pattern array, body: BlockStatement, @@ -152,11 +160,10 @@ module PrinterExtensions = if isArrow then // Remove parens if we only have one argument? (and no annotation) printer.PrintOptional(typeParameters) - printer.Print("(") + printer.Print("lambda ") printer.PrintCommaSeparatedArray(parameters) - printer.Print(")") printer.PrintOptional(returnType) - printer.Print(" => ") + printer.Print(": ") match body.Body with | [|:? ReturnStatement as r |] -> match r.Argument with @@ -168,14 +175,14 @@ module PrinterExtensions = | _ -> printer.ComplexExpressionWithParens(r.Argument) | _ -> printer.PrintBlock(body.Body, skipNewLineAtEnd=true) else - printer.Print("function ") + printer.Print("def ") printer.PrintOptional(id) printer.PrintOptional(typeParameters) printer.Print("(") printer.PrintCommaSeparatedArray(parameters) printer.Print(")") printer.PrintOptional(returnType) - printer.Print(" ") + printer.Print(":") printer.PrintBlock(body.Body, skipNewLineAtEnd=true) member printer.WithParens(expr: Expression) = @@ -368,7 +375,7 @@ type Undefined(?loc) = // TODO: Use `void 0` instead? Just remove this node? interface Expression with member _.Print(printer) = - printer.Print("undefined", ?loc=loc) + printer.Print("None", ?loc=loc) type NullLiteral(?loc) = interface Literal with @@ -599,14 +606,14 @@ type VariableDeclarator(id, ?init) = type VariableDeclarationKind = Var | Let | Const type VariableDeclaration(kind_, declarations, ?loc) = - let kind = match kind_ with Var -> "var" | Let -> "let" | Const -> "const" + let kind = match kind_ with Var -> "" | Let -> "" | Const -> "" new (var, ?init, ?kind, ?loc) = VariableDeclaration(defaultArg kind Let, [|VariableDeclarator(var, ?init=init)|], ?loc=loc) member _.Declarations: VariableDeclarator array = declarations member _.Kind: string = kind interface Declaration with member _.Print(printer) = - printer.Print(kind + " ", ?loc=loc) + printer.Print(kind, ?loc=loc) let canConflict = declarations.Length > 1 for i = 0 to declarations.Length - 1 do let decl = declarations.[i] @@ -697,7 +704,7 @@ type Super(?loc) = type ThisExpression(?loc) = interface Expression with member _.Print(printer) = - printer.Print("this", ?loc=loc) + printer.Print("self", ?loc=loc) /// A fat arrow function expression, e.g., let foo = (bar) => { /* body */ }. type ArrowFunctionExpression(``params``, body: BlockStatement, ?returnType, ?typeParameters, ?loc) = //?async_, ?generator_, @@ -832,7 +839,7 @@ type ObjectMethod(kind_, key, ``params``, body, ?computed_, ?returnType, ?typePa printer.PrintCommaSeparatedArray(``params``) printer.Print(")") printer.PrintOptional(returnType) - printer.Print(" ") + printer.Print(":") printer.PrintBlock(body.Body, skipNewLineAtEnd=true) @@ -914,7 +921,7 @@ type NewExpression(callee, arguments, ?typeArguments, ?loc) = member _.TypeArguments: TypeParameterInstantiation option = typeArguments interface Expression with member _.Print(printer) = - printer.Print("new ", ?loc=loc) + printer.Print("", ?loc=loc) printer.ComplexExpressionWithParens(callee) printer.Print("(") printer.PrintCommaSeparatedArray(arguments) @@ -1027,8 +1034,8 @@ type AssignmentExpression(operator_, left, right, ?loc) = type LogicalExpression(operator_, left, right, ?loc) = let operator = match operator_ with - | LogicalOr -> "||" - | LogicalAnd-> "&&" + | LogicalOr -> "or" + | LogicalAnd-> "and" member _.Left: Expression = left member _.Right: Expression = right member _.Operator: string = operator @@ -1063,7 +1070,7 @@ type RestElement(argument, ?typeAnnotation, ?loc) = interface Pattern with member _.Name = argument.Name member _.Print(printer) = - printer.Print("...", ?loc=loc) + printer.Print("*", ?loc=loc) argument.Print(printer) printer.PrintOptional(typeAnnotation) @@ -1076,7 +1083,7 @@ type ClassMethodKind = type ClassMethod(kind_, key, ``params``, body, ?computed_, ?``static``, ?``abstract``, ?returnType, ?typeParameters, ?loc) = let kind = match kind_ with - | ClassImplicitConstructor -> "constructor" + | ClassImplicitConstructor -> "__init__" | ClassGetter -> "get" | ClassSetter -> "set" | ClassFunction -> "method" @@ -1097,14 +1104,17 @@ type ClassMethod(kind_, key, ``params``, body, ?computed_, ?``static``, ?``abstr printer.AddLocation(loc) let keywords = [ - if ``static`` = Some true then yield "static" - if ``abstract`` = Some true then yield "abstract" - if kind = "get" || kind = "set" then yield kind + if ``static`` = Some true then yield "@staticmethod" + if ``abstract`` = Some true then yield "@abstractmethod" + if kind = "get" || kind = "set" then yield "@property" ] if not (List.isEmpty keywords) then - printer.Print((String.concat " " keywords) + " ") + printer.PrintNewLine() + printer.Print(String.concat " " keywords) + printer.PrintNewLine() + printer.Print("def ") if computed then printer.Print("[") key.Print(printer) @@ -1112,12 +1122,18 @@ type ClassMethod(kind_, key, ``params``, body, ?computed_, ?``static``, ?``abstr else key.Print(printer) + let args = + if ``static`` = Some true + then ``params`` + else + let self = Identifier "self" :> Pattern |> Array.singleton + Array.concat [self; ``params``] printer.PrintOptional(typeParameters) printer.Print("(") - printer.PrintCommaSeparatedArray(``params``) + printer.PrintCommaSeparatedArray(args) printer.Print(")") printer.PrintOptional(returnType) - printer.Print(" ") + printer.Print(":") printer.Print(body) @@ -1199,6 +1215,22 @@ type PrivateModuleDeclaration(statement) = if printer.IsProductiveStatement(statement) then printer.Print(statement) + +type PrivateMainModuleDeclaration(statement) = + member _.Statement: Statement = statement + interface ModuleDeclaration with + member _.Print(printer) = + if printer.IsProductiveStatement(statement) then + printer.Print(statement) + printer.PrintNewLine() + printer.Print("if __name__ == '__main__':") + printer.PrintNewLine() + printer.PushIndentation() + printer.Print("import sys") // FIXME: now to move this to the top of the file? + printer.PrintNewLine() + printer.Print("main(sys.argv)") + printer.PopIndentation() + type ImportSpecifier = inherit Node /// An imported variable binding, e.g., {foo} in import {foo} from "mod" or {foo as bar} in import {foo as bar} from "mod". @@ -1239,7 +1271,12 @@ type ImportDeclaration(specifiers, source) = let defaults = specifiers|> Array.choose (function :? ImportDefaultSpecifier as x -> Some x | _ -> None) let namespaces = specifiers |> Array.choose (function :? ImportNamespaceSpecifier as x -> Some x | _ -> None) - printer.Print("import ") + printer.Print("from ") + + printer.Print(printer.MakeImportPath(source.Value)) + + if not(Array.isEmpty defaults && Array.isEmpty namespaces && Array.isEmpty members) then + printer.Print(" import ") if not(Array.isEmpty defaults) then printer.PrintCommaSeparatedArray(defaults) @@ -1252,16 +1289,9 @@ type ImportDeclaration(specifiers, source) = printer.Print(", ") if not(Array.isEmpty members) then - printer.Print("{ ") + printer.Print("(") printer.PrintCommaSeparatedArray(members) - printer.Print(" }") - - if not(Array.isEmpty defaults && Array.isEmpty namespaces && Array.isEmpty members) then - printer.Print(" from ") - - printer.Print("\"") - printer.Print(printer.MakeImportPath(source.Value)) - printer.Print("\"") + printer.Print(")") /// An exported variable binding, e.g., {foo} in export {foo} or {bar as foo} in export {bar as foo}. /// The exported field refers to the name exported in the module. @@ -1285,7 +1315,7 @@ type ExportNamedDeclaration(declaration) = member _.Declaration: Declaration = declaration interface ModuleDeclaration with member _.Print(printer) = - printer.Print("export ") + printer.Print("") printer.Print(declaration) type ExportNamedReferences(specifiers, ?source) = @@ -1376,7 +1406,7 @@ type AnyTypeAnnotation() = type VoidTypeAnnotation() = interface TypeAnnotationInfo with member _.Print(printer) = - printer.Print("void") + printer.Print("None") type TupleTypeAnnotation(types) = member _.Types: TypeAnnotationInfo array = types diff --git a/src/Fable.Transforms/Global/Prelude.fs b/src/Fable.Transforms/Global/Prelude.fs index 55d90c821e..9e2000ed19 100644 --- a/src/Fable.Transforms/Global/Prelude.fs +++ b/src/Fable.Transforms/Global/Prelude.fs @@ -394,7 +394,7 @@ module Naming = | StaticMemberPart(_,o) -> o | NoMemberPart -> "" - let reflectionSuffix = "$reflection" + let reflectionSuffix = "_reflection" let private printPart sanitize separator part overloadSuffix = (if part = "" then "" else separator + (sanitize part)) + diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 5dab5754f4..7ad2e911dd 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -748,18 +748,20 @@ let rec equals (com: ICompiler) ctx r equal (left: Expr) (right: Expr) = | Builtin (BclInt64|BclUInt64|BclDecimal|BclBigInt as bt) -> Helper.LibCall(com, coreModFor bt, "equals", Boolean, [left; right], ?loc=r) |> is equal | DeclaredType _ -> - Helper.LibCall(com, "Util", "equals", Boolean, [left; right], ?loc=r) |> is equal + Helper.LibCall(com, "Util", "equals1", Boolean, [left; right], ?loc=r) |> is equal | Array t -> let f = makeComparerFunction com ctx t Helper.LibCall(com, "Array", "equalsWith", Boolean, [f; left; right], ?loc=r) |> is equal | List _ -> - Helper.LibCall(com, "Util", "equals", Boolean, [left; right], ?loc=r) |> is equal + Helper.LibCall(com, "Util", "equals2", Boolean, [left; right], ?loc=r) |> is equal | MetaType -> - Helper.LibCall(com, "Reflection", "equals", Boolean, [left; right], ?loc=r) |> is equal + Helper.LibCall(com, "Reflection", "equals3", Boolean, [left; right], ?loc=r) |> is equal | Tuple _ -> Helper.LibCall(com, "Util", "equalArrays", Boolean, [left; right], ?loc=r) |> is equal | _ -> - Helper.LibCall(com, "Util", "equals", Boolean, [left; right], ?loc=r) |> is equal + //Helper.LibCall(com, "Util", "equals", Boolean, [left; right], ?loc=r) |> is equal + let op = if equal then BinaryEqual else BinaryUnequal + makeBinOp r Boolean left right op /// Compare function that will call Util.compare or instance `CompareTo` as appropriate and compare (com: ICompiler) ctx r (left: Expr) (right: Expr) = @@ -1226,7 +1228,7 @@ let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp | "jsOptions", [arg] -> makePojoFromLambda com arg |> Some | "jsThis", _ -> - emitJsExpr r t [] "this" |> Some + emitJsExpr r t [] "self" |> Some | "jsConstructor", _ -> match (genArg com ctx r 0 i.GenericArgs) with | DeclaredType(ent, _) -> com.GetEntity(ent) |> jsConstructor com |> Some @@ -2435,7 +2437,7 @@ let log (com: ICompiler) r t (i: CallInfo) (_: Expr option) (args: Expr list) = | [v] -> [v] | (StringConst _)::_ -> [Helper.LibCall(com, "String", "format", t, args, i.SignatureArgTypes)] | _ -> [args.Head] - Helper.GlobalCall("console", t, args, memb="log", ?loc=r) + Helper.GlobalCall("print", t, args, ?loc=r) let bitConvert (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) = match i.CompiledName with diff --git a/src/Fable.Transforms/Transforms.Util.fs b/src/Fable.Transforms/Transforms.Util.fs index 34718f832e..2a707282ce 100644 --- a/src/Fable.Transforms/Transforms.Util.fs +++ b/src/Fable.Transforms/Transforms.Util.fs @@ -423,8 +423,8 @@ module AST = let makeIntConst (x: int) = NumberConstant (float x, Int32) |> makeValue None let makeFloatConst (x: float) = NumberConstant (x, Float64) |> makeValue None - let getLibPath (com: Compiler) moduleName = - com.LibraryDir + "/" + moduleName + ".js" + let getLibPath (com: Compiler) (moduleName: string) = + $"expression.system.{moduleName.ToLower()}" let makeImportUserGenerated r t (selector: string) (path: string) = Import({ Selector = selector.Trim() From db879d64ce507b4dd352af830f24040729d4cfde Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 5 Jan 2021 23:58:56 +0100 Subject: [PATCH 002/145] `return None` instead not nothing --- src/Fable.Transforms/Fable2Babel.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index f74764b93d..d14bc96910 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -1099,7 +1099,8 @@ module Util = let resolveExpr t strategy babelExpr: Statement = match strategy with - | None | Some ReturnUnit -> upcast ExpressionStatement babelExpr + | None -> upcast ExpressionStatement babelExpr + | Some ReturnUnit -> upcast ReturnStatement(wrapIntExpression t babelExpr) // TODO: Where to put these int wrappings? Add them also for function arguments? | Some Return -> upcast ReturnStatement(wrapIntExpression t babelExpr) | Some(Assign left) -> upcast ExpressionStatement(assign None left babelExpr) From f46b93355d494c8dc84f97eed224b0ebac004d52 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 5 Jan 2021 23:59:13 +0100 Subject: [PATCH 003/145] This is not a reserved word in python --- src/Fable.Transforms/Global/Prelude.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fable.Transforms/Global/Prelude.fs b/src/Fable.Transforms/Global/Prelude.fs index 9e2000ed19..1e1aa24d3a 100644 --- a/src/Fable.Transforms/Global/Prelude.fs +++ b/src/Fable.Transforms/Global/Prelude.fs @@ -215,7 +215,7 @@ module Naming = "return" "super" "switch" - "this" + //"this" "throw" "try" "typeof" From 3fd17eb31e6891c85982c7d98c23ae4e9d021afc Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 5 Jan 2021 23:59:52 +0100 Subject: [PATCH 004/145] Generate .fs.py files instead --- build.fsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.fsx b/build.fsx index c51236d227..d0b30713d2 100644 --- a/build.fsx +++ b/build.fsx @@ -520,7 +520,7 @@ match argsLower with | "test-integration"::_ -> testIntegration() | "quicktest"::_ -> buildLibraryIfNotExists() - run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --forcePkgs --extension .py" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --forcePkgs --extension .fs.py false" | "run"::_ -> buildLibraryIfNotExists() From cee56154df0c3746b5b2700b0611813c7b6f0800 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 6 Jan 2021 20:59:47 +0100 Subject: [PATCH 005/145] Fable is now a separate module in Expression --- src/Fable.Transforms/Transforms.Util.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fable.Transforms/Transforms.Util.fs b/src/Fable.Transforms/Transforms.Util.fs index 2a707282ce..4801497ed8 100644 --- a/src/Fable.Transforms/Transforms.Util.fs +++ b/src/Fable.Transforms/Transforms.Util.fs @@ -424,7 +424,7 @@ module AST = let makeFloatConst (x: float) = NumberConstant (x, Float64) |> makeValue None let getLibPath (com: Compiler) (moduleName: string) = - $"expression.system.{moduleName.ToLower()}" + $"expression.fable.{moduleName.ToLower()}" let makeImportUserGenerated r t (selector: string) (path: string) = Import({ Selector = selector.Trim() From cfb7ed0b153dd8eec2b3c6f78151bf13bf168935 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 9 Jan 2021 10:56:27 +0100 Subject: [PATCH 006/145] Fixes for undefined, null, and ofArray (of_seq) --- src/Fable.Transforms/Fable2Babel.fs | 2 +- src/Fable.Transforms/Global/Babel.fs | 4 ++-- src/Fable.Transforms/Replacements.fs | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index d14bc96910..57bf432bc9 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -947,7 +947,7 @@ module Util = libCall com ctx r "List" "singleton" [|expr|] | exprs, None -> [|makeArray com ctx exprs|] - |> libCall com ctx r "List" "ofArray" + |> libCall com ctx r "List" "of_seq" | [TransformExpr com ctx head], Some(TransformExpr com ctx tail) -> libCall com ctx r "List" "cons" [|head; tail|] | exprs, Some(TransformExpr com ctx tail) -> diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index 2ed305b2fb..573cce0de7 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -317,7 +317,7 @@ type EmitExpression(value, args, ?loc) = let argIndex = int m.Value.[1..] match Array.tryItem argIndex args with | Some e -> printer.ComplexExpressionWithParens(e) - | None -> printer.Print("undefined") + | None -> printer.Print("pass") let lastMatch = matches.[matches.Count - 1] printSegment printer value (lastMatch.Index + lastMatch.Length) value.Length @@ -380,7 +380,7 @@ type Undefined(?loc) = type NullLiteral(?loc) = interface Literal with member _.Print(printer) = - printer.Print("null", ?loc=loc) + printer.Print("None", ?loc=loc) type StringLiteral(value, ?loc) = member _.Value: string = value diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 7ad2e911dd..59e4f7e2b0 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -1352,7 +1352,9 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o // KeyValuePair is already compiled as a tuple | ("KeyValuePattern"|"Identity"|"Box"|"Unbox"|"ToEnum"), [arg] -> TypeCast(arg, t, None) |> Some // Cast to unit to make sure nothing is returned when wrapped in a lambda, see #1360 - | "Ignore", _ -> "void $0" |> emitJsExpr r t args |> Some + | "Ignore", _ -> + // "void3 $0" |> emitJsExpr r t args |> Some + None // Number and String conversions | ("ToSByte"|"ToByte"|"ToInt8"|"ToUInt8"|"ToInt16"|"ToUInt16"|"ToInt"|"ToUInt"|"ToInt32"|"ToUInt32"), _ -> toInt com ctx r t args |> Some @@ -1991,7 +1993,7 @@ let optionModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: E let args = args |> List.replaceLast (toArray None t) let moduleName, meth = if meth = "ToList" - then "List", "ofArray" + then "List", "of_seq" else "Seq", Naming.lowerFirst meth Helper.LibCall(com, moduleName, meth, t, args, i.SignatureArgTypes, ?loc=r) |> Some | _ -> None From d052a92a3b20c7c06f63e615256026f1376b98cf Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 10 Jan 2021 10:28:44 +0100 Subject: [PATCH 007/145] Python AST (wip) --- src/Fable.Transforms/Python/Python.fs | 736 ++++++++++++++++++++++++++ 1 file changed, 736 insertions(+) create mode 100644 src/Fable.Transforms/Python/Python.fs diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs new file mode 100644 index 0000000000..d85b6c1ead --- /dev/null +++ b/src/Fable.Transforms/Python/Python.fs @@ -0,0 +1,736 @@ +namespace rec Fable.AST.Python + +open Fable.AST +open PrinterExtensions + +type Printer = + abstract Line: int + abstract Column: int + abstract PushIndentation: unit -> unit + abstract PopIndentation: unit -> unit + abstract Print: string * ?loc:SourceLocation -> unit + abstract PrintNewLine: unit -> unit + abstract AddLocation: SourceLocation option -> unit + abstract EscapeJsStringLiteral: string -> string + abstract MakeImportPath: string -> string + +module PrinterExtensions = + type Printer with + member printer.Print(node: AST) = node.Print(printer) + + member printer.PrintBlock + ( + nodes: 'a array, + printNode: Printer -> 'a -> unit, + printSeparator: Printer -> unit, + ?skipNewLineAtEnd + ) = + let skipNewLineAtEnd = defaultArg skipNewLineAtEnd false + printer.Print("") + printer.PrintNewLine() + printer.PushIndentation() + + for node in nodes do + printNode printer node + printSeparator printer + + printer.PopIndentation() + printer.Print("") + + if not skipNewLineAtEnd then + printer.PrintNewLine() + + member printer.PrintStatementSeparator() = + if printer.Column > 0 then + printer.Print("") + printer.PrintNewLine() + +// member _.IsProductiveStatement(s: Statement) = +// let rec hasNoSideEffects (e: Expression) = +// match e with +// | :? Undefined +// | :? NullLiteral +// | :? StringLiteral +// | :? BooleanLiteral +// | :? NumericLiteral -> true +// // Constructors of classes deriving from System.Object add an empty object at the end +// | :? ObjectExpression as o -> o.Properties.Length = 0 +// | :? UnaryExpression as e when e.Operator = "void2" -> hasNoSideEffects e.Argument +// // Some identifiers may be stranded as the result of imports +// // intended only for side effects, see #2228 +// | :? Identifier -> true +// | _ -> false + +// match s with +// | :? ExpressionStatement as e -> hasNoSideEffects e.Expression |> not +// | _ -> true + +// member printer.PrintProductiveStatement(s: Statement, ?printSeparator) = +// if printer.IsProductiveStatement(s) then +// s.Print(printer) +// printSeparator |> Option.iter (fun f -> f printer) + +// member printer.PrintProductiveStatements(statements: Statement[]) = +// for s in statements do +// printer.PrintProductiveStatement(s, (fun p -> p.PrintStatementSeparator())) + +// member printer.PrintBlock(nodes: Statement array, ?skipNewLineAtEnd) = +// printer.PrintBlock(nodes, +// (fun p s -> p.PrintProductiveStatement(s)), +// (fun p -> p.PrintStatementSeparator()), +// ?skipNewLineAtEnd=skipNewLineAtEnd) + +// member printer.PrintOptional(before: string, node: #Node option) = +// match node with +// | None -> () +// | Some node -> +// printer.Print(before) +// node.Print(printer) + +// member printer.PrintOptional(before: string, node: #Node option, after: string) = +// match node with +// | None -> () +// | Some node -> +// printer.Print(before) +// node.Print(printer) +// printer.Print(after) + +// member printer.PrintOptional(node: #Node option) = +// match node with +// | None -> () +// | Some node -> node.Print(printer) + +// member printer.PrintArray(nodes: 'a array, printNode: Printer -> 'a -> unit, printSeparator: Printer -> unit) = +// for i = 0 to nodes.Length - 1 do +// printNode printer nodes.[i] +// if i < nodes.Length - 1 then +// printSeparator printer + +// member printer.PrintCommaSeparatedArray(nodes: Expression array) = +// printer.PrintArray(nodes, (fun p x -> p.SequenceExpressionWithParens(x)), (fun p -> p.Print(", "))) + +// member printer.PrintCommaSeparatedArray(nodes: #Node array) = +// printer.PrintArray(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) + +// // TODO: (super) type parameters, implements +// member printer.PrintClass(id: Identifier option, superClass: Expression option, +// superTypeParameters: TypeParameterInstantiation option, +// typeParameters: TypeParameterDeclaration option, +// implements: ClassImplements array option, body: ClassBody, loc) = +// printer.Print("class", ?loc=loc) +// printer.PrintOptional(" ", id) +// printer.PrintOptional(typeParameters) +// match superClass with +// | Some (:? Identifier as id) when id.TypeAnnotation.IsSome -> +// printer.Print(" extends "); +// printer.Print(id.TypeAnnotation.Value.TypeAnnotation) +// | _ -> printer.PrintOptional("(", superClass, ")") +// // printer.PrintOptional(superTypeParameters) +// match implements with +// | Some implements when not (Array.isEmpty implements) -> +// printer.Print(" implements ") +// printer.PrintArray(implements, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) +// | _ -> () +// printer.Print(":") +// printer.Print(body) + +// member printer.PrintFunction(id: Identifier option, parameters: Pattern array, body: BlockStatement, +// typeParameters: TypeParameterDeclaration option, returnType: TypeAnnotation option, loc, ?isDeclaration, ?isArrow) = +// let areEqualPassedAndAppliedArgs (passedArgs: Pattern[]) (appliedAgs: Expression[]) = +// Array.zip passedArgs appliedAgs +// |> Array.forall (function +// | (:? Identifier as p), (:? Identifier as a) -> p.Name = a.Name +// | _ -> false) + +// let isDeclaration = defaultArg isDeclaration false +// let isArrow = defaultArg isArrow false + +// printer.AddLocation(loc) + +// // Check if we can remove the function +// let skipExpr = +// match body.Body with +// | [|:? ReturnStatement as r|] when not isDeclaration -> +// match r.Argument with +// | :? CallExpression as c when parameters.Length = c.Arguments.Length -> +// // To be sure we're not running side effects when deleting the function, +// // check the callee is an identifier (accept non-computed member expressions too?) +// match c.Callee with +// | :? Identifier when areEqualPassedAndAppliedArgs parameters c.Arguments -> +// Some c.Callee +// | _ -> None +// | _ -> None +// | _ -> None + +// match skipExpr with +// | Some e -> e.Print(printer) +// | None -> +// if false then //isArrow then +// // Remove parens if we only have one argument? (and no annotation) +// printer.PrintOptional(typeParameters) +// printer.Print("lambda-inline ") +// printer.PrintCommaSeparatedArray(parameters) +// printer.PrintOptional(returnType) +// printer.Print(": ") +// match body.Body with +// | [|:? ReturnStatement as r |] -> +// match r.Argument with +// | :? ObjectExpression as e -> printer.WithParens(e) +// | :? MemberExpression as e -> +// match e.Object with +// | :? ObjectExpression -> e.Print(printer, objectWithParens=true) +// | _ -> e.Print(printer) +// | _ -> printer.ComplexExpressionWithParens(r.Argument) +// | _ -> printer.PrintBlock(body.Body, skipNewLineAtEnd=true) +// else +// printer.Print("def ") +// printer.PrintOptional(id) +// printer.PrintOptional(typeParameters) +// printer.Print("(") +// printer.PrintCommaSeparatedArray(parameters) +// printer.Print(")") +// printer.PrintOptional(returnType) +// printer.Print(":") +// printer.PrintBlock(body.Body, skipNewLineAtEnd=true) + +// member printer.WithParens(expr: Expression) = +// printer.Print("(") +// expr.Print(printer) +// printer.Print(")") + +// member printer.SequenceExpressionWithParens(expr: Expression) = +// match expr with +// | :? SequenceExpression -> printer.WithParens(expr) +// | _ -> printer.Print(expr) + +// /// Surround with parens anything that can potentially conflict with operator precedence +// member printer.ComplexExpressionWithParens(expr: Expression) = +// match expr with +// | :? Undefined +// | :? NullLiteral +// | :? StringLiteral +// | :? BooleanLiteral +// | :? NumericLiteral +// | :? Identifier +// | :? MemberExpression +// | :? CallExpression +// | :? ThisExpression +// | :? Super +// | :? SpreadElement +// | :? ArrayExpression +// | :? ObjectExpression -> expr.Print(printer) +// | _ -> printer.WithParens(expr) + +// member printer.PrintOperation(left, operator, right, loc) = +// printer.AddLocation(loc) +// printer.ComplexExpressionWithParens(left) +// printer.Print(" " + operator + " ") +// printer.ComplexExpressionWithParens(right) + +type AST = + //int col + abstract Print: Printer -> unit + +[] +type Expression () = + member val Lineno : int = 0 with get, set + member val ColOffset : int = 0 with get, set + member val EndLineno : int option = None with get, set + member val EndColOffset : int option = None with get, set + + interface AST with + member this.Print(printer) = this.Print printer + + abstract Print: Printer -> unit + +type Operator = + inherit AST + +type BoolOperator = + inherit AST + +type ComparisonOperator = + inherit AST + +type UnaryOperator = + inherit AST + +type Identifier = + abstract Name: string + + inherit AST + +[] +type Statement () = + member val Lineno : int = 0 with get, set + member val ColOffset : int = 0 with get, set + member val EndLineno : int option = None with get, set + member val EndColOffset : int option = None with get, set + + interface AST with + member this.Print(printer) = this.Print printer + + abstract Print: Printer -> unit + +type Module(body) = + member _.Body: List = body + + +type Alias(name, asname) = + member _.Name: Identifier = name + member _.AsName: Identifier option = asname + + interface AST with + member _.Print(printer) = () + +/// A single argument in a list. arg is a raw string of the argument name, annotation is its annotation, such as a Str +/// or Name node. +/// +/// - type_comment is an optional string with the type annotation as a comment +type Arg(arg, ?annotation, ?typeComment) = + member val Lineno : int = 0 with get, set + member val ColOffset : int = 0 with get, set + member val EndLineno : int option = None with get, set + member val EndColOffset : int option = None with get, set + + member _.Arg: Identifier = arg + member _.Annotation : Expression option = annotation + member _.TypeComment : string option = typeComment + +/// The arguments for a function. +/// +/// - posonlyargs, args and kwonlyargs are lists of arg nodes. +/// - vararg and kwarg are single arg nodes, referring to the *args, **kwargs parameters. +/// - kwDefaults is a list of default values for keyword-only arguments. If one is None, the corresponding argument is +/// required. +/// - defaults is a list of default values for arguments that can be passed positionally. If there are fewer defaults, +/// they correspond to the last n arguments. +type Arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ?defaults) = + member _.PosOnlyArgs : Arg list = defaultArg posonlyargs [] + member _.Args : Arg list = defaultArg args [] + member _.VarArg : Arg option = vararg + member _.KwOnlyArgs : Arg list = defaultArg kwonlyargs [] + member _.KwDefaults : Expression list = defaultArg kwDefaults [] + member _.KwArg : Arg option = kwarg + member _.Defaults : Expression list = defaultArg defaults [] + + interface AST with + member _.Print(printer) = () + +//#region Statements + +/// When an expression, such as a function call, appears as a statement by itself with its return value not used or +/// stored, it is wrapped in this container. value holds one of the other nodes in this section, a Constant, a Name, a +/// Lambda, a Yield or YieldFrom node. +/// ```python +/// >>> print(ast.dump(ast.parse('-a'), indent=4)) +/// Module( +/// body=[ +/// Expr( +/// value=UnaryOp( +/// op=USub(), +/// operand=Name(id='a', ctx=Load())))], +/// type_ignores=[]) +///``` +type Expr(value) = + inherit Statement () + + member _.Value: Expression = value + + override _.Print(printer) = () + +/// A function definition. +/// +/// - name is a raw string of the function name. +/// - args is a arguments node. +/// - body is the list of nodes inside the function. +/// - decorator_list is the list of decorators to be applied, stored outermost first (i.e. the first in the list will be +/// applied last). +/// - returns is the return annotation. +/// - type_comment is an optional string with the type annotation as a comment. +type FunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = + + inherit Statement () + + member _.Name: Identifier = name + member _.Args: Arguments = args + member _.Body: Statement list = body + member _.DecoratorList: Expression list = decoratorList + member _.Returns: Expression option = returns + member _.TypeComment: string option = typeComment + + override _.Print(printer) = () + +/// An async function definition. +/// +/// - name is a raw string of the function name. +/// - args is a arguments node. +/// - body is the list of nodes inside the function. +/// - decorator_list is the list of decorators to be applied, stored outermost first (i.e. the first in the list will be +/// applied last). +/// - returns is the return annotation. +/// - type_comment is an optional string with the type annotation as a comment. +type AsyncFunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = + + inherit Statement () + + member _.Name: Identifier = name + member _.Args: Arguments = args + member _.Body: Statement list = body + member _.DecoratorList: Expression list = decoratorList + member _.Returns: Expression option = returns + member _.TypeComment: string option = typeComment + + override _.Print(printer) = () + +/// An import statement. names is a list of alias nodes. +/// ```python +/// >>> print(ast.dump(ast.parse('import x,y,z'), indent=4)) +/// Module( +/// body=[ +/// Import( +/// names=[ +/// alias(name='x'), +/// alias(name='y'), +/// alias(name='z')])], +/// type_ignores=[]) +/// ````` +type Import(names) = + inherit Statement () + + member _.Names : Alias list = names + + override _.Print(printer) = () + +/// Represents from x import y. module is a raw string of the ‘from’ name, without any leading dots, or None for +/// statements such as from . import foo. level is an integer holding the level of the relative import (0 means absolute +/// import). +///```python +/// >>> print(ast.dump(ast.parse('from y import x,y,z'), indent=4)) +/// Module( +/// body=[ +/// ImportFrom( +/// module='y', +/// names=[ +/// alias(name='x'), +/// alias(name='y'), +/// alias(name='z')], +/// level=0)], +/// type_ignores=[]) +/// ``` +type ImportFrom(``module``, names, level) = + inherit Statement () + + member _.Module : Identifier option = ``module`` + member _.Names : Alias list = names + member _.Level : int option = level + + override _.Print(printer) = () + +/// A return statement. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('return 4'), indent=4)) +/// Module( +/// body=[ +/// Return( +/// value=Constant(value=4))], +/// type_ignores=[]) +/// ``` +type Return (?value) = + inherit Statement () + member _.Value : Expression option = value + + override _.Print(printer) = () + +//#endregion + +//#region Expressions + +/// A constant value. The value attribute of the Constant literal contains the Python object it represents. The values +/// represented can be simple types such as a number, string or None, but also immutable container types (tuples and +/// frozensets) if all of their elements are constant. +/// ```python +/// >>> print(ast.dump(ast.parse('123', mode='eval'), indent=4)) +/// Expression( +/// body=Constant(value=123)) +/// ````` +type Constant<'T>(value: 'T) = + inherit Expression () + + member _.Value : 'T = value + + override _.Print(printer) = () + +/// Node representing a single formatting field in an f-string. If the string contains a single formatting field and +/// nothing else the node can be isolated otherwise it appears in JoinedStr. +/// +/// - value is any expression node (such as a literal, a variable, or a function call). +/// - conversion is an integer: +/// - -1: no formatting +/// - 115: !s string formatting +/// - 114: !r repr formatting +/// - 97: !a ascii formatting +/// - format_spec is a JoinedStr node representing the formatting of the value, or None if no format was specified. Both +/// conversion and format_spec can be set at the same time. +type FormattedValue(value, ?conversion, ?formatSpec) = + inherit Expression () + + member _.Value : Expression = value + member _.Conversion : int option = conversion + member _.FormatSpec : Expression option = formatSpec + + override _.Print(printer) = () + +/// lambda is a minimal function definition that can be used inside an expression. Unlike FunctionDef, body holds a +/// single node. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('lambda x,y: ...'), indent=4)) +/// Module( +/// body=[ +/// Expr( +/// value=Lambda( +/// args=arguments( +/// posonlyargs=[], +/// args=[ +/// arg(arg='x'), +/// arg(arg='y')], +/// kwonlyargs=[], +/// kw_defaults=[], +/// defaults=[]), +/// body=Constant(value=Ellipsis)))], +/// type_ignores=[]) +/// ``` +type Lambda(args, body) = + + inherit Expression () + + member _.Args: Arguments = args + member _.Body: Statement list = body + + override _.Print(printer) = () + + +/// A tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an assignment target +/// (i.e. (x,y)=something), and Load otherwise. +/// +/// ```python +/// >>> print(ast.dump(ast.parse('(1, 2, 3)', mode='eval'), indent=4)) +/// Expression( +/// body=Tuple( +/// elts=[ +/// Constant(value=1), +/// Constant(value=2), +/// Constant(value=3)], +/// ctx=Load())) +///``` +type Tuple(elts) = + inherit Expression () + + member _.Elements : Expression list = elts + override _.Print(printer) = () + +/// A list or tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an +/// assignment target (i.e. (x,y)=something), and Load otherwise. +/// +/// ```python +/// >>> print(ast.dump(ast.parse('[1, 2, 3]', mode='eval'), indent=4)) +/// Expression( +/// body=List( +/// elts=[ +/// Constant(value=1), +/// Constant(value=2), +/// Constant(value=3)], +/// ctx=Load())) +///``` +type List(elts) = + inherit Expression () + + member _.Elements : Expression list = elts + + override _.Print(printer) = () + +/// A set. elts holds a list of nodes representing the set’s elements. +/// +/// ```python +/// >>> print(ast.dump(ast.parse('{1, 2, 3}', mode='eval'), indent=4)) +/// Expression( +/// body=Set( +/// elts=[ +/// Constant(value=1), +/// Constant(value=2), +/// Constant(value=3)])) +/// ````` +type Set(elts) = + inherit Expression () + + member _.Elements : Expression list = elts + + override _.Print(printer) = () + +/// A dictionary. keys and values hold lists of nodes representing the keys and the values respectively, in matching +/// order (what would be returned when calling dictionary.keys() and dictionary.values()). +/// +/// When doing dictionary unpacking using dictionary literals the expression to be expanded goes in the values list, +/// with a None at the corresponding position in keys. +/// +/// ```python +/// >>> print(ast.dump(ast.parse('{"a":1, **d}', mode='eval'), indent=4)) +/// Expression( +/// body=Dict( +/// keys=[ +/// Constant(value='a'), +/// None], +/// values=[ +/// Constant(value=1), +/// Name(id='d', ctx=Load())])) +/// ``` +type Dict(keys, values) = + inherit Expression () + + member _.Keys : Expression list= keys + member _.Values : Expression list= values + + override _.Print(printer) = () + +/// A yield expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back is not +/// used. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('yield from x'), indent=4)) +/// Module( +/// body=[ +/// Expr( +/// value=YieldFrom( +/// value=Name(id='x', ctx=Load())))], +/// type_ignores=[]) +///``` +type Yield (?value) = + inherit Expression () + member _.Value : Expression option = value + + override _.Print(printer) = () + +/// A yield from expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back +/// is not used. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('yield from x'), indent=4)) +/// Module( +/// body=[ +/// Expr( +/// value=YieldFrom( +/// value=Name(id='x', ctx=Load())))], +/// type_ignores=[]) +///``` +type YieldFrom (?value) = + inherit Expression () + member _.Value : Expression option = value + + override _.Print(printer) = () + +//#endregion + +//#region Operators + +type Add = + inherit Operator + +type Sub = + inherit Operator + +type Mult = + inherit Operator + +type Div = + inherit Operator + +type FloorDiv = + inherit Operator + +type Mod = + inherit Operator + +type Pow = + inherit Operator + +type LShift = + inherit Operator + +type RShift = + inherit Operator + +type BitOr = + inherit Operator + +type BitXor = + inherit Operator + +type BitAnd = + inherit Operator + +type MatMult = + inherit Operator + +//#endregion + +//#region Comparison operator tokens. + +type Eq = + inherit ComparisonOperator + +type NotEq = + inherit ComparisonOperator + +type Lt = + inherit ComparisonOperator + +type LtE = + inherit ComparisonOperator + +type Gt = + inherit ComparisonOperator + +type GtE = + inherit ComparisonOperator + +type Is = + inherit ComparisonOperator + +type IsNot = + inherit ComparisonOperator + +type In = + inherit ComparisonOperator + +type NotIn = + inherit ComparisonOperator + +//#endregion + +//#region Bool Operators + +type And = + inherit BoolOperator + +type Or = + inherit BoolOperator + +//#region + +//#region Unary Operators + +type Invert = + inherit UnaryOperator + +type Not = + inherit UnaryOperator + +type UAdd = + inherit UnaryOperator + +type USub = + inherit UnaryOperator + +//#endregion \ No newline at end of file From f893abc90ba6712a912ca617af8d060919a1a622 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 10 Jan 2021 20:23:24 +0100 Subject: [PATCH 008/145] Update Python AST --- src/Fable.Transforms/Python/Babel2Python.fs | 134 +++++++ src/Fable.Transforms/Python/Python.fs | 377 +++++++++++++++---- src/Fable.Transforms/Python/PythonPrinter.fs | 114 ++++++ src/Fable.Transforms/Python/README.md | 3 + 4 files changed, 565 insertions(+), 63 deletions(-) create mode 100644 src/Fable.Transforms/Python/Babel2Python.fs create mode 100644 src/Fable.Transforms/Python/PythonPrinter.fs create mode 100644 src/Fable.Transforms/Python/README.md diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs new file mode 100644 index 0000000000..f1c3c00e53 --- /dev/null +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -0,0 +1,134 @@ +module rec Fable.Transforms.Babel2Python + +open System +open Fable +open Fable.AST +open Fable.AST.Python +open System.Collections.Generic + +type ReturnStrategy = + | Return + | ReturnUnit + | Assign of Expression + | Target of Identifier + +type Import = + { Selector: string + LocalIdent: string option + Path: string } + +type ITailCallOpportunity = + abstract Label: string + abstract Args: string list + abstract IsRecursiveRef: Fable.Expr -> bool + +type UsedNames = + { RootScope: HashSet + DeclarationScopes: HashSet + CurrentDeclarationScope: HashSet } + +type Context = + { //UsedNames: UsedNames + DecisionTargets: (Fable.Ident list * Fable.Expr) list + HoistVars: Fable.Ident list -> bool + TailCallOpportunity: ITailCallOpportunity option + OptimizeTailCall: unit -> unit + ScopedTypeParams: Set } + +type IPythonCompiler = + inherit Compiler + //abstract GetAllImports: unit -> seq + //abstract GetImportExpr: Context * selector: string * path: string * SourceLocation option -> Expression + abstract TransformAsExpr: Context * Babel.Expression -> Python.Expression + //abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Python.Statement list + //abstract TransformImport: Context * selector:string * path:string -> Expression + //abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> (Pattern array) * BlockStatement + + abstract WarnOnlyOnce: string * ?range: SourceLocation -> unit + +module Util = + let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Babel.Expression): Expression = + printfn "transformAsExpr" + raise <| NotImplementedException () + + let getIdentForImport (ctx: Context) (path: string) (selector: string) = + if System.String.IsNullOrEmpty selector then None + else + match selector with + | "*" | "default" -> Path.GetFileNameWithoutExtension(path) + | _ -> selector + //|> getUniqueNameInRootScope ctx + |> Some + +module Compiler = + open Util + + type PythonCompiler (com: Compiler) = + let onlyOnceWarnings = HashSet() + let imports = Dictionary() + + interface IPythonCompiler with + member _.WarnOnlyOnce(msg, ?range) = + if onlyOnceWarnings.Add(msg) then + addWarning com [] range msg + + // member _.GetImportExpr(ctx, selector, path, r) = + // let cachedName = path + "::" + selector + // match imports.TryGetValue(cachedName) with + // | true, i -> + // match i.LocalIdent with + // | Some localIdent -> upcast Babel.Identifier(localIdent) + // | None -> upcast Babel.NullLiteral () + // | false, _ -> + // let localId = getIdentForImport ctx path selector + // let i = + // { Selector = + // if selector = Naming.placeholder then + // "`importMember` must be assigned to a variable" + // |> addError com [] r; selector + // else selector + // Path = path + // LocalIdent = localId } + // imports.Add(cachedName, i) + // match localId with + // | Some localId -> upcast Babel.Identifier(localId) + // | None -> upcast Babel.NullLiteral () + //member _.GetAllImports() = upcast imports.Values + member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e + //member bcom.TransformAsStatements(ctx, ret, e) = transformAsStatements bcom ctx ret e + //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body + //member bcom.TransformImport(ctx, selector, path) = transformImport bcom ctx None selector path + + interface Compiler with + member _.Options = com.Options + member _.Plugins = com.Plugins + member _.LibraryDir = com.LibraryDir + member _.CurrentFile = com.CurrentFile + member _.GetEntity(fullName) = com.GetEntity(fullName) + member _.GetImplementationFile(fileName) = com.GetImplementationFile(fileName) + member _.GetRootModule(fileName) = com.GetRootModule(fileName) + member _.GetOrAddInlineExpr(fullName, generate) = com.GetOrAddInlineExpr(fullName, generate) + member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) + member _.AddLog(msg, severity, ?range, ?fileName:string, ?tag: string) = + com.AddLog(msg, severity, ?range=range, ?fileName=fileName, ?tag=tag) + + let makeCompiler com = PythonCompiler(com) + + let transformFile (com: Compiler) (file: Babel.Program) = + let com = makeCompiler com :> IPythonCompiler + let declScopes = + let hs = HashSet() + //for decl in file.Body .Declarations do + // hs.UnionWith(decl.UsedNames) + hs + + let ctx = + { DecisionTargets = [] + HoistVars = fun _ -> false + TailCallOpportunity = None + OptimizeTailCall = fun () -> () + ScopedTypeParams = Set.empty } + //let rootDecls = List.collect (transformDeclaration com ctx) file.Declarations + //let importDecls = com.GetAllImports() |> transformImports + let body = [] //importDecls @ rootDecls |> List.toArray + Python.Module(body) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index d85b6c1ead..5fef857b6b 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -11,7 +11,6 @@ type Printer = abstract Print: string * ?loc:SourceLocation -> unit abstract PrintNewLine: unit -> unit abstract AddLocation: SourceLocation option -> unit - abstract EscapeJsStringLiteral: string -> string abstract MakeImportPath: string -> string module PrinterExtensions = @@ -80,34 +79,34 @@ module PrinterExtensions = // (fun p -> p.PrintStatementSeparator()), // ?skipNewLineAtEnd=skipNewLineAtEnd) -// member printer.PrintOptional(before: string, node: #Node option) = -// match node with -// | None -> () -// | Some node -> -// printer.Print(before) -// node.Print(printer) - -// member printer.PrintOptional(before: string, node: #Node option, after: string) = -// match node with -// | None -> () -// | Some node -> -// printer.Print(before) -// node.Print(printer) -// printer.Print(after) - -// member printer.PrintOptional(node: #Node option) = -// match node with -// | None -> () -// | Some node -> node.Print(printer) - -// member printer.PrintArray(nodes: 'a array, printNode: Printer -> 'a -> unit, printSeparator: Printer -> unit) = -// for i = 0 to nodes.Length - 1 do -// printNode printer nodes.[i] -// if i < nodes.Length - 1 then -// printSeparator printer - -// member printer.PrintCommaSeparatedArray(nodes: Expression array) = -// printer.PrintArray(nodes, (fun p x -> p.SequenceExpressionWithParens(x)), (fun p -> p.Print(", "))) + member printer.PrintOptional(before: string, node: #AST option) = + match node with + | None -> () + | Some node -> + printer.Print(before) + node.Print(printer) + + member printer.PrintOptional(before: string, node: #AST option, after: string) = + match node with + | None -> () + | Some node -> + printer.Print(before) + node.Print(printer) + printer.Print(after) + + member printer.PrintOptional(node: #AST option) = + match node with + | None -> () + | Some node -> node.Print(printer) + + member printer.PrintArray(nodes: 'a array, printNode: Printer -> 'a -> unit, printSeparator: Printer -> unit) = + for i = 0 to nodes.Length - 1 do + printNode printer nodes.[i] + if i < nodes.Length - 1 then + printSeparator printer + + member printer.PrintCommaSeparatedArray(nodes: Expression array) = + printer.PrintArray(nodes, (fun p x -> p.SequenceExpressionWithParens(x)), (fun p -> p.Print(", "))) // member printer.PrintCommaSeparatedArray(nodes: #Node array) = // printer.PrintArray(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) @@ -193,39 +192,39 @@ module PrinterExtensions = // printer.Print(":") // printer.PrintBlock(body.Body, skipNewLineAtEnd=true) -// member printer.WithParens(expr: Expression) = -// printer.Print("(") -// expr.Print(printer) -// printer.Print(")") - -// member printer.SequenceExpressionWithParens(expr: Expression) = -// match expr with -// | :? SequenceExpression -> printer.WithParens(expr) -// | _ -> printer.Print(expr) - -// /// Surround with parens anything that can potentially conflict with operator precedence -// member printer.ComplexExpressionWithParens(expr: Expression) = -// match expr with -// | :? Undefined -// | :? NullLiteral -// | :? StringLiteral -// | :? BooleanLiteral -// | :? NumericLiteral -// | :? Identifier -// | :? MemberExpression -// | :? CallExpression -// | :? ThisExpression -// | :? Super -// | :? SpreadElement -// | :? ArrayExpression -// | :? ObjectExpression -> expr.Print(printer) -// | _ -> printer.WithParens(expr) - -// member printer.PrintOperation(left, operator, right, loc) = -// printer.AddLocation(loc) -// printer.ComplexExpressionWithParens(left) -// printer.Print(" " + operator + " ") -// printer.ComplexExpressionWithParens(right) + member printer.WithParens(expr: Expression) = + printer.Print("(") + expr.Print(printer) + printer.Print(")") + + member printer.SequenceExpressionWithParens(expr: Expression) = + match expr with + //| :? SequenceExpression -> printer.WithParens(expr) + | _ -> printer.Print(expr) + + /// Surround with parens anything that can potentially conflict with operator precedence + member printer.ComplexExpressionWithParens(expr: Expression) = + match expr with + // | :? Undefined + // | :? NullLiteral + // | :? StringLiteral + // | :? BooleanLiteral + // | :? NumericLiteral + // | :? Identifier + // | :? MemberExpression + // | :? CallExpression + // | :? ThisExpression + // | :? Super + // | :? SpreadElement + // | :? ArrayExpression + // | :? ObjectExpression -> expr.Print(printer) + | _ -> printer.WithParens(expr) + + member printer.PrintOperation(left, operator, right, loc) = + printer.AddLocation(loc) + printer.ComplexExpressionWithParens(left) + printer.Print(" " + operator + " ") + printer.ComplexExpressionWithParens(right) type AST = //int col @@ -275,13 +274,19 @@ type Statement () = type Module(body) = member _.Body: List = body + interface AST with + member this.Print(printer) = this.Print printer + + member this.Print(printer) = () type Alias(name, asname) = member _.Name: Identifier = name member _.AsName: Identifier option = asname interface AST with - member _.Print(printer) = () + member this.Print(printer) = this.Print printer + + member this.Print(printer) = () /// A single argument in a list. arg is a raw string of the argument name, annotation is its annotation, such as a Str /// or Name node. @@ -319,6 +324,46 @@ type Arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ? //#region Statements +/// An assignment. targets is a list of nodes, and value is a single node. +/// +/// Multiple nodes in targets represents assigning the same value to each. Unpacking is represented by putting a Tuple +/// or List within targets. +/// +/// type_comment is an optional string with the type annotation as a comment. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('a = b = 1'), indent=4)) # Multiple assignment +/// Module( +/// body=[ +/// Assign( +/// targets=[ +/// Name(id='a', ctx=Store()), +/// Name(id='b', ctx=Store())], +/// value=Constant(value=1))], +/// type_ignores=[]) +/// +/// >>> print(ast.dump(ast.parse('a,b = c'), indent=4)) # Unpacking +/// Module( +/// body=[ +/// Assign( +/// targets=[ +/// Tuple( +/// elts=[ +/// Name(id='a', ctx=Store()), +/// Name(id='b', ctx=Store())], +/// ctx=Store())], +/// value=Name(id='c', ctx=Load()))], +/// type_ignores=[]) +/// ``` +type Assign(targets, value, ?typeComment) = + inherit Statement () + + member _.Targets: Expression list = targets + member _.Value: Expression = value + member _.TypeComment: string option = typeComment + + override _.Print(printer) = () + /// When an expression, such as a function call, appears as a statement by itself with its return value not used or /// stored, it is wrapped in this container. value holds one of the other nodes in this section, a Constant, a Name, a /// Lambda, a Yield or YieldFrom node. @@ -339,6 +384,145 @@ type Expr(value) = override _.Print(printer) = () +/// A for loop. target holds the variable(s) the loop assigns to, as a single Name, Tuple or List node. iter holds the +/// item to be looped over, again as a single node. body and orelse contain lists of nodes to execute. Those in orelse +/// are executed if the loop finishes normally, rather than via a break statement. +/// +/// type_comment is an optional string with the type annotation as a comment. +/// +/// ```py +/// >>> print(ast.dump(ast.parse(""" +/// ... for x in y: +/// ... ... +/// ... else: +/// ... ... +/// ... """), indent=4)) +/// Module( +/// body=[ +/// For( +/// target=Name(id='x', ctx=Store()), +/// iter=Name(id='y', ctx=Load()), +/// body=[ +/// Expr( +/// value=Constant(value=Ellipsis))], +/// orelse=[ +/// Expr( +/// value=Constant(value=Ellipsis))])], +/// type_ignores=[]) +///``` +type For(target, iter, body, orelse, ?typeComment) = + inherit Statement () + + member _.Target: Expression = target + member _.Iterator: Expression = iter + member _.Body: Statement list = body + member _.Else: Statement list = orelse + member _.TypeComment: string option = typeComment + + override _.Print(printer) = () + +type AsyncFor(target, iter, body, orelse, ?typeComment) = + inherit Statement () + + member _.Target: Expression = target + member _.Iterator: Expression = iter + member _.Body: Statement list = body + member _.Else: Statement list = orelse + member _.TypeComment: string option = typeComment + + override _.Print(printer) = () + +/// A while loop. test holds the condition, such as a Compare node. +/// +/// ```py +/// >> print(ast.dump(ast.parse(""" +/// ... while x: +/// ... ... +/// ... else: +/// ... ... +/// ... """), indent=4)) +/// Module( +/// body=[ +/// While( +/// test=Name(id='x', ctx=Load()), +/// body=[ +/// Expr( +/// value=Constant(value=Ellipsis))], +/// orelse=[ +/// Expr( +/// value=Constant(value=Ellipsis))])], +/// type_ignores=[]) +/// ``` +type While(test, body, orelse) = + inherit Statement () + + member _.Test: Expression = test + member _.Body: Statement list = body + member _.Else: Statement list = orelse + + override _.Print(printer) = () + +/// An if statement. test holds a single node, such as a Compare node. body and orelse each hold a list of nodes. +/// +/// elif clauses don’t have a special representation in the AST, but rather appear as extra If nodes within the orelse +/// section of the previous one. +/// +/// ```py +/// >>> print(ast.dump(ast.parse(""" +/// ... if x: +/// ... ... +/// ... elif y: +/// ... ... +/// ... else: +/// ... ... +/// ... """), indent=4)) +/// Module( +/// body=[ +/// If( +/// test=Name(id='x', ctx=Load()), +/// body=[ +/// Expr( +/// value=Constant(value=Ellipsis))], +/// orelse=[ +/// If( +/// test=Name(id='y', ctx=Load()), +/// body=[ +/// Expr( +/// value=Constant(value=Ellipsis))], +/// orelse=[ +/// Expr( +/// value=Constant(value=Ellipsis))])])], +/// type_ignores=[]) +/// ``` +type If(test, body, orelse) = + inherit Statement () + + member _.Test: Expression = test + member _.Body: Statement list = body + member _.Else: Statement list = orelse + + override _.Print(printer) = () + +/// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone +/// raise. cause is the optional part for y in raise x from y. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('raise x from y'), indent=4)) +/// Module( +/// body=[ +/// Raise( +/// exc=Name(id='x', ctx=Load()), +/// cause=Name(id='y', ctx=Load()))], +/// type_ignores=[]) +/// ``` +type Raise(exc, ?cause) = + inherit Statement () + + member _.Exception: Expression = exc + member _.Cause: Expression option = cause + + override _.Print(printer) = () + /// A function definition. /// /// - name is a raw string of the function name. @@ -361,6 +545,73 @@ type FunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = override _.Print(printer) = () +/// global and nonlocal statements. names is a list of raw strings. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('global x,y,z'), indent=4)) +/// Module( +/// body=[ +/// Global( +/// names=[ +/// 'x', +/// 'y', +/// 'z'])], +/// type_ignores=[]) +/// +/// ``` +type Global(names) = + inherit Statement () + + member _.Names : Identifier list = names + + override _.Print(printer) = () + +/// global and nonlocal statements. names is a list of raw strings. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('nonlocal x,y,z'), indent=4)) +/// Module( +/// body=[ +/// Nonlocal( +/// names=[ +/// 'x', +/// 'y', +/// 'z'])], +/// type_ignores=[]) +/// ````` +type NonLocal(names) = + inherit Statement () + + member _.Names : Identifier list = names + + override _.Print(printer) = () + +/// A pass statement. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('pass'), indent=4)) +/// Module( +/// body=[ +/// Pass()], +/// type_ignores=[]) +/// ``` +type Pass() = + inherit Statement () + + override _.Print(printer) = () + +/// The break statement. +type Break() = + inherit Statement () + + override _.Print(printer) = () + +/// The continue statement. +type Continue() = + inherit Statement () + + override _.Print(printer) = () + /// An async function definition. /// /// - name is a raw string of the function name. diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs new file mode 100644 index 0000000000..7e28910d2f --- /dev/null +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -0,0 +1,114 @@ +module Fable.Transforms.PythonPrinter + +open System +open Fable +open Fable.AST +open Fable.AST.Python + +type SourceMapGenerator = + abstract AddMapping: + originalLine: int + * originalColumn: int + * generatedLine: int + * generatedColumn: int + * ?name: string + -> unit + +type Writer = + inherit IDisposable + abstract MakeImportPath: string -> string + abstract Write: string -> Async + +type PrinterImpl(writer: Writer, map: SourceMapGenerator) = + // TODO: We can make this configurable later + let indentSpaces = " " + let builder = Text.StringBuilder() + let mutable indent = 0 + let mutable line = 1 + let mutable column = 0 + + let addLoc (loc: SourceLocation option) = + match loc with + | None -> () + | Some loc -> + map.AddMapping(originalLine = loc.start.line, + originalColumn = loc.start.column, + generatedLine = line, + generatedColumn = column, + ?name = loc.identifierName) + + member _.Flush(): Async = + async { + do! writer.Write(builder.ToString()) + builder.Clear() |> ignore + } + + member _.PrintNewLine() = + builder.AppendLine() |> ignore + line <- line + 1 + column <- 0 + + interface IDisposable with + member _.Dispose() = writer.Dispose() + + interface Printer with + member _.Line = line + member _.Column = column + + member _.PushIndentation() = + indent <- indent + 1 + + member _.PopIndentation() = + if indent > 0 then indent <- indent - 1 + + member _.AddLocation(loc) = + addLoc loc + + member _.Print(str, loc) = + addLoc loc + + if column = 0 then + let indent = String.replicate indent indentSpaces + builder.Append(indent) |> ignore + column <- indent.Length + + builder.Append(str) |> ignore + column <- column + str.Length + + member this.PrintNewLine() = + this.PrintNewLine() + + member this.MakeImportPath(path) = + writer.MakeImportPath(path) + +let run writer map (program: Module): Async = + + let printDeclWithExtraLine extraLine (printer: Printer) (decl: Statement) = + decl.Print(printer) + + if printer.Column > 0 then + //printer.Print(";") + printer.PrintNewLine() + if extraLine then + printer.PrintNewLine() + + async { + use printer = new PrinterImpl(writer, map) + + let imports, restDecls = + program.Body |> List.splitWhile (function + | :? Import + | :? ImportFrom -> true + | _ -> false) + + for decl in imports do + printDeclWithExtraLine false printer decl + + printer.PrintNewLine() + do! printer.Flush() + + for decl in restDecls do + printDeclWithExtraLine true printer decl + // TODO: Only flush every XXX lines? + do! printer.Flush() + } diff --git a/src/Fable.Transforms/Python/README.md b/src/Fable.Transforms/Python/README.md new file mode 100644 index 0000000000..560cee80fd --- /dev/null +++ b/src/Fable.Transforms/Python/README.md @@ -0,0 +1,3 @@ +# Fable to Python + +Experimental support for Python. First try is to transform the Babel AST into a Python AST. From 69c1177a27cac833157c60c42b0ed39cdecb0334 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 11 Jan 2021 21:32:48 +0100 Subject: [PATCH 009/145] Transform from Babel AST to Python AST --- build.fsx | 2 +- src/Fable.AST/Plugins.fs | 6 +- src/Fable.Cli/Main.fs | 29 + src/Fable.Transforms/BabelPrinter.fs | 4 +- src/Fable.Transforms/Fable.Transforms.fsproj | 4 + src/Fable.Transforms/Fable2Babel.fs | 60 ++- src/Fable.Transforms/Global/Babel.fs | 112 ++-- src/Fable.Transforms/Global/Compiler.fs | 4 +- src/Fable.Transforms/Python/Babel2Python.fs | 178 +++++-- src/Fable.Transforms/Python/Python.fs | 527 +++++++++++++------ 10 files changed, 615 insertions(+), 311 deletions(-) diff --git a/build.fsx b/build.fsx index d0b30713d2..401afd2005 100644 --- a/build.fsx +++ b/build.fsx @@ -520,7 +520,7 @@ match argsLower with | "test-integration"::_ -> testIntegration() | "quicktest"::_ -> buildLibraryIfNotExists() - run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --forcePkgs --extension .fs.py false" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --forcePkgs false" | "run"::_ -> buildLibraryIfNotExists() diff --git a/src/Fable.AST/Plugins.fs b/src/Fable.AST/Plugins.fs index e4dbe41918..cb2daf8ef3 100644 --- a/src/Fable.AST/Plugins.fs +++ b/src/Fable.AST/Plugins.fs @@ -9,10 +9,14 @@ type Verbosity = | Verbose | Silent +type Language = + | JavaScript + | TypeScript + type CompilerOptions = abstract TypedArrays: bool abstract ClampByteArrays: bool - abstract Typescript: bool + abstract Language: Language abstract Define: string list abstract DebugMode: bool abstract OptimizeFSharpAst: bool diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index 97cde04872..ebc3abd766 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -139,6 +139,7 @@ module private Util = if fileExt.EndsWith(".ts") then Path.replaceExtension ".js" fileExt else fileExt let targetDir = Path.GetDirectoryName(targetPath) let stream = new IO.StreamWriter(targetPath) + interface BabelPrinter.Writer with member _.Write(str) = stream.WriteAsync(str) |> Async.AwaitTask @@ -153,12 +154,35 @@ module private Util = else path member _.Dispose() = stream.Dispose() + type PythonFileWriter(sourcePath: string, targetPath: string, cliArgs: CliArgs, dedupTargetDir) = + let fileExt = ".py" + let targetDir = Path.GetDirectoryName(targetPath) + let targetPath = sourcePath + fileExt // Override + let stream = new IO.StreamWriter(targetPath) + + do printfn $"PythonFileWriter: {sourcePath}, {targetPath}" + + interface PythonPrinter.Writer with + member _.Write(str) = + stream.WriteAsync(str) |> Async.AwaitTask + member _.MakeImportPath(path) = + let projDir = IO.Path.GetDirectoryName(cliArgs.ProjectFile) + let path = Imports.getImportPath dedupTargetDir sourcePath targetPath projDir cliArgs.OutDir path + if path.EndsWith(".fs") then + let isInFableHiddenDir = Path.Combine(targetDir, path) |> Naming.isInFableHiddenDir + changeFsExtension isInFableHiddenDir path fileExt + else path + member _.Dispose() = stream.Dispose() + let compileFile (cliArgs: CliArgs) dedupTargetDir (com: CompilerImpl) = async { try let babel = FSharp2Fable.Compiler.transformFile com |> FableTransforms.transformFile com |> Fable2Babel.Compiler.transformFile com + let python = + babel + |> Babel2Python.Compiler.transformFile com // TODO: Dummy interface until we have a dotnet port of SourceMapGenerator // https://github.com/mozilla/source-map#with-sourcemapgenerator-low-level-api @@ -175,6 +199,11 @@ module private Util = let writer = new FileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) do! BabelPrinter.run writer map babel + let map = { new PythonPrinter.SourceMapGenerator with + member _.AddMapping(_,_,_,_,_) = () } + let writer = new PythonFileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) + do! PythonPrinter.run writer map python + Log.always("Compiled " + File.getRelativePathFromCwd com.CurrentFile) return Ok {| File = com.CurrentFile diff --git a/src/Fable.Transforms/BabelPrinter.fs b/src/Fable.Transforms/BabelPrinter.fs index d72dba88b3..7bbb8df954 100644 --- a/src/Fable.Transforms/BabelPrinter.fs +++ b/src/Fable.Transforms/BabelPrinter.fs @@ -91,7 +91,7 @@ let run writer map (program: Program): Async = decl.Print(printer) if printer.Column > 0 then - //printer.Print(";") + printer.Print(";") printer.PrintNewLine() if extraLine then printer.PrintNewLine() @@ -114,4 +114,4 @@ let run writer map (program: Program): Async = printDeclWithExtraLine true printer decl // TODO: Only flush every XXX lines? do! printer.Flush() - } + } \ No newline at end of file diff --git a/src/Fable.Transforms/Fable.Transforms.fsproj b/src/Fable.Transforms/Fable.Transforms.fsproj index 367c27f9c5..ffdf7302ce 100644 --- a/src/Fable.Transforms/Fable.Transforms.fsproj +++ b/src/Fable.Transforms/Fable.Transforms.fsproj @@ -21,6 +21,10 @@ + + + + diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 57bf432bc9..779ec0b30d 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -513,7 +513,7 @@ module Annotation = upcast AnyTypeAnnotation() // TODO: let typedIdent (com: IBabelCompiler) ctx (id: Fable.Ident) = - if com.Options.Typescript then + if com.Options.Language = TypeScript then let ta = typeAnnotation com ctx id.Type |> TypeAnnotation |> Some let optional = None // match id.Type with | Fable.Option _ -> Some true | _ -> None Identifier(id.Name, ?optional=optional, ?typeAnnotation=ta, ?loc=id.Range) @@ -521,7 +521,7 @@ module Annotation = Identifier(id.Name, ?loc=id.Range) let transformFunctionWithAnnotations (com: IBabelCompiler) ctx name (args: Fable.Ident list) (body: Fable.Expr) = - if com.Options.Typescript then + if com.Options.Language = TypeScript then let argTypes = args |> List.map (fun id -> id.Type) let genTypeParams = Util.getGenericTypeParams (argTypes @ [body.Type]) let newTypeParams = Set.difference genTypeParams ctx.ScopedTypeParams @@ -745,8 +745,7 @@ module Util = ExpressionStatement(callSuper args) :> Statement let makeClassConstructor args body = - //let args = args |> Seq.toList |> (fun args -> (Identifier "self" :> Pattern)::args) |> Array.ofList - ClassMethod(ClassImplicitConstructor, Identifier "__init__", args, body) :> ClassMember + ClassMethod(ClassImplicitConstructor, Identifier "constructor", args, body) :> ClassMember let callFunction r funcExpr (args: Expression list) = CallExpression(funcExpr, List.toArray args, ?loc=r) :> Expression @@ -759,8 +758,8 @@ module Util = EmitExpression(txt, List.toArray args, ?loc=range) :> Expression let undefined range = - Undefined(?loc=range) :> Expression - //UnaryExpression(UnaryVoid, NumericLiteral(1.), ?loc=range) :> Expression +// Undefined(?loc=range) :> Expression + UnaryExpression(UnaryVoid, NumericLiteral(0.), ?loc=range) :> Expression let getGenericTypeParams (types: Fable.Type list) = let rec getGenParams = function @@ -790,7 +789,7 @@ module Util = let body = // TODO: If ident is not captured maybe we can just replace it with "this" if FableTransforms.isIdentUsed thisArg.Name body then - let thisKeyword = Fable.IdentExpr { thisArg with Name = "self" } + let thisKeyword = Fable.IdentExpr { thisArg with Name = "this" } Fable.Let(thisArg, thisKeyword, body) else body None, genTypeParams, args, body @@ -803,7 +802,7 @@ module Util = let args, body, returnType, typeParamDecl = transformFunctionWithAnnotations com ctx funcName args body let typeParamDecl = - if com.Options.Typescript then + if com.Options.Language = TypeScript then makeTypeParamDecl genTypeParams |> mergeTypeParamDecls typeParamDecl else typeParamDecl @@ -888,6 +887,10 @@ module Util = match tag with | Some (Naming.StartsWith "optimizable:" optimization) -> match optimization, e with + | "array", Fable.Call(_,info,_,_) -> + match info.Args with + | [Replacements.ArrayOrListLiteral(vals,_)] -> Fable.Value(Fable.NewArray(vals, Fable.Any), e.Range) |> Some + | _ -> None | "pojo", Fable.Call(_,info,_,_) -> match info.Args with | keyValueList::caseRule::_ -> Replacements.makePojo com (Some caseRule) keyValueList @@ -947,7 +950,7 @@ module Util = libCall com ctx r "List" "singleton" [|expr|] | exprs, None -> [|makeArray com ctx exprs|] - |> libCall com ctx r "List" "of_seq" + |> libCall com ctx r "List" "ofArray" | [TransformExpr com ctx head], Some(TransformExpr com ctx tail) -> libCall com ctx r "List" "cons" [|head; tail|] | exprs, Some(TransformExpr com ctx tail) -> @@ -966,7 +969,7 @@ module Util = let values = List.mapToArray (fun x -> com.TransformAsExpr(ctx, x)) values let consRef = com.GetEntity(ent) |> jsConstructor com ctx let typeParamInst = - if com.Options.Typescript && (ent.FullName = Types.reference) + if com.Options.Language = TypeScript && (ent.FullName = Types.reference) then makeGenTypeParamInst com ctx genArgs else None upcast NewExpression(consRef, values, ?typeArguments=typeParamInst, ?loc=r) @@ -978,7 +981,7 @@ module Util = let consRef = com.GetEntity(ent) |> jsConstructor com ctx let values = List.map (fun x -> com.TransformAsExpr(ctx, x)) values let typeParamInst = - if com.Options.Typescript + if com.Options.Language = TypeScript then makeGenTypeParamInst com ctx genArgs else None // let caseName = ent.UnionCases |> List.item tag |> getUnionCaseName |> ofString @@ -1099,8 +1102,7 @@ module Util = let resolveExpr t strategy babelExpr: Statement = match strategy with - | None -> upcast ExpressionStatement babelExpr - | Some ReturnUnit -> upcast ReturnStatement(wrapIntExpression t babelExpr) + | None | Some ReturnUnit -> upcast ExpressionStatement babelExpr // TODO: Where to put these int wrappings? Add them also for function arguments? | Some Return -> upcast ReturnStatement(wrapIntExpression t babelExpr) | Some(Assign left) -> upcast ExpressionStatement(assign None left babelExpr) @@ -1221,7 +1223,7 @@ module Util = | Fable.OptionValue -> let expr = com.TransformAsExpr(ctx, fableExpr) - if mustWrapOption typ || com.Options.Typescript + if mustWrapOption typ || com.Options.Language = TypeScript then libCall com ctx None "Option" "value" [|expr|] else expr @@ -1459,7 +1461,7 @@ module Util = // If some targets are referenced multiple times, hoist bound idents, // resolve the decision index and compile the targets as a switch let targetsWithMultiRefs = - if com.Options.Typescript then [] // no hoisting when compiled with types + if com.Options.Language = TypeScript then [] // no hoisting when compiled with types else getTargetsWithMultipleReferences treeExpr match targetsWithMultiRefs with | [] -> @@ -1735,9 +1737,12 @@ module Util = BlockStatement(Array.append [|varDeclStatement|] body.Body) args |> List.mapToArray (fun a -> a :> Pattern), body - let declareEntryPoint _com _ctx (name: string) (funcExpr: Expression) = + let declareEntryPoint _com _ctx (funcExpr: Expression) = let argv = emitExpression None "typeof process === 'object' ? process.argv.slice(2) : []" [] - PrivateMainModuleDeclaration(ExpressionStatement(funcExpr)) :> ModuleDeclaration + let main = CallExpression (funcExpr, [|argv|]) :> Expression + // Don't exit the process after leaving main, as there may be a server running + // ExpressionStatement(emitExpression funcExpr.loc "process.exit($0)" [main], ?loc=funcExpr.loc) + PrivateModuleDeclaration(ExpressionStatement(main)) :> ModuleDeclaration let declareModuleMember isPublic membName isMutable (expr: Expression) = let membName = Identifier membName @@ -1761,7 +1766,7 @@ module Util = else ExportNamedDeclaration(decl) :> _ let makeEntityTypeParamDecl (com: IBabelCompiler) _ctx (ent: Fable.Entity) = - if com.Options.Typescript then + if com.Options.Language = TypeScript then getEntityGenParams ent |> makeTypeParamDecl else None @@ -1815,13 +1820,13 @@ module Util = let declareClassType (com: IBabelCompiler) ctx (ent: Fable.Entity) entName (consArgs: Pattern[]) (consBody: BlockStatement) (baseExpr: Expression option) classMembers = let typeParamDecl = makeEntityTypeParamDecl com ctx ent let implements = - if com.Options.Typescript then + if com.Options.Language = TypeScript then let implements = Util.getClassImplements com ctx ent |> Seq.toArray if Array.isEmpty implements then None else Some implements else None let classCons = makeClassConstructor consArgs consBody let classFields = - if com.Options.Typescript then + if com.Options.Language = TypeScript then getEntityFieldsAsProps com ctx ent |> Array.map (fun prop -> let ta = prop.Value |> TypeAnnotation |> Some @@ -1836,7 +1841,7 @@ module Util = let typeDeclaration = declareClassType com ctx ent entName consArgs consBody baseExpr classMembers let reflectionDeclaration = let ta = - if com.Options.Typescript then + if com.Options.Language = TypeScript then makeImportTypeAnnotation com ctx [] "Reflection" "TypeInfo" |> TypeAnnotation |> Some else None @@ -1852,14 +1857,11 @@ module Util = let transformModuleFunction (com: IBabelCompiler) ctx (info: Fable.MemberInfo) (membName: string) args body = let args, body, returnType, typeParamDecl = getMemberArgsAndBody com ctx (NonAttached membName) info.HasSpread args body - - let id = (Identifier membName) |> Some - let expr = FunctionExpression(args, body, ?id=id, ?returnType=returnType, ?typeParameters=typeParamDecl) :> Expression + let expr = FunctionExpression(args, body, ?returnType=returnType, ?typeParameters=typeParamDecl) :> Expression info.Attributes |> Seq.exists (fun att -> att.Entity.FullName = Atts.entryPoint) |> function - | true -> - declareEntryPoint com ctx membName expr + | true -> declareEntryPoint com ctx expr | false -> declareModuleMember info.IsPublic membName false expr let transformAction (com: IBabelCompiler) ctx expr = @@ -1922,7 +1924,7 @@ module Util = |> ReturnStatement :> Statement |> Array.singleton |> BlockStatement - ClassMethod(ClassFunction, Identifier "cases", [||], body, ``static``=true) :> ClassMember + ClassMethod(ClassFunction, Identifier "cases", [||], body) :> ClassMember let baseExpr = libValue com ctx "Types" "Union" |> Some let classMembers = Array.append [|cases|] classMembers @@ -1959,7 +1961,7 @@ module Util = let returnType, typeParamDecl = // change constructor's return type from void to entity type - if com.Options.Typescript then + if com.Options.Language = TypeScript then let genParams = getEntityGenParams classEnt let returnType = getGenericTypeAnnotation com ctx classDecl.Name genParams let typeParamDecl = makeTypeParamDecl genParams |> mergeTypeParamDecls typeParamDecl @@ -2170,4 +2172,4 @@ module Compiler = let rootDecls = List.collect (transformDeclaration com ctx) file.Declarations let importDecls = com.GetAllImports() |> transformImports let body = importDecls @ rootDecls |> List.toArray - Program(body) + Program(body) \ No newline at end of file diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index 573cce0de7..90d5a2e955 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -21,20 +21,20 @@ module PrinterExtensions = member printer.PrintBlock(nodes: 'a array, printNode: Printer -> 'a -> unit, printSeparator: Printer -> unit, ?skipNewLineAtEnd) = let skipNewLineAtEnd = defaultArg skipNewLineAtEnd false - printer.Print("") + printer.Print("{") printer.PrintNewLine() printer.PushIndentation() for node in nodes do printNode printer node printSeparator printer printer.PopIndentation() - printer.Print("") + printer.Print("}") if not skipNewLineAtEnd then printer.PrintNewLine() member printer.PrintStatementSeparator() = if printer.Column > 0 then - printer.Print("") + printer.Print(";") printer.PrintNewLine() member _.IsProductiveStatement(s: Statement) = @@ -79,14 +79,6 @@ module PrinterExtensions = printer.Print(before) node.Print(printer) - member printer.PrintOptional(before: string, node: #Node option, after: string) = - match node with - | None -> () - | Some node -> - printer.Print(before) - node.Print(printer) - printer.Print(after) - member printer.PrintOptional(node: #Node option) = match node with | None -> () @@ -116,14 +108,14 @@ module PrinterExtensions = | Some (:? Identifier as id) when id.TypeAnnotation.IsSome -> printer.Print(" extends "); printer.Print(id.TypeAnnotation.Value.TypeAnnotation) - | _ -> printer.PrintOptional("(", superClass, ")") + | _ -> printer.PrintOptional(" extends ", superClass) // printer.PrintOptional(superTypeParameters) match implements with | Some implements when not (Array.isEmpty implements) -> printer.Print(" implements ") printer.PrintArray(implements, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) | _ -> () - printer.Print(":") + printer.Print(" ") printer.Print(body) member printer.PrintFunction(id: Identifier option, parameters: Pattern array, body: BlockStatement, @@ -160,10 +152,11 @@ module PrinterExtensions = if isArrow then // Remove parens if we only have one argument? (and no annotation) printer.PrintOptional(typeParameters) - printer.Print("lambda ") + printer.Print("(") printer.PrintCommaSeparatedArray(parameters) + printer.Print(")") printer.PrintOptional(returnType) - printer.Print(": ") + printer.Print(" => ") match body.Body with | [|:? ReturnStatement as r |] -> match r.Argument with @@ -175,14 +168,14 @@ module PrinterExtensions = | _ -> printer.ComplexExpressionWithParens(r.Argument) | _ -> printer.PrintBlock(body.Body, skipNewLineAtEnd=true) else - printer.Print("def ") + printer.Print("function ") printer.PrintOptional(id) printer.PrintOptional(typeParameters) printer.Print("(") printer.PrintCommaSeparatedArray(parameters) printer.Print(")") printer.PrintOptional(returnType) - printer.Print(":") + printer.Print(" ") printer.PrintBlock(body.Body, skipNewLineAtEnd=true) member printer.WithParens(expr: Expression) = @@ -317,7 +310,7 @@ type EmitExpression(value, args, ?loc) = let argIndex = int m.Value.[1..] match Array.tryItem argIndex args with | Some e -> printer.ComplexExpressionWithParens(e) - | None -> printer.Print("pass") + | None -> printer.Print("undefined") let lastMatch = matches.[matches.Count - 1] printSegment printer value (lastMatch.Index + lastMatch.Length) value.Length @@ -375,12 +368,12 @@ type Undefined(?loc) = // TODO: Use `void 0` instead? Just remove this node? interface Expression with member _.Print(printer) = - printer.Print("None", ?loc=loc) + printer.Print("undefined", ?loc=loc) type NullLiteral(?loc) = interface Literal with member _.Print(printer) = - printer.Print("None", ?loc=loc) + printer.Print("null", ?loc=loc) type StringLiteral(value, ?loc) = member _.Value: string = value @@ -606,14 +599,14 @@ type VariableDeclarator(id, ?init) = type VariableDeclarationKind = Var | Let | Const type VariableDeclaration(kind_, declarations, ?loc) = - let kind = match kind_ with Var -> "" | Let -> "" | Const -> "" + let kind = match kind_ with Var -> "var" | Let -> "let" | Const -> "const" new (var, ?init, ?kind, ?loc) = VariableDeclaration(defaultArg kind Let, [|VariableDeclarator(var, ?init=init)|], ?loc=loc) member _.Declarations: VariableDeclarator array = declarations member _.Kind: string = kind interface Declaration with member _.Print(printer) = - printer.Print(kind, ?loc=loc) + printer.Print(kind + " ", ?loc=loc) let canConflict = declarations.Length > 1 for i = 0 to declarations.Length - 1 do let decl = declarations.[i] @@ -704,7 +697,7 @@ type Super(?loc) = type ThisExpression(?loc) = interface Expression with member _.Print(printer) = - printer.Print("self", ?loc=loc) + printer.Print("this", ?loc=loc) /// A fat arrow function expression, e.g., let foo = (bar) => { /* body */ }. type ArrowFunctionExpression(``params``, body: BlockStatement, ?returnType, ?typeParameters, ?loc) = //?async_, ?generator_, @@ -839,7 +832,7 @@ type ObjectMethod(kind_, key, ``params``, body, ?computed_, ?returnType, ?typePa printer.PrintCommaSeparatedArray(``params``) printer.Print(")") printer.PrintOptional(returnType) - printer.Print(":") + printer.Print(" ") printer.PrintBlock(body.Body, skipNewLineAtEnd=true) @@ -921,7 +914,7 @@ type NewExpression(callee, arguments, ?typeArguments, ?loc) = member _.TypeArguments: TypeParameterInstantiation option = typeArguments interface Expression with member _.Print(printer) = - printer.Print("", ?loc=loc) + printer.Print("new ", ?loc=loc) printer.ComplexExpressionWithParens(callee) printer.Print("(") printer.PrintCommaSeparatedArray(arguments) @@ -1034,8 +1027,8 @@ type AssignmentExpression(operator_, left, right, ?loc) = type LogicalExpression(operator_, left, right, ?loc) = let operator = match operator_ with - | LogicalOr -> "or" - | LogicalAnd-> "and" + | LogicalOr -> "||" + | LogicalAnd-> "&&" member _.Left: Expression = left member _.Right: Expression = right member _.Operator: string = operator @@ -1070,7 +1063,7 @@ type RestElement(argument, ?typeAnnotation, ?loc) = interface Pattern with member _.Name = argument.Name member _.Print(printer) = - printer.Print("*", ?loc=loc) + printer.Print("...", ?loc=loc) argument.Print(printer) printer.PrintOptional(typeAnnotation) @@ -1083,7 +1076,7 @@ type ClassMethodKind = type ClassMethod(kind_, key, ``params``, body, ?computed_, ?``static``, ?``abstract``, ?returnType, ?typeParameters, ?loc) = let kind = match kind_ with - | ClassImplicitConstructor -> "__init__" + | ClassImplicitConstructor -> "constructor" | ClassGetter -> "get" | ClassSetter -> "set" | ClassFunction -> "method" @@ -1104,17 +1097,14 @@ type ClassMethod(kind_, key, ``params``, body, ?computed_, ?``static``, ?``abstr printer.AddLocation(loc) let keywords = [ - if ``static`` = Some true then yield "@staticmethod" - if ``abstract`` = Some true then yield "@abstractmethod" - if kind = "get" || kind = "set" then yield "@property" + if ``static`` = Some true then yield "static" + if ``abstract`` = Some true then yield "abstract" + if kind = "get" || kind = "set" then yield kind ] if not (List.isEmpty keywords) then - printer.PrintNewLine() - printer.Print(String.concat " " keywords) - printer.PrintNewLine() + printer.Print((String.concat " " keywords) + " ") - printer.Print("def ") if computed then printer.Print("[") key.Print(printer) @@ -1122,18 +1112,12 @@ type ClassMethod(kind_, key, ``params``, body, ?computed_, ?``static``, ?``abstr else key.Print(printer) - let args = - if ``static`` = Some true - then ``params`` - else - let self = Identifier "self" :> Pattern |> Array.singleton - Array.concat [self; ``params``] printer.PrintOptional(typeParameters) printer.Print("(") - printer.PrintCommaSeparatedArray(args) + printer.PrintCommaSeparatedArray(``params``) printer.Print(")") printer.PrintOptional(returnType) - printer.Print(":") + printer.Print(" ") printer.Print(body) @@ -1215,22 +1199,6 @@ type PrivateModuleDeclaration(statement) = if printer.IsProductiveStatement(statement) then printer.Print(statement) - -type PrivateMainModuleDeclaration(statement) = - member _.Statement: Statement = statement - interface ModuleDeclaration with - member _.Print(printer) = - if printer.IsProductiveStatement(statement) then - printer.Print(statement) - printer.PrintNewLine() - printer.Print("if __name__ == '__main__':") - printer.PrintNewLine() - printer.PushIndentation() - printer.Print("import sys") // FIXME: now to move this to the top of the file? - printer.PrintNewLine() - printer.Print("main(sys.argv)") - printer.PopIndentation() - type ImportSpecifier = inherit Node /// An imported variable binding, e.g., {foo} in import {foo} from "mod" or {foo as bar} in import {foo as bar} from "mod". @@ -1271,12 +1239,7 @@ type ImportDeclaration(specifiers, source) = let defaults = specifiers|> Array.choose (function :? ImportDefaultSpecifier as x -> Some x | _ -> None) let namespaces = specifiers |> Array.choose (function :? ImportNamespaceSpecifier as x -> Some x | _ -> None) - printer.Print("from ") - - printer.Print(printer.MakeImportPath(source.Value)) - - if not(Array.isEmpty defaults && Array.isEmpty namespaces && Array.isEmpty members) then - printer.Print(" import ") + printer.Print("import ") if not(Array.isEmpty defaults) then printer.PrintCommaSeparatedArray(defaults) @@ -1289,9 +1252,16 @@ type ImportDeclaration(specifiers, source) = printer.Print(", ") if not(Array.isEmpty members) then - printer.Print("(") + printer.Print("{ ") printer.PrintCommaSeparatedArray(members) - printer.Print(")") + printer.Print(" }") + + if not(Array.isEmpty defaults && Array.isEmpty namespaces && Array.isEmpty members) then + printer.Print(" from ") + + printer.Print("\"") + printer.Print(printer.MakeImportPath(source.Value)) + printer.Print("\"") /// An exported variable binding, e.g., {foo} in export {foo} or {bar as foo} in export {bar as foo}. /// The exported field refers to the name exported in the module. @@ -1315,7 +1285,7 @@ type ExportNamedDeclaration(declaration) = member _.Declaration: Declaration = declaration interface ModuleDeclaration with member _.Print(printer) = - printer.Print("") + printer.Print("export ") printer.Print(declaration) type ExportNamedReferences(specifiers, ?source) = @@ -1406,7 +1376,7 @@ type AnyTypeAnnotation() = type VoidTypeAnnotation() = interface TypeAnnotationInfo with member _.Print(printer) = - printer.Print("None") + printer.Print("void") type TupleTypeAnnotation(types) = member _.Types: TypeAnnotationInfo array = types @@ -1569,4 +1539,4 @@ type InterfaceDeclaration(id, body, ?extends_, ?typeParameters, ?implements_) = printer.Print(" implements ") printer.PrintArray(implements, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) printer.Print(" ") - printer.Print(body) + printer.Print(body) \ No newline at end of file diff --git a/src/Fable.Transforms/Global/Compiler.fs b/src/Fable.Transforms/Global/Compiler.fs index 9ade9fb323..ffd89fed17 100644 --- a/src/Fable.Transforms/Global/Compiler.fs +++ b/src/Fable.Transforms/Global/Compiler.fs @@ -14,10 +14,12 @@ type CompilerOptionsHelper = ?clampByteArrays) = let define = defaultArg define [] let isDebug = List.contains "DEBUG" define + let language = typescript |> Option.bind(fun ts -> if ts then Some TypeScript else None) + { new CompilerOptions with member _.Define = define member _.DebugMode = isDebug - member _.Typescript = defaultArg typescript false + member _.Language = defaultArg language JavaScript member _.TypedArrays = defaultArg typedArrays true member _.OptimizeFSharpAst = defaultArg optimizeFSharpAst false member _.Verbosity = defaultArg verbosity Verbosity.Normal diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index f1c3c00e53..559870a355 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -1,11 +1,13 @@ module rec Fable.Transforms.Babel2Python open System +open System.Collections.Generic + open Fable open Fable.AST open Fable.AST.Python -open System.Collections.Generic +[] type ReturnStrategy = | Return | ReturnUnit @@ -13,9 +15,9 @@ type ReturnStrategy = | Target of Identifier type Import = - { Selector: string - LocalIdent: string option - Path: string } + { Selector: string + LocalIdent: string option + Path: string } type ITailCallOpportunity = abstract Label: string @@ -23,39 +25,136 @@ type ITailCallOpportunity = abstract IsRecursiveRef: Fable.Expr -> bool type UsedNames = - { RootScope: HashSet - DeclarationScopes: HashSet - CurrentDeclarationScope: HashSet } + { RootScope: HashSet + DeclarationScopes: HashSet + CurrentDeclarationScope: HashSet } type Context = - { //UsedNames: UsedNames - DecisionTargets: (Fable.Ident list * Fable.Expr) list - HoistVars: Fable.Ident list -> bool - TailCallOpportunity: ITailCallOpportunity option - OptimizeTailCall: unit -> unit - ScopedTypeParams: Set } + { //UsedNames: UsedNames + DecisionTargets: (Fable.Ident list * Fable.Expr) list + HoistVars: Fable.Ident list -> bool + TailCallOpportunity: ITailCallOpportunity option + OptimizeTailCall: unit -> unit + ScopedTypeParams: Set } type IPythonCompiler = inherit Compiler //abstract GetAllImports: unit -> seq //abstract GetImportExpr: Context * selector: string * path: string * SourceLocation option -> Expression - abstract TransformAsExpr: Context * Babel.Expression -> Python.Expression + abstract TransformAsExpr: Context * Babel.Expression -> Expression //abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Python.Statement list //abstract TransformImport: Context * selector:string * path:string -> Expression //abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> (Pattern array) * BlockStatement - abstract WarnOnlyOnce: string * ?range: SourceLocation -> unit + abstract WarnOnlyOnce: string * ?range:SourceLocation -> unit module Util = let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Babel.Expression): Expression = - printfn "transformAsExpr" - raise <| NotImplementedException () + printfn "transformAsExpr: %A" expr + + match expr with + | :? Babel.BinaryExpression as bin -> + let left = transformAsExpr com ctx bin.Left + let right = transformAsExpr com ctx bin.Right + + let op: Operator = + match bin.Operator with + | "+" -> Add() :> _ + | "-" -> Sub() :> _ + | "*" -> Mult() :> _ + | "/" -> Div() :> _ + | "%" -> Mod() :> _ + | "**" -> Pow() :> _ + | "<<" -> LShift() :> _ + | ">>" -> RShift() :> _ + | "|" -> BitOr() :> _ + | "^" -> BitXor() :> _ + | "&" -> BitAnd() :> _ + | _ -> failwith $"Unknown operator: {bin.Operator}" + + BinOp(left, op, right) :> _ + | :? Babel.UnaryExpression as ue -> + let op: UnaryOperator = + match ue.Operator with + | "-" -> USub() :> _ + | "+" -> UAdd() :> _ + | "~" -> Invert() :> _ + | "!" -> Not() :> _ + | a -> failwith $"Unhandled unary operator: {a}" + + let operand = transformAsExpr com ctx ue.Argument + UnaryOp(op, operand) :> _ + | :? Babel.ArrowFunctionExpression as afe -> + let args = afe.Params |> List.ofArray + |> List.map (fun pattern -> Arg(Identifier pattern.Name)) + let arguments = Arguments(args = args) + let body = transformAsStatements com ctx ReturnStrategy.Return afe.Body + Lambda(arguments, body) :> _ + | :? Babel.CallExpression as ce -> + let func = transformAsExpr com ctx ce.Callee + let args = ce.Arguments |> List.ofArray |> List.map (fun arg -> transformAsExpr com ctx arg) + Call(func, args) :> _ + | :? Babel.NumericLiteral as nl -> Constant(value = nl.Value) :> _ + | :? Babel.StringLiteral as sl -> Constant(value = sl.Value) :> _ + | :? Babel.Identifier as ident -> Name(id = Identifier ident.Name, ctx = Load()) :> _ + | a -> failwith $"Unhandled value: {a}" + + let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = + let stmt: Statement list = + [ for md in body do + match md with + | :? Babel.ExportNamedDeclaration as decl -> + match decl.Declaration with + | :? Babel.VariableDeclaration as decl -> + for decls in decl.Declarations do + let value: Expression = transformAsExpr com ctx decls.Init.Value + + let targets: Expression list = + [ Name(id = Identifier(decls.Id.Name), ctx = Store()) ] + + yield Assign(targets = targets, value = value) + | :? Babel.FunctionDeclaration as fd -> + let args = + fd.Params + |> List.ofArray + |> List.map (fun pattern -> Arg(Identifier pattern.Name)) + + let arguments = Arguments(args = args) + + let body = + transformAsStatements com ctx ReturnStrategy.Return fd.Body + + yield FunctionDef(Identifier fd.Id.Name, arguments, body = body) + | a -> printfn "Declaration: %A" a + | a -> printfn "Module %A" a ] + + Module(stmt) + + let rec transformAsStatements (com: IPythonCompiler) ctx returnStrategy (expr: Babel.Statement): Statement list = + match expr with + | :? Babel.BlockStatement as bl -> + [ for stmt in bl.Body do + yield! transformAsStatements com ctx returnStrategy stmt ] + | :? Babel.ReturnStatement as rtn -> + let expr = transformAsExpr com ctx rtn.Argument + [ Return expr ] + | :? Babel.VariableDeclaration as vd -> + [ for vd in vd.Declarations do + let targets: Expression list = + [ Name(id = Identifier(vd.Id.Name), ctx = Store()) ] + + match vd.Init with + | Some value -> Assign(targets, transformAsExpr com ctx value) + | None -> () ] + | a -> failwith $"Unhandled statement: {a}" let getIdentForImport (ctx: Context) (path: string) (selector: string) = - if System.String.IsNullOrEmpty selector then None + if System.String.IsNullOrEmpty selector then + None else match selector with - | "*" | "default" -> Path.GetFileNameWithoutExtension(path) + | "*" + | "default" -> Path.GetFileNameWithoutExtension(path) | _ -> selector //|> getUniqueNameInRootScope ctx |> Some @@ -63,9 +162,9 @@ module Util = module Compiler = open Util - type PythonCompiler (com: Compiler) = + type PythonCompiler(com: Compiler) = let onlyOnceWarnings = HashSet() - let imports = Dictionary() + let imports = Dictionary() interface IPythonCompiler with member _.WarnOnlyOnce(msg, ?range) = @@ -95,9 +194,9 @@ module Compiler = // | None -> upcast Babel.NullLiteral () //member _.GetAllImports() = upcast imports.Values member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e - //member bcom.TransformAsStatements(ctx, ret, e) = transformAsStatements bcom ctx ret e - //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body - //member bcom.TransformImport(ctx, selector, path) = transformImport bcom ctx None selector path + //member bcom.TransformAsStatements(ctx, ret, e) = transformAsStatements bcom ctx ret e + //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body + //member bcom.TransformImport(ctx, selector, path) = transformImport bcom ctx None selector path interface Compiler with member _.Options = com.Options @@ -107,15 +206,20 @@ module Compiler = member _.GetEntity(fullName) = com.GetEntity(fullName) member _.GetImplementationFile(fileName) = com.GetImplementationFile(fileName) member _.GetRootModule(fileName) = com.GetRootModule(fileName) - member _.GetOrAddInlineExpr(fullName, generate) = com.GetOrAddInlineExpr(fullName, generate) + + member _.GetOrAddInlineExpr(fullName, generate) = + com.GetOrAddInlineExpr(fullName, generate) + member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) - member _.AddLog(msg, severity, ?range, ?fileName:string, ?tag: string) = - com.AddLog(msg, severity, ?range=range, ?fileName=fileName, ?tag=tag) + + member _.AddLog(msg, severity, ?range, ?fileName: string, ?tag: string) = + com.AddLog(msg, severity, ?range = range, ?fileName = fileName, ?tag = tag) let makeCompiler com = PythonCompiler(com) - let transformFile (com: Compiler) (file: Babel.Program) = + let transformFile (com: Compiler) (program: Babel.Program) = let com = makeCompiler com :> IPythonCompiler + let declScopes = let hs = HashSet() //for decl in file.Body .Declarations do @@ -123,12 +227,12 @@ module Compiler = hs let ctx = - { DecisionTargets = [] - HoistVars = fun _ -> false - TailCallOpportunity = None - OptimizeTailCall = fun () -> () - ScopedTypeParams = Set.empty } - //let rootDecls = List.collect (transformDeclaration com ctx) file.Declarations - //let importDecls = com.GetAllImports() |> transformImports - let body = [] //importDecls @ rootDecls |> List.toArray - Python.Module(body) + { DecisionTargets = [] + HoistVars = fun _ -> false + TailCallOpportunity = None + OptimizeTailCall = fun () -> () + ScopedTypeParams = Set.empty } + + let body = transformProgram com ctx program.Body + printfn "Body: %A" body + body diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 5fef857b6b..335560d0af 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -19,7 +19,7 @@ module PrinterExtensions = member printer.PrintBlock ( - nodes: 'a array, + nodes: 'a list, printNode: Printer -> 'a -> unit, printSeparator: Printer -> unit, ?skipNewLineAtEnd @@ -44,40 +44,34 @@ module PrinterExtensions = printer.Print("") printer.PrintNewLine() -// member _.IsProductiveStatement(s: Statement) = -// let rec hasNoSideEffects (e: Expression) = -// match e with -// | :? Undefined -// | :? NullLiteral -// | :? StringLiteral -// | :? BooleanLiteral -// | :? NumericLiteral -> true -// // Constructors of classes deriving from System.Object add an empty object at the end -// | :? ObjectExpression as o -> o.Properties.Length = 0 -// | :? UnaryExpression as e when e.Operator = "void2" -> hasNoSideEffects e.Argument -// // Some identifiers may be stranded as the result of imports -// // intended only for side effects, see #2228 -// | :? Identifier -> true -// | _ -> false - -// match s with -// | :? ExpressionStatement as e -> hasNoSideEffects e.Expression |> not -// | _ -> true - -// member printer.PrintProductiveStatement(s: Statement, ?printSeparator) = -// if printer.IsProductiveStatement(s) then -// s.Print(printer) -// printSeparator |> Option.iter (fun f -> f printer) - -// member printer.PrintProductiveStatements(statements: Statement[]) = -// for s in statements do -// printer.PrintProductiveStatement(s, (fun p -> p.PrintStatementSeparator())) - -// member printer.PrintBlock(nodes: Statement array, ?skipNewLineAtEnd) = -// printer.PrintBlock(nodes, -// (fun p s -> p.PrintProductiveStatement(s)), -// (fun p -> p.PrintStatementSeparator()), -// ?skipNewLineAtEnd=skipNewLineAtEnd) + member _.IsProductiveStatement(s: Statement) = + let rec hasNoSideEffects(e: Statement) = + match e with + //| :? Identifier -> true + | _ -> true + + match s with + //| :? Statement as e -> hasNoSideEffects e |> not + | _ -> true + + member printer.PrintProductiveStatement(s: Statement, ?printSeparator) = + printfn "PrintProductiveStatement: %A" s + + if printer.IsProductiveStatement(s) then + s.Print(printer) + printSeparator |> Option.iter (fun f -> f printer) + + member printer.PrintProductiveStatements(statements: Statement list) = + for s in statements do + printer.PrintProductiveStatement(s, (fun p -> p.PrintStatementSeparator())) + + member printer.PrintBlock(nodes: Statement list, ?skipNewLineAtEnd) = + printer.PrintBlock( + nodes, + (fun p s -> p.PrintProductiveStatement(s)), + (fun p -> p.PrintStatementSeparator()), + ?skipNewLineAtEnd = skipNewLineAtEnd + ) member printer.PrintOptional(before: string, node: #AST option) = match node with @@ -99,19 +93,30 @@ module PrinterExtensions = | None -> () | Some node -> node.Print(printer) - member printer.PrintArray(nodes: 'a array, printNode: Printer -> 'a -> unit, printSeparator: Printer -> unit) = + member printer.PrintList(nodes: 'a list, printNode: Printer -> 'a -> unit, printSeparator: Printer -> unit) = for i = 0 to nodes.Length - 1 do printNode printer nodes.[i] + if i < nodes.Length - 1 then printSeparator printer - member printer.PrintCommaSeparatedArray(nodes: Expression array) = - printer.PrintArray(nodes, (fun p x -> p.SequenceExpressionWithParens(x)), (fun p -> p.Print(", "))) - -// member printer.PrintCommaSeparatedArray(nodes: #Node array) = + member printer.PrintCommaSeparatedList(nodes: AST list) = + printer.PrintList( + nodes, + (fun p x -> p.Print(x)), + (fun p -> p.Print(", ")) + ) + member printer.PrintCommaSeparatedList(nodes: Expression list) = + printer.PrintList( + nodes, + (fun p x -> p.SequenceExpressionWithParens(x)), + (fun p -> p.Print(", ")) + ) + + // member printer.PrintCommaSeparatedArray(nodes: #Node array) = // printer.PrintArray(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) -// // TODO: (super) type parameters, implements + // // TODO: (super) type parameters, implements // member printer.PrintClass(id: Identifier option, superClass: Expression option, // superTypeParameters: TypeParameterInstantiation option, // typeParameters: TypeParameterDeclaration option, @@ -133,20 +138,35 @@ module PrinterExtensions = // printer.Print(":") // printer.Print(body) -// member printer.PrintFunction(id: Identifier option, parameters: Pattern array, body: BlockStatement, -// typeParameters: TypeParameterDeclaration option, returnType: TypeAnnotation option, loc, ?isDeclaration, ?isArrow) = -// let areEqualPassedAndAppliedArgs (passedArgs: Pattern[]) (appliedAgs: Expression[]) = + member printer.PrintFunction + ( + id: Identifier option, + args: Arguments, + body: Statement list, + returnType, + ?isDeclaration + ) = + printer.Print("def ") + printer.PrintOptional(id) + printer.Print("(") + printer.PrintCommaSeparatedList(args.Args |> List.map (fun arg -> arg :> AST)) + printer.Print(")") + printer.PrintOptional(returnType) + printer.Print(":") + printer.PrintBlock(body, skipNewLineAtEnd = true) + + // let areEqualPassedAndAppliedArgs (passedArgs: Pattern[]) (appliedAgs: Expression[]) = // Array.zip passedArgs appliedAgs // |> Array.forall (function // | (:? Identifier as p), (:? Identifier as a) -> p.Name = a.Name // | _ -> false) -// let isDeclaration = defaultArg isDeclaration false + // let isDeclaration = defaultArg isDeclaration false // let isArrow = defaultArg isArrow false -// printer.AddLocation(loc) + // printer.AddLocation(loc) -// // Check if we can remove the function + // // Check if we can remove the function // let skipExpr = // match body.Body with // | [|:? ReturnStatement as r|] when not isDeclaration -> @@ -161,7 +181,7 @@ module PrinterExtensions = // | _ -> None // | _ -> None -// match skipExpr with + // match skipExpr with // | Some e -> e.Print(printer) // | None -> // if false then //isArrow then @@ -204,13 +224,15 @@ module PrinterExtensions = /// Surround with parens anything that can potentially conflict with operator precedence member printer.ComplexExpressionWithParens(expr: Expression) = + printfn "Expr: %A" expr + match expr with // | :? Undefined // | :? NullLiteral - // | :? StringLiteral + | :? Constant -> expr.Print(printer) // | :? BooleanLiteral // | :? NumericLiteral - // | :? Identifier + | :? Name -> expr.Print(printer) // | :? MemberExpression // | :? CallExpression // | :? ThisExpression @@ -220,10 +242,10 @@ module PrinterExtensions = // | :? ObjectExpression -> expr.Print(printer) | _ -> printer.WithParens(expr) - member printer.PrintOperation(left, operator, right, loc) = + member printer.PrintOperation(left, operator, right, ?loc) = printer.AddLocation(loc) printer.ComplexExpressionWithParens(left) - printer.Print(" " + operator + " ") + printer.Print(operator) printer.ComplexExpressionWithParens(right) type AST = @@ -231,11 +253,11 @@ type AST = abstract Print: Printer -> unit [] -type Expression () = - member val Lineno : int = 0 with get, set - member val ColOffset : int = 0 with get, set - member val EndLineno : int option = None with get, set - member val EndColOffset : int option = None with get, set +type Expression() = + member val Lineno: int = 0 with get, set + member val ColOffset: int = 0 with get, set + member val EndLineno: int option = None with get, set + member val EndColOffset: int option = None with get, set interface AST with member this.Print(printer) = this.Print printer @@ -254,17 +276,24 @@ type ComparisonOperator = type UnaryOperator = inherit AST +type ExpressionContext = + inherit AST + + type Identifier = - abstract Name: string + | Identifier of string - inherit AST + interface AST with + member this.Print(printer: Printer) = + let (Identifier id) = this + printer.Print id [] -type Statement () = - member val Lineno : int = 0 with get, set - member val ColOffset : int = 0 with get, set - member val EndLineno : int option = None with get, set - member val EndColOffset : int option = None with get, set +type Statement() = + member val Lineno: int = 0 with get, set + member val ColOffset: int = 0 with get, set + member val EndLineno: int option = None with get, set + member val EndColOffset: int option = None with get, set interface AST with member this.Print(printer) = this.Print printer @@ -277,7 +306,7 @@ type Module(body) = interface AST with member this.Print(printer) = this.Print printer - member this.Print(printer) = () + member _.Print(printer: Printer) = printer.PrintProductiveStatements(body) type Alias(name, asname) = member _.Name: Identifier = name @@ -286,21 +315,42 @@ type Alias(name, asname) = interface AST with member this.Print(printer) = this.Print printer - member this.Print(printer) = () + member _.Print(printer) = () /// A single argument in a list. arg is a raw string of the argument name, annotation is its annotation, such as a Str /// or Name node. /// /// - type_comment is an optional string with the type annotation as a comment type Arg(arg, ?annotation, ?typeComment) = - member val Lineno : int = 0 with get, set - member val ColOffset : int = 0 with get, set - member val EndLineno : int option = None with get, set - member val EndColOffset : int option = None with get, set + member val Lineno: int = 0 with get, set + member val ColOffset: int = 0 with get, set + member val EndLineno: int option = None with get, set + member val EndColOffset: int option = None with get, set member _.Arg: Identifier = arg - member _.Annotation : Expression option = annotation - member _.TypeComment : string option = typeComment + member _.Annotation: Expression option = annotation + member _.TypeComment: string option = typeComment + + interface AST with + member _.Print(printer) = + let (Identifier name) = arg + printer.Print(name) + +type Keyword(arg, value) = + member val Lineno: int = 0 with get, set + member val ColOffset: int = 0 with get, set + member val EndLineno: int option = None with get, set + member val EndColOffset: int option = None with get, set + + member _.Arg: Identifier = arg + member _.Value: Expression = value + + interface AST with + member _.Print(printer) = + let (Identifier name) = arg + printer.Print(name) + printer.Print(" = ") + printer.Print(value) /// The arguments for a function. /// @@ -311,13 +361,13 @@ type Arg(arg, ?annotation, ?typeComment) = /// - defaults is a list of default values for arguments that can be passed positionally. If there are fewer defaults, /// they correspond to the last n arguments. type Arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ?defaults) = - member _.PosOnlyArgs : Arg list = defaultArg posonlyargs [] - member _.Args : Arg list = defaultArg args [] - member _.VarArg : Arg option = vararg - member _.KwOnlyArgs : Arg list = defaultArg kwonlyargs [] - member _.KwDefaults : Expression list = defaultArg kwDefaults [] - member _.KwArg : Arg option = kwarg - member _.Defaults : Expression list = defaultArg defaults [] + member _.PosOnlyArgs: Arg list = defaultArg posonlyargs [] + member _.Args: Arg list = defaultArg args [] + member _.VarArg: Arg option = vararg + member _.KwOnlyArgs: Arg list = defaultArg kwonlyargs [] + member _.KwDefaults: Expression list = defaultArg kwDefaults [] + member _.KwArg: Arg option = kwarg + member _.Defaults: Expression list = defaultArg defaults [] interface AST with member _.Print(printer) = () @@ -356,13 +406,21 @@ type Arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ? /// type_ignores=[]) /// ``` type Assign(targets, value, ?typeComment) = - inherit Statement () + inherit Statement() member _.Targets: Expression list = targets member _.Value: Expression = value member _.TypeComment: string option = typeComment - override _.Print(printer) = () + override _.Print(printer) = + printfn "Assign: %A" (targets, value) + //printer.PrintOperation(targets.[0], "=", value, None) + + for target in targets do + printer.Print(target) + printer.Print(" = ") + + printer.Print(value) /// When an expression, such as a function call, appears as a statement by itself with its return value not used or /// stored, it is wrapped in this container. value holds one of the other nodes in this section, a Constant, a Name, a @@ -378,7 +436,7 @@ type Assign(targets, value, ?typeComment) = /// type_ignores=[]) ///``` type Expr(value) = - inherit Statement () + inherit Statement() member _.Value: Expression = value @@ -411,7 +469,7 @@ type Expr(value) = /// type_ignores=[]) ///``` type For(target, iter, body, orelse, ?typeComment) = - inherit Statement () + inherit Statement() member _.Target: Expression = target member _.Iterator: Expression = iter @@ -422,7 +480,7 @@ type For(target, iter, body, orelse, ?typeComment) = override _.Print(printer) = () type AsyncFor(target, iter, body, orelse, ?typeComment) = - inherit Statement () + inherit Statement() member _.Target: Expression = target member _.Iterator: Expression = iter @@ -454,7 +512,7 @@ type AsyncFor(target, iter, body, orelse, ?typeComment) = /// type_ignores=[]) /// ``` type While(test, body, orelse) = - inherit Statement () + inherit Statement() member _.Test: Expression = test member _.Body: Statement list = body @@ -495,7 +553,7 @@ type While(test, body, orelse) = /// type_ignores=[]) /// ``` type If(test, body, orelse) = - inherit Statement () + inherit Statement() member _.Test: Expression = test member _.Body: Statement list = body @@ -516,7 +574,7 @@ type If(test, body, orelse) = /// type_ignores=[]) /// ``` type Raise(exc, ?cause) = - inherit Statement () + inherit Statement() member _.Exception: Expression = exc member _.Cause: Expression option = cause @@ -532,18 +590,20 @@ type Raise(exc, ?cause) = /// applied last). /// - returns is the return annotation. /// - type_comment is an optional string with the type annotation as a comment. -type FunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = +type FunctionDef(name, args, body, ?decoratorList, ?returns, ?typeComment) = - inherit Statement () + inherit Statement() member _.Name: Identifier = name member _.Args: Arguments = args member _.Body: Statement list = body - member _.DecoratorList: Expression list = decoratorList + member _.DecoratorList: Expression list = defaultArg decoratorList [] member _.Returns: Expression option = returns member _.TypeComment: string option = typeComment - override _.Print(printer) = () + override _.Print(printer) = + printer.PrintFunction(Some name, args, body, returns, isDeclaration = true) + printer.PrintNewLine() /// global and nonlocal statements. names is a list of raw strings. /// @@ -560,9 +620,9 @@ type FunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = /// /// ``` type Global(names) = - inherit Statement () + inherit Statement() - member _.Names : Identifier list = names + member _.Names: Identifier list = names override _.Print(printer) = () @@ -580,9 +640,9 @@ type Global(names) = /// type_ignores=[]) /// ````` type NonLocal(names) = - inherit Statement () + inherit Statement() - member _.Names : Identifier list = names + member _.Names: Identifier list = names override _.Print(printer) = () @@ -596,19 +656,19 @@ type NonLocal(names) = /// type_ignores=[]) /// ``` type Pass() = - inherit Statement () + inherit Statement() override _.Print(printer) = () /// The break statement. type Break() = - inherit Statement () + inherit Statement() override _.Print(printer) = () /// The continue statement. type Continue() = - inherit Statement () + inherit Statement() override _.Print(printer) = () @@ -623,7 +683,7 @@ type Continue() = /// - type_comment is an optional string with the type annotation as a comment. type AsyncFunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = - inherit Statement () + inherit Statement() member _.Name: Identifier = name member _.Args: Arguments = args @@ -647,9 +707,9 @@ type AsyncFunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = /// type_ignores=[]) /// ````` type Import(names) = - inherit Statement () + inherit Statement() - member _.Names : Alias list = names + member _.Names: Alias list = names override _.Print(printer) = () @@ -670,11 +730,11 @@ type Import(names) = /// type_ignores=[]) /// ``` type ImportFrom(``module``, names, level) = - inherit Statement () + inherit Statement() - member _.Module : Identifier option = ``module`` - member _.Names : Alias list = names - member _.Level : int option = level + member _.Module: Identifier option = ``module`` + member _.Names: Alias list = names + member _.Level: int option = level override _.Print(printer) = () @@ -688,16 +748,48 @@ type ImportFrom(``module``, names, level) = /// value=Constant(value=4))], /// type_ignores=[]) /// ``` -type Return (?value) = - inherit Statement () - member _.Value : Expression option = value +type Return(?value) = + inherit Statement() + member _.Value: Expression option = value - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("return ") + printer.PrintOptional(value) //#endregion //#region Expressions +type BinOp(left, op, right) = + inherit Expression() + + member _.Left: Expression = left + member _.Right: Expression = right + member _.Operator: Operator = op + + override this.Print(printer) = printer.PrintOperation(left, op, right) + +/// A unary operation. op is the operator, and operand any expression node. +type UnaryOp(op, operand, ?loc) = + inherit Expression() + + member _.Op: UnaryOperator = op + member _.Operand: Expression = operand + + override this.Print(printer) = + printer.AddLocation(loc) + + match op with + | :? USub + | :? UAdd + | :? Not + | :? Invert -> printer.Print(op) + | _ -> + printer.Print(op) + printer.Print(" ") + + printer.ComplexExpressionWithParens(operand) + /// A constant value. The value attribute of the Constant literal contains the Python object it represents. The values /// represented can be simple types such as a number, string or None, but also immutable container types (tuples and /// frozensets) if all of their elements are constant. @@ -706,12 +798,18 @@ type Return (?value) = /// Expression( /// body=Constant(value=123)) /// ````` -type Constant<'T>(value: 'T) = - inherit Expression () +type Constant(value: obj) = + inherit Expression() - member _.Value : 'T = value + member _.Value: obj = value - override _.Print(printer) = () + override _.Print(printer) = + match box value with + | :? string as str -> + printer.Print("\"") + printer.Print(string value) + printer.Print("\"") + | _ -> printer.Print(string value) /// Node representing a single formatting field in an f-string. If the string contains a single formatting field and /// nothing else the node can be isolated otherwise it appears in JoinedStr. @@ -725,14 +823,53 @@ type Constant<'T>(value: 'T) = /// - format_spec is a JoinedStr node representing the formatting of the value, or None if no format was specified. Both /// conversion and format_spec can be set at the same time. type FormattedValue(value, ?conversion, ?formatSpec) = - inherit Expression () + inherit Expression() - member _.Value : Expression = value - member _.Conversion : int option = conversion - member _.FormatSpec : Expression option = formatSpec + member _.Value: Expression = value + member _.Conversion: int option = conversion + member _.FormatSpec: Expression option = formatSpec override _.Print(printer) = () +/// A function call. func is the function, which will often be a Name or Attribute object. Of the arguments: +/// +/// args holds a list of the arguments passed by position. +/// +/// keywords holds a list of keyword objects representing arguments passed by keyword. +/// +/// When creating a Call node, args and keywords are required, but they can be empty lists. starargs and kwargs are optional. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('func(a, b=c, *d, **e)', mode='eval'), indent=4)) +/// Expression( +/// body=Call( +/// func=Name(id='func', ctx=Load()), +/// args=[ +/// Name(id='a', ctx=Load()), +/// Starred( +/// value=Name(id='d', ctx=Load()), +/// ctx=Load())], +/// keywords=[ +/// keyword( +/// arg='b', +/// value=Name(id='c', ctx=Load())), +/// keyword( +/// value=Name(id='e', ctx=Load()))])) +/// ``` +type Call(func, ?args, ?kw) = + inherit Expression() + + member _.Func: Expression = func + member _.Args: Expression list = defaultArg args [] + member _.Keywords: Keyword list = defaultArg kw [] + + override this.Print(printer) = + printer.Print(func) + printer.Print("(") + printer.PrintCommaSeparatedList(this.Args) + printer.PrintCommaSeparatedList(this.Keywords |> List.map (fun x -> x :> AST)) + printer.Print(")") + /// lambda is a minimal function definition that can be used inside an expression. Unlike FunctionDef, body holds a /// single node. /// @@ -755,13 +892,27 @@ type FormattedValue(value, ?conversion, ?formatSpec) = /// ``` type Lambda(args, body) = - inherit Expression () + inherit Expression() member _.Args: Arguments = args member _.Body: Statement list = body - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("lambda ") + printer.PrintCommaSeparatedList(args.Args |> List.map (fun arg -> arg :> AST)) + printer.Print(":") + printer.PrintBlock(body, skipNewLineAtEnd = true) + +/// A variable name. id holds the name as a string, and ctx is one of the following types. +type Name(id, ctx) = + inherit Expression() + + member _.Id: Identifier = id + member _.Context: ExpressionContext = ctx + override _.Print(printer) = + let (Identifier name) = id + printer.Print(name) /// A tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an assignment target /// (i.e. (x,y)=something), and Load otherwise. @@ -777,9 +928,9 @@ type Lambda(args, body) = /// ctx=Load())) ///``` type Tuple(elts) = - inherit Expression () + inherit Expression() - member _.Elements : Expression list = elts + member _.Elements: Expression list = elts override _.Print(printer) = () /// A list or tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an @@ -796,9 +947,9 @@ type Tuple(elts) = /// ctx=Load())) ///``` type List(elts) = - inherit Expression () + inherit Expression() - member _.Elements : Expression list = elts + member _.Elements: Expression list = elts override _.Print(printer) = () @@ -814,9 +965,9 @@ type List(elts) = /// Constant(value=3)])) /// ````` type Set(elts) = - inherit Expression () + inherit Expression() - member _.Elements : Expression list = elts + member _.Elements: Expression list = elts override _.Print(printer) = () @@ -838,10 +989,10 @@ type Set(elts) = /// Name(id='d', ctx=Load())])) /// ``` type Dict(keys, values) = - inherit Expression () + inherit Expression() - member _.Keys : Expression list= keys - member _.Values : Expression list= values + member _.Keys: Expression list = keys + member _.Values: Expression list = values override _.Print(printer) = () @@ -857,9 +1008,9 @@ type Dict(keys, values) = /// value=Name(id='x', ctx=Load())))], /// type_ignores=[]) ///``` -type Yield (?value) = - inherit Expression () - member _.Value : Expression option = value +type Yield(?value) = + inherit Expression() + member _.Value: Expression option = value override _.Print(printer) = () @@ -875,9 +1026,9 @@ type Yield (?value) = /// value=Name(id='x', ctx=Load())))], /// type_ignores=[]) ///``` -type YieldFrom (?value) = - inherit Expression () - member _.Value : Expression option = value +type YieldFrom(?value) = + inherit Expression() + member _.Value: Expression option = value override _.Print(printer) = () @@ -885,44 +1036,59 @@ type YieldFrom (?value) = //#region Operators -type Add = - inherit Operator +type Add() = + interface Operator with + member _.Print(printer: Printer) = printer.Print(" + ") -type Sub = - inherit Operator +type Sub() = + static member Pattern = "-" -type Mult = - inherit Operator + interface Operator with + member _.Print(printer: Printer) = printer.Print(" - ") -type Div = - inherit Operator +type Mult() = + interface Operator with + member _.Print(printer: Printer) = printer.Print(" * ") -type FloorDiv = - inherit Operator +type Div() = + interface Operator with + member _.Print(printer: Printer) = printer.Print(" / ") -type Mod = - inherit Operator +type FloorDiv() = + interface Operator with + member _.Print(printer: Printer) = printer.Print(" // ") -type Pow = - inherit Operator +type Mod() = + interface Operator with + member _.Print(printer: Printer) = printer.Print(" % ") -type LShift = - inherit Operator +type Pow() = + interface Operator with + member _.Print(printer: Printer) = printer.Print(" ** ") -type RShift = - inherit Operator +type LShift() = + interface Operator with + member _.Print(printer: Printer) = printer.Print(" << ") -type BitOr = - inherit Operator +type RShift() = + interface Operator with + member _.Print(printer: Printer) = printer.Print(" >> ") -type BitXor = - inherit Operator +type BitOr() = + interface Operator with + member _.Print(printer: Printer) = printer.Print(" | ") -type BitAnd = - inherit Operator +type BitXor() = + interface Operator with + member _.Print(printer: Printer) = printer.Print(" ^ ") -type MatMult = - inherit Operator +type BitAnd() = + interface Operator with + member _.Print(printer: Printer) = printer.Print($" & ") + +type MatMult() = + interface Operator with + member _.Print(printer: Printer) = printer.Print($" @ ") //#endregion @@ -972,16 +1138,39 @@ type Or = //#region Unary Operators -type Invert = - inherit UnaryOperator +type Invert() = + interface UnaryOperator with + member this.Print(printer) = printer.Print "~" + +type Not() = + interface UnaryOperator with + member this.Print(printer) = printer.Print "not " -type Not = - inherit UnaryOperator +type UAdd() = + interface UnaryOperator with + member this.Print(printer) = printer.Print "+" -type UAdd = - inherit UnaryOperator +type USub() = + interface UnaryOperator with + member this.Print(printer) = printer.Print "-" + +//#endregion -type USub = - inherit UnaryOperator +//#region Expression Context -//#endregion \ No newline at end of file +type Load() = + interface ExpressionContext + + interface AST with + member this.Print(printer) = () + +type Del = + inherit ExpressionContext + +type Store() = + interface ExpressionContext + + interface AST with + member this.Print(printer) = () + +//#endregion From 900dc24265651322e51e928dbb058c6f58d4e426 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 11 Jan 2021 21:37:09 +0100 Subject: [PATCH 010/145] Revert --- src/Fable.Transforms/Replacements.fs | 2 +- src/Fable.Transforms/Transforms.Util.fs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 59e4f7e2b0..6a48daff52 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -3201,4 +3201,4 @@ let tryBaseConstructor com ctx (ent: Entity) (argTypes: Lazy) genArgs | _ -> failwith "Unexpected hashset constructor" let entityName = FSharp2Fable.Helpers.cleanNameAsJsIdentifier "HashSet" Some(makeImportLib com Any entityName "MutableSet", args) - | _ -> None + | _ -> None \ No newline at end of file diff --git a/src/Fable.Transforms/Transforms.Util.fs b/src/Fable.Transforms/Transforms.Util.fs index 4801497ed8..29d353db59 100644 --- a/src/Fable.Transforms/Transforms.Util.fs +++ b/src/Fable.Transforms/Transforms.Util.fs @@ -423,8 +423,8 @@ module AST = let makeIntConst (x: int) = NumberConstant (float x, Int32) |> makeValue None let makeFloatConst (x: float) = NumberConstant (x, Float64) |> makeValue None - let getLibPath (com: Compiler) (moduleName: string) = - $"expression.fable.{moduleName.ToLower()}" + let getLibPath (com: Compiler) moduleName = + com.LibraryDir + "/" + moduleName + ".js" let makeImportUserGenerated r t (selector: string) (path: string) = Import({ Selector = selector.Trim() @@ -627,4 +627,4 @@ module AST = | None, Some r2 -> Some r2 | None, None -> None | Some r1, Some r2 -> Some(r1 + r2) - (None, locs) ||> Seq.fold addTwo + (None, locs) ||> Seq.fold addTwo \ No newline at end of file From e68eeda0a9317ede0b99776670961c864536096a Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 11 Jan 2021 21:38:38 +0100 Subject: [PATCH 011/145] Revert --- src/Fable.Transforms/Replacements.fs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 6a48daff52..870e660dea 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -748,20 +748,18 @@ let rec equals (com: ICompiler) ctx r equal (left: Expr) (right: Expr) = | Builtin (BclInt64|BclUInt64|BclDecimal|BclBigInt as bt) -> Helper.LibCall(com, coreModFor bt, "equals", Boolean, [left; right], ?loc=r) |> is equal | DeclaredType _ -> - Helper.LibCall(com, "Util", "equals1", Boolean, [left; right], ?loc=r) |> is equal + Helper.LibCall(com, "Util", "equals", Boolean, [left; right], ?loc=r) |> is equal | Array t -> let f = makeComparerFunction com ctx t Helper.LibCall(com, "Array", "equalsWith", Boolean, [f; left; right], ?loc=r) |> is equal | List _ -> - Helper.LibCall(com, "Util", "equals2", Boolean, [left; right], ?loc=r) |> is equal + Helper.LibCall(com, "Util", "equals", Boolean, [left; right], ?loc=r) |> is equal | MetaType -> - Helper.LibCall(com, "Reflection", "equals3", Boolean, [left; right], ?loc=r) |> is equal + Helper.LibCall(com, "Reflection", "equals", Boolean, [left; right], ?loc=r) |> is equal | Tuple _ -> Helper.LibCall(com, "Util", "equalArrays", Boolean, [left; right], ?loc=r) |> is equal | _ -> - //Helper.LibCall(com, "Util", "equals", Boolean, [left; right], ?loc=r) |> is equal - let op = if equal then BinaryEqual else BinaryUnequal - makeBinOp r Boolean left right op + Helper.LibCall(com, "Util", "equals", Boolean, [left; right], ?loc=r) |> is equal /// Compare function that will call Util.compare or instance `CompareTo` as appropriate and compare (com: ICompiler) ctx r (left: Expr) (right: Expr) = @@ -1228,7 +1226,7 @@ let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp | "jsOptions", [arg] -> makePojoFromLambda com arg |> Some | "jsThis", _ -> - emitJsExpr r t [] "self" |> Some + emitJsExpr r t [] "this" |> Some | "jsConstructor", _ -> match (genArg com ctx r 0 i.GenericArgs) with | DeclaredType(ent, _) -> com.GetEntity(ent) |> jsConstructor com |> Some @@ -1352,9 +1350,7 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o // KeyValuePair is already compiled as a tuple | ("KeyValuePattern"|"Identity"|"Box"|"Unbox"|"ToEnum"), [arg] -> TypeCast(arg, t, None) |> Some // Cast to unit to make sure nothing is returned when wrapped in a lambda, see #1360 - | "Ignore", _ -> - // "void3 $0" |> emitJsExpr r t args |> Some - None + | "Ignore", _ -> "void $0" |> emitJsExpr r t args |> Some // Number and String conversions | ("ToSByte"|"ToByte"|"ToInt8"|"ToUInt8"|"ToInt16"|"ToUInt16"|"ToInt"|"ToUInt"|"ToInt32"|"ToUInt32"), _ -> toInt com ctx r t args |> Some @@ -1993,7 +1989,7 @@ let optionModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: E let args = args |> List.replaceLast (toArray None t) let moduleName, meth = if meth = "ToList" - then "List", "of_seq" + then "List", "ofArray" else "Seq", Naming.lowerFirst meth Helper.LibCall(com, moduleName, meth, t, args, i.SignatureArgTypes, ?loc=r) |> Some | _ -> None @@ -2439,7 +2435,7 @@ let log (com: ICompiler) r t (i: CallInfo) (_: Expr option) (args: Expr list) = | [v] -> [v] | (StringConst _)::_ -> [Helper.LibCall(com, "String", "format", t, args, i.SignatureArgTypes)] | _ -> [args.Head] - Helper.GlobalCall("print", t, args, ?loc=r) + Helper.GlobalCall("console", t, args, memb="log", ?loc=r) let bitConvert (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) = match i.CompiledName with From bd360591eb271de01aff89bbc686595be4efd484 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 11 Jan 2021 21:39:55 +0100 Subject: [PATCH 012/145] Revert --- src/Fable.Transforms/Global/Prelude.fs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Fable.Transforms/Global/Prelude.fs b/src/Fable.Transforms/Global/Prelude.fs index 1e1aa24d3a..61386b05bf 100644 --- a/src/Fable.Transforms/Global/Prelude.fs +++ b/src/Fable.Transforms/Global/Prelude.fs @@ -215,7 +215,7 @@ module Naming = "return" "super" "switch" - //"this" + "this" "throw" "try" "typeof" @@ -394,7 +394,7 @@ module Naming = | StaticMemberPart(_,o) -> o | NoMemberPart -> "" - let reflectionSuffix = "_reflection" + let reflectionSuffix = "$reflection" let private printPart sanitize separator part overloadSuffix = (if part = "" then "" else separator + (sanitize part)) + @@ -582,4 +582,4 @@ module Path = |> fun path -> path.Split('/') |> Array.filter (String.IsNullOrWhiteSpace >> not)) |> Seq.toList |> getCommonPrefix - |> String.concat "/" + |> String.concat "/" \ No newline at end of file From 680a9f8fc2662f2805159e439eed7d91df182e96 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 11 Jan 2021 23:31:14 +0100 Subject: [PATCH 013/145] Fix import statement --- src/Fable.Transforms/Python/Babel2Python.fs | 13 ++++++++++- src/Fable.Transforms/Python/Python.fs | 26 ++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 559870a355..bfdcdc7587 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -126,7 +126,18 @@ module Util = yield FunctionDef(Identifier fd.Id.Name, arguments, body = body) | a -> printfn "Declaration: %A" a - | a -> printfn "Module %A" a ] + | :? Babel.ImportDeclaration as imp -> + let source = imp.Source.Value |> Identifier |> Some + let mapper (expr: Babel.ImportSpecifier) = + match expr with + | :? Babel.ImportMemberSpecifier as im -> + let a = im.Imported.Name + Alias(Identifier a, None) + + let sa = imp.Specifiers |> List.ofArray |> List.map mapper + yield ImportFrom(source, sa) :> _ + | a -> + failwith "Unknown module declaration: %A" a ] Module(stmt) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 335560d0af..793a20d02e 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -315,7 +315,14 @@ type Alias(name, asname) = interface AST with member this.Print(printer) = this.Print printer - member _.Print(printer) = () + member _.Print(printer: Printer) = + printer.Print(name) + match asname with + | Some (Identifier alias) -> + printer.Print("as ") + printer.Print(alias) + | _ -> () + /// A single argument in a list. arg is a raw string of the argument name, annotation is its annotation, such as a Str /// or Name node. @@ -729,14 +736,27 @@ type Import(names) = /// level=0)], /// type_ignores=[]) /// ``` -type ImportFrom(``module``, names, level) = +type ImportFrom(``module``, names, ?level) = inherit Statement() member _.Module: Identifier option = ``module`` member _.Names: Alias list = names member _.Level: int option = level - override _.Print(printer) = () + override this.Print(printer) = + let (Identifier path) = this.Module |> Option.defaultValue (Identifier ".") + printer.Print("from ") + + printer.Print("\"") + printer.Print(printer.MakeImportPath(path)) + printer.Print("\"") + + printer.Print(" import ") + + if not(List.isEmpty names) then + printer.Print("(") + printer.PrintCommaSeparatedList(names |> List.map (fun x -> x :> AST)) + printer.Print(")") /// A return statement. /// From 591a93a088602d340b0776586be2476695e2aec2 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 12 Jan 2021 00:01:52 +0100 Subject: [PATCH 014/145] Fix expression statements --- src/Fable.Transforms/FSharp2Fable.Util.fs | 6 +++--- src/Fable.Transforms/Python/Babel2Python.fs | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index 85feca523d..c1516c2240 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -311,8 +311,8 @@ module Helpers = | _ -> fullName let cleanNameAsJsIdentifier (name: string) = - if name = ".ctor" then "_ctor" - else name.Replace('.','_').Replace('`','_') + if name = ".ctor" then "$ctor" + else name.Replace('.','_').Replace('`','$') let getEntityDeclarationName (com: Compiler) (ent: Fable.EntityRef) = let entityName = getEntityMangledName com true ent |> cleanNameAsJsIdentifier @@ -1572,4 +1572,4 @@ module Util = | Emitted com r typ None emitted, _ -> emitted | Imported com r typ None imported -> imported | Try (tryGetIdentFromScope ctx r) expr, _ -> expr - | _ -> memberRefTyped com ctx r typ v + | _ -> memberRefTyped com ctx r typ v \ No newline at end of file diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index bfdcdc7587..b61d151ded 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -136,8 +136,11 @@ module Util = let sa = imp.Specifiers |> List.ofArray |> List.map mapper yield ImportFrom(source, sa) :> _ - | a -> - failwith "Unknown module declaration: %A" a ] + | :? Babel.PrivateModuleDeclaration as pmd -> + let st = pmd.Statement + yield! transformAsStatements com ctx ReturnStrategy.Return st + | _ -> + failwith $"Unknown module declaration: {md}" ] Module(stmt) @@ -157,6 +160,8 @@ module Util = match vd.Init with | Some value -> Assign(targets, transformAsExpr com ctx value) | None -> () ] + | :? Babel.ExpressionStatement as es -> + [ Expr(transformAsExpr com ctx es.Expression) ] | a -> failwith $"Unhandled statement: {a}" let getIdentForImport (ctx: Context) (path: string) (selector: string) = From a6344f229cbfc3df41813ca39a119c0bfc715916 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 12 Jan 2021 22:00:51 +0100 Subject: [PATCH 015/145] Add language handling More Python AST fixes --- build.fsx | 2 +- src/Fable.AST/Plugins.fs | 1 + src/Fable.Cli/Entry.fs | 10 +- src/Fable.Cli/Main.fs | 15 +- src/Fable.Transforms/Global/Compiler.fs | 9 +- src/Fable.Transforms/Python/Babel2Python.fs | 169 +++++++++++------- src/Fable.Transforms/Python/Python.fs | 183 ++++++++++++++------ 7 files changed, 265 insertions(+), 124 deletions(-) diff --git a/build.fsx b/build.fsx index 401afd2005..dd56e69b42 100644 --- a/build.fsx +++ b/build.fsx @@ -520,7 +520,7 @@ match argsLower with | "test-integration"::_ -> testIntegration() | "quicktest"::_ -> buildLibraryIfNotExists() - run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --forcePkgs false" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --forcePkgs --python" | "run"::_ -> buildLibraryIfNotExists() diff --git a/src/Fable.AST/Plugins.fs b/src/Fable.AST/Plugins.fs index cb2daf8ef3..4917c06813 100644 --- a/src/Fable.AST/Plugins.fs +++ b/src/Fable.AST/Plugins.fs @@ -12,6 +12,7 @@ type Verbosity = type Language = | JavaScript | TypeScript + | Python type CompilerOptions = abstract TypedArrays: bool diff --git a/src/Fable.Cli/Entry.fs b/src/Fable.Cli/Entry.fs index 99c3aeba6c..27a32af013 100644 --- a/src/Fable.Cli/Entry.fs +++ b/src/Fable.Cli/Entry.fs @@ -72,6 +72,7 @@ Arguments: --optimize Compile with optimized F# AST (experimental) --typescript Compile to TypeScript (experimental) + --python Compile to Python (experimental) Environment variables: DOTNET_USE_POLLING_FILE_WATCHER @@ -120,14 +121,15 @@ type Runner = // TODO: Remove this check when typed arrays are compatible with typescript |> Result.bind (fun projFile -> let typescript = flagEnabled "--typescript" args + let python = flagEnabled "--python" args let typedArrays = tryFlag "--typedArrays" args |> Option.defaultValue true if typescript && typedArrays then Error("Typescript output is currently not compatible with typed arrays, pass: --typedArrays false") else - Ok(projFile, typescript, typedArrays) + Ok(projFile, typescript, python, typedArrays) ) - |> Result.bind (fun (projFile, typescript, typedArrays) -> + |> Result.bind (fun (projFile, typescript, python, typedArrays) -> let verbosity = if flagEnabled "--verbose" args then Log.makeVerbose() @@ -147,7 +149,8 @@ type Runner = argValue "--extension" args |> Option.defaultValue (defaultFileExt typescript args) let compilerOptions = - CompilerOptionsHelper.Make(typescript = typescript, + CompilerOptionsHelper.Make(python = python, + typescript = typescript, typedArrays = typedArrays, fileExtension = fileExt, define = define, @@ -185,6 +188,7 @@ type Runner = let clean args dir = let typescript = flagEnabled "--typescript" args + let python = flagEnabled "--python" args let ignoreDirs = set ["bin"; "obj"; "node_modules"] let fileExt = argValue "--extension" args |> Option.defaultValue (defaultFileExt typescript args) diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index ebc3abd766..7bc6cec7d8 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -180,9 +180,6 @@ module private Util = FSharp2Fable.Compiler.transformFile com |> FableTransforms.transformFile com |> Fable2Babel.Compiler.transformFile com - let python = - babel - |> Babel2Python.Compiler.transformFile com // TODO: Dummy interface until we have a dotnet port of SourceMapGenerator // https://github.com/mozilla/source-map#with-sourcemapgenerator-low-level-api @@ -199,10 +196,14 @@ module private Util = let writer = new FileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) do! BabelPrinter.run writer map babel - let map = { new PythonPrinter.SourceMapGenerator with - member _.AddMapping(_,_,_,_,_) = () } - let writer = new PythonFileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) - do! PythonPrinter.run writer map python + if com.Options.Language = Python then + printfn "Generating Python" + let python = babel |> Babel2Python.Compiler.transformFile com + + let map = { new PythonPrinter.SourceMapGenerator with + member _.AddMapping(_,_,_,_,_) = () } + let writer = new PythonFileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) + do! PythonPrinter.run writer map python Log.always("Compiled " + File.getRelativePathFromCwd com.CurrentFile) diff --git a/src/Fable.Transforms/Global/Compiler.fs b/src/Fable.Transforms/Global/Compiler.fs index ffd89fed17..4c1cd0c97c 100644 --- a/src/Fable.Transforms/Global/Compiler.fs +++ b/src/Fable.Transforms/Global/Compiler.fs @@ -5,7 +5,8 @@ module Literals = type CompilerOptionsHelper = static member DefaultExtension = ".fs.js" - static member Make(?typedArrays, + static member Make(?python, + ?typedArrays, ?typescript, ?define, ?optimizeFSharpAst, @@ -14,8 +15,12 @@ type CompilerOptionsHelper = ?clampByteArrays) = let define = defaultArg define [] let isDebug = List.contains "DEBUG" define - let language = typescript |> Option.bind(fun ts -> if ts then Some TypeScript else None) + let language = + let typescript = typescript |> Option.bind(fun ts -> if ts then Some TypeScript else None) + let python = python |> Option.bind(fun py -> if py then Some Python else None) + typescript |> Option.orElse python + printfn "Language: %A" language { new CompilerOptions with member _.Define = define member _.DebugMode = isDebug diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index b61d151ded..5e21a6df6f 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -42,20 +42,38 @@ type IPythonCompiler = //abstract GetAllImports: unit -> seq //abstract GetImportExpr: Context * selector: string * path: string * SourceLocation option -> Expression abstract TransformAsExpr: Context * Babel.Expression -> Expression - //abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Python.Statement list + abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Statement list + abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Expression -> Statement list + abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement list //abstract TransformImport: Context * selector:string * path:string -> Expression //abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> (Pattern array) * BlockStatement abstract WarnOnlyOnce: string * ?range:SourceLocation -> unit module Util = - let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Babel.Expression): Expression = - printfn "transformAsExpr: %A" expr + let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration) : Statement list= + let body : Statement list = [ + for mber in cls.Body.Body do + match mber with + | :? Babel.ClassMethod as cm -> + match cm.Kind with + | "constructor" -> + let args = cm.Params |> List.ofArray |> List.map (fun arg -> Arg(Identifier arg.Name)) + let arguments = Arguments(args=args) + let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), cm.Body) + FunctionDef(Identifier "__init__", arguments, body=body) + | _ -> failwith $"Unknown kind: {cm.Kind}" + | _ -> failwith $"Unhandled class member {mber}" + //yield! com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), stmt) + ] + //yield ClassDef(Identifier cd.Id.Value.Name, body=body) + body + let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Babel.Expression): Expression = match expr with | :? Babel.BinaryExpression as bin -> - let left = transformAsExpr com ctx bin.Left - let right = transformAsExpr com ctx bin.Right + let left = com.TransformAsExpr(ctx, bin.Left) + let right = com.TransformAsExpr(ctx, bin.Right) let op: Operator = match bin.Operator with @@ -72,34 +90,88 @@ module Util = | "&" -> BitAnd() :> _ | _ -> failwith $"Unknown operator: {bin.Operator}" - BinOp(left, op, right) :> _ + BinOp(left, op, right).AsExpr() | :? Babel.UnaryExpression as ue -> - let op: UnaryOperator = + let op = match ue.Operator with - | "-" -> USub() :> _ - | "+" -> UAdd() :> _ - | "~" -> Invert() :> _ - | "!" -> Not() :> _ - | a -> failwith $"Unhandled unary operator: {a}" - - let operand = transformAsExpr com ctx ue.Argument - UnaryOp(op, operand) :> _ + | "-" -> USub() :> UnaryOperator |> Some + | "+" -> UAdd() :> UnaryOperator |> Some + | "~" -> Invert() :> UnaryOperator |> Some + | "!" -> Not() :> UnaryOperator |> Some + | "void" -> None + | _ -> failwith $"Unhandled unary operator: {ue.Operator}" + + let operand = com.TransformAsExpr(ctx, ue.Argument) + match op with + | Some op -> UnaryOp(op, operand).AsExpr() + | _ -> operand + | :? Babel.ArrowFunctionExpression as afe -> let args = afe.Params |> List.ofArray |> List.map (fun pattern -> Arg(Identifier pattern.Name)) let arguments = Arguments(args = args) - let body = transformAsStatements com ctx ReturnStrategy.Return afe.Body - Lambda(arguments, body) :> _ - | :? Babel.CallExpression as ce -> - let func = transformAsExpr com ctx ce.Callee - let args = ce.Arguments |> List.ofArray |> List.map (fun arg -> transformAsExpr com ctx arg) - Call(func, args) :> _ - | :? Babel.NumericLiteral as nl -> Constant(value = nl.Value) :> _ - | :? Babel.StringLiteral as sl -> Constant(value = sl.Value) :> _ - | :? Babel.Identifier as ident -> Name(id = Identifier ident.Name, ctx = Load()) :> _ + let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), afe.Body) + Lambda(arguments, body).AsExpr() + | :? Babel.CallExpression as ce -> // FIXME: use transformAsCall + let func = com.TransformAsExpr(ctx, ce.Callee) + let args = ce.Arguments |> List.ofArray |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) + Call(func, args).AsExpr() + | :? Babel.ArrayExpression as ae -> + let elems = ae.Elements |> List.ofArray |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) + Tuple(elems).AsExpr() + | :? Babel.NumericLiteral as nl -> Constant(value = nl.Value).AsExpr() + | :? Babel.StringLiteral as sl -> Constant(value = sl.Value).AsExpr() + | :? Babel.Identifier as ident -> Name(id = Identifier ident.Name, ctx = Load()).AsExpr() + | :? Babel.NewExpression as ne -> // FIXME: use transformAsCall + let func = com.TransformAsExpr(ctx, ne.Callee) + let args = ne.Arguments |> List.ofArray |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) + Call(func, args).AsExpr() + | :? Babel.Super -> + Name(Identifier("super().__init__"), ctx = Load()).AsExpr() + // | :? Babel.ObjectExpression as oe -> + // oe.Properties | a -> failwith $"Unhandled value: {a}" + let rec transformExprAsStatements (com: IPythonCompiler) ctx (returnStrategy: ReturnStrategy option) (expr: Babel.Expression): Statement list = + match expr with + | :? Babel.AssignmentExpression as ae -> + let value = com.TransformAsExpr(ctx, ae.Right) + let targets: Expression list = + let attr = + match ae.Left with + | :? Babel.Identifier as identifier -> identifier.Name + | _ -> failwith $"AssignmentExpression, unknow expression: {ae.Left}" + [ Attribute(value=Name(id=Identifier("self"), ctx=Load()), attr=Identifier(attr), ctx = Store()) ] + + [ Assign(targets = targets, value = value) ] + + | _ -> failwith $"transformAsStatements: unknown expr: {expr}" + + let rec transformStmtAsStatements (com: IPythonCompiler) ctx (returnStrategy: ReturnStrategy option) (stmt: Babel.Statement): Statement list = + match stmt with + | :? Babel.BlockStatement as bl -> + [ for st in bl.Body do + yield! com.TransformAsStatements(ctx, returnStrategy, st) ] + | :? Babel.ReturnStatement as rtn -> + let expr = transformAsExpr com ctx rtn.Argument + [ Return expr ] + | :? Babel.VariableDeclaration as vd -> + [ for vd in vd.Declarations do + let targets: Expression list = + [ Name(id = Identifier(vd.Id.Name), ctx = Store()) ] + + match vd.Init with + | Some value -> Assign(targets, com.TransformAsExpr(ctx, value)) + | None -> () ] + + | :? Babel.ExpressionStatement as es -> + com.TransformAsStatements(ctx, returnStrategy, es.Expression) + + | _ -> failwith $"transformStmtAsStatements: Unhandled stmt: {stmt}" + let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = + let returnStrategy = Some ReturnStrategy.Return + let stmt: Statement list = [ for md in body do match md with @@ -107,8 +179,7 @@ module Util = match decl.Declaration with | :? Babel.VariableDeclaration as decl -> for decls in decl.Declarations do - let value: Expression = transformAsExpr com ctx decls.Init.Value - + let value = com.TransformAsExpr(ctx, decls.Init.Value) let targets: Expression list = [ Name(id = Identifier(decls.Id.Name), ctx = Store()) ] @@ -122,10 +193,13 @@ module Util = let arguments = Arguments(args = args) let body = - transformAsStatements com ctx ReturnStrategy.Return fd.Body + com.TransformAsStatements(ctx, returnStrategy, fd.Body) yield FunctionDef(Identifier fd.Id.Name, arguments, body = body) - | a -> printfn "Declaration: %A" a + | :? Babel.ClassDeclaration as cd -> + yield! com.TransformAsClassDef(ctx, cd) + | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" + | :? Babel.ImportDeclaration as imp -> let source = imp.Source.Value |> Identifier |> Some let mapper (expr: Babel.ImportSpecifier) = @@ -133,39 +207,20 @@ module Util = | :? Babel.ImportMemberSpecifier as im -> let a = im.Imported.Name Alias(Identifier a, None) + | _ -> failwith $"Unhandled import: {expr}" let sa = imp.Specifiers |> List.ofArray |> List.map mapper yield ImportFrom(source, sa) :> _ | :? Babel.PrivateModuleDeclaration as pmd -> let st = pmd.Statement - yield! transformAsStatements com ctx ReturnStrategy.Return st + yield! com.TransformAsStatements(ctx, returnStrategy, st) | _ -> failwith $"Unknown module declaration: {md}" ] Module(stmt) - let rec transformAsStatements (com: IPythonCompiler) ctx returnStrategy (expr: Babel.Statement): Statement list = - match expr with - | :? Babel.BlockStatement as bl -> - [ for stmt in bl.Body do - yield! transformAsStatements com ctx returnStrategy stmt ] - | :? Babel.ReturnStatement as rtn -> - let expr = transformAsExpr com ctx rtn.Argument - [ Return expr ] - | :? Babel.VariableDeclaration as vd -> - [ for vd in vd.Declarations do - let targets: Expression list = - [ Name(id = Identifier(vd.Id.Name), ctx = Store()) ] - - match vd.Init with - | Some value -> Assign(targets, transformAsExpr com ctx value) - | None -> () ] - | :? Babel.ExpressionStatement as es -> - [ Expr(transformAsExpr com ctx es.Expression) ] - | a -> failwith $"Unhandled statement: {a}" - let getIdentForImport (ctx: Context) (path: string) (selector: string) = - if System.String.IsNullOrEmpty selector then + if String.IsNullOrEmpty selector then None else match selector with @@ -210,7 +265,9 @@ module Compiler = // | None -> upcast Babel.NullLiteral () //member _.GetAllImports() = upcast imports.Values member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e - //member bcom.TransformAsStatements(ctx, ret, e) = transformAsStatements bcom ctx ret e + member bcom.TransformAsStatements(ctx, ret, e) = transformExprAsStatements bcom ctx ret e + member bcom.TransformAsStatements(ctx, ret, e) = transformStmtAsStatements bcom ctx ret e + member bcom.TransformAsClassDef(ctx, cls) = transformAsClassDef bcom ctx cls //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body //member bcom.TransformImport(ctx, selector, path) = transformImport bcom ctx None selector path @@ -236,12 +293,6 @@ module Compiler = let transformFile (com: Compiler) (program: Babel.Program) = let com = makeCompiler com :> IPythonCompiler - let declScopes = - let hs = HashSet() - //for decl in file.Body .Declarations do - // hs.UnionWith(decl.UsedNames) - hs - let ctx = { DecisionTargets = [] HoistVars = fun _ -> false @@ -249,6 +300,4 @@ module Compiler = OptimizeTailCall = fun () -> () ScopedTypeParams = Set.empty } - let body = transformProgram com ctx program.Body - printfn "Body: %A" body - body + transformProgram com ctx program.Body diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 793a20d02e..56c857056e 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -116,27 +116,6 @@ module PrinterExtensions = // member printer.PrintCommaSeparatedArray(nodes: #Node array) = // printer.PrintArray(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) - // // TODO: (super) type parameters, implements -// member printer.PrintClass(id: Identifier option, superClass: Expression option, -// superTypeParameters: TypeParameterInstantiation option, -// typeParameters: TypeParameterDeclaration option, -// implements: ClassImplements array option, body: ClassBody, loc) = -// printer.Print("class", ?loc=loc) -// printer.PrintOptional(" ", id) -// printer.PrintOptional(typeParameters) -// match superClass with -// | Some (:? Identifier as id) when id.TypeAnnotation.IsSome -> -// printer.Print(" extends "); -// printer.Print(id.TypeAnnotation.Value.TypeAnnotation) -// | _ -> printer.PrintOptional("(", superClass, ")") -// // printer.PrintOptional(superTypeParameters) -// match implements with -// | Some implements when not (Array.isEmpty implements) -> -// printer.Print(" implements ") -// printer.PrintArray(implements, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) -// | _ -> () -// printer.Print(":") -// printer.Print(body) member printer.PrintFunction ( @@ -264,6 +243,10 @@ type Expression() = abstract Print: Printer -> unit + member x.AsExpr() = + x + + type Operator = inherit AST @@ -377,7 +360,8 @@ type Arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ? member _.Defaults: Expression list = defaultArg defaults [] interface AST with - member _.Print(printer) = () + member _.Print(printer) = + printer.Print("(Arguments)") //#region Statements @@ -432,7 +416,8 @@ type Assign(targets, value, ?typeComment) = /// When an expression, such as a function call, appears as a statement by itself with its return value not used or /// stored, it is wrapped in this container. value holds one of the other nodes in this section, a Constant, a Name, a /// Lambda, a Yield or YieldFrom node. -/// ```python +/// +/// ```py /// >>> print(ast.dump(ast.parse('-a'), indent=4)) /// Module( /// body=[ @@ -447,7 +432,8 @@ type Expr(value) = member _.Value: Expression = value - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(Expr)") /// A for loop. target holds the variable(s) the loop assigns to, as a single Name, Tuple or List node. iter holds the /// item to be looped over, again as a single node. body and orelse contain lists of nodes to execute. Those in orelse @@ -484,7 +470,8 @@ type For(target, iter, body, orelse, ?typeComment) = member _.Else: Statement list = orelse member _.TypeComment: string option = typeComment - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(For)") type AsyncFor(target, iter, body, orelse, ?typeComment) = inherit Statement() @@ -495,7 +482,8 @@ type AsyncFor(target, iter, body, orelse, ?typeComment) = member _.Else: Statement list = orelse member _.TypeComment: string option = typeComment - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(AsyncFor)") /// A while loop. test holds the condition, such as a Compare node. /// @@ -525,7 +513,66 @@ type While(test, body, orelse) = member _.Body: Statement list = body member _.Else: Statement list = orelse - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(While)") + +/// A class definition. +/// +/// - name is a raw string for the class name +/// - bases is a list of nodes for explicitly specified base classes. +/// - keywords is a list of keyword nodes, principally for ‘metaclass’. Other keywords will be passed to the metaclass, +/// as per PEP-3115. +/// - starargs and kwargs are each a single node, as in a function call. starargs will be expanded to join the list of +/// base classes, and kwargs will be passed to the metaclass. +/// - body is a list of nodes representing the code within the class definition. +/// - decorator_list is a list of nodes, as in FunctionDef. +/// +/// ```py +/// >>> print(ast.dump(ast.parse("""\ +/// ... @decorator1 +/// ... @decorator2 +/// ... class Foo(base1, base2, metaclass=meta): +/// ... pass +/// ... """), indent=4)) +/// Module( +/// body=[ +/// ClassDef( +/// name='Foo', +/// bases=[ +/// Name(id='base1', ctx=Load()), +/// Name(id='base2', ctx=Load())], +/// keywords=[ +/// keyword( +/// arg='metaclass', +/// value=Name(id='meta', ctx=Load()))], +/// body=[ +/// Pass()], +/// decorator_list=[ +/// Name(id='decorator1', ctx=Load()), +/// Name(id='decorator2', ctx=Load())])], +/// type_ignores=[]) +///``` +type ClassDef(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc) = + inherit Statement() + + member _.Name: Identifier = name + member _.Bases: Expression list = defaultArg bases [] + member _.Keyword: Keyword list = defaultArg keywords [] + member _.Body: Statement list = defaultArg body [] + member _.DecoratorList: Expression list = defaultArg decoratorList [] + + override this.Print(printer) = + let (Identifier name) = name + printer.Print("class ", ?loc=loc) + printer.Print(name) + //match superClass with + //| Some (:? Identifier as id) when id.TypeAnnotation.IsSome -> + // printer.Print(" extends "); + // printer.Print(id.TypeAnnotation.Value.TypeAnnotation) + // _ -> printer.PrintOptional("(", superClass, ")") + // printer.PrintOptional(superTypeParameters) + printer.Print(":") + printer.PrintProductiveStatements(this.Body) /// An if statement. test holds a single node, such as a Compare node. body and orelse each hold a list of nodes. /// @@ -566,7 +613,8 @@ type If(test, body, orelse) = member _.Body: Statement list = body member _.Else: Statement list = orelse - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(If)") /// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone /// raise. cause is the optional part for y in raise x from y. @@ -586,7 +634,8 @@ type Raise(exc, ?cause) = member _.Exception: Expression = exc member _.Cause: Expression option = cause - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(Raise)") /// A function definition. /// @@ -631,7 +680,8 @@ type Global(names) = member _.Names: Identifier list = names - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(Global)") /// global and nonlocal statements. names is a list of raw strings. /// @@ -651,7 +701,8 @@ type NonLocal(names) = member _.Names: Identifier list = names - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(NonLocal)") /// A pass statement. /// @@ -665,19 +716,22 @@ type NonLocal(names) = type Pass() = inherit Statement() - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("pass") /// The break statement. type Break() = inherit Statement() - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("break") /// The continue statement. type Continue() = inherit Statement() - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("continue") /// An async function definition. /// @@ -699,10 +753,12 @@ type AsyncFunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = member _.Returns: Expression option = returns member _.TypeComment: string option = typeComment - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(AsyncFunctionDef)") /// An import statement. names is a list of alias nodes. -/// ```python +/// +/// ```py /// >>> print(ast.dump(ast.parse('import x,y,z'), indent=4)) /// Module( /// body=[ @@ -718,12 +774,14 @@ type Import(names) = member _.Names: Alias list = names - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(Import)") /// Represents from x import y. module is a raw string of the ‘from’ name, without any leading dots, or None for /// statements such as from . import foo. level is an integer holding the level of the relative import (0 means absolute /// import). -///```python +/// +/// ```py /// >>> print(ast.dump(ast.parse('from y import x,y,z'), indent=4)) /// Module( /// body=[ @@ -780,6 +838,16 @@ type Return(?value) = //#region Expressions +type Attribute(value, attr, ctx) = + inherit Expression() + + member _.Value: Expression = value + member _.Attr: Identifier = attr + member _.Ctx: ExpressionContext = ctx + + override this.Print(printer) = + printer.Print("(Attribute)") + type BinOp(left, op, right) = inherit Expression() @@ -789,6 +857,7 @@ type BinOp(left, op, right) = override this.Print(printer) = printer.PrintOperation(left, op, right) + /// A unary operation. op is the operator, and operand any expression node. type UnaryOp(op, operand, ?loc) = inherit Expression() @@ -813,7 +882,8 @@ type UnaryOp(op, operand, ?loc) = /// A constant value. The value attribute of the Constant literal contains the Python object it represents. The values /// represented can be simple types such as a number, string or None, but also immutable container types (tuples and /// frozensets) if all of their elements are constant. -/// ```python +/// +/// ```py /// >>> print(ast.dump(ast.parse('123', mode='eval'), indent=4)) /// Expression( /// body=Constant(value=123)) @@ -849,7 +919,8 @@ type FormattedValue(value, ?conversion, ?formatSpec) = member _.Conversion: int option = conversion member _.FormatSpec: Expression option = formatSpec - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(FormattedValue)") /// A function call. func is the function, which will often be a Name or Attribute object. Of the arguments: /// @@ -937,7 +1008,7 @@ type Name(id, ctx) = /// A tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an assignment target /// (i.e. (x,y)=something), and Load otherwise. /// -/// ```python +/// ```py /// >>> print(ast.dump(ast.parse('(1, 2, 3)', mode='eval'), indent=4)) /// Expression( /// body=Tuple( @@ -947,11 +1018,14 @@ type Name(id, ctx) = /// Constant(value=3)], /// ctx=Load())) ///``` -type Tuple(elts) = +type Tuple(elts, ?loc) = inherit Expression() member _.Elements: Expression list = elts - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(", ?loc=loc) + printer.PrintCommaSeparatedList(elts) + printer.Print(")") /// A list or tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an /// assignment target (i.e. (x,y)=something), and Load otherwise. @@ -971,11 +1045,12 @@ type List(elts) = member _.Elements: Expression list = elts - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(List)") /// A set. elts holds a list of nodes representing the set’s elements. /// -/// ```python +/// ```py /// >>> print(ast.dump(ast.parse('{1, 2, 3}', mode='eval'), indent=4)) /// Expression( /// body=Set( @@ -983,13 +1058,14 @@ type List(elts) = /// Constant(value=1), /// Constant(value=2), /// Constant(value=3)])) -/// ````` +/// ``` type Set(elts) = inherit Expression() member _.Elements: Expression list = elts - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(Set)") /// A dictionary. keys and values hold lists of nodes representing the keys and the values respectively, in matching /// order (what would be returned when calling dictionary.keys() and dictionary.values()). @@ -997,7 +1073,7 @@ type Set(elts) = /// When doing dictionary unpacking using dictionary literals the expression to be expanded goes in the values list, /// with a None at the corresponding position in keys. /// -/// ```python +/// ```py /// >>> print(ast.dump(ast.parse('{"a":1, **d}', mode='eval'), indent=4)) /// Expression( /// body=Dict( @@ -1014,7 +1090,8 @@ type Dict(keys, values) = member _.Keys: Expression list = keys member _.Values: Expression list = values - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(Dict)") /// A yield expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back is not /// used. @@ -1032,7 +1109,8 @@ type Yield(?value) = inherit Expression() member _.Value: Expression option = value - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(Yield)") /// A yield from expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back /// is not used. @@ -1050,7 +1128,8 @@ type YieldFrom(?value) = inherit Expression() member _.Value: Expression option = value - override _.Print(printer) = () + override _.Print(printer) = + printer.Print("(YieldFrom)") //#endregion @@ -1194,3 +1273,5 @@ type Store() = member this.Print(printer) = () //#endregion + + From 0701377bfb44d3e1c71f220e4d798f8ef973b4fd Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 13 Jan 2021 00:05:55 +0100 Subject: [PATCH 016/145] Fix Babel expression to Python statements --- src/Fable.Transforms/Python/Babel2Python.fs | 35 ++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 5e21a6df6f..eed5e0cd7b 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -69,6 +69,7 @@ module Util = //yield ClassDef(Identifier cd.Id.Value.Name, body=body) body + /// Transform Babel expression as Python expression let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Babel.Expression): Expression = match expr with | :? Babel.BinaryExpression as bin -> @@ -132,22 +133,28 @@ module Util = // oe.Properties | a -> failwith $"Unhandled value: {a}" - let rec transformExprAsStatements (com: IPythonCompiler) ctx (returnStrategy: ReturnStrategy option) (expr: Babel.Expression): Statement list = - match expr with - | :? Babel.AssignmentExpression as ae -> + /// Transform Babel expressions as Python statements. + let rec transformExpressionAsStatements (com: IPythonCompiler) ctx (returnStrategy: ReturnStrategy option) (expr: Babel.Expression): Statement list = + match expr with + | :? Babel.AssignmentExpression as ae -> let value = com.TransformAsExpr(ctx, ae.Right) let targets: Expression list = let attr = match ae.Left with - | :? Babel.Identifier as identifier -> identifier.Name + | :? Babel.Identifier as identifier -> Identifier(identifier.Name) + | :? Babel.MemberExpression as me -> + match me.Property with + | :? Babel.Identifier as id -> Identifier(id.Name) + | _ -> failwith "transformExpressionAsStatements: unknown property {me.Property}" | _ -> failwith $"AssignmentExpression, unknow expression: {ae.Left}" - [ Attribute(value=Name(id=Identifier("self"), ctx=Load()), attr=Identifier(attr), ctx = Store()) ] + [ Attribute(value=Name(id=Identifier("self"), ctx=Load()), attr=attr, ctx = Store()) ] [ Assign(targets = targets, value = value) ] - | _ -> failwith $"transformAsStatements: unknown expr: {expr}" + | _ -> failwith $"transformExpressionAsStatements: unknown expr: {expr}" - let rec transformStmtAsStatements (com: IPythonCompiler) ctx (returnStrategy: ReturnStrategy option) (stmt: Babel.Statement): Statement list = + /// Transform Babel statement as Python statements. + let rec transformStatementAsStatements (com: IPythonCompiler) ctx (returnStrategy: ReturnStrategy option) (stmt: Babel.Statement): Statement list = match stmt with | :? Babel.BlockStatement as bl -> [ for st in bl.Body do @@ -165,10 +172,16 @@ module Util = | None -> () ] | :? Babel.ExpressionStatement as es -> - com.TransformAsStatements(ctx, returnStrategy, es.Expression) + // Handle Babel expressions that needs to be transformed as Python statements + match es.Expression with + | :? Babel.AssignmentExpression -> + com.TransformAsStatements(ctx, returnStrategy, es.Expression) + | _ -> + [ Expr(com.TransformAsExpr(ctx, es.Expression)) ] - | _ -> failwith $"transformStmtAsStatements: Unhandled stmt: {stmt}" + | _ -> failwith $"transformStatementAsStatements: Unhandled stmt: {stmt}" + /// Transform Babel program to Python module. let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = let returnStrategy = Some ReturnStrategy.Return @@ -265,8 +278,8 @@ module Compiler = // | None -> upcast Babel.NullLiteral () //member _.GetAllImports() = upcast imports.Values member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e - member bcom.TransformAsStatements(ctx, ret, e) = transformExprAsStatements bcom ctx ret e - member bcom.TransformAsStatements(ctx, ret, e) = transformStmtAsStatements bcom ctx ret e + member bcom.TransformAsStatements(ctx, ret, e) = transformExpressionAsStatements bcom ctx ret e + member bcom.TransformAsStatements(ctx, ret, e) = transformStatementAsStatements bcom ctx ret e member bcom.TransformAsClassDef(ctx, cls) = transformAsClassDef bcom ctx cls //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body //member bcom.TransformImport(ctx, selector, path) = transformImport bcom ctx None selector path From c0268eb484bf7b8f0d3b77f73884d4a8f4659343 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 13 Jan 2021 20:27:57 +0100 Subject: [PATCH 017/145] Fixes for class definitions --- src/Fable.Transforms/Python/Babel2Python.fs | 117 ++++++++++++++------ src/Fable.Transforms/Python/Python.fs | 90 ++++++++++----- 2 files changed, 143 insertions(+), 64 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index eed5e0cd7b..87de7a4eaa 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -44,54 +44,66 @@ type IPythonCompiler = abstract TransformAsExpr: Context * Babel.Expression -> Expression abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Expression -> Statement list - abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement list + abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement //abstract TransformImport: Context * selector:string * path:string -> Expression //abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> (Pattern array) * BlockStatement abstract WarnOnlyOnce: string * ?range:SourceLocation -> unit + +module Helpers = + let cleanNameAsPythonIdentifier (name: string) = + name.Replace('$','_') + module Util = - let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration) : Statement list= + let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration) : Statement= + printfn $"transformAsClassDef" + let body : Statement list = [ for mber in cls.Body.Body do match mber with | :? Babel.ClassMethod as cm -> match cm.Kind with | "constructor" -> - let args = cm.Params |> List.ofArray |> List.map (fun arg -> Arg(Identifier arg.Name)) - let arguments = Arguments(args=args) + let self = Arg(Identifier("self")) + let args = cm.Params |> List.ofArray |> List.map (fun arg -> Arg(Identifier(arg.Name))) + let arguments = Arguments(args=self :: args) let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), cm.Body) FunctionDef(Identifier "__init__", arguments, body=body) | _ -> failwith $"Unknown kind: {cm.Kind}" | _ -> failwith $"Unhandled class member {mber}" //yield! com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), stmt) ] - //yield ClassDef(Identifier cd.Id.Value.Name, body=body) - body + ClassDef(Identifier(cls.Id.Value.Name), body=body) :> _ /// Transform Babel expression as Python expression let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Babel.Expression): Expression = + printfn $"transformAsExpr: {expr}" match expr with - | :? Babel.BinaryExpression as bin -> - let left = com.TransformAsExpr(ctx, bin.Left) - let right = com.TransformAsExpr(ctx, bin.Right) - - let op: Operator = - match bin.Operator with - | "+" -> Add() :> _ - | "-" -> Sub() :> _ - | "*" -> Mult() :> _ - | "/" -> Div() :> _ - | "%" -> Mod() :> _ - | "**" -> Pow() :> _ - | "<<" -> LShift() :> _ - | ">>" -> RShift() :> _ - | "|" -> BitOr() :> _ - | "^" -> BitXor() :> _ - | "&" -> BitAnd() :> _ - | _ -> failwith $"Unknown operator: {bin.Operator}" - - BinOp(left, op, right).AsExpr() + | :? Babel.BinaryExpression as be -> + let left = com.TransformAsExpr(ctx, be.Left) + let right = com.TransformAsExpr(ctx, be.Right) + + let bin op = + BinOp(left, op, right).AsExpr() + let cmp op = + Compare(left, [op], [right]).AsExpr() + + match be.Operator with + | "+" -> Add() |> bin + | "-" -> Sub() |> bin + | "*" -> Mult() |> bin + | "/" -> Div() |> bin + | "%" -> Mod() |> bin + | "**" -> Pow() |> bin + | "<<" -> LShift() |> bin + | ">>" -> RShift() |> bin + | "|" -> BitOr() |> bin + | "^" -> BitXor() |> bin + | "&" -> BitAnd() |> bin + | "===" -> Eq() |> cmp + | _ -> failwith $"Unknown operator: {be.Operator}" + | :? Babel.UnaryExpression as ue -> let op = match ue.Operator with @@ -122,19 +134,43 @@ module Util = Tuple(elems).AsExpr() | :? Babel.NumericLiteral as nl -> Constant(value = nl.Value).AsExpr() | :? Babel.StringLiteral as sl -> Constant(value = sl.Value).AsExpr() - | :? Babel.Identifier as ident -> Name(id = Identifier ident.Name, ctx = Load()).AsExpr() + | :? Babel.Identifier as ident -> + let name = Helpers.cleanNameAsPythonIdentifier ident.Name + Name(id = Identifier name, ctx = Load()).AsExpr() | :? Babel.NewExpression as ne -> // FIXME: use transformAsCall let func = com.TransformAsExpr(ctx, ne.Callee) let args = ne.Arguments |> List.ofArray |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) Call(func, args).AsExpr() | :? Babel.Super -> Name(Identifier("super().__init__"), ctx = Load()).AsExpr() - // | :? Babel.ObjectExpression as oe -> - // oe.Properties - | a -> failwith $"Unhandled value: {a}" + | :? Babel.ObjectExpression as oe -> + let kv = [ + for prop in oe.Properties do + match prop with + | :? Babel.ObjectProperty as op -> + let key = com.TransformAsExpr(ctx, op.Key) + let value = com.TransformAsExpr(ctx, op.Value) + key, value + | _ -> failwith $"transformAsExpr: unhandled object expression property: {prop}" + ] + let keys = kv |> List.map fst + let values = kv |> List.map snd + Dict(keys=keys, values=values).AsExpr() + | :? Babel.EmitExpression as ee -> + failwith "Not Implemented" + | :? Babel.MemberExpression as me -> + let value = com.TransformAsExpr(ctx, me.Object) + let attr = + match me.Property with + | :? Babel.Identifier as id -> Identifier(id.Name) + | _ -> failwith "transformExpressionAsStatements: unknown property {me.Property}" + Attribute(value=value, attr=attr, ctx=Load()).AsExpr() + | _ -> failwith $"Unhandled value: {expr}" /// Transform Babel expressions as Python statements. let rec transformExpressionAsStatements (com: IPythonCompiler) ctx (returnStrategy: ReturnStrategy option) (expr: Babel.Expression): Statement list = + printfn $"transformExpressionAsStatements: {expr}" + match expr with | :? Babel.AssignmentExpression as ae -> let value = com.TransformAsExpr(ctx, ae.Right) @@ -155,6 +191,8 @@ module Util = /// Transform Babel statement as Python statements. let rec transformStatementAsStatements (com: IPythonCompiler) ctx (returnStrategy: ReturnStrategy option) (stmt: Babel.Statement): Statement list = + printfn $"transformStatementAsStatements: {stmt}" + match stmt with | :? Babel.BlockStatement as bl -> [ for st in bl.Body do @@ -172,14 +210,21 @@ module Util = | None -> () ] | :? Babel.ExpressionStatement as es -> - // Handle Babel expressions that needs to be transformed as Python statements + // Handle Babel expressions that we need to transforme here as Python statements match es.Expression with | :? Babel.AssignmentExpression -> com.TransformAsStatements(ctx, returnStrategy, es.Expression) | _ -> [ Expr(com.TransformAsExpr(ctx, es.Expression)) ] - - | _ -> failwith $"transformStatementAsStatements: Unhandled stmt: {stmt}" + | :? Babel.IfStatement as iff -> + let test = com.TransformAsExpr(ctx, iff.Test) + let body = com.TransformAsStatements(ctx, returnStrategy, iff.Consequent) + let orElse = + match iff.Alternate with + | Some alt -> com.TransformAsStatements(ctx, returnStrategy, alt) + | _ -> [] + [ If(test=test, body=body, orelse=orElse) ] + | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" /// Transform Babel program to Python module. let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = @@ -208,9 +253,11 @@ module Util = let body = com.TransformAsStatements(ctx, returnStrategy, fd.Body) - yield FunctionDef(Identifier fd.Id.Name, arguments, body = body) + let name = Helpers.cleanNameAsPythonIdentifier(fd.Id.Name) + yield FunctionDef(Identifier(name), arguments, body = body) | :? Babel.ClassDeclaration as cd -> - yield! com.TransformAsClassDef(ctx, cd) + printfn "TransformAsClassDef" + yield com.TransformAsClassDef(ctx, cd) | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" | :? Babel.ImportDeclaration as imp -> diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 56c857056e..f1dab57595 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -45,13 +45,15 @@ module PrinterExtensions = printer.PrintNewLine() member _.IsProductiveStatement(s: Statement) = - let rec hasNoSideEffects(e: Statement) = + let rec hasNoSideEffects(e: Expression) = + printfn "hasNoSideEffects: {e}" match e with - //| :? Identifier -> true - | _ -> true + | :? Constant -> true + | :? Dict as d -> d.Keys.IsEmpty + | _ -> false match s with - //| :? Statement as e -> hasNoSideEffects e |> not + | :? Expr as e -> hasNoSideEffects e.Value |> not | _ -> true member printer.PrintProductiveStatement(s: Statement, ?printSeparator) = @@ -433,7 +435,7 @@ type Expr(value) = member _.Value: Expression = value override _.Print(printer) = - printer.Print("(Expr)") + value.Print(printer) /// A for loop. target holds the variable(s) the loop assigns to, as a single Name, Tuple or List node. iter holds the /// item to be looped over, again as a single node. body and orelse contain lists of nodes to execute. Those in orelse @@ -572,7 +574,10 @@ type ClassDef(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc) = // _ -> printer.PrintOptional("(", superClass, ")") // printer.PrintOptional(superTypeParameters) printer.Print(":") + printer.PrintNewLine() + printer.PushIndentation() printer.PrintProductiveStatements(this.Body) + printer.PopIndentation() /// An if statement. test holds a single node, such as a Compare node. body and orelse each hold a list of nodes. /// @@ -830,9 +835,9 @@ type Return(?value) = inherit Statement() member _.Value: Expression option = value - override _.Print(printer) = + override this.Print(printer) = printer.Print("return ") - printer.PrintOptional(value) + printer.PrintOptional(this.Value) //#endregion @@ -846,7 +851,9 @@ type Attribute(value, attr, ctx) = member _.Ctx: ExpressionContext = ctx override this.Print(printer) = - printer.Print("(Attribute)") + printer.Print(this.Value) + printer.Print(".") + printer.Print(this.Attr) type BinOp(left, op, right) = inherit Expression() @@ -857,6 +864,16 @@ type BinOp(left, op, right) = override this.Print(printer) = printer.PrintOperation(left, op, right) +type Compare(left, ops, comparators) = + inherit Expression() + + member _.Left: Expression = left + member _.Comparators: Expression list= comparators + member _.Ops: ComparisonOperator list = ops + + override this.Print(printer) = + printer.Print("(Compare)") + //printer.PrintOperation(left, op, right) /// A unary operation. op is the operator, and operand any expression node. type UnaryOp(op, operand, ?loc) = @@ -1091,7 +1108,12 @@ type Dict(keys, values) = member _.Values: Expression list = values override _.Print(printer) = - printer.Print("(Dict)") + printer.Print("{") + for key, value in List.zip keys values do + key.Print(printer) + printer.Print(":") + value.Print(printer) + printer.Print("}") /// A yield expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back is not /// used. @@ -1193,35 +1215,45 @@ type MatMult() = //#region Comparison operator tokens. -type Eq = - inherit ComparisonOperator +type Eq() = + interface ComparisonOperator with + member _.Print(printer: Printer) = printer.Print($" = ") -type NotEq = - inherit ComparisonOperator +type NotEq() = + interface ComparisonOperator with + member _.Print(printer: Printer) = printer.Print($" <> ") -type Lt = - inherit ComparisonOperator +type Lt() = + interface ComparisonOperator with + member _.Print(printer: Printer) = printer.Print($" < ") -type LtE = - inherit ComparisonOperator +type LtE() = + interface ComparisonOperator with + member _.Print(printer: Printer) = printer.Print($" <= ") -type Gt = - inherit ComparisonOperator +type Gt() = + interface ComparisonOperator with + member _.Print(printer: Printer) = printer.Print($" > ") -type GtE = - inherit ComparisonOperator +type GtE() = + interface ComparisonOperator with + member _.Print(printer: Printer) = printer.Print($" >= ") -type Is = - inherit ComparisonOperator +type Is() = + interface ComparisonOperator with + member _.Print(printer: Printer) = printer.Print($" is ") -type IsNot = - inherit ComparisonOperator +type IsNot() = + interface ComparisonOperator with + member _.Print(printer: Printer) = printer.Print($" is not ") -type In = - inherit ComparisonOperator +type In() = + interface ComparisonOperator with + member _.Print(printer: Printer) = printer.Print($" in ") -type NotIn = - inherit ComparisonOperator +type NotIn() = + interface ComparisonOperator with + member _.Print(printer: Printer) = printer.Print($" not in ") //#endregion From 86a9fa66fd51b835a3a07021de03dd1fff8b7f48 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 13 Jan 2021 21:05:09 +0100 Subject: [PATCH 018/145] Fix fable library imports --- src/Fable.Transforms/Python/Babel2Python.fs | 40 ++++++++++++++------- src/Fable.Transforms/Python/Python.fs | 2 -- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 87de7a4eaa..8a19543d3a 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -2,6 +2,7 @@ module rec Fable.Transforms.Babel2Python open System open System.Collections.Generic +open System.Text.RegularExpressions open Fable open Fable.AST @@ -45,7 +46,7 @@ type IPythonCompiler = abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Expression -> Statement list abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement - //abstract TransformImport: Context * selector:string * path:string -> Expression + abstract TransformAsImport: Context * Babel.ImportDeclaration -> Statement //abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> (Pattern array) * BlockStatement abstract WarnOnlyOnce: string * ?range:SourceLocation -> unit @@ -56,6 +57,30 @@ module Helpers = name.Replace('$','_') module Util = + let transformAsImport (com: IPythonCompiler) ctx (imp: Babel.ImportDeclaration) : Statement = + let reFableLib = Regex(".*\/fable-library[\.0-9]*\/(?[^\/]*)\.js", RegexOptions.Compiled) + let transform name = + let m = reFableLib.Match(name) + if m.Groups.Count > 0 then + let pymodule = m.Groups.["module"].Value.ToLower() + String.concat "." ["expression"; "fable"; pymodule] + else + name + let pyModule = + imp.Source.Value + |> transform + |> Identifier + |> Some + let mapper (expr: Babel.ImportSpecifier) = + match expr with + | :? Babel.ImportMemberSpecifier as im -> + let a = im.Imported.Name + Alias(Identifier a, None) + | _ -> failwith $"Unhandled import: {expr}" + + let aliases = imp.Specifiers |> List.ofArray |> List.map mapper + ImportFrom(pyModule, aliases) :> _ + let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration) : Statement= printfn $"transformAsClassDef" @@ -261,16 +286,7 @@ module Util = | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" | :? Babel.ImportDeclaration as imp -> - let source = imp.Source.Value |> Identifier |> Some - let mapper (expr: Babel.ImportSpecifier) = - match expr with - | :? Babel.ImportMemberSpecifier as im -> - let a = im.Imported.Name - Alias(Identifier a, None) - | _ -> failwith $"Unhandled import: {expr}" - - let sa = imp.Specifiers |> List.ofArray |> List.map mapper - yield ImportFrom(source, sa) :> _ + yield com.TransformAsImport(ctx, imp) | :? Babel.PrivateModuleDeclaration as pmd -> let st = pmd.Statement yield! com.TransformAsStatements(ctx, returnStrategy, st) @@ -329,7 +345,7 @@ module Compiler = member bcom.TransformAsStatements(ctx, ret, e) = transformStatementAsStatements bcom ctx ret e member bcom.TransformAsClassDef(ctx, cls) = transformAsClassDef bcom ctx cls //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body - //member bcom.TransformImport(ctx, selector, path) = transformImport bcom ctx None selector path + member bcom.TransformAsImport(ctx, imp) = transformAsImport bcom ctx imp interface Compiler with member _.Options = com.Options diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index f1dab57595..e511b97c0f 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -810,9 +810,7 @@ type ImportFrom(``module``, names, ?level) = let (Identifier path) = this.Module |> Option.defaultValue (Identifier ".") printer.Print("from ") - printer.Print("\"") printer.Print(printer.MakeImportPath(path)) - printer.Print("\"") printer.Print(" import ") From 2b1e138516de2bd12c917bea98e2f5512704542d Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 13 Jan 2021 21:12:00 +0100 Subject: [PATCH 019/145] Revert newline changes --- src/Fable.Transforms/Replacements.fs | 2 +- src/Fable.Transforms/Transforms.Util.fs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 870e660dea..5dab5754f4 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -3197,4 +3197,4 @@ let tryBaseConstructor com ctx (ent: Entity) (argTypes: Lazy) genArgs | _ -> failwith "Unexpected hashset constructor" let entityName = FSharp2Fable.Helpers.cleanNameAsJsIdentifier "HashSet" Some(makeImportLib com Any entityName "MutableSet", args) - | _ -> None \ No newline at end of file + | _ -> None diff --git a/src/Fable.Transforms/Transforms.Util.fs b/src/Fable.Transforms/Transforms.Util.fs index 29d353db59..34718f832e 100644 --- a/src/Fable.Transforms/Transforms.Util.fs +++ b/src/Fable.Transforms/Transforms.Util.fs @@ -627,4 +627,4 @@ module AST = | None, Some r2 -> Some r2 | None, None -> None | Some r1, Some r2 -> Some(r1 + r2) - (None, locs) ||> Seq.fold addTwo \ No newline at end of file + (None, locs) ||> Seq.fold addTwo From fb50321ef336bdd411aba4a630acbd50c5dac5de Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 13 Jan 2021 21:15:54 +0100 Subject: [PATCH 020/145] Fix more newlines --- src/Fable.Transforms/BabelPrinter.fs | 2 +- src/Fable.Transforms/FSharp2Fable.Util.fs | 2 +- src/Fable.Transforms/Fable2Babel.fs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Fable.Transforms/BabelPrinter.fs b/src/Fable.Transforms/BabelPrinter.fs index 7bbb8df954..8fcf6d8d82 100644 --- a/src/Fable.Transforms/BabelPrinter.fs +++ b/src/Fable.Transforms/BabelPrinter.fs @@ -114,4 +114,4 @@ let run writer map (program: Program): Async = printDeclWithExtraLine true printer decl // TODO: Only flush every XXX lines? do! printer.Flush() - } \ No newline at end of file + } diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index c1516c2240..94b15f8f7d 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -1572,4 +1572,4 @@ module Util = | Emitted com r typ None emitted, _ -> emitted | Imported com r typ None imported -> imported | Try (tryGetIdentFromScope ctx r) expr, _ -> expr - | _ -> memberRefTyped com ctx r typ v \ No newline at end of file + | _ -> memberRefTyped com ctx r typ v diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 779ec0b30d..987302adbe 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -2172,4 +2172,4 @@ module Compiler = let rootDecls = List.collect (transformDeclaration com ctx) file.Declarations let importDecls = com.GetAllImports() |> transformImports let body = importDecls @ rootDecls |> List.toArray - Program(body) \ No newline at end of file + Program(body) From e9878024e639445ae224c5ae55734d3dc5de56f0 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 13 Jan 2021 21:16:38 +0100 Subject: [PATCH 021/145] More newlines --- src/Fable.Transforms/Global/Babel.fs | 2 +- src/Fable.Transforms/Global/Prelude.fs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index 90d5a2e955..586ef57382 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -1539,4 +1539,4 @@ type InterfaceDeclaration(id, body, ?extends_, ?typeParameters, ?implements_) = printer.Print(" implements ") printer.PrintArray(implements, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) printer.Print(" ") - printer.Print(body) \ No newline at end of file + printer.Print(body) diff --git a/src/Fable.Transforms/Global/Prelude.fs b/src/Fable.Transforms/Global/Prelude.fs index 61386b05bf..55d90c821e 100644 --- a/src/Fable.Transforms/Global/Prelude.fs +++ b/src/Fable.Transforms/Global/Prelude.fs @@ -582,4 +582,4 @@ module Path = |> fun path -> path.Split('/') |> Array.filter (String.IsNullOrWhiteSpace >> not)) |> Seq.toList |> getCommonPrefix - |> String.concat "/" \ No newline at end of file + |> String.concat "/" From 9dda9e63eb5ac66a370041f8055a7637c492915d Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 14 Jan 2021 00:52:43 +0100 Subject: [PATCH 022/145] Fix comparison operators --- src/Fable.Transforms/Python/Babel2Python.fs | 9 ++++++++- src/Fable.Transforms/Python/Python.fs | 9 ++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 8a19543d3a..d333fe3afa 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -126,7 +126,14 @@ module Util = | "|" -> BitOr() |> bin | "^" -> BitXor() |> bin | "&" -> BitAnd() |> bin - | "===" -> Eq() |> cmp + | "===" + | "==" -> Eq() |> cmp + | "!==" + | "!=" -> NotEq() |> cmp + | ">" -> Gt() |> cmp + | ">=" -> GtE() |> cmp + | "<" -> Lt() |> cmp + | "<=" -> LtE() |> cmp | _ -> failwith $"Unknown operator: {be.Operator}" | :? Babel.UnaryExpression as ue -> diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index e511b97c0f..7cdc1db7be 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -870,8 +870,11 @@ type Compare(left, ops, comparators) = member _.Ops: ComparisonOperator list = ops override this.Print(printer) = - printer.Print("(Compare)") - //printer.PrintOperation(left, op, right) + //printer.AddLocation(loc) + printer.ComplexExpressionWithParens(left) + for op, comparator in List.zip this.Ops this.Comparators do + printer.Print(op) + printer.ComplexExpressionWithParens(comparator) /// A unary operation. op is the operator, and operand any expression node. type UnaryOp(op, operand, ?loc) = @@ -1219,7 +1222,7 @@ type Eq() = type NotEq() = interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" <> ") + member _.Print(printer: Printer) = printer.Print($" != ") type Lt() = interface ComparisonOperator with From 08328c47619faedac96d7bceea5592e7aec2998a Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 14 Jan 2021 02:19:49 +0100 Subject: [PATCH 023/145] Fix subscript operator --- src/Fable.Transforms/Python/Babel2Python.fs | 17 +++++--- src/Fable.Transforms/Python/Python.fs | 46 ++++++++++++++++++++- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index d333fe3afa..27ef0ad104 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -192,11 +192,18 @@ module Util = failwith "Not Implemented" | :? Babel.MemberExpression as me -> let value = com.TransformAsExpr(ctx, me.Object) - let attr = - match me.Property with - | :? Babel.Identifier as id -> Identifier(id.Name) - | _ -> failwith "transformExpressionAsStatements: unknown property {me.Property}" - Attribute(value=value, attr=attr, ctx=Load()).AsExpr() + if me.Computed then + let attr = + match me.Property with + | :? Babel.NumericLiteral as nl -> Constant(nl.Value) + | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" + Subscript(value=value, slice=attr, ctx=Load()).AsExpr() + else + let attr = + match me.Property with + | :? Babel.Identifier as id -> Identifier(id.Name) + | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" + Attribute(value=value, attr=attr, ctx=Load()).AsExpr() | _ -> failwith $"Unhandled value: {expr}" /// Transform Babel expressions as Python statements. diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 7cdc1db7be..0d029c485f 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -46,7 +46,7 @@ module PrinterExtensions = member _.IsProductiveStatement(s: Statement) = let rec hasNoSideEffects(e: Expression) = - printfn "hasNoSideEffects: {e}" + printfn $"hasNoSideEffects: {e}" match e with | :? Constant -> true | :? Dict as d -> d.Keys.IsEmpty @@ -841,6 +841,17 @@ type Return(?value) = //#region Expressions +/// Attribute access, e.g. d.keys. value is a node, typically a Name. attr is a bare string giving the name of the +/// attribute, and ctx is Load, Store or Del according to how the attribute is acted on. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('snake.colour', mode='eval'), indent=4)) +/// Expression( +/// body=Attribute( +/// value=Name(id='snake', ctx=Load()), +/// attr='colour', +/// ctx=Load())) +/// ``` type Attribute(value, attr, ctx) = inherit Expression() @@ -853,6 +864,37 @@ type Attribute(value, attr, ctx) = printer.Print(".") printer.Print(this.Attr) +/// A subscript, such as l[1]. value is the subscripted object (usually sequence or mapping). slice is an index, slice +/// or key. It can be a Tuple and contain a Slice. ctx is Load, Store or Del according to the action performed with the +/// subscript. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('l[1:2, 3]', mode='eval'), indent=4)) +/// Expression( +/// body=Subscript( +/// value=Name(id='l', ctx=Load()), +/// slice=Tuple( +/// elts=[ +/// Slice( +/// lower=Constant(value=1), +/// upper=Constant(value=2)), +/// Constant(value=3)], +/// ctx=Load()), +/// ctx=Load())) +/// ``` +type Subscript(value, slice, ctx) = + inherit Expression() + + member _.Value: Expression = value + member _.Slice: Expression = slice + member _.Ctx: ExpressionContext = ctx + + override this.Print(printer) = + printer.Print(this.Value) + printer.Print("[") + printer.Print(this.Slice) + printer.Print("]") + type BinOp(left, op, right) = inherit Expression() @@ -1218,7 +1260,7 @@ type MatMult() = type Eq() = interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" = ") + member _.Print(printer: Printer) = printer.Print($" == ") type NotEq() = interface ComparisonOperator with From aa7013ec5c62bb9f84dfea50c135e82aa14ae95b Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 14 Jan 2021 02:46:09 +0100 Subject: [PATCH 024/145] Fix single line lambda --- src/Fable.Transforms/Python/Babel2Python.fs | 4 +++- src/Fable.Transforms/Python/Python.fs | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 27ef0ad104..05accb7645 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -238,7 +238,9 @@ module Util = yield! com.TransformAsStatements(ctx, returnStrategy, st) ] | :? Babel.ReturnStatement as rtn -> let expr = transformAsExpr com ctx rtn.Argument - [ Return expr ] + match returnStrategy with + | Some ReturnStrategy.ReturnUnit -> [ Expr(expr) ] + | _ -> [ Return expr ] | :? Babel.VariableDeclaration as vd -> [ for vd in vd.Declarations do let targets: Expression list = diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 0d029c485f..59bbbaf9f0 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -1049,10 +1049,13 @@ type Lambda(args, body) = member _.Body: Statement list = body override _.Print(printer) = - printer.Print("lambda ") + printer.Print("lambda") + if (List.isEmpty >> not) args.Args then + printer.Print(" ") printer.PrintCommaSeparatedList(args.Args |> List.map (fun arg -> arg :> AST)) - printer.Print(":") - printer.PrintBlock(body, skipNewLineAtEnd = true) + printer.Print(": ") + for stmt in body do + printer.Print(stmt) /// A variable name. id holds the name as a string, and ctx is one of the following types. type Name(id, ctx) = From e1595d701f423f6a968757ea454671d64112aab9 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 14 Jan 2021 03:21:50 +0100 Subject: [PATCH 025/145] Fixes for while and for loops --- src/Fable.Transforms/Python/Babel2Python.fs | 7 ++++++ src/Fable.Transforms/Python/Python.fs | 27 +++++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 05accb7645..8ddfcbb2da 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -204,6 +204,8 @@ module Util = | :? Babel.Identifier as id -> Identifier(id.Name) | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" Attribute(value=value, attr=attr, ctx=Load()).AsExpr() + | :? Babel.BooleanLiteral as bl -> + Constant(value=bl.Value).AsExpr() | _ -> failwith $"Unhandled value: {expr}" /// Transform Babel expressions as Python statements. @@ -265,6 +267,11 @@ module Util = | Some alt -> com.TransformAsStatements(ctx, returnStrategy, alt) | _ -> [] [ If(test=test, body=body, orelse=orElse) ] + | :? Babel.WhileStatement as ws -> + let test = com.TransformAsExpr(ctx, ws.Test) + let body = com.TransformAsStatements(ctx, returnStrategy, ws.Body) + + [ While(test=test, body=body, orelse=[]) ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" /// Transform Babel program to Python module. diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 59bbbaf9f0..7021b32b8c 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -64,8 +64,10 @@ module PrinterExtensions = printSeparator |> Option.iter (fun f -> f printer) member printer.PrintProductiveStatements(statements: Statement list) = - for s in statements do - printer.PrintProductiveStatement(s, (fun p -> p.PrintStatementSeparator())) + let productive = statements |> List.choose (fun s -> if printer.IsProductiveStatement(s) then Some s else None) + let productive = if List.isEmpty productive then [ Pass() :> Statement ] else productive + for stmt in productive do + printer.PrintProductiveStatement(stmt, (fun p -> p.PrintStatementSeparator())) member printer.PrintBlock(nodes: Statement list, ?skipNewLineAtEnd) = printer.PrintBlock( @@ -472,9 +474,14 @@ type For(target, iter, body, orelse, ?typeComment) = member _.Else: Statement list = orelse member _.TypeComment: string option = typeComment - override _.Print(printer) = - printer.Print("(For)") - + override this.Print(printer) = + printer.Print("for ") + printer.Print(iter) + printer.Print(":") + printer.PrintNewLine() + printer.PushIndentation() + printer.PrintProductiveStatements(this.Body) + printer.PopIndentation() type AsyncFor(target, iter, body, orelse, ?typeComment) = inherit Statement() @@ -515,8 +522,14 @@ type While(test, body, orelse) = member _.Body: Statement list = body member _.Else: Statement list = orelse - override _.Print(printer) = - printer.Print("(While)") + override this.Print(printer) = + printer.Print("while ") + printer.Print(test) + printer.Print(":") + printer.PrintNewLine() + printer.PushIndentation() + printer.PrintProductiveStatements(this.Body) + printer.PopIndentation() /// A class definition. /// From 3324b819b42dadab5082cd8c22db6b703c2ea7b5 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 14 Jan 2021 22:00:04 +0100 Subject: [PATCH 026/145] Lift arrow and function expressions to function definitions --- src/Fable.Transforms/Python/Babel2Python.fs | 346 ++++++++++++++------ 1 file changed, 244 insertions(+), 102 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 8ddfcbb2da..444c155dba 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -42,7 +42,7 @@ type IPythonCompiler = inherit Compiler //abstract GetAllImports: unit -> seq //abstract GetImportExpr: Context * selector: string * path: string * SourceLocation option -> Expression - abstract TransformAsExpr: Context * Babel.Expression -> Expression + abstract TransformAsExpr: Context * Babel.Expression -> Expression * Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Expression -> Statement list abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement @@ -53,66 +53,111 @@ type IPythonCompiler = module Helpers = - let cleanNameAsPythonIdentifier (name: string) = - name.Replace('$','_') + let random = Random () + + let getIdentifier(name: string) : Identifier = + let rand = random.Next(9999).ToString() + Identifier($"{name}_{rand}") + + /// Camel case to snake case converter. + let camel2snake (name: string) = + name + |> Seq.mapi + (fun i n -> + if i > 0 && Char.IsUpper n then + "_" + n.ToString().ToLower() + else + n.ToString()) + |> String.concat "" + + /// Replaces all '$' with '_' + let cleanNameAsPythonIdentifier (name: string) = name.Replace('$', '_') + + let rewriteFableImport moduleName specifiers = + let _reFableLib = + Regex(".*\/fable-library[\.0-9]*\/(?[^\/]*)\.js", RegexOptions.Compiled) + + let m = _reFableLib.Match(moduleName) + + if m.Groups.Count > 0 then + let pymodule = m.Groups.["module"].Value.ToLower() + + let moduleName = + String.concat "." [ "expression"; "fable"; pymodule ] + + moduleName, specifiers + else + moduleName, specifiers + + let unzipArgs (args: (Expression * Statement list) list): Expression list * Statement list = + let stmts = args |> List.map snd |> List.collect id + let args = args |> List.map fst + args, stmts module Util = - let transformAsImport (com: IPythonCompiler) ctx (imp: Babel.ImportDeclaration) : Statement = - let reFableLib = Regex(".*\/fable-library[\.0-9]*\/(?[^\/]*)\.js", RegexOptions.Compiled) - let transform name = - let m = reFableLib.Match(name) - if m.Groups.Count > 0 then - let pymodule = m.Groups.["module"].Value.ToLower() - String.concat "." ["expression"; "fable"; pymodule] - else - name - let pyModule = - imp.Source.Value - |> transform - |> Identifier - |> Some - let mapper (expr: Babel.ImportSpecifier) = + let transformAsImport (com: IPythonCompiler) (ctx: Context) (imp: Babel.ImportDeclaration): Statement = + + let specifier2string (expr: Babel.ImportSpecifier) = match expr with - | :? Babel.ImportMemberSpecifier as im -> - let a = im.Imported.Name - Alias(Identifier a, None) + | :? Babel.ImportMemberSpecifier as im -> im.Imported.Name | _ -> failwith $"Unhandled import: {expr}" - let aliases = imp.Specifiers |> List.ofArray |> List.map mapper - ImportFrom(pyModule, aliases) :> _ + let specifiers = + imp.Specifiers + |> List.ofArray + |> List.map specifier2string + + let pymodule, names = + Helpers.rewriteFableImport imp.Source.Value specifiers - let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration) : Statement= + + let aliases = + names + |> List.map (fun name -> Alias(Identifier name, None)) + + ImportFrom(pymodule |> Identifier |> Some, aliases) :> _ + + let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration): Statement = printfn $"transformAsClassDef" - let body : Statement list = [ - for mber in cls.Body.Body do + let body: Statement list = + [ for mber in cls.Body.Body do match mber with | :? Babel.ClassMethod as cm -> match cm.Kind with | "constructor" -> let self = Arg(Identifier("self")) - let args = cm.Params |> List.ofArray |> List.map (fun arg -> Arg(Identifier(arg.Name))) - let arguments = Arguments(args=self :: args) - let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), cm.Body) - FunctionDef(Identifier "__init__", arguments, body=body) + + let args = + cm.Params + |> List.ofArray + |> List.map (fun arg -> Arg(Identifier(arg.Name))) + + let arguments = Arguments(args = self :: args) + + let body = + com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), cm.Body) + + FunctionDef(Identifier "__init__", arguments, body = body) | _ -> failwith $"Unknown kind: {cm.Kind}" - | _ -> failwith $"Unhandled class member {mber}" - //yield! com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), stmt) - ] - ClassDef(Identifier(cls.Id.Value.Name), body=body) :> _ + | _ -> failwith $"Unhandled class member {mber}" ] + + ClassDef(Identifier(cls.Id.Value.Name), body = body) :> _ /// Transform Babel expression as Python expression - let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Babel.Expression): Expression = + let rec transformAsExpr (com: IPythonCompiler) (ctx: Context) (expr: Babel.Expression): Expression * list = printfn $"transformAsExpr: {expr}" + match expr with | :? Babel.BinaryExpression as be -> - let left = com.TransformAsExpr(ctx, be.Left) - let right = com.TransformAsExpr(ctx, be.Right) + let left, leftStmts = com.TransformAsExpr(ctx, be.Left) + let right, rightStmts = com.TransformAsExpr(ctx, be.Right) let bin op = - BinOp(left, op, right).AsExpr() + BinOp(left, op, right).AsExpr(), leftStmts @ rightStmts + let cmp op = - Compare(left, [op], [right]).AsExpr() + Compare(left, [ op ], [ right ]).AsExpr(), leftStmts @ rightStmts match be.Operator with | "+" -> Add() |> bin @@ -146,75 +191,144 @@ module Util = | "void" -> None | _ -> failwith $"Unhandled unary operator: {ue.Operator}" - let operand = com.TransformAsExpr(ctx, ue.Argument) + let operand, stmts = com.TransformAsExpr(ctx, ue.Argument) + match op with - | Some op -> UnaryOp(op, operand).AsExpr() - | _ -> operand + | Some op -> UnaryOp(op, operand).AsExpr(), stmts + | _ -> operand, stmts | :? Babel.ArrowFunctionExpression as afe -> - let args = afe.Params |> List.ofArray - |> List.map (fun pattern -> Arg(Identifier pattern.Name)) + let args = + afe.Params + |> List.ofArray + |> List.map (fun pattern -> Arg(Identifier pattern.Name)) + let arguments = Arguments(args = args) - let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), afe.Body) - Lambda(arguments, body).AsExpr() - | :? Babel.CallExpression as ce -> // FIXME: use transformAsCall - let func = com.TransformAsExpr(ctx, ce.Callee) - let args = ce.Arguments |> List.ofArray |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) - Call(func, args).AsExpr() + match afe.Body.Body.Length with + | 1 -> + let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), afe.Body) + Lambda(arguments, body).AsExpr(), [] + | _ -> + let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), afe.Body) + let name = Helpers.getIdentifier("lifted") + let func = + FunctionDef( + name=name, + args=arguments, + body=body + ) + Name(name, Load()).AsExpr(), [func] + | :? Babel.CallExpression as ce -> // FIXME: use transformAsCall + let func, stmts = com.TransformAsExpr(ctx, ce.Callee) + + let args, stmtArgs = + ce.Arguments + |> List.ofArray + |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) + |> Helpers.unzipArgs + + Call(func, args).AsExpr(), stmts @ stmtArgs | :? Babel.ArrayExpression as ae -> - let elems = ae.Elements |> List.ofArray |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) - Tuple(elems).AsExpr() - | :? Babel.NumericLiteral as nl -> Constant(value = nl.Value).AsExpr() - | :? Babel.StringLiteral as sl -> Constant(value = sl.Value).AsExpr() + let elems, stmts = + ae.Elements + |> List.ofArray + |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) + |> Helpers.unzipArgs + + Tuple(elems).AsExpr(), stmts + | :? Babel.NumericLiteral as nl -> Constant(value = nl.Value).AsExpr(), [] + | :? Babel.StringLiteral as sl -> Constant(value = sl.Value).AsExpr(), [] | :? Babel.Identifier as ident -> - let name = Helpers.cleanNameAsPythonIdentifier ident.Name - Name(id = Identifier name, ctx = Load()).AsExpr() + let name = + Helpers.cleanNameAsPythonIdentifier ident.Name + + Name(id = Identifier name, ctx = Load()).AsExpr(), [] | :? Babel.NewExpression as ne -> // FIXME: use transformAsCall - let func = com.TransformAsExpr(ctx, ne.Callee) - let args = ne.Arguments |> List.ofArray |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) - Call(func, args).AsExpr() + let func, stmts = com.TransformAsExpr(ctx, ne.Callee) + + let args, stmtArgs = + ne.Arguments + |> List.ofArray + |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) + |> Helpers.unzipArgs + + Call(func, args).AsExpr(), stmts @ stmtArgs | :? Babel.Super -> - Name(Identifier("super().__init__"), ctx = Load()).AsExpr() + Name(Identifier("super().__init__"), ctx = Load()) + .AsExpr(), + [] | :? Babel.ObjectExpression as oe -> - let kv = [ - for prop in oe.Properties do + let kv = + [ for prop in oe.Properties do match prop with | :? Babel.ObjectProperty as op -> - let key = com.TransformAsExpr(ctx, op.Key) - let value = com.TransformAsExpr(ctx, op.Value) + let key, _ = com.TransformAsExpr(ctx, op.Key) + let value, _ = com.TransformAsExpr(ctx, op.Value) key, value - | _ -> failwith $"transformAsExpr: unhandled object expression property: {prop}" - ] - let keys = kv |> List.map fst - let values = kv |> List.map snd - Dict(keys=keys, values=values).AsExpr() - | :? Babel.EmitExpression as ee -> - failwith "Not Implemented" + | _ -> failwith $"transformAsExpr: unhandled object expression property: {prop}" ] + + let keys = kv |> List.map fst + let values = kv |> List.map snd + Dict(keys = keys, values = values).AsExpr(), [] + | :? Babel.EmitExpression as ee -> failwith "Not Implemented" | :? Babel.MemberExpression as me -> - let value = com.TransformAsExpr(ctx, me.Object) + let value, stmts = com.TransformAsExpr(ctx, me.Object) + if me.Computed then let attr = match me.Property with | :? Babel.NumericLiteral as nl -> Constant(nl.Value) | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" - Subscript(value=value, slice=attr, ctx=Load()).AsExpr() + + Subscript(value = value, slice = attr, ctx = Load()) + .AsExpr(), + stmts else let attr = match me.Property with | :? Babel.Identifier as id -> Identifier(id.Name) | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" - Attribute(value=value, attr=attr, ctx=Load()).AsExpr() - | :? Babel.BooleanLiteral as bl -> - Constant(value=bl.Value).AsExpr() + + Attribute(value = value, attr = attr, ctx = Load()) + .AsExpr(), + stmts + | :? Babel.BooleanLiteral as bl -> Constant(value = bl.Value).AsExpr(), [] + | :? Babel.FunctionExpression as fe -> + let args = + fe.Params + |> List.ofArray + |> List.map (fun pattern -> Arg(Identifier pattern.Name)) + + let arguments = Arguments(args = args) + match fe.Body.Body.Length with + | 1 -> + let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), fe.Body) + Lambda(arguments, body).AsExpr(), [] + | _ -> + let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), fe.Body) + let name = Helpers.getIdentifier("lifted") + let func = + FunctionDef( + name=name, + args=arguments, + body=body + ) + Name(name, Load()).AsExpr(), [func] | _ -> failwith $"Unhandled value: {expr}" /// Transform Babel expressions as Python statements. - let rec transformExpressionAsStatements (com: IPythonCompiler) ctx (returnStrategy: ReturnStrategy option) (expr: Babel.Expression): Statement list = + let rec transformExpressionAsStatements + (com: IPythonCompiler) + (ctx: Context) + (returnStrategy: ReturnStrategy option) + (expr: Babel.Expression) + : Statement list = printfn $"transformExpressionAsStatements: {expr}" match expr with | :? Babel.AssignmentExpression as ae -> - let value = com.TransformAsExpr(ctx, ae.Right) + let value, stmts = com.TransformAsExpr(ctx, ae.Right) + let targets: Expression list = let attr = match ae.Left with @@ -224,14 +338,21 @@ module Util = | :? Babel.Identifier as id -> Identifier(id.Name) | _ -> failwith "transformExpressionAsStatements: unknown property {me.Property}" | _ -> failwith $"AssignmentExpression, unknow expression: {ae.Left}" - [ Attribute(value=Name(id=Identifier("self"), ctx=Load()), attr=attr, ctx = Store()) ] - [ Assign(targets = targets, value = value) ] + [ Attribute(value = Name(id = Identifier("self"), ctx = Load()), attr = attr, ctx = Store()) ] + + [ yield! stmts + Assign(targets = targets, value = value) ] | _ -> failwith $"transformExpressionAsStatements: unknown expr: {expr}" /// Transform Babel statement as Python statements. - let rec transformStatementAsStatements (com: IPythonCompiler) ctx (returnStrategy: ReturnStrategy option) (stmt: Babel.Statement): Statement list = + let rec transformStatementAsStatements + (com: IPythonCompiler) + (ctx: Context) + (returnStrategy: ReturnStrategy option) + (stmt: Babel.Statement) + : list = printfn $"transformStatementAsStatements: {stmt}" match stmt with @@ -239,39 +360,52 @@ module Util = [ for st in bl.Body do yield! com.TransformAsStatements(ctx, returnStrategy, st) ] | :? Babel.ReturnStatement as rtn -> - let expr = transformAsExpr com ctx rtn.Argument + let expr, stmts = transformAsExpr com ctx rtn.Argument + match returnStrategy with | Some ReturnStrategy.ReturnUnit -> [ Expr(expr) ] - | _ -> [ Return expr ] + | _ -> stmts @ [ Return(expr) ] | :? Babel.VariableDeclaration as vd -> [ for vd in vd.Declarations do let targets: Expression list = [ Name(id = Identifier(vd.Id.Name), ctx = Store()) ] match vd.Init with - | Some value -> Assign(targets, com.TransformAsExpr(ctx, value)) + | Some value -> + let expr, stmts = com.TransformAsExpr(ctx, value) + yield! stmts + Assign(targets, expr) | None -> () ] | :? Babel.ExpressionStatement as es -> // Handle Babel expressions that we need to transforme here as Python statements match es.Expression with - | :? Babel.AssignmentExpression -> - com.TransformAsStatements(ctx, returnStrategy, es.Expression) + | :? Babel.AssignmentExpression -> com.TransformAsStatements(ctx, returnStrategy, es.Expression) | _ -> - [ Expr(com.TransformAsExpr(ctx, es.Expression)) ] + [ let expr, stmts = com.TransformAsExpr(ctx, es.Expression) + yield! stmts + Expr(expr) ] | :? Babel.IfStatement as iff -> - let test = com.TransformAsExpr(ctx, iff.Test) - let body = com.TransformAsStatements(ctx, returnStrategy, iff.Consequent) + let test, stmts = com.TransformAsExpr(ctx, iff.Test) + + let body = + com.TransformAsStatements(ctx, returnStrategy, iff.Consequent) + let orElse = match iff.Alternate with | Some alt -> com.TransformAsStatements(ctx, returnStrategy, alt) | _ -> [] - [ If(test=test, body=body, orelse=orElse) ] + + [ yield! stmts + If(test = test, body = body, orelse = orElse) ] | :? Babel.WhileStatement as ws -> - let test = com.TransformAsExpr(ctx, ws.Test) - let body = com.TransformAsStatements(ctx, returnStrategy, ws.Body) + let expr, stmts = com.TransformAsExpr(ctx, ws.Test) - [ While(test=test, body=body, orelse=[]) ] + let body = + com.TransformAsStatements(ctx, returnStrategy, ws.Body) + + [ yield! stmts + While(test = expr, body = body, orelse = []) ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" /// Transform Babel program to Python module. @@ -285,10 +419,13 @@ module Util = match decl.Declaration with | :? Babel.VariableDeclaration as decl -> for decls in decl.Declarations do - let value = com.TransformAsExpr(ctx, decls.Init.Value) + let value, stmts = + com.TransformAsExpr(ctx, decls.Init.Value) + let targets: Expression list = [ Name(id = Identifier(decls.Id.Name), ctx = Store()) ] + yield! stmts yield Assign(targets = targets, value = value) | :? Babel.FunctionDeclaration as fd -> let args = @@ -301,20 +438,20 @@ module Util = let body = com.TransformAsStatements(ctx, returnStrategy, fd.Body) - let name = Helpers.cleanNameAsPythonIdentifier(fd.Id.Name) + let name = + Helpers.cleanNameAsPythonIdentifier (fd.Id.Name) + yield FunctionDef(Identifier(name), arguments, body = body) | :? Babel.ClassDeclaration as cd -> printfn "TransformAsClassDef" yield com.TransformAsClassDef(ctx, cd) | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" - | :? Babel.ImportDeclaration as imp -> - yield com.TransformAsImport(ctx, imp) + | :? Babel.ImportDeclaration as imp -> yield com.TransformAsImport(ctx, imp) | :? Babel.PrivateModuleDeclaration as pmd -> let st = pmd.Statement yield! com.TransformAsStatements(ctx, returnStrategy, st) - | _ -> - failwith $"Unknown module declaration: {md}" ] + | _ -> failwith $"Unknown module declaration: {md}" ] Module(stmt) @@ -364,10 +501,15 @@ module Compiler = // | None -> upcast Babel.NullLiteral () //member _.GetAllImports() = upcast imports.Values member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e - member bcom.TransformAsStatements(ctx, ret, e) = transformExpressionAsStatements bcom ctx ret e - member bcom.TransformAsStatements(ctx, ret, e) = transformStatementAsStatements bcom ctx ret e + + member bcom.TransformAsStatements(ctx, ret, e) = + transformExpressionAsStatements bcom ctx ret e + + member bcom.TransformAsStatements(ctx, ret, e) = + transformStatementAsStatements bcom ctx ret e + member bcom.TransformAsClassDef(ctx, cls) = transformAsClassDef bcom ctx cls - //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body + //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body member bcom.TransformAsImport(ctx, imp) = transformAsImport bcom ctx imp interface Compiler with From 5b58e79d0d83efd12a57ac53b66f0bd29a735123 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 15 Jan 2021 05:34:09 +0100 Subject: [PATCH 027/145] Print bases for classes --- src/Fable.Transforms/Python/Babel2Python.fs | 53 +++++--- src/Fable.Transforms/Python/Python.fs | 143 ++++++++++---------- 2 files changed, 106 insertions(+), 90 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 444c155dba..499313c3e6 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -45,7 +45,7 @@ type IPythonCompiler = abstract TransformAsExpr: Context * Babel.Expression -> Expression * Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Expression -> Statement list - abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement + abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement list abstract TransformAsImport: Context * Babel.ImportDeclaration -> Statement //abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> (Pattern array) * BlockStatement @@ -117,33 +117,45 @@ module Util = ImportFrom(pymodule |> Identifier |> Some, aliases) :> _ - let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration): Statement = + let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration): Statement list = printfn $"transformAsClassDef" - + let bases, stmts = + let entries = cls.SuperClass |> Option.map (fun expr -> com.TransformAsExpr(ctx, expr)) + match entries with + | Some (expr, stmts) -> [ expr ], stmts + | None -> [], [] let body: Statement list = [ for mber in cls.Body.Body do match mber with | :? Babel.ClassMethod as cm -> + let self = Arg(Identifier("self")) + let args = + cm.Params + |> List.ofArray + |> List.map (fun arg -> Arg(Identifier(arg.Name))) + let arguments = Arguments(args = self :: args) + match cm.Kind with + | "method" -> + let body = + com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), cm.Body) + let name = + match cm.Key with + | :? Babel.Identifier as id -> Identifier(id.Name) + | _ -> failwith "transformAsClassDef: Unknown key: {cm.Key}" + FunctionDef(name, arguments, body = body) | "constructor" -> - let self = Arg(Identifier("self")) - - let args = - cm.Params - |> List.ofArray - |> List.map (fun arg -> Arg(Identifier(arg.Name))) - - let arguments = Arguments(args = self :: args) - + let name = Identifier("__init__") let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), cm.Body) - - FunctionDef(Identifier "__init__", arguments, body = body) - | _ -> failwith $"Unknown kind: {cm.Kind}" - | _ -> failwith $"Unhandled class member {mber}" ] - - ClassDef(Identifier(cls.Id.Value.Name), body = body) :> _ - + FunctionDef(name, arguments, body = body) + | _ -> failwith $"transformAsClassDef: Unknown kind: {cm.Kind}" + | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] + + [ + yield! stmts + ClassDef(Identifier(cls.Id.Value.Name), body=body, bases=bases) + ] /// Transform Babel expression as Python expression let rec transformAsExpr (com: IPythonCompiler) (ctx: Context) (expr: Babel.Expression): Expression * list = printfn $"transformAsExpr: {expr}" @@ -443,8 +455,7 @@ module Util = yield FunctionDef(Identifier(name), arguments, body = body) | :? Babel.ClassDeclaration as cd -> - printfn "TransformAsClassDef" - yield com.TransformAsClassDef(ctx, cd) + yield! com.TransformAsClassDef(ctx, cd) | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" | :? Babel.ImportDeclaration as imp -> yield com.TransformAsImport(ctx, imp) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 7021b32b8c..7ef4228bc6 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -47,6 +47,7 @@ module PrinterExtensions = member _.IsProductiveStatement(s: Statement) = let rec hasNoSideEffects(e: Expression) = printfn $"hasNoSideEffects: {e}" + match e with | :? Constant -> true | :? Dict as d -> d.Keys.IsEmpty @@ -64,8 +65,21 @@ module PrinterExtensions = printSeparator |> Option.iter (fun f -> f printer) member printer.PrintProductiveStatements(statements: Statement list) = - let productive = statements |> List.choose (fun s -> if printer.IsProductiveStatement(s) then Some s else None) - let productive = if List.isEmpty productive then [ Pass() :> Statement ] else productive + let productive = + statements + |> List.choose + (fun s -> + if printer.IsProductiveStatement(s) then + Some s + else + None) + + let productive = + if List.isEmpty productive then + [ Pass() :> Statement ] + else + productive + for stmt in productive do printer.PrintProductiveStatement(stmt, (fun p -> p.PrintStatementSeparator())) @@ -105,17 +119,10 @@ module PrinterExtensions = printSeparator printer member printer.PrintCommaSeparatedList(nodes: AST list) = - printer.PrintList( - nodes, - (fun p x -> p.Print(x)), - (fun p -> p.Print(", ")) - ) + printer.PrintList(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) + member printer.PrintCommaSeparatedList(nodes: Expression list) = - printer.PrintList( - nodes, - (fun p x -> p.SequenceExpressionWithParens(x)), - (fun p -> p.Print(", ")) - ) + printer.PrintList(nodes, (fun p x -> p.SequenceExpressionWithParens(x)), (fun p -> p.Print(", "))) // member printer.PrintCommaSeparatedArray(nodes: #Node array) = // printer.PrintArray(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) @@ -247,8 +254,7 @@ type Expression() = abstract Print: Printer -> unit - member x.AsExpr() = - x + member x.AsExpr() = x type Operator = @@ -304,6 +310,7 @@ type Alias(name, asname) = member _.Print(printer: Printer) = printer.Print(name) + match asname with | Some (Identifier alias) -> printer.Print("as ") @@ -364,8 +371,7 @@ type Arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ? member _.Defaults: Expression list = defaultArg defaults [] interface AST with - member _.Print(printer) = - printer.Print("(Arguments)") + member _.Print(printer) = printer.Print("(Arguments)") //#region Statements @@ -436,8 +442,7 @@ type Expr(value) = member _.Value: Expression = value - override _.Print(printer) = - value.Print(printer) + override _.Print(printer) = value.Print(printer) /// A for loop. target holds the variable(s) the loop assigns to, as a single Name, Tuple or List node. iter holds the /// item to be looped over, again as a single node. body and orelse contain lists of nodes to execute. Those in orelse @@ -482,6 +487,7 @@ type For(target, iter, body, orelse, ?typeComment) = printer.PushIndentation() printer.PrintProductiveStatements(this.Body) printer.PopIndentation() + type AsyncFor(target, iter, body, orelse, ?typeComment) = inherit Statement() @@ -491,8 +497,7 @@ type AsyncFor(target, iter, body, orelse, ?typeComment) = member _.Else: Statement list = orelse member _.TypeComment: string option = typeComment - override _.Print(printer) = - printer.Print("(AsyncFor)") + override _.Print(printer) = printer.Print("(AsyncFor)") /// A while loop. test holds the condition, such as a Compare node. /// @@ -578,14 +583,16 @@ type ClassDef(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc) = override this.Print(printer) = let (Identifier name) = name - printer.Print("class ", ?loc=loc) + printer.Print("class ", ?loc = loc) printer.Print(name) - //match superClass with - //| Some (:? Identifier as id) when id.TypeAnnotation.IsSome -> - // printer.Print(" extends "); - // printer.Print(id.TypeAnnotation.Value.TypeAnnotation) - // _ -> printer.PrintOptional("(", superClass, ")") - // printer.PrintOptional(superTypeParameters) + + match this.Bases with + | [] -> () + | xs -> + printer.Print("(") + printer.PrintCommaSeparatedList(this.Bases) + printer.Print(")") + printer.Print(":") printer.PrintNewLine() printer.PushIndentation() @@ -631,8 +638,7 @@ type If(test, body, orelse) = member _.Body: Statement list = body member _.Else: Statement list = orelse - override _.Print(printer) = - printer.Print("(If)") + override _.Print(printer) = printer.Print("(If)") /// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone /// raise. cause is the optional part for y in raise x from y. @@ -652,8 +658,7 @@ type Raise(exc, ?cause) = member _.Exception: Expression = exc member _.Cause: Expression option = cause - override _.Print(printer) = - printer.Print("(Raise)") + override _.Print(printer) = printer.Print("(Raise)") /// A function definition. /// @@ -698,8 +703,7 @@ type Global(names) = member _.Names: Identifier list = names - override _.Print(printer) = - printer.Print("(Global)") + override _.Print(printer) = printer.Print("(Global)") /// global and nonlocal statements. names is a list of raw strings. /// @@ -719,8 +723,7 @@ type NonLocal(names) = member _.Names: Identifier list = names - override _.Print(printer) = - printer.Print("(NonLocal)") + override _.Print(printer) = printer.Print("(NonLocal)") /// A pass statement. /// @@ -734,22 +737,19 @@ type NonLocal(names) = type Pass() = inherit Statement() - override _.Print(printer) = - printer.Print("pass") + override _.Print(printer) = printer.Print("pass") /// The break statement. type Break() = inherit Statement() - override _.Print(printer) = - printer.Print("break") + override _.Print(printer) = printer.Print("break") /// The continue statement. type Continue() = inherit Statement() - override _.Print(printer) = - printer.Print("continue") + override _.Print(printer) = printer.Print("continue") /// An async function definition. /// @@ -771,8 +771,7 @@ type AsyncFunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = member _.Returns: Expression option = returns member _.TypeComment: string option = typeComment - override _.Print(printer) = - printer.Print("(AsyncFunctionDef)") + override _.Print(printer) = printer.Print("(AsyncFunctionDef)") /// An import statement. names is a list of alias nodes. /// @@ -792,8 +791,7 @@ type Import(names) = member _.Names: Alias list = names - override _.Print(printer) = - printer.Print("(Import)") + override _.Print(printer) = printer.Print("(Import)") /// Represents from x import y. module is a raw string of the ‘from’ name, without any leading dots, or None for /// statements such as from . import foo. level is an integer holding the level of the relative import (0 means absolute @@ -820,17 +818,20 @@ type ImportFrom(``module``, names, ?level) = member _.Level: int option = level override this.Print(printer) = - let (Identifier path) = this.Module |> Option.defaultValue (Identifier ".") - printer.Print("from ") + let (Identifier path) = + this.Module + |> Option.defaultValue (Identifier ".") + + printer.Print("from ") - printer.Print(printer.MakeImportPath(path)) + printer.Print(printer.MakeImportPath(path)) - printer.Print(" import ") + printer.Print(" import ") - if not(List.isEmpty names) then - printer.Print("(") - printer.PrintCommaSeparatedList(names |> List.map (fun x -> x :> AST)) - printer.Print(")") + if not (List.isEmpty names) then + printer.Print("(") + printer.PrintCommaSeparatedList(names |> List.map (fun x -> x :> AST)) + printer.Print(")") /// A return statement. /// @@ -921,12 +922,13 @@ type Compare(left, ops, comparators) = inherit Expression() member _.Left: Expression = left - member _.Comparators: Expression list= comparators + member _.Comparators: Expression list = comparators member _.Ops: ComparisonOperator list = ops override this.Print(printer) = //printer.AddLocation(loc) printer.ComplexExpressionWithParens(left) + for op, comparator in List.zip this.Ops this.Comparators do printer.Print(op) printer.ComplexExpressionWithParens(comparator) @@ -992,8 +994,7 @@ type FormattedValue(value, ?conversion, ?formatSpec) = member _.Conversion: int option = conversion member _.FormatSpec: Expression option = formatSpec - override _.Print(printer) = - printer.Print("(FormattedValue)") + override _.Print(printer) = printer.Print("(FormattedValue)") /// A function call. func is the function, which will often be a Name or Attribute object. Of the arguments: /// @@ -1063,10 +1064,13 @@ type Lambda(args, body) = override _.Print(printer) = printer.Print("lambda") + if (List.isEmpty >> not) args.Args then printer.Print(" ") + printer.PrintCommaSeparatedList(args.Args |> List.map (fun arg -> arg :> AST)) printer.Print(": ") + for stmt in body do printer.Print(stmt) @@ -1098,10 +1102,15 @@ type Tuple(elts, ?loc) = inherit Expression() member _.Elements: Expression list = elts + override _.Print(printer) = - printer.Print("(", ?loc=loc) - printer.PrintCommaSeparatedList(elts) - printer.Print(")") + printer.Print("(", ?loc = loc) + printer.PrintCommaSeparatedList(elts) + + if elts.Length = 1 then + printer.Print(",") + + printer.Print(")") /// A list or tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an /// assignment target (i.e. (x,y)=something), and Load otherwise. @@ -1121,8 +1130,7 @@ type List(elts) = member _.Elements: Expression list = elts - override _.Print(printer) = - printer.Print("(List)") + override _.Print(printer) = printer.Print("(List)") /// A set. elts holds a list of nodes representing the set’s elements. /// @@ -1140,8 +1148,7 @@ type Set(elts) = member _.Elements: Expression list = elts - override _.Print(printer) = - printer.Print("(Set)") + override _.Print(printer) = printer.Print("(Set)") /// A dictionary. keys and values hold lists of nodes representing the keys and the values respectively, in matching /// order (what would be returned when calling dictionary.keys() and dictionary.values()). @@ -1168,10 +1175,12 @@ type Dict(keys, values) = override _.Print(printer) = printer.Print("{") + for key, value in List.zip keys values do key.Print(printer) printer.Print(":") value.Print(printer) + printer.Print("}") /// A yield expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back is not @@ -1190,8 +1199,7 @@ type Yield(?value) = inherit Expression() member _.Value: Expression option = value - override _.Print(printer) = - printer.Print("(Yield)") + override _.Print(printer) = printer.Print("(Yield)") /// A yield from expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back /// is not used. @@ -1209,8 +1217,7 @@ type YieldFrom(?value) = inherit Expression() member _.Value: Expression option = value - override _.Print(printer) = - printer.Print("(YieldFrom)") + override _.Print(printer) = printer.Print("(YieldFrom)") //#endregion @@ -1364,5 +1371,3 @@ type Store() = member this.Print(printer) = () //#endregion - - From 2cecd0c76d03e195142bdf0b5fdedf04110b220e Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 15 Jan 2021 05:41:33 +0100 Subject: [PATCH 028/145] Better name mangling of extracted functions --- src/Fable.Transforms/Python/Babel2Python.fs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 499313c3e6..3922d7c731 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -53,11 +53,12 @@ type IPythonCompiler = module Helpers = - let random = Random () + let index = (Seq.initInfinite id).GetEnumerator() let getIdentifier(name: string) : Identifier = - let rand = random.Next(9999).ToString() - Identifier($"{name}_{rand}") + do index.MoveNext() |> ignore + let idx = index.Current.ToString() + Identifier($"{name}_{idx}") /// Camel case to snake case converter. let camel2snake (name: string) = From c83eb2c50658b84f396cc6d6d3c42cbe35ffb52a Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 15 Jan 2021 06:08:24 +0100 Subject: [PATCH 029/145] Fixes for python dicts --- src/Fable.Transforms/Python/Python.fs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 7ef4228bc6..c335dfa2af 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -1175,12 +1175,22 @@ type Dict(keys, values) = override _.Print(printer) = printer.Print("{") + printer.PrintNewLine() + printer.PushIndentation() - for key, value in List.zip keys values do - key.Print(printer) - printer.Print(":") - value.Print(printer) + let nodes = List.zip keys values |> List.mapi (fun i n -> (i, n)) + for (i, (key, value)) in nodes do + printer.Print("\"") + printer.Print(key) + printer.Print("\"") + printer.Print(": ") + printer.Print(value) + if i < nodes.Length - 1 then + printer.Print(",") + printer.PrintNewLine() + printer.PrintNewLine() + printer.PopIndentation() printer.Print("}") /// A yield expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back is not From b30cea96d94b6df9d8657126bb8775c27d46054a Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 15 Jan 2021 20:59:06 +0100 Subject: [PATCH 030/145] Move isProductive transformation out of the Python AST --- src/Fable.Transforms/Python/Babel2Python.fs | 107 ++++++++++++++------ src/Fable.Transforms/Python/Python.fs | 47 +++------ 2 files changed, 94 insertions(+), 60 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 3922d7c731..502b606a76 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -55,7 +55,7 @@ type IPythonCompiler = module Helpers = let index = (Seq.initInfinite id).GetEnumerator() - let getIdentifier(name: string) : Identifier = + let getIdentifier (name: string): Identifier = do index.MoveNext() |> ignore let idx = index.Current.ToString() Identifier($"{name}_{idx}") @@ -95,7 +95,34 @@ module Helpers = let args = args |> List.map fst args, stmts + /// Moving transforms out of the printing stage. + let isProductiveStatement (stmt: Statement) = + let rec hasNoSideEffects (e: Expression) = + printfn $"hasNoSideEffects: {e}" + + match e with + | :? Constant -> true + | :? Dict as d -> d.Keys.IsEmpty + | _ -> false + + match stmt with + | :? Expr as expr -> + if hasNoSideEffects expr.Value then + None + else + Some stmt + | _ -> Some stmt + module Util = + let transformBody (body: Statement list) = + let body = + body |> List.choose Helpers.isProductiveStatement + + if body.IsEmpty then + [ Return() :> Statement ] + else + body + let transformAsImport (com: IPythonCompiler) (ctx: Context) (imp: Babel.ImportDeclaration): Statement = let specifier2string (expr: Babel.ImportSpecifier) = @@ -120,45 +147,60 @@ module Util = let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration): Statement list = printfn $"transformAsClassDef" + let bases, stmts = - let entries = cls.SuperClass |> Option.map (fun expr -> com.TransformAsExpr(ctx, expr)) + let entries = + cls.SuperClass + |> Option.map (fun expr -> com.TransformAsExpr(ctx, expr)) + match entries with | Some (expr, stmts) -> [ expr ], stmts | None -> [], [] + let body: Statement list = [ for mber in cls.Body.Body do match mber with | :? Babel.ClassMethod as cm -> let self = Arg(Identifier("self")) + let args = cm.Params |> List.ofArray |> List.map (fun arg -> Arg(Identifier(arg.Name))) + let arguments = Arguments(args = self :: args) match cm.Kind with | "method" -> let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), cm.Body) + let name = match cm.Key with | :? Babel.Identifier as id -> Identifier(id.Name) | _ -> failwith "transformAsClassDef: Unknown key: {cm.Key}" + FunctionDef(name, arguments, body = body) | "constructor" -> let name = Identifier("__init__") + let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), cm.Body) + FunctionDef(name, arguments, body = body) | _ -> failwith $"transformAsClassDef: Unknown kind: {cm.Kind}" | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] - [ - yield! stmts - ClassDef(Identifier(cls.Id.Value.Name), body=body, bases=bases) - ] + printfn $"Body length: {body.Length}: ${body}" + + [ yield! stmts + ClassDef(Identifier(cls.Id.Value.Name), body = body, bases = bases) ] /// Transform Babel expression as Python expression - let rec transformAsExpr (com: IPythonCompiler) (ctx: Context) (expr: Babel.Expression): Expression * list = + let rec transformAsExpr + (com: IPythonCompiler) + (ctx: Context) + (expr: Babel.Expression) + : Expression * list = printfn $"transformAsExpr: {expr}" match expr with @@ -217,20 +259,23 @@ module Util = |> List.map (fun pattern -> Arg(Identifier pattern.Name)) let arguments = Arguments(args = args) + match afe.Body.Body.Length with | 1 -> - let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), afe.Body) + let body = + com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), afe.Body) + Lambda(arguments, body).AsExpr(), [] | _ -> - let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), afe.Body) - let name = Helpers.getIdentifier("lifted") + let body = + com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), afe.Body) + + let name = Helpers.getIdentifier ("lifted") + let func = - FunctionDef( - name=name, - args=arguments, - body=body - ) - Name(name, Load()).AsExpr(), [func] + FunctionDef(name = name, args = arguments, body = body) + + Name(name, Load()).AsExpr(), [ func ] | :? Babel.CallExpression as ce -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, ce.Callee) @@ -313,20 +358,23 @@ module Util = |> List.map (fun pattern -> Arg(Identifier pattern.Name)) let arguments = Arguments(args = args) + match fe.Body.Body.Length with | 1 -> - let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), fe.Body) + let body = + com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), fe.Body) + Lambda(arguments, body).AsExpr(), [] | _ -> - let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), fe.Body) - let name = Helpers.getIdentifier("lifted") + let body = + com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), fe.Body) + + let name = Helpers.getIdentifier ("lifted") + let func = - FunctionDef( - name=name, - args=arguments, - body=body - ) - Name(name, Load()).AsExpr(), [func] + FunctionDef(name = name, args = arguments, body = body) + + Name(name, Load()).AsExpr(), [ func ] | _ -> failwith $"Unhandled value: {expr}" /// Transform Babel expressions as Python statements. @@ -366,17 +414,19 @@ module Util = (returnStrategy: ReturnStrategy option) (stmt: Babel.Statement) : list = - printfn $"transformStatementAsStatements: {stmt}" + printfn $"transformStatementAsStatements: {stmt}, returnStrategy: {returnStrategy}" match stmt with | :? Babel.BlockStatement as bl -> [ for st in bl.Body do yield! com.TransformAsStatements(ctx, returnStrategy, st) ] + |> transformBody + | :? Babel.ReturnStatement as rtn -> let expr, stmts = transformAsExpr com ctx rtn.Argument match returnStrategy with - | Some ReturnStrategy.ReturnUnit -> [ Expr(expr) ] + | Some ReturnStrategy.ReturnUnit -> stmts @ [ Expr(expr) ] | _ -> stmts @ [ Return(expr) ] | :? Babel.VariableDeclaration as vd -> [ for vd in vd.Declarations do @@ -455,8 +505,7 @@ module Util = Helpers.cleanNameAsPythonIdentifier (fd.Id.Name) yield FunctionDef(Identifier(name), arguments, body = body) - | :? Babel.ClassDeclaration as cd -> - yield! com.TransformAsClassDef(ctx, cd) + | :? Babel.ClassDeclaration as cd -> yield! com.TransformAsClassDef(ctx, cd) | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" | :? Babel.ImportDeclaration as imp -> yield com.TransformAsImport(ctx, imp) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index c335dfa2af..d273cdfb4d 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -44,7 +44,7 @@ module PrinterExtensions = printer.Print("") printer.PrintNewLine() - member _.IsProductiveStatement(s: Statement) = + member _.IsProductiveStatement(stmt: Statement) = let rec hasNoSideEffects(e: Expression) = printfn $"hasNoSideEffects: {e}" @@ -53,40 +53,25 @@ module PrinterExtensions = | :? Dict as d -> d.Keys.IsEmpty | _ -> false - match s with - | :? Expr as e -> hasNoSideEffects e.Value |> not + match stmt with + | :? Expr as expr -> hasNoSideEffects expr.Value |> not | _ -> true - member printer.PrintProductiveStatement(s: Statement, ?printSeparator) = - printfn "PrintProductiveStatement: %A" s + member printer.PrintStatement(stmt: Statement, ?printSeparator) = + printfn "PrintStatement: %A" stmt - if printer.IsProductiveStatement(s) then - s.Print(printer) - printSeparator |> Option.iter (fun f -> f printer) + stmt.Print(printer) + printSeparator |> Option.iter (fun fn -> fn printer) - member printer.PrintProductiveStatements(statements: Statement list) = - let productive = - statements - |> List.choose - (fun s -> - if printer.IsProductiveStatement(s) then - Some s - else - None) + member printer.PrintStatements(statements: Statement list) = - let productive = - if List.isEmpty productive then - [ Pass() :> Statement ] - else - productive - - for stmt in productive do - printer.PrintProductiveStatement(stmt, (fun p -> p.PrintStatementSeparator())) + for stmt in statements do + printer.PrintStatement(stmt, (fun p -> p.PrintStatementSeparator())) member printer.PrintBlock(nodes: Statement list, ?skipNewLineAtEnd) = printer.PrintBlock( nodes, - (fun p s -> p.PrintProductiveStatement(s)), + (fun p s -> p.PrintStatement(s)), (fun p -> p.PrintStatementSeparator()), ?skipNewLineAtEnd = skipNewLineAtEnd ) @@ -299,7 +284,7 @@ type Module(body) = interface AST with member this.Print(printer) = this.Print printer - member _.Print(printer: Printer) = printer.PrintProductiveStatements(body) + member _.Print(printer: Printer) = printer.PrintStatements(body) type Alias(name, asname) = member _.Name: Identifier = name @@ -485,7 +470,7 @@ type For(target, iter, body, orelse, ?typeComment) = printer.Print(":") printer.PrintNewLine() printer.PushIndentation() - printer.PrintProductiveStatements(this.Body) + printer.PrintStatements(this.Body) printer.PopIndentation() type AsyncFor(target, iter, body, orelse, ?typeComment) = @@ -533,7 +518,7 @@ type While(test, body, orelse) = printer.Print(":") printer.PrintNewLine() printer.PushIndentation() - printer.PrintProductiveStatements(this.Body) + printer.PrintStatements(this.Body) printer.PopIndentation() /// A class definition. @@ -596,7 +581,7 @@ type ClassDef(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc) = printer.Print(":") printer.PrintNewLine() printer.PushIndentation() - printer.PrintProductiveStatements(this.Body) + printer.PrintStatements(this.Body) printer.PopIndentation() /// An if statement. test holds a single node, such as a Compare node. body and orelse each hold a list of nodes. @@ -1179,7 +1164,7 @@ type Dict(keys, values) = printer.PushIndentation() let nodes = List.zip keys values |> List.mapi (fun i n -> (i, n)) - for (i, (key, value)) in nodes do + for i, (key, value) in nodes do printer.Print("\"") printer.Print(key) printer.Print("\"") From 5db9b893f2a7074918250a40610dc670f6ef0045 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 15 Jan 2021 21:21:27 +0100 Subject: [PATCH 031/145] Translate `void 0` to None --- src/Fable.Transforms/Python/Babel2Python.fs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 502b606a76..9784905890 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -102,7 +102,8 @@ module Helpers = match e with | :? Constant -> true - | :? Dict as d -> d.Keys.IsEmpty + | :? Dict as d -> d.Keys.IsEmpty // Empty object + | :? Name -> true // E.g `void 0` is translated to Name(None) | _ -> false match stmt with @@ -250,7 +251,9 @@ module Util = match op with | Some op -> UnaryOp(op, operand).AsExpr(), stmts - | _ -> operand, stmts + | _ -> + // TODO: Should be Contant(value=None) but we cannot create that in F# + Name(id=Identifier("None"), ctx=Load()).AsExpr(), stmts | :? Babel.ArrowFunctionExpression as afe -> let args = From 53d03126a04010c5a1bdd611245eac18f69e7dd6 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 16 Jan 2021 12:21:04 +0100 Subject: [PATCH 032/145] Fix local imports - Use lower case module names - Cannot use .fs.py as file extension since modules cannot have dots in their name --- src/Fable.Cli/Main.fs | 10 +- src/Fable.Transforms/Global/Babel.fs | 2 + src/Fable.Transforms/Python/Babel2Python.fs | 116 +++++++++++--------- src/Fable.Transforms/Python/Python.fs | 16 ++- 4 files changed, 87 insertions(+), 57 deletions(-) diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index 46e4cf11e4..e454d89510 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -156,8 +156,14 @@ module private Util = type PythonFileWriter(sourcePath: string, targetPath: string, cliArgs: CliArgs, dedupTargetDir) = let fileExt = ".py" + do printfn "TargetPath: %s" targetPath let targetDir = Path.GetDirectoryName(targetPath) - let targetPath = sourcePath + fileExt // Override + // PEP8: Modules should have short, all-lowercase names + let fileName = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(targetPath)).ToLower() + // Note that Python modules cannot contain dots or it will be impossible to import them + let targetPath = Path.Combine(targetDir, fileName + fileExt) + do printfn "TargetPath: %s" targetPath + let stream = new IO.StreamWriter(targetPath) do printfn $"PythonFileWriter: {sourcePath}, {targetPath}" @@ -170,7 +176,7 @@ module private Util = let path = Imports.getImportPath dedupTargetDir sourcePath targetPath projDir cliArgs.OutDir path if path.EndsWith(".fs") then let isInFableHiddenDir = Path.Combine(targetDir, path) |> Naming.isInFableHiddenDir - changeFsExtension isInFableHiddenDir path fileExt + changeFsExtension isInFableHiddenDir path "" // Remove file extension else path member _.Dispose() = stream.Dispose() diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index 586ef57382..a62c9016d5 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -1207,7 +1207,9 @@ type ImportSpecifier = inherit Node /// If it is a basic named import, such as in import {foo} from "mod", both imported and local are equivalent Identifier nodes; in this case an Identifier node representing foo. /// If it is an aliased import, such as in import {foo as bar} from "mod", the imported field is an Identifier node representing foo, and the local field is an Identifier node representing bar. type ImportMemberSpecifier(local: Identifier, imported) = + member _.Local: Identifier = local member _.Imported: Identifier = imported + interface ImportSpecifier with member this.Print(printer) = // Don't print the braces, this will be done in the import declaration diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 9784905890..47a3a954fa 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -11,14 +11,9 @@ open Fable.AST.Python [] type ReturnStrategy = | Return - | ReturnUnit - | Assign of Expression - | Target of Identifier - -type Import = - { Selector: string - LocalIdent: string option - Path: string } + | ReturnNone + //| Assign of Expression + //| Target of Identifier type ITailCallOpportunity = abstract Label: string @@ -40,8 +35,8 @@ type Context = type IPythonCompiler = inherit Compiler - //abstract GetAllImports: unit -> seq - //abstract GetImportExpr: Context * selector: string * path: string * SourceLocation option -> Expression + abstract GetAllImports: unit -> Statement list + abstract GetImportExpr: Context * selector: string * path: string * SourceLocation option -> Expression abstract TransformAsExpr: Context * Babel.Expression -> Expression * Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Expression -> Statement list @@ -60,16 +55,6 @@ module Helpers = let idx = index.Current.ToString() Identifier($"{name}_{idx}") - /// Camel case to snake case converter. - let camel2snake (name: string) = - name - |> Seq.mapi - (fun i n -> - if i > 0 && Char.IsUpper n then - "_" + n.ToString().ToLower() - else - n.ToString()) - |> String.concat "" /// Replaces all '$' with '_' let cleanNameAsPythonIdentifier (name: string) = name.Replace('$', '_') @@ -79,8 +64,8 @@ module Helpers = Regex(".*\/fable-library[\.0-9]*\/(?[^\/]*)\.js", RegexOptions.Compiled) let m = _reFableLib.Match(moduleName) - - if m.Groups.Count > 0 then + printfn "Count: %d" m.Groups.Count + if m.Groups.Count > 1 then let pymodule = m.Groups.["module"].Value.ToLower() let moduleName = @@ -88,6 +73,9 @@ module Helpers = moduleName, specifiers else + // TODO: Can we expect all modules to be lower case? + let moduleName = moduleName.Replace("/", "").ToLower() + printfn "moduleName: %s" moduleName moduleName, specifiers let unzipArgs (args: (Expression * Statement list) list): Expression list * Statement list = @@ -95,7 +83,8 @@ module Helpers = let args = args |> List.map fst args, stmts - /// Moving transforms out of the printing stage. + /// A few statements in the generated Babel AST do not produce any effect, and will not be printet. But they are + /// left in the AST and we need to skip them since they are not valid for Python (either). let isProductiveStatement (stmt: Statement) = let rec hasNoSideEffects (e: Expression) = printfn $"hasNoSideEffects: {e}" @@ -128,7 +117,7 @@ module Util = let specifier2string (expr: Babel.ImportSpecifier) = match expr with - | :? Babel.ImportMemberSpecifier as im -> im.Imported.Name + | :? Babel.ImportMemberSpecifier as im -> im.Imported.Name, im.Local.Name | _ -> failwith $"Unhandled import: {expr}" let specifiers = @@ -141,8 +130,9 @@ module Util = let aliases = + let local = imp.Specifiers names - |> List.map (fun name -> Alias(Identifier name, None)) + |> List.map (fun (name, alias) -> Alias(Identifier(name), if alias <> name then Identifier(alias) |> Some else None)) ImportFrom(pymodule |> Identifier |> Some, aliases) :> _ @@ -186,16 +176,17 @@ module Util = let name = Identifier("__init__") let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), cm.Body) + com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnNone), cm.Body) FunctionDef(name, arguments, body = body) | _ -> failwith $"transformAsClassDef: Unknown kind: {cm.Kind}" | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] printfn $"Body length: {body.Length}: ${body}" + let name = Helpers.cleanNameAsPythonIdentifier(cls.Id.Value.Name) [ yield! stmts - ClassDef(Identifier(cls.Id.Value.Name), body = body, bases = bases) ] + ClassDef(Identifier(name), body = body, bases = bases) ] /// Transform Babel expression as Python expression let rec transformAsExpr (com: IPythonCompiler) @@ -266,7 +257,7 @@ module Util = match afe.Body.Body.Length with | 1 -> let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), afe.Body) + com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnNone), afe.Body) Lambda(arguments, body).AsExpr(), [] | _ -> @@ -350,6 +341,21 @@ module Util = | :? Babel.Identifier as id -> Identifier(id.Name) | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" + let value = + match value with + | :? Name as name -> + let (Identifier id) = name.Id + + // TODO: Need to make this more generic and robust + let id = + if id = "Math" then + //com.imports.Add("math", ) + "math" + else + id + + Name(id=Identifier(id), ctx=name.Context).AsExpr() + | _ -> value Attribute(value = value, attr = attr, ctx = Load()) .AsExpr(), stmts @@ -365,7 +371,7 @@ module Util = match fe.Body.Body.Length with | 1 -> let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnUnit), fe.Body) + com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnNone), fe.Body) Lambda(arguments, body).AsExpr(), [] | _ -> @@ -429,7 +435,7 @@ module Util = let expr, stmts = transformAsExpr com ctx rtn.Argument match returnStrategy with - | Some ReturnStrategy.ReturnUnit -> stmts @ [ Expr(expr) ] + | Some ReturnStrategy.ReturnNone -> stmts @ [ Expr(expr) ] | _ -> stmts @ [ Return(expr) ] | :? Babel.VariableDeclaration as vd -> [ for vd in vd.Declarations do @@ -516,8 +522,8 @@ module Util = let st = pmd.Statement yield! com.TransformAsStatements(ctx, returnStrategy, st) | _ -> failwith $"Unknown module declaration: {md}" ] - - Module(stmt) + let imports = com.GetAllImports () + Module(imports @ stmt) let getIdentForImport (ctx: Context) (path: string) (selector: string) = if String.IsNullOrEmpty selector then @@ -542,28 +548,30 @@ module Compiler = if onlyOnceWarnings.Add(msg) then addWarning com [] range msg - // member _.GetImportExpr(ctx, selector, path, r) = - // let cachedName = path + "::" + selector - // match imports.TryGetValue(cachedName) with - // | true, i -> - // match i.LocalIdent with - // | Some localIdent -> upcast Babel.Identifier(localIdent) - // | None -> upcast Babel.NullLiteral () - // | false, _ -> - // let localId = getIdentForImport ctx path selector - // let i = - // { Selector = - // if selector = Naming.placeholder then - // "`importMember` must be assigned to a variable" - // |> addError com [] r; selector - // else selector - // Path = path - // LocalIdent = localId } - // imports.Add(cachedName, i) - // match localId with - // | Some localId -> upcast Babel.Identifier(localId) - // | None -> upcast Babel.NullLiteral () - //member _.GetAllImports() = upcast imports.Values + member _.GetImportExpr(ctx, selector, path, r) = + let cachedName = path + "::" + selector + // match imports.TryGetValue(cachedName) with + // | true, i -> + // match i.LocalIdent with + // | Some localIdent -> upcast Babel.Identifier(localIdent) + // | None -> upcast Babel.NullLiteral () + // | false, _ -> + // let localId = getIdentForImport ctx path selector + // let i = + // { Selector = + // if selector = Naming.placeholder then + // "`importMember` must be assigned to a variable" + // |> addError com [] r; selector + // else selector + // Path = path + // LocalIdent = localId } + // imports.Add(cachedName, i) + // match localId with + // | Some localId -> upcast Babel.Identifier(localId) + // | None -> upcast Babel.NullLiteral () + failwith "Not implemented" + + member _.GetAllImports() = imports.Values |> List.ofSeq |> List.map (fun im -> upcast im) member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e member bcom.TransformAsStatements(ctx, ret, e) = diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index d273cdfb4d..3a41b2a202 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -286,6 +286,20 @@ type Module(body) = member _.Print(printer: Printer) = printer.PrintStatements(body) +/// Both parameters are raw strings of the names. asname can be None if the regular name is to be used. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('from ..foo.bar import a as b, c'), indent=4)) +/// Module( +/// body=[ +/// ImportFrom( +/// module='foo.bar', +/// names=[ +/// alias(name='a', asname='b'), +/// alias(name='c')], +/// level=2)], +/// type_ignores=[]) +/// ``` type Alias(name, asname) = member _.Name: Identifier = name member _.AsName: Identifier option = asname @@ -298,7 +312,7 @@ type Alias(name, asname) = match asname with | Some (Identifier alias) -> - printer.Print("as ") + printer.Print(" as ") printer.Print(alias) | _ -> () From 3863ca8d506d96ab7a935724eb286b29f784b15b Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 18 Jan 2021 07:24:20 +0100 Subject: [PATCH 033/145] Add pyNative and Fable.Core.PyInterop --- src/Fable.Core/Fable.Core.Pyinterop.fs | 124 ++++++++++++++++++++ src/Fable.Core/Fable.Core.Util.fs | 2 + src/Fable.Core/Fable.Core.fsproj | 1 + src/Fable.Transforms/Global/Babel.fs | 4 + src/Fable.Transforms/Python/Babel2Python.fs | 15 ++- src/Fable.Transforms/Python/Python.fs | 85 +++++++++++++- src/Fable.Transforms/Python/README.md | 2 +- src/Fable.Transforms/Replacements.fs | 1 + 8 files changed, 227 insertions(+), 7 deletions(-) create mode 100644 src/Fable.Core/Fable.Core.Pyinterop.fs diff --git a/src/Fable.Core/Fable.Core.Pyinterop.fs b/src/Fable.Core/Fable.Core.Pyinterop.fs new file mode 100644 index 0000000000..fa654c2ee4 --- /dev/null +++ b/src/Fable.Core/Fable.Core.Pyinterop.fs @@ -0,0 +1,124 @@ +module Fable.Core.PyInterop + +open System +open Fable.Core + +/// Has same effect as `unbox` (dynamic casting erased in compiled JS code). +/// The casted type can be defined on the call site: `!!myObj?bar(5): float` +let (!!) x: 'T = pyNative + +/// Implicit cast for erased unions (U2, U3...) +let inline (!^) (x:^t1) : ^t2 = ((^t1 or ^t2) : (static member op_ErasedCast : ^t1 -> ^t2) x) + +/// Dynamically access a property of an arbitrary object. +/// `myObj?propA` in JS becomes `myObj.propA` +/// `myObj?(propA)` in JS becomes `myObj[propA]` +let (?) (o: obj) (prop: obj): 'a = pyNative + +/// Dynamically assign a value to a property of an arbitrary object. +/// `myObj?propA <- 5` in JS becomes `myObj.propA = 5` +/// `myObj?(propA) <- 5` in JS becomes `myObj[propA] = 5` +let (?<-) (o: obj) (prop: obj) (v: obj): unit = pyNative + +/// Destructure and apply a tuple to an arbitrary value. +/// E.g. `myFn $ (arg1, arg2)` in JS becomes `myFn(arg1, arg2)` +let ($) (callee: obj) (args: obj): 'a = pyNative + +/// Upcast the right operand to obj (and uncurry it if it's a function) and create a key-value tuple. +/// Mostly convenient when used with `createObj`. +/// E.g. `createObj [ "a" ==> 5 ]` in Python becomes `{ a: 5 }` +let (==>) (key: string) (v: obj): string*obj = pyNative + +/// Destructure and apply a tuple to an arbitrary value with `new` keyword. +/// E.g. `createNew myCons (arg1, arg2)` in JS becomes `new myCons(arg1, arg2)` +/// let createNew (o: obj) (args: obj): obj = pyNative + +/// Destructure a tuple of arguments and applies to literal JS code as with EmitAttribute. +/// E.g. `emitJsExpr (arg1, arg2) "$0 + $1"` in Python becomes `arg1 + arg2` +let emitJsExpr<'T> (args: obj) (jsCode: string): 'T = pyNative + +/// Same as emitJsExpr but intended for JS code that must appear in a statement position +/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements +/// E.g. `emitJsExpr aValue "while($0 < 5) doSomething()"` +let emitJsStatement<'T> (args: obj) (jsCode: string): 'T = pyNative + +/// Create a literal Python object from a collection of key-value tuples. +/// E.g. `createObj [ "a" ==> 5 ]` in Python becomes `{ a: 5 }` +let createObj (fields: #seq): obj = pyNative + +/// Create a literal Python object from a collection of union constructors. +/// E.g. `keyValueList CaseRules.LowerFirst [ MyUnion 4 ]` in Python becomes `{ myUnion: 4 }` +let keyValueList (caseRule: CaseRules) (li: 'T seq): obj = pyNative + +/// Create a literal JS object from a mutator lambda. Normally used when +/// the options interface has too many fields to be represented with a Pojo record. +/// E.g. `jsOptions (fun o -> o.foo <- 5)` in JS becomes `{ foo: 5 }` +let jsOptions<'T> (f: 'T->unit): 'T = pyNative + +/// Create an empty JS object: {} +///let createEmpty<'T> : 'T = pyNative + +/// Get the JS function constructor for class types +let jsConstructor<'T> : obj = pyNative + +/// Makes an expression the default export for the JS module. +/// Used to interact with JS tools that require a default export. +/// ATTENTION: This statement must appear on the root level of the file module. +[] +let exportDefault (x: obj): unit = pyNative + +/// Works like `ImportAttribute` (same semantics as ES6 imports). +/// You can use "*" or "default" selectors. +let import<'T> (selector: string) (path: string):'T = pyNative + +/// F#: let myMember = importMember "myModule" +/// Py: from my_module import my_member +/// Note the import must be immediately assigned to a value in a let binding +let importMember<'T> (path: string):'T = pyNative + +/// F#: let defaultMember = importDefaultobj> "myModule" +/// JS: import defaultMember from "myModule" +let importDefault<'T> (path: string):'T = pyNative + +/// F#: let myLib = importAll "myLib" +/// JS: import * as myLib from "myLib" +let importAll<'T> (path: string):'T = pyNative + +/// Imports a file only for its side effects +let importSideEffects (path: string): unit = pyNative + +/// Imports a file dynamically at runtime +let importDynamic<'T> (path: string): JS.Promise<'T> = pyNative + +/// Imports a reference from an external file dynamically at runtime +/// ATTENTION: Needs fable-compiler 2.3, pass the reference directly in argument position (avoid pipes) +let importValueDynamic (x: 'T): JS.Promise<'T> = pyNative + +/// Used when you need to send an F# record to a JS library accepting only plain JS objects (POJOs) +let toPlainJsObj(o: 'T): obj = pyNative + +/// Compiles to JS `this` keyword. +/// +/// ## Sample +/// jqueryMethod(fun x y -> jsThis?add(x, y)) +let [] jsThis<'T> : 'T = pyNative + +/// JS `in` operator +let [] isIn (key: string) (target: obj): bool = pyNative + +/// JS non-strict null checking +let [] isNullOrUndefined (target: obj): bool = pyNative + +/// Use it when importing a constructor from a JS library. +type [] JsConstructor = + [] + abstract Create: []args: obj[] -> obj + +/// Use it when importing dynamic functions from JS. If you need a constructor, use `JsConstructor`. +/// +/// ## Sample +/// let f: JsFunc = import "myFunction" "./myLib" +/// f.Invoke(5, "bar") +type [] JsFunc private () = + [] + member __.Invoke([]args:obj[]): obj = pyNative diff --git a/src/Fable.Core/Fable.Core.Util.fs b/src/Fable.Core/Fable.Core.Util.fs index e79cc17ca2..ed70fc68b7 100644 --- a/src/Fable.Core/Fable.Core.Util.fs +++ b/src/Fable.Core/Fable.Core.Util.fs @@ -9,6 +9,8 @@ module Util = try failwith "JS only" // try/catch is just for padding so it doesn't get optimized with ex -> raise ex + let inline pyNative<'T> : 'T = jsNative + module Experimental = /// Reads the name of an identifier, a property or a type let inline nameof(expr: 'a): string = jsNative diff --git a/src/Fable.Core/Fable.Core.fsproj b/src/Fable.Core/Fable.Core.fsproj index 9385966a12..b50a072e3b 100644 --- a/src/Fable.Core/Fable.Core.fsproj +++ b/src/Fable.Core/Fable.Core.fsproj @@ -13,6 +13,7 @@ + diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index a62c9016d5..401c153310 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -1220,12 +1220,16 @@ type ImportMemberSpecifier(local: Identifier, imported) = /// A default import specifier, e.g., foo in import foo from "mod". type ImportDefaultSpecifier(local) = + member _.Local: Identifier = local + interface ImportSpecifier with member _.Print(printer) = printer.Print(local) /// A namespace import specifier, e.g., * as foo in import * as foo from "mod". type ImportNamespaceSpecifier(local) = + member _.Local: Identifier = local + interface ImportSpecifier with member _.Print(printer) = printer.Print("* as ") diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 47a3a954fa..eaa6e90a46 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -117,7 +117,15 @@ module Util = let specifier2string (expr: Babel.ImportSpecifier) = match expr with - | :? Babel.ImportMemberSpecifier as im -> im.Imported.Name, im.Local.Name + | :? Babel.ImportMemberSpecifier as im -> + printfn "ImportMemberSpecifier" + im.Imported.Name, im.Local.Name + | :? Babel.ImportDefaultSpecifier as ids -> + printfn "ImportDefaultSpecifier" + ids.Local.Name, ids.Local.Name + | :? Babel.ImportNamespaceSpecifier as ins -> + printfn "ImportNamespaceSpecifier" + ins.Local.Name, ins.Local.Name | _ -> failwith $"Unhandled import: {expr}" let specifiers = @@ -322,7 +330,10 @@ module Util = let keys = kv |> List.map fst let values = kv |> List.map snd Dict(keys = keys, values = values).AsExpr(), [] - | :? Babel.EmitExpression as ee -> failwith "Not Implemented" + | :? Babel.EmitExpression as ee -> + let args, stmts = ee.Args |> List.ofArray |> List.map (fun expr -> com.TransformAsExpr(ctx, expr)) |> Helpers.unzipArgs + + Emit(ee.Value, args).AsExpr(), stmts | :? Babel.MemberExpression as me -> let value, stmts = com.TransformAsExpr(ctx, me.Object) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 3a41b2a202..2da9f1f455 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -828,9 +828,11 @@ type ImportFrom(``module``, names, ?level) = printer.Print(" import ") if not (List.isEmpty names) then - printer.Print("(") + if List.length names > 1 then + printer.Print("(") printer.PrintCommaSeparatedList(names |> List.map (fun x -> x :> AST)) - printer.Print(")") + if List.length names > 1 then + printer.Print(")") /// A return statement. /// @@ -1034,6 +1036,78 @@ type Call(func, ?args, ?kw) = printer.PrintCommaSeparatedList(this.Keywords |> List.map (fun x -> x :> AST)) printer.Print(")") +type Emit(value, ?args) = + inherit Expression() + + member _.Value: string = value + member _.Args: Expression list = defaultArg args [] + + override this.Print(printer) = + let inline replace pattern (f: System.Text.RegularExpressions.Match -> string) input = + System.Text.RegularExpressions.Regex.Replace(input, pattern, f) + + let printSegment (printer: Printer) (value: string) segmentStart segmentEnd = + let segmentLength = segmentEnd - segmentStart + if segmentLength > 0 then + let segment = value.Substring(segmentStart, segmentLength) + let subSegments = System.Text.RegularExpressions.Regex.Split(segment, @"\r?\n") + for i = 1 to subSegments.Length do + let subSegment = + // Remove whitespace in front of new lines, + // indent will be automatically applied + if printer.Column = 0 then subSegments.[i - 1].TrimStart() + else subSegments.[i - 1] + if subSegment.Length > 0 then + printer.Print(subSegment) + if i < subSegments.Length then + printer.PrintNewLine() + + + // Macro transformations + // https://fable.io/docs/communicate/js-from-fable.html#Emit-when-F-is-not-enough + let value = + value + |> replace @"\$(\d+)\.\.\." (fun m -> + let rep = ResizeArray() + let i = int m.Groups.[1].Value + for j = i to this.Args.Length - 1 do + rep.Add("$" + string j) + String.concat ", " rep) + + |> replace @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" (fun m -> + let i = int m.Groups.[1].Value + match this.Args.[i] with + | :? Constant as b -> m.Groups.[2].Value + | _ -> m.Groups.[3].Value) + + |> replace @"\{\{([^\}]*\$(\d+).*?)\}\}" (fun m -> + let i = int m.Groups.[2].Value + match List.tryItem i this.Args with + | Some _ -> m.Groups.[1].Value + | None -> "") + + let matches = System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+") + if matches.Count > 0 then + for i = 0 to matches.Count - 1 do + let m = matches.[i] + + let segmentStart = + if i > 0 then matches.[i-1].Index + matches.[i-1].Length + else 0 + + printSegment printer value segmentStart m.Index + + let argIndex = int m.Value.[1..] + match List.tryItem argIndex this.Args with + | Some e -> printer.ComplexExpressionWithParens(e) + | None -> printer.Print("None") + + let lastMatch = matches.[matches.Count - 1] + printSegment printer value (lastMatch.Index + lastMatch.Length) value.Length + else + printSegment printer value 0 value.Length + + /// lambda is a minimal function definition that can be used inside an expression. Unlike FunctionDef, body holds a /// single node. /// @@ -1370,8 +1444,11 @@ type Load() = interface AST with member this.Print(printer) = () -type Del = - inherit ExpressionContext +type Del()= + interface ExpressionContext + + interface AST with + member this.Print(printer) = () type Store() = interface ExpressionContext diff --git a/src/Fable.Transforms/Python/README.md b/src/Fable.Transforms/Python/README.md index 560cee80fd..0c22338a8a 100644 --- a/src/Fable.Transforms/Python/README.md +++ b/src/Fable.Transforms/Python/README.md @@ -1,3 +1,3 @@ # Fable to Python -Experimental support for Python. First try is to transform the Babel AST into a Python AST. +Experimental support for Python transforming the Babel AST into a Python AST. diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index ff1e5aea2b..ba06a2ee06 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -1080,6 +1080,7 @@ let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp match i.DeclaringEntityFullName, i.CompiledName with | _, "op_ErasedCast" -> List.tryHead args | _, ".ctor" -> typedObjExpr t [] |> Some + | _, "pyNative" | _, "jsNative" -> // TODO: Fail at compile time? addWarning com ctx.InlinePath r "jsNative is being compiled without replacement, this will fail at runtime." From 8540447f3fb69a9edbda0961c91829cc718d8dbd Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 20 Jan 2021 08:39:59 +0100 Subject: [PATCH 034/145] Try out active patterns --- src/Fable.Transforms/Global/Babel.fs | 41 ++++++++++ src/Fable.Transforms/Python/Babel2Python.fs | 91 +++++++++++---------- 2 files changed, 87 insertions(+), 45 deletions(-) diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index 401c153310..f02b24d1ef 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -242,6 +242,7 @@ type ModuleDeclaration = inherit Node type EmitExpression(value, args, ?loc) = member _.Value: string = value member _.Args: Expression array = args + member _.Loc: SourceLocation option = loc interface Expression with member _.Print(printer) = printer.AddLocation(loc) @@ -339,6 +340,7 @@ type Identifier(name, ?optional, ?typeAnnotation, ?loc) = member _.Name: string = name member _.Optional: bool option = optional member _.TypeAnnotation: TypeAnnotation option = typeAnnotation + member _.Loc: SourceLocation option = loc interface PatternExpression with member _.Name = name member _.Print(printer) = @@ -357,6 +359,7 @@ type RegExpLiteral(pattern, flags_, ?loc) = | RegexSticky -> "y") |> Seq.fold (+) "" member _.Pattern: string = pattern member _.Flags: string = flags + member _.Loc: SourceLocation option = loc interface Literal with member _.Print(printer) = printer.Print("/", ?loc=loc) @@ -681,6 +684,7 @@ type FunctionDeclaration(``params``, body, id, ?returnType, ?typeParameters, ?lo member _.Id: Identifier = id member _.ReturnType: TypeAnnotation option = returnType member _.TypeParameters: TypeParameterDeclaration option = typeParameters + member _.Loc: SourceLocation option = loc interface Declaration with member _.Print(printer) = printer.PrintFunction(Some id, ``params``, body, typeParameters, returnType, loc, isDeclaration=true) @@ -899,6 +903,7 @@ type CallExpression(callee, arguments, ?loc) = member _.Callee: Expression = callee // member _.Arguments: Choice array = arguments member _.Arguments: Expression array = arguments + member _.Loc: SourceLocation option = loc interface Expression with member _.Print(printer) = printer.AddLocation(loc) @@ -1546,3 +1551,39 @@ type InterfaceDeclaration(id, body, ?extends_, ?typeParameters, ?implements_) = printer.PrintArray(implements, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) printer.Print(" ") printer.Print(body) + +module Patterns = + let (|EmitExpression|_|) (expr: Expression) = + match expr with + | :? EmitExpression as ex -> Some (ex.Value, ex.Args, ex.Loc) + | _ -> None + + let (|Identifier|_|) (expr: Expression) = + match expr with + | :? Identifier as ex -> Some (ex.Name, ex.Optional, ex.TypeAnnotation, ex.Loc) + | _ -> None + + let (|RegExpLiteral|_|) (literal: Literal) = + match literal with + | :? RegExpLiteral as lit -> Some (lit.Pattern, lit.Flags, lit.Loc) + | _ -> None + + let (|CallExpression|_|) (expr: Expression) = + match expr with + | :? CallExpression as ex -> Some (ex.Callee, ex.Arguments, ex.Loc) + | _ -> None + + let (|ObjectTypeAnnotation|_|) (typ: TypeAnnotationInfo) = + match typ with + | :? ObjectTypeAnnotation as ann -> Some (ann.Properties, ann.Indexers, ann.CallProperties, ann.InternalSlots, ann.Exact) + | _ -> None + + let (|InterfaceExtends|_|) (node: Node) = + match node with + | :? InterfaceExtends as n -> Some (n.Id, n.TypeParameters) + | _ -> None + + let (|InterfaceDeclaration|_|) (decl: Declaration) = + match decl with + | :? InterfaceDeclaration as decl -> Some (decl.Id, decl.Body, decl.Extends, decl.Implements, decl.TypeParameters) + | _ -> None diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index eaa6e90a46..3447a77f20 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -11,9 +11,7 @@ open Fable.AST.Python [] type ReturnStrategy = | Return - | ReturnNone - //| Assign of Expression - //| Target of Identifier + | NoReturn type ITailCallOpportunity = abstract Label: string @@ -41,7 +39,7 @@ type IPythonCompiler = abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Expression -> Statement list abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement list - abstract TransformAsImport: Context * Babel.ImportDeclaration -> Statement + abstract TransformAsImports: Context * Babel.ImportDeclaration -> Statement list //abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> (Pattern array) * BlockStatement abstract WarnOnlyOnce: string * ?range:SourceLocation -> unit @@ -59,7 +57,7 @@ module Helpers = /// Replaces all '$' with '_' let cleanNameAsPythonIdentifier (name: string) = name.Replace('$', '_') - let rewriteFableImport moduleName specifiers = + let rewriteFableImport moduleName = let _reFableLib = Regex(".*\/fable-library[\.0-9]*\/(?[^\/]*)\.js", RegexOptions.Compiled) @@ -69,14 +67,14 @@ module Helpers = let pymodule = m.Groups.["module"].Value.ToLower() let moduleName = - String.concat "." [ "expression"; "fable"; pymodule ] + String.concat "." [ "fable"; pymodule ] - moduleName, specifiers + moduleName else // TODO: Can we expect all modules to be lower case? let moduleName = moduleName.Replace("/", "").ToLower() printfn "moduleName: %s" moduleName - moduleName, specifiers + moduleName let unzipArgs (args: (Expression * Statement list) list): Expression list * Statement list = let stmts = args |> List.map snd |> List.collect id @@ -113,36 +111,40 @@ module Util = else body - let transformAsImport (com: IPythonCompiler) (ctx: Context) (imp: Babel.ImportDeclaration): Statement = + let transformAsImports (com: IPythonCompiler) (ctx: Context) (imp: Babel.ImportDeclaration): Statement list = + let pymodule = + imp.Source.Value + |> Helpers.rewriteFableImport - let specifier2string (expr: Babel.ImportSpecifier) = + printfn "Module: %A" pymodule + + let imports: ResizeArray = ResizeArray () + let importFroms = ResizeArray () + + for expr in imp.Specifiers do match expr with | :? Babel.ImportMemberSpecifier as im -> printfn "ImportMemberSpecifier" - im.Imported.Name, im.Local.Name + let alias = Alias(Identifier(im.Imported.Name), if im.Imported.Name <> im.Local.Name then Identifier(im.Local.Name) |> Some else None) + importFroms.Add(alias) | :? Babel.ImportDefaultSpecifier as ids -> printfn "ImportDefaultSpecifier" - ids.Local.Name, ids.Local.Name + let alias = Alias(Identifier(pymodule), if ids.Local.Name <> pymodule then Identifier(ids.Local.Name) |> Some else None) + imports.Add(alias) | :? Babel.ImportNamespaceSpecifier as ins -> - printfn "ImportNamespaceSpecifier" - ins.Local.Name, ins.Local.Name + printfn "ImportNamespaceSpecifier: %A" (ins.Local.Name, ins.Local.Name) + let alias = Alias(Identifier(pymodule), if pymodule <> ins.Local.Name then Identifier(ins.Local.Name) |> Some else None) + importFroms.Add(alias) | _ -> failwith $"Unhandled import: {expr}" - let specifiers = - imp.Specifiers - |> List.ofArray - |> List.map specifier2string - - let pymodule, names = - Helpers.rewriteFableImport imp.Source.Value specifiers - + [ + if imports.Count > 0 then + Import(imports |> List.ofSeq) - let aliases = - let local = imp.Specifiers - names - |> List.map (fun (name, alias) -> Alias(Identifier(name), if alias <> name then Identifier(alias) |> Some else None)) + if importFroms.Count > 0 then + ImportFrom(Some(Identifier(pymodule)), importFroms |> List.ofSeq) + ] - ImportFrom(pymodule |> Identifier |> Some, aliases) :> _ let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration): Statement list = printfn $"transformAsClassDef" @@ -184,7 +186,7 @@ module Util = let name = Identifier("__init__") let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnNone), cm.Body) + com.TransformAsStatements(ctx, (Some ReturnStrategy.NoReturn), cm.Body) FunctionDef(name, arguments, body = body) | _ -> failwith $"transformAsClassDef: Unknown kind: {cm.Kind}" @@ -265,7 +267,7 @@ module Util = match afe.Body.Body.Length with | 1 -> let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnNone), afe.Body) + com.TransformAsStatements(ctx, (Some ReturnStrategy.NoReturn), afe.Body) Lambda(arguments, body).AsExpr(), [] | _ -> @@ -278,11 +280,11 @@ module Util = FunctionDef(name = name, args = arguments, body = body) Name(name, Load()).AsExpr(), [ func ] - | :? Babel.CallExpression as ce -> // FIXME: use transformAsCall - let func, stmts = com.TransformAsExpr(ctx, ce.Callee) + | Babel.Patterns.CallExpression(callee, args, loc) -> // FIXME: use transformAsCall + let func, stmts = com.TransformAsExpr(ctx, callee) let args, stmtArgs = - ce.Arguments + args |> List.ofArray |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) |> Helpers.unzipArgs @@ -298,9 +300,9 @@ module Util = Tuple(elems).AsExpr(), stmts | :? Babel.NumericLiteral as nl -> Constant(value = nl.Value).AsExpr(), [] | :? Babel.StringLiteral as sl -> Constant(value = sl.Value).AsExpr(), [] - | :? Babel.Identifier as ident -> + | Babel.Patterns.Identifier(name, _, _, _) -> let name = - Helpers.cleanNameAsPythonIdentifier ident.Name + Helpers.cleanNameAsPythonIdentifier name Name(id = Identifier name, ctx = Load()).AsExpr(), [] | :? Babel.NewExpression as ne -> // FIXME: use transformAsCall @@ -330,10 +332,10 @@ module Util = let keys = kv |> List.map fst let values = kv |> List.map snd Dict(keys = keys, values = values).AsExpr(), [] - | :? Babel.EmitExpression as ee -> - let args, stmts = ee.Args |> List.ofArray |> List.map (fun expr -> com.TransformAsExpr(ctx, expr)) |> Helpers.unzipArgs + | Babel.Patterns.EmitExpression(value, args, _) -> + let args, stmts = args |> List.ofArray |> List.map (fun expr -> com.TransformAsExpr(ctx, expr)) |> Helpers.unzipArgs - Emit(ee.Value, args).AsExpr(), stmts + Emit(value, args).AsExpr(), stmts | :? Babel.MemberExpression as me -> let value, stmts = com.TransformAsExpr(ctx, me.Object) @@ -344,12 +346,11 @@ module Util = | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" Subscript(value = value, slice = attr, ctx = Load()) - .AsExpr(), - stmts + .AsExpr(), stmts else let attr = match me.Property with - | :? Babel.Identifier as id -> Identifier(id.Name) + | Babel.Patterns.Identifier(name, _, _, _) -> Identifier(name) | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" let value = @@ -382,14 +383,14 @@ module Util = match fe.Body.Body.Length with | 1 -> let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.ReturnNone), fe.Body) + com.TransformAsStatements(ctx, (Some ReturnStrategy.NoReturn), fe.Body) Lambda(arguments, body).AsExpr(), [] | _ -> let body = com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), fe.Body) - let name = Helpers.getIdentifier ("lifted") + let name = Helpers.getIdentifier "lifted" let func = FunctionDef(name = name, args = arguments, body = body) @@ -446,7 +447,7 @@ module Util = let expr, stmts = transformAsExpr com ctx rtn.Argument match returnStrategy with - | Some ReturnStrategy.ReturnNone -> stmts @ [ Expr(expr) ] + | Some ReturnStrategy.NoReturn -> stmts @ [ Expr(expr) ] | _ -> stmts @ [ Return(expr) ] | :? Babel.VariableDeclaration as vd -> [ for vd in vd.Declarations do @@ -528,7 +529,7 @@ module Util = | :? Babel.ClassDeclaration as cd -> yield! com.TransformAsClassDef(ctx, cd) | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" - | :? Babel.ImportDeclaration as imp -> yield com.TransformAsImport(ctx, imp) + | :? Babel.ImportDeclaration as imp -> yield! com.TransformAsImports(ctx, imp) | :? Babel.PrivateModuleDeclaration as pmd -> let st = pmd.Statement yield! com.TransformAsStatements(ctx, returnStrategy, st) @@ -593,7 +594,7 @@ module Compiler = member bcom.TransformAsClassDef(ctx, cls) = transformAsClassDef bcom ctx cls //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body - member bcom.TransformAsImport(ctx, imp) = transformAsImport bcom ctx imp + member bcom.TransformAsImports(ctx, imp) = transformAsImports bcom ctx imp interface Compiler with member _.Options = com.Options From 875a469ae30b4c8c665e6a4a179ecbcaac84c0e0 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 20 Jan 2021 22:20:10 +0100 Subject: [PATCH 035/145] Rewrite Python AST to DU / Records --- src/Fable.Transforms/Python/Babel2Python.fs | 301 +-- src/Fable.Transforms/Python/Python.fs | 1767 ++++++++++-------- src/Fable.Transforms/Python/PythonPrinter.fs | 7 +- 3 files changed, 1205 insertions(+), 870 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 3447a77f20..1c9ecd1edf 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -1,4 +1,4 @@ -module rec Fable.Transforms.Babel2Python +module Fable.Transforms.Babel2Python open System open System.Collections.Generic @@ -34,10 +34,10 @@ type Context = type IPythonCompiler = inherit Compiler abstract GetAllImports: unit -> Statement list - abstract GetImportExpr: Context * selector: string * path: string * SourceLocation option -> Expression + abstract GetImportExpr: Context * selector:string * path:string * SourceLocation option -> Expression abstract TransformAsExpr: Context * Babel.Expression -> Expression * Statement list - abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Statement -> Statement list - abstract TransformAsStatements: Context * ReturnStrategy option * Babel.Expression -> Statement list + abstract TransformAsStatements: Context * ReturnStrategy * Babel.Statement -> Statement list + abstract TransformAsStatements: Context * ReturnStrategy * Babel.Expression -> Statement list abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement list abstract TransformAsImports: Context * Babel.ImportDeclaration -> Statement list //abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> (Pattern array) * BlockStatement @@ -63,11 +63,11 @@ module Helpers = let m = _reFableLib.Match(moduleName) printfn "Count: %d" m.Groups.Count + if m.Groups.Count > 1 then let pymodule = m.Groups.["module"].Value.ToLower() - let moduleName = - String.concat "." [ "fable"; pymodule ] + let moduleName = String.concat "." [ "fable"; pymodule ] moduleName else @@ -88,13 +88,13 @@ module Helpers = printfn $"hasNoSideEffects: {e}" match e with - | :? Constant -> true - | :? Dict as d -> d.Keys.IsEmpty // Empty object - | :? Name -> true // E.g `void 0` is translated to Name(None) + | Constant (_) -> true + | Dict ({ Keys = keys }) -> keys.IsEmpty // Empty object + | Name (_) -> true // E.g `void 0` is translated to Name(None) | _ -> false match stmt with - | :? Expr as expr -> + | Expr (expr) -> if hasNoSideEffects expr.Value then None else @@ -102,48 +102,71 @@ module Helpers = | _ -> Some stmt module Util = - let transformBody (body: Statement list) = + let transformBody (returnStrategy: ReturnStrategy) (body: Statement list) = let body = body |> List.choose Helpers.isProductiveStatement - if body.IsEmpty then - [ Return() :> Statement ] - else - body + match body, returnStrategy with + | [], ReturnStrategy.Return -> [ Return.Create() ] + | _ -> body let transformAsImports (com: IPythonCompiler) (ctx: Context) (imp: Babel.ImportDeclaration): Statement list = let pymodule = - imp.Source.Value - |> Helpers.rewriteFableImport + imp.Source.Value |> Helpers.rewriteFableImport printfn "Module: %A" pymodule - let imports: ResizeArray = ResizeArray () - let importFroms = ResizeArray () + let imports: ResizeArray = ResizeArray() + let importFroms = ResizeArray() for expr in imp.Specifiers do match expr with | :? Babel.ImportMemberSpecifier as im -> printfn "ImportMemberSpecifier" - let alias = Alias(Identifier(im.Imported.Name), if im.Imported.Name <> im.Local.Name then Identifier(im.Local.Name) |> Some else None) + + let alias = + Alias.Create( + Identifier(im.Imported.Name), + if im.Imported.Name <> im.Local.Name then + Identifier(im.Local.Name) |> Some + else + None + ) + importFroms.Add(alias) | :? Babel.ImportDefaultSpecifier as ids -> printfn "ImportDefaultSpecifier" - let alias = Alias(Identifier(pymodule), if ids.Local.Name <> pymodule then Identifier(ids.Local.Name) |> Some else None) + + let alias = + Alias.Create( + Identifier(pymodule), + if ids.Local.Name <> pymodule then + Identifier(ids.Local.Name) |> Some + else + None + ) + imports.Add(alias) | :? Babel.ImportNamespaceSpecifier as ins -> printfn "ImportNamespaceSpecifier: %A" (ins.Local.Name, ins.Local.Name) - let alias = Alias(Identifier(pymodule), if pymodule <> ins.Local.Name then Identifier(ins.Local.Name) |> Some else None) + + let alias = + Alias.Create( + Identifier(pymodule), + if pymodule <> ins.Local.Name then + Identifier(ins.Local.Name) |> Some + else + None + ) + importFroms.Add(alias) | _ -> failwith $"Unhandled import: {expr}" - [ - if imports.Count > 0 then - Import(imports |> List.ofSeq) + [ if imports.Count > 0 then + Import.Create(imports |> List.ofSeq) - if importFroms.Count > 0 then - ImportFrom(Some(Identifier(pymodule)), importFroms |> List.ofSeq) - ] + if importFroms.Count > 0 then + ImportFrom.Create(Some(Identifier(pymodule)), importFroms |> List.ofSeq) ] let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration): Statement list = @@ -162,41 +185,43 @@ module Util = [ for mber in cls.Body.Body do match mber with | :? Babel.ClassMethod as cm -> - let self = Arg(Identifier("self")) + let self = Arg.Create(Identifier("self")) let args = cm.Params |> List.ofArray - |> List.map (fun arg -> Arg(Identifier(arg.Name))) + |> List.map (fun arg -> Arg.Create(Identifier(arg.Name))) - let arguments = Arguments(args = self :: args) + let arguments = Arguments.Create(args = self :: args) match cm.Kind with | "method" -> let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), cm.Body) + com.TransformAsStatements(ctx, ReturnStrategy.Return, cm.Body) let name = match cm.Key with | :? Babel.Identifier as id -> Identifier(id.Name) | _ -> failwith "transformAsClassDef: Unknown key: {cm.Key}" - FunctionDef(name, arguments, body = body) + FunctionDef.Create(name, arguments, body = body) | "constructor" -> let name = Identifier("__init__") let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.NoReturn), cm.Body) + com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, cm.Body) - FunctionDef(name, arguments, body = body) + FunctionDef.Create(name, arguments, body = body) | _ -> failwith $"transformAsClassDef: Unknown kind: {cm.Kind}" | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] printfn $"Body length: {body.Length}: ${body}" - let name = Helpers.cleanNameAsPythonIdentifier(cls.Id.Value.Name) + + let name = + Helpers.cleanNameAsPythonIdentifier (cls.Id.Value.Name) [ yield! stmts - ClassDef(Identifier(name), body = body, bases = bases) ] + ClassDef.Create(Identifier(name), body = body, bases = bases) ] /// Transform Babel expression as Python expression let rec transformAsExpr (com: IPythonCompiler) @@ -211,76 +236,76 @@ module Util = let right, rightStmts = com.TransformAsExpr(ctx, be.Right) let bin op = - BinOp(left, op, right).AsExpr(), leftStmts @ rightStmts + BinOp.Create(left, op, right), leftStmts @ rightStmts let cmp op = - Compare(left, [ op ], [ right ]).AsExpr(), leftStmts @ rightStmts + Compare.Create(left, [ op ], [ right ]), leftStmts @ rightStmts match be.Operator with - | "+" -> Add() |> bin - | "-" -> Sub() |> bin - | "*" -> Mult() |> bin - | "/" -> Div() |> bin - | "%" -> Mod() |> bin - | "**" -> Pow() |> bin - | "<<" -> LShift() |> bin - | ">>" -> RShift() |> bin - | "|" -> BitOr() |> bin - | "^" -> BitXor() |> bin - | "&" -> BitAnd() |> bin + | "+" -> Add |> bin + | "-" -> Sub |> bin + | "*" -> Mult |> bin + | "/" -> Div |> bin + | "%" -> Mod |> bin + | "**" -> Pow |> bin + | "<<" -> LShift |> bin + | ">>" -> RShift |> bin + | "|" -> BitOr |> bin + | "^" -> BitXor |> bin + | "&" -> BitAnd |> bin | "===" - | "==" -> Eq() |> cmp + | "==" -> Eq |> cmp | "!==" - | "!=" -> NotEq() |> cmp - | ">" -> Gt() |> cmp - | ">=" -> GtE() |> cmp - | "<" -> Lt() |> cmp - | "<=" -> LtE() |> cmp + | "!=" -> NotEq |> cmp + | ">" -> Gt |> cmp + | ">=" -> GtE |> cmp + | "<" -> Lt |> cmp + | "<=" -> LtE |> cmp | _ -> failwith $"Unknown operator: {be.Operator}" | :? Babel.UnaryExpression as ue -> let op = match ue.Operator with - | "-" -> USub() :> UnaryOperator |> Some - | "+" -> UAdd() :> UnaryOperator |> Some - | "~" -> Invert() :> UnaryOperator |> Some - | "!" -> Not() :> UnaryOperator |> Some + | "-" -> USub |> Some + | "+" -> UAdd |> Some + | "~" -> Invert |> Some + | "!" -> Not |> Some | "void" -> None | _ -> failwith $"Unhandled unary operator: {ue.Operator}" let operand, stmts = com.TransformAsExpr(ctx, ue.Argument) match op with - | Some op -> UnaryOp(op, operand).AsExpr(), stmts + | Some op -> UnaryOp.Create(op, operand), stmts | _ -> // TODO: Should be Contant(value=None) but we cannot create that in F# - Name(id=Identifier("None"), ctx=Load()).AsExpr(), stmts + Name.Create(id = Identifier("None"), ctx = Load), stmts | :? Babel.ArrowFunctionExpression as afe -> let args = afe.Params |> List.ofArray - |> List.map (fun pattern -> Arg(Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.Create(Identifier pattern.Name)) - let arguments = Arguments(args = args) + let arguments = Arguments.Create(args = args) match afe.Body.Body.Length with | 1 -> let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.NoReturn), afe.Body) + com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, afe.Body) - Lambda(arguments, body).AsExpr(), [] + Lambda.Create(arguments, body), [] | _ -> let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), afe.Body) + com.TransformAsStatements(ctx, ReturnStrategy.Return, afe.Body) let name = Helpers.getIdentifier ("lifted") let func = - FunctionDef(name = name, args = arguments, body = body) + FunctionDef.Create(name = name, args = arguments, body = body) - Name(name, Load()).AsExpr(), [ func ] - | Babel.Patterns.CallExpression(callee, args, loc) -> // FIXME: use transformAsCall + Name.Create(name, Load), [ func ] + | Babel.Patterns.CallExpression (callee, args, loc) -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, callee) let args, stmtArgs = @@ -289,7 +314,7 @@ module Util = |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) |> Helpers.unzipArgs - Call(func, args).AsExpr(), stmts @ stmtArgs + Call.Create(func, args), stmts @ stmtArgs | :? Babel.ArrayExpression as ae -> let elems, stmts = ae.Elements @@ -297,14 +322,13 @@ module Util = |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) |> Helpers.unzipArgs - Tuple(elems).AsExpr(), stmts - | :? Babel.NumericLiteral as nl -> Constant(value = nl.Value).AsExpr(), [] - | :? Babel.StringLiteral as sl -> Constant(value = sl.Value).AsExpr(), [] - | Babel.Patterns.Identifier(name, _, _, _) -> - let name = - Helpers.cleanNameAsPythonIdentifier name + Tuple.Create(elems), stmts + | :? Babel.NumericLiteral as nl -> Constant.Create(value = nl.Value), [] + | :? Babel.StringLiteral as sl -> Constant.Create(value = sl.Value), [] + | Babel.Patterns.Identifier (name, _, _, _) -> + let name = Helpers.cleanNameAsPythonIdentifier name - Name(id = Identifier name, ctx = Load()).AsExpr(), [] + Name.Create(id = Identifier name, ctx = Load), [] | :? Babel.NewExpression as ne -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, ne.Callee) @@ -314,11 +338,8 @@ module Util = |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) |> Helpers.unzipArgs - Call(func, args).AsExpr(), stmts @ stmtArgs - | :? Babel.Super -> - Name(Identifier("super().__init__"), ctx = Load()) - .AsExpr(), - [] + Call.Create(func, args), stmts @ stmtArgs + | :? Babel.Super -> Name.Create(Identifier("super().__init__"), ctx = Load), [] | :? Babel.ObjectExpression as oe -> let kv = [ for prop in oe.Properties do @@ -331,33 +352,35 @@ module Util = let keys = kv |> List.map fst let values = kv |> List.map snd - Dict(keys = keys, values = values).AsExpr(), [] - | Babel.Patterns.EmitExpression(value, args, _) -> - let args, stmts = args |> List.ofArray |> List.map (fun expr -> com.TransformAsExpr(ctx, expr)) |> Helpers.unzipArgs + Dict.Create(keys = keys, values = values), [] + | Babel.Patterns.EmitExpression (value, args, _) -> + let args, stmts = + args + |> List.ofArray + |> List.map (fun expr -> com.TransformAsExpr(ctx, expr)) + |> Helpers.unzipArgs - Emit(value, args).AsExpr(), stmts + Emit.Create(value, args), stmts | :? Babel.MemberExpression as me -> let value, stmts = com.TransformAsExpr(ctx, me.Object) if me.Computed then let attr = match me.Property with - | :? Babel.NumericLiteral as nl -> Constant(nl.Value) + | :? Babel.NumericLiteral as nl -> Constant.Create(nl.Value) + | :? Babel.StringLiteral as literal -> Constant.Create(literal.Value) | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" - Subscript(value = value, slice = attr, ctx = Load()) - .AsExpr(), stmts + Subscript.Create(value = value, slice = attr, ctx = Load), stmts else let attr = match me.Property with - | Babel.Patterns.Identifier(name, _, _, _) -> Identifier(name) + | Babel.Patterns.Identifier (name, _, _, _) -> Identifier(name) | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" let value = match value with - | :? Name as name -> - let (Identifier id) = name.Id - + | Name { Id = Identifier (id); Context = ctx } -> // TODO: Need to make this more generic and robust let id = if id = "Math" then @@ -366,43 +389,42 @@ module Util = else id - Name(id=Identifier(id), ctx=name.Context).AsExpr() + Name.Create(id = Identifier(id), ctx = ctx) | _ -> value - Attribute(value = value, attr = attr, ctx = Load()) - .AsExpr(), - stmts - | :? Babel.BooleanLiteral as bl -> Constant(value = bl.Value).AsExpr(), [] + + Attribute.Create(value = value, attr = attr, ctx = Load), stmts + | :? Babel.BooleanLiteral as bl -> Constant.Create(value = bl.Value), [] | :? Babel.FunctionExpression as fe -> let args = fe.Params |> List.ofArray - |> List.map (fun pattern -> Arg(Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.Create(Identifier pattern.Name)) - let arguments = Arguments(args = args) + let arguments = Arguments.Create(args = args) match fe.Body.Body.Length with | 1 -> let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.NoReturn), fe.Body) + com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, fe.Body) - Lambda(arguments, body).AsExpr(), [] + Lambda.Create(arguments, body), [] | _ -> let body = - com.TransformAsStatements(ctx, (Some ReturnStrategy.Return), fe.Body) + com.TransformAsStatements(ctx, ReturnStrategy.Return, fe.Body) let name = Helpers.getIdentifier "lifted" let func = - FunctionDef(name = name, args = arguments, body = body) + FunctionDef.Create(name = name, args = arguments, body = body) - Name(name, Load()).AsExpr(), [ func ] + Name.Create(name, Load), [ func ] | _ -> failwith $"Unhandled value: {expr}" /// Transform Babel expressions as Python statements. let rec transformExpressionAsStatements (com: IPythonCompiler) (ctx: Context) - (returnStrategy: ReturnStrategy option) + (returnStrategy: ReturnStrategy) (expr: Babel.Expression) : Statement list = printfn $"transformExpressionAsStatements: {expr}" @@ -421,10 +443,10 @@ module Util = | _ -> failwith "transformExpressionAsStatements: unknown property {me.Property}" | _ -> failwith $"AssignmentExpression, unknow expression: {ae.Left}" - [ Attribute(value = Name(id = Identifier("self"), ctx = Load()), attr = attr, ctx = Store()) ] + [ Attribute.Create(value = Name.Create(id = Identifier("self"), ctx = Load), attr = attr, ctx = Store) ] [ yield! stmts - Assign(targets = targets, value = value) ] + Assign.Create(targets = targets, value = value) ] | _ -> failwith $"transformExpressionAsStatements: unknown expr: {expr}" @@ -432,7 +454,7 @@ module Util = let rec transformStatementAsStatements (com: IPythonCompiler) (ctx: Context) - (returnStrategy: ReturnStrategy option) + (returnStrategy: ReturnStrategy) (stmt: Babel.Statement) : list = printfn $"transformStatementAsStatements: {stmt}, returnStrategy: {returnStrategy}" @@ -441,24 +463,24 @@ module Util = | :? Babel.BlockStatement as bl -> [ for st in bl.Body do yield! com.TransformAsStatements(ctx, returnStrategy, st) ] - |> transformBody + |> transformBody returnStrategy | :? Babel.ReturnStatement as rtn -> let expr, stmts = transformAsExpr com ctx rtn.Argument match returnStrategy with - | Some ReturnStrategy.NoReturn -> stmts @ [ Expr(expr) ] - | _ -> stmts @ [ Return(expr) ] + | ReturnStrategy.NoReturn -> stmts @ [ Expr.Create(expr) ] + | _ -> stmts @ [ Return.Create(expr) ] | :? Babel.VariableDeclaration as vd -> [ for vd in vd.Declarations do let targets: Expression list = - [ Name(id = Identifier(vd.Id.Name), ctx = Store()) ] + [ Name.Create(id = Identifier(vd.Id.Name), ctx = Store) ] match vd.Init with | Some value -> let expr, stmts = com.TransformAsExpr(ctx, value) yield! stmts - Assign(targets, expr) + Assign.Create(targets, expr) | None -> () ] | :? Babel.ExpressionStatement as es -> @@ -468,7 +490,7 @@ module Util = | _ -> [ let expr, stmts = com.TransformAsExpr(ctx, es.Expression) yield! stmts - Expr(expr) ] + Expr.Create(expr) ] | :? Babel.IfStatement as iff -> let test, stmts = com.TransformAsExpr(ctx, iff.Test) @@ -481,7 +503,7 @@ module Util = | _ -> [] [ yield! stmts - If(test = test, body = body, orelse = orElse) ] + If.Create(test = test, body = body, orelse = orElse) ] | :? Babel.WhileStatement as ws -> let expr, stmts = com.TransformAsExpr(ctx, ws.Test) @@ -489,12 +511,26 @@ module Util = com.TransformAsStatements(ctx, returnStrategy, ws.Body) [ yield! stmts - While(test = expr, body = body, orelse = []) ] + While.Create(test = expr, body = body, orelse = []) ] + | :? Babel.TryStatement as ts -> + let body = com.TransformAsStatements(ctx, returnStrategy, ts.Block) + let finalBody = ts.Finalizer |> Option.map (fun f -> com.TransformAsStatements(ctx, returnStrategy, f)) + let handlers = + match ts.Handler with + | Some cc -> + let body = com.TransformAsStatements(ctx, returnStrategy, cc.Body) + let exn = Name.Create(Identifier("Exception"), ctx=Load) |> Some + let identifier = Identifier(cc.Param.Name) + let handlers = [ ExceptHandler.Create(``type``=exn, name=identifier, body=body) ] + handlers + | _ -> [] + + [ Try.Create(body=body, handlers=handlers, ?finalBody=finalBody) ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" /// Transform Babel program to Python module. let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = - let returnStrategy = Some ReturnStrategy.Return + let returnStrategy = ReturnStrategy.NoReturn let stmt: Statement list = [ for md in body do @@ -507,17 +543,17 @@ module Util = com.TransformAsExpr(ctx, decls.Init.Value) let targets: Expression list = - [ Name(id = Identifier(decls.Id.Name), ctx = Store()) ] + [ Name.Create(id = Identifier(decls.Id.Name), ctx = Store) ] yield! stmts - yield Assign(targets = targets, value = value) + yield Assign.Create(targets = targets, value = value) | :? Babel.FunctionDeclaration as fd -> let args = fd.Params |> List.ofArray - |> List.map (fun pattern -> Arg(Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.Create(Identifier pattern.Name)) - let arguments = Arguments(args = args) + let arguments = Arguments.Create(args = args) let body = com.TransformAsStatements(ctx, returnStrategy, fd.Body) @@ -525,17 +561,19 @@ module Util = let name = Helpers.cleanNameAsPythonIdentifier (fd.Id.Name) - yield FunctionDef(Identifier(name), arguments, body = body) + yield FunctionDef.Create(Identifier(name), arguments, body = body) | :? Babel.ClassDeclaration as cd -> yield! com.TransformAsClassDef(ctx, cd) | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" | :? Babel.ImportDeclaration as imp -> yield! com.TransformAsImports(ctx, imp) | :? Babel.PrivateModuleDeclaration as pmd -> - let st = pmd.Statement - yield! com.TransformAsStatements(ctx, returnStrategy, st) + yield! + com.TransformAsStatements(ctx, returnStrategy, pmd.Statement) + |> transformBody returnStrategy | _ -> failwith $"Unknown module declaration: {md}" ] - let imports = com.GetAllImports () - Module(imports @ stmt) + + let imports = com.GetAllImports() + Module.Create(imports @ stmt) let getIdentForImport (ctx: Context) (path: string) (selector: string) = if String.IsNullOrEmpty selector then @@ -550,7 +588,6 @@ module Util = module Compiler = open Util - type PythonCompiler(com: Compiler) = let onlyOnceWarnings = HashSet() let imports = Dictionary() @@ -583,7 +620,11 @@ module Compiler = // | None -> upcast Babel.NullLiteral () failwith "Not implemented" - member _.GetAllImports() = imports.Values |> List.ofSeq |> List.map (fun im -> upcast im) + member _.GetAllImports() = + imports.Values + |> List.ofSeq + |> List.map Import + member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e member bcom.TransformAsStatements(ctx, ret, e) = diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 2da9f1f455..ed2461116b 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -15,7 +15,7 @@ type Printer = module PrinterExtensions = type Printer with - member printer.Print(node: AST) = node.Print(printer) + member printer.Print(node: IPrint) = node.Print(printer) member printer.PrintBlock ( @@ -49,18 +49,18 @@ module PrinterExtensions = printfn $"hasNoSideEffects: {e}" match e with - | :? Constant -> true - | :? Dict as d -> d.Keys.IsEmpty + | Constant(_) -> true + | Dict { Keys=keys }-> keys.IsEmpty | _ -> false match stmt with - | :? Expr as expr -> hasNoSideEffects expr.Value |> not + | Expr(expr) -> hasNoSideEffects expr.Value |> not | _ -> true member printer.PrintStatement(stmt: Statement, ?printSeparator) = printfn "PrintStatement: %A" stmt - stmt.Print(printer) + printer.Print(stmt) printSeparator |> Option.iter (fun fn -> fn printer) member printer.PrintStatements(statements: Statement list) = @@ -76,14 +76,14 @@ module PrinterExtensions = ?skipNewLineAtEnd = skipNewLineAtEnd ) - member printer.PrintOptional(before: string, node: #AST option) = + member printer.PrintOptional(before: string, node: IPrint option) = match node with | None -> () | Some node -> printer.Print(before) node.Print(printer) - member printer.PrintOptional(before: string, node: #AST option, after: string) = + member printer.PrintOptional(before: string, node: IPrint option, after: string) = match node with | None -> () | Some node -> @@ -91,7 +91,7 @@ module PrinterExtensions = node.Print(printer) printer.Print(after) - member printer.PrintOptional(node: #AST option) = + member printer.PrintOptional(node: IPrint option) = match node with | None -> () | Some node -> node.Print(printer) @@ -103,7 +103,7 @@ module PrinterExtensions = if i < nodes.Length - 1 then printSeparator printer - member printer.PrintCommaSeparatedList(nodes: AST list) = + member printer.PrintCommaSeparatedList(nodes: IPrint list) = printer.PrintList(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) member printer.PrintCommaSeparatedList(nodes: Expression list) = @@ -118,15 +118,15 @@ module PrinterExtensions = id: Identifier option, args: Arguments, body: Statement list, - returnType, + returnType: Expression option, ?isDeclaration ) = printer.Print("def ") - printer.PrintOptional(id) + printer.PrintOptional(id |> Option.map (fun x -> x :> IPrint)) printer.Print("(") - printer.PrintCommaSeparatedList(args.Args |> List.map (fun arg -> arg :> AST)) + printer.PrintCommaSeparatedList(args.Args |> List.map (fun arg -> arg :> IPrint)) printer.Print(")") - printer.PrintOptional(returnType) + printer.PrintOptional(returnType |> Option.map (fun x -> x :> IPrint)) printer.Print(":") printer.PrintBlock(body, skipNewLineAtEnd = true) @@ -189,7 +189,7 @@ module PrinterExtensions = member printer.WithParens(expr: Expression) = printer.Print("(") - expr.Print(printer) + printer.Print(expr) printer.Print(")") member printer.SequenceExpressionWithParens(expr: Expression) = @@ -204,10 +204,10 @@ module PrinterExtensions = match expr with // | :? Undefined // | :? NullLiteral - | :? Constant -> expr.Print(printer) + | Constant(_) -> printer.Print(expr) // | :? BooleanLiteral // | :? NumericLiteral - | :? Name -> expr.Print(printer) + | Name(_) -> printer.Print(expr) // | :? MemberExpression // | :? CallExpression // | :? ThisExpression @@ -223,68 +223,252 @@ module PrinterExtensions = printer.Print(operator) printer.ComplexExpressionWithParens(right) -type AST = - //int col +type IPrint = abstract Print: Printer -> unit -[] -type Expression() = - member val Lineno: int = 0 with get, set - member val ColOffset: int = 0 with get, set - member val EndLineno: int option = None with get, set - member val EndColOffset: int option = None with get, set - - interface AST with - member this.Print(printer) = this.Print printer - - abstract Print: Printer -> unit - - member x.AsExpr() = x - +type AST = + | Expression of Expression + | Statement of Statement + | Operator of Operator + | BoolOperator of BoolOperator + | ComparisonOperator of ComparisonOperator + | UnaryOperator of UnaryOperator + | ExpressionContext of ExpressionContext + | Alias of Alias + | Module of Module + | Arguments of Arguments + | Keyword of Keyword + | Arg of Arg + + interface IPrint with + member x.Print(printer: Printer) = + match x with + | Expression(ex) -> printer.Print(ex) + | Operator(op) -> printer.Print(op) + | BoolOperator(op) -> printer.Print(op) + | ComparisonOperator(op) -> printer.Print(op) + | UnaryOperator(op) -> printer.Print(op) + | ExpressionContext(ctx) -> printer.Print(ctx) + | Alias(al) -> printer.Print(al) + | Module(mod) -> printer.Print(mod) + | Arguments(arg) -> printer.Print(arg) + | Keyword(kw) -> printer.Print(kw) + | Arg(arg) -> printer.Print(arg) + | Statement(st) -> printer.Print(st) + +type Expression = + | Attribute of Attribute + | Subscript of Subscript + | BinOp of BinOp + /// A yield from expression. Because these are expressions, they must be wrapped in a Expr node if the value sent + /// back is not used. + | YieldFrom of Expression option + /// A yield expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back is + /// not used. + | Yield of Expression option + | Emit of Emit + | UnaryOp of UnaryOp + | FormattedValue of FormattedValue + | Constant of Constant + | Call of Call + | Compare of Compare + | Lambda of Lambda + /// A variable name. id holds the name as a string, and ctx is one of the following types. + | Name of Name + | Dict of Dict + | Tuple of Tuple + + // member val Lineno: int = 0 with get, set + // member val ColOffset: int = 0 with get, set + // member val EndLineno: int option = None with get, set + // member val EndColOffset: int option = None with get, set + interface IPrint with + member x.Print(printer: Printer) = + match x with + | Attribute(ex) -> printer.Print(ex) + | Subscript(ex) -> printer.Print(ex) + | BinOp(ex) -> printer.Print(ex) + | Emit(ex) -> printer.Print(ex) + | UnaryOp(ex) -> printer.Print(ex) + | FormattedValue(ex) -> printer.Print(ex) + | Constant(ex) -> printer.Print(ex) + | Call(ex) -> printer.Print(ex) + | Lambda(ex) -> printer.Print(ex) + | Name(ex) -> printer.Print(ex) + | Yield(expr) -> printer.Print("(Yield)") + | YieldFrom(expr) -> printer.Print("(Yield)") + | Compare(cp) -> printer.Print(cp) + | Dict(di) -> printer.Print(di) + | Tuple(tu) -> printer.Print(tu) type Operator = - inherit AST + | Add + | Sub + | Mult + | Div + | FloorDiv + | Mod + | Pow + | LShift + | RShift + | BitOr + | BitXor + | BitAnd + | MatMult + + interface IPrint with + member x.Print(printer: Printer) = + let op = + match x with + | Add -> " + " + | Sub -> " - " + | Mult -> " * " + | Div -> " / " + | FloorDiv -> " // " + | Mod -> " % " + | Pow -> " ** " + | LShift -> " << " + | RShift -> " >> " + | BitOr -> " | " + | BitXor -> " ^ " + | BitAnd -> $" & " + | MatMult -> $" @ " + printer.Print(op) type BoolOperator = - inherit AST + | And + | Or + + interface IPrint with + member x.Print(printer: Printer) = + let op = + match x with + | And -> "and" + | Or -> "or" + printer.Print(op) + type ComparisonOperator = - inherit AST + | Eq + | NotEq + | Lt + | LtE + | Gt + | GtE + | Is + | IsNot + | In + | NotIn + + interface IPrint with + member x.Print(printer) = + let op = + match x with + | Eq -> " == " + | NotEq -> " != " + | Lt -> " < " + | LtE -> " <= " + | Gt -> " > " + | GtE -> " >= " + | Is -> " is " + | IsNot -> " is not " + | In -> " in " + | NotIn -> " not in " + printer.Print(op) type UnaryOperator = - inherit AST + | Invert + | Not + | UAdd + | USub + + interface IPrint with + member this.Print(printer) = + let op = + match this with + | Invert -> "~" + | Not -> "not " + | UAdd -> "+" + | USub -> "-" + + printer.Print(op) type ExpressionContext = - inherit AST + | Load + | Del + | Store + interface IPrint with + member this.Print(printer) = () type Identifier = | Identifier of string - interface AST with + + interface IPrint with member this.Print(printer: Printer) = let (Identifier id) = this - printer.Print id - -[] -type Statement() = - member val Lineno: int = 0 with get, set - member val ColOffset: int = 0 with get, set - member val EndLineno: int option = None with get, set - member val EndColOffset: int option = None with get, set - - interface AST with - member this.Print(printer) = this.Print printer - - abstract Print: Printer -> unit - -type Module(body) = - member _.Body: List = body - - interface AST with - member this.Print(printer) = this.Print printer - - member _.Print(printer: Printer) = printer.PrintStatements(body) + printer.Print(id) + +type Statement = + | Assign of Assign + | Import of Import + | ImportFrom of ImportFrom + | Expr of Expr + | For of For + | AsyncFor of AsyncFor + | If of If + | ClassDef of ClassDef + | Raise of Raise + | Global of Global + | NonLocal of NonLocal + | AsyncFunctionDef of AsyncFunctionDef + | FunctionDef of FunctionDef + | Return of Return + | While of While + | Try of Try + | Pass + | Break + | Continue + + // member val Lineno: int = 0 with get, set + // member val ColOffset: int = 0 with get, set + // member val EndLineno: int option = None with get, set + // member val EndColOffset: int option = None with get, set + + interface IPrint with + member x.Print(printer) = + match x with + | FunctionDef(def) -> printer.Print(def) + | AsyncFunctionDef(def) -> printer.Print(def) + | Assign(st) -> printer.Print(st) + | Expr(st) -> printer.Print(st) + | For(st) -> printer.Print(st) + | AsyncFor(st) -> printer.Print(st) + | If(st) -> printer.Print(st) + | ClassDef(st) -> printer.Print(st) + | Raise(st) -> printer.Print(st) + | Global(st) -> printer.Print(st) + | NonLocal(st) -> printer.Print(st) + | Pass -> printer.Print("pass") + | Break -> printer.Print("break") + | Continue -> printer.Print("continue") + | Return(rtn) -> printer.Print(rtn) + | Import(im) -> printer.Print(im) + | ImportFrom(im) -> printer.Print(im) + | While(wh) -> printer.Print(wh) + | Try(st) -> printer.Print(st) + +type Module = + { + Body: Statement list + } + + static member Create(body) = + { Body = body } + + interface IPrint with + member x.Print(printer: Printer) = printer.PrintStatements(x.Body) /// Both parameters are raw strings of the names. asname can be None if the regular name is to be used. /// @@ -300,57 +484,152 @@ type Module(body) = /// level=2)], /// type_ignores=[]) /// ``` -type Alias(name, asname) = - member _.Name: Identifier = name - member _.AsName: Identifier option = asname - - interface AST with - member this.Print(printer) = this.Print printer - - member _.Print(printer: Printer) = - printer.Print(name) - - match asname with - | Some (Identifier alias) -> - printer.Print(" as ") - printer.Print(alias) - | _ -> () - +type Alias = + { + Name: Identifier + AsName: Identifier option + } + + static member Create(name, asname) = + { + Name = name + AsName = asname + } + + interface IPrint with + member x.Print(printer) = + printer.Print(x.Name) + + match x.AsName with + | Some (Identifier alias) -> + printer.Print(" as ") + printer.Print(alias) + | _ -> () + +/// A single except clause. type is the exception type it will match, typically a Name node (or None for a catch-all +/// except: clause). name is a raw string for the name to hold the exception, or None if the clause doesn’t have as foo. +/// body is a list of nodes. +type ExceptHandler = + { + Type: Expression option + Name: Identifier option + Body: Statement list + Loc: SourceLocation option + } + + static member Create(``type``, ?name, ?body, ?loc) = + { + Type = ``type`` + Name = name + Body = defaultArg body [] + Loc = loc + } + interface IPrint with + member x.Print(printer) = + printer.Print("except ", ?loc=x.Loc) + printer.PrintOptional(x.Type |> Option.map (fun t -> t :> IPrint)) + printer.PrintOptional(" as ", x.Name |> Option.map (fun t -> t :> IPrint)) + printer.Print(":") + match x.Body with + | [] -> printer.PrintBlock([Pass]) + | _ -> printer.PrintBlock(x.Body) + + +/// try blocks. All attributes are list of nodes to execute, except for handlers, which is a list of ExceptHandler +/// nodes. +type Try = + { + Body: Statement list + Handlers: ExceptHandler list + OrElse: Statement list + FinalBody: Statement list + Loc: SourceLocation option + } + + static member Create(body, ?handlers, ?orElse, ?finalBody, ?loc) : Statement = + { + Body = body + Handlers = defaultArg handlers [] + OrElse = defaultArg orElse [] + FinalBody = defaultArg finalBody [] + Loc = loc + } |> Try + + interface IPrint with + member x.Print(printer) = + printer.Print("try: ", ?loc=x.Loc) + printer.PrintBlock(x.Body) + for handler in x.Handlers do + printer.Print(handler) + if x.OrElse.Length > 0 then + printer.Print("else: ") + printer.PrintBlock(x.OrElse) + if x.FinalBody.Length > 0 then + printer.Print("finally: ") + printer.PrintBlock(x.FinalBody) /// A single argument in a list. arg is a raw string of the argument name, annotation is its annotation, such as a Str /// or Name node. /// /// - type_comment is an optional string with the type annotation as a comment -type Arg(arg, ?annotation, ?typeComment) = - member val Lineno: int = 0 with get, set - member val ColOffset: int = 0 with get, set - member val EndLineno: int option = None with get, set - member val EndColOffset: int option = None with get, set - - member _.Arg: Identifier = arg - member _.Annotation: Expression option = annotation - member _.TypeComment: string option = typeComment - - interface AST with - member _.Print(printer) = - let (Identifier name) = arg +type Arg = + { + Lineno: int + ColOffset: int + EndLineno: int option + EndColOffset: int option + + Arg: Identifier + Annotation: Expression option + TypeComment: string option + } + + + static member Create(arg, ?annotation, ?typeComment) = + { + Lineno = 0 + ColOffset = 0 + EndLineno = None + EndColOffset = None + + Arg = arg + Annotation = annotation + TypeComment = typeComment + } + + interface IPrint with + member x.Print(printer) = + let (Identifier name) = x.Arg printer.Print(name) -type Keyword(arg, value) = - member val Lineno: int = 0 with get, set - member val ColOffset: int = 0 with get, set - member val EndLineno: int option = None with get, set - member val EndColOffset: int option = None with get, set - - member _.Arg: Identifier = arg - member _.Value: Expression = value - - interface AST with - member _.Print(printer) = - let (Identifier name) = arg +type Keyword = + { + Lineno: int + ColOffset: int + EndLineno: int option + EndColOffset: int option + + Arg: Identifier + Value: Expression + } + + static member Create(arg, value) = + { + Lineno = 0 + ColOffset = 0 + EndLineno = None + EndColOffset = None + + Arg = arg + Value = value + } + + interface IPrint with + member x.Print(printer) = + let (Identifier name) = x.Arg printer.Print(name) printer.Print(" = ") - printer.Print(value) + printer.Print(x.Value) /// The arguments for a function. /// @@ -360,16 +639,29 @@ type Keyword(arg, value) = /// required. /// - defaults is a list of default values for arguments that can be passed positionally. If there are fewer defaults, /// they correspond to the last n arguments. -type Arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ?defaults) = - member _.PosOnlyArgs: Arg list = defaultArg posonlyargs [] - member _.Args: Arg list = defaultArg args [] - member _.VarArg: Arg option = vararg - member _.KwOnlyArgs: Arg list = defaultArg kwonlyargs [] - member _.KwDefaults: Expression list = defaultArg kwDefaults [] - member _.KwArg: Arg option = kwarg - member _.Defaults: Expression list = defaultArg defaults [] - - interface AST with +type Arguments = + { + PosOnlyArgs: Arg list + Args: Arg list + VarArg: Arg option + KwOnlyArgs: Arg list + KwDefaults: Expression list + KwArg: Arg option + Defaults: Expression list + } + + static member Create(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ?defaults) = + { + PosOnlyArgs = defaultArg posonlyargs [] + Args = defaultArg args [] + VarArg = vararg + KwOnlyArgs = defaultArg kwonlyargs [] + KwDefaults = defaultArg kwDefaults [] + KwArg = kwarg + Defaults = defaultArg defaults [] + } + + interface IPrint with member _.Print(printer) = printer.Print("(Arguments)") //#region Statements @@ -405,22 +697,30 @@ type Arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ? /// value=Name(id='c', ctx=Load()))], /// type_ignores=[]) /// ``` -type Assign(targets, value, ?typeComment) = - inherit Statement() - - member _.Targets: Expression list = targets - member _.Value: Expression = value - member _.TypeComment: string option = typeComment - - override _.Print(printer) = - printfn "Assign: %A" (targets, value) - //printer.PrintOperation(targets.[0], "=", value, None) - - for target in targets do - printer.Print(target) - printer.Print(" = ") - - printer.Print(value) +type Assign = + { + Targets: Expression list + Value: Expression + TypeComment: string option + } + + static member Create(targets, value, ?typeComment) : Statement = + { + Targets = targets + Value = value + TypeComment = typeComment + } |> Assign + + interface IPrint with + member x.Print(printer) = + printfn "Assign: %A" (x.Targets, x.Value) + //printer.PrintOperation(targets.[0], "=", value, None) + + for target in x.Targets do + printer.Print(target) + printer.Print(" = ") + + printer.Print(x.Value) /// When an expression, such as a function call, appears as a statement by itself with its return value not used or /// stored, it is wrapped in this container. value holds one of the other nodes in this section, a Constant, a Name, a @@ -436,12 +736,18 @@ type Assign(targets, value, ?typeComment) = /// operand=Name(id='a', ctx=Load())))], /// type_ignores=[]) ///``` -type Expr(value) = - inherit Statement() +type Expr = + { + Value: Expression + } - member _.Value: Expression = value + static member Create(value) : Statement = + { + Value = value + } |> Expr - override _.Print(printer) = value.Print(printer) + interface IPrint with + member x.Print(printer) = printer.Print(x.Value) /// A for loop. target holds the variable(s) the loop assigns to, as a single Name, Tuple or List node. iter holds the /// item to be looped over, again as a single node. body and orelse contain lists of nodes to execute. Those in orelse @@ -469,34 +775,54 @@ type Expr(value) = /// value=Constant(value=Ellipsis))])], /// type_ignores=[]) ///``` -type For(target, iter, body, orelse, ?typeComment) = - inherit Statement() - - member _.Target: Expression = target - member _.Iterator: Expression = iter - member _.Body: Statement list = body - member _.Else: Statement list = orelse - member _.TypeComment: string option = typeComment - - override this.Print(printer) = - printer.Print("for ") - printer.Print(iter) - printer.Print(":") - printer.PrintNewLine() - printer.PushIndentation() - printer.PrintStatements(this.Body) - printer.PopIndentation() - -type AsyncFor(target, iter, body, orelse, ?typeComment) = - inherit Statement() - - member _.Target: Expression = target - member _.Iterator: Expression = iter - member _.Body: Statement list = body - member _.Else: Statement list = orelse - member _.TypeComment: string option = typeComment - - override _.Print(printer) = printer.Print("(AsyncFor)") +type For = + { + Target: Expression + Iterator: Expression + Body: Statement list + Else: Statement list + TypeComment: string option + } + + static member Create(target, iter, body, orelse, ?typeComment) = + { + Target = target + Iterator = iter + Body = body + Else = orelse + TypeComment = typeComment + } + + interface IPrint with + member x.Print(printer: Printer) = + printer.Print("for ") + printer.Print(x.Iterator) + printer.Print(":") + printer.PrintNewLine() + printer.PushIndentation() + printer.PrintStatements(x.Body) + printer.PopIndentation() + +type AsyncFor = + { + Target: Expression + Iterator: Expression + Body: Statement list + Else: Statement list + TypeComment: string option + } + + static member Create(target, iter, body, ?orelse, ?typeComment) = + { + Target = target + Iterator = iter + Body = body + Else = defaultArg orelse [] + TypeComment = typeComment + } + + interface IPrint with + member _.Print(printer) = printer.Print("(AsyncFor)") /// A while loop. test holds the condition, such as a Compare node. /// @@ -519,21 +845,29 @@ type AsyncFor(target, iter, body, orelse, ?typeComment) = /// value=Constant(value=Ellipsis))])], /// type_ignores=[]) /// ``` -type While(test, body, orelse) = - inherit Statement() - - member _.Test: Expression = test - member _.Body: Statement list = body - member _.Else: Statement list = orelse - - override this.Print(printer) = - printer.Print("while ") - printer.Print(test) - printer.Print(":") - printer.PrintNewLine() - printer.PushIndentation() - printer.PrintStatements(this.Body) - printer.PopIndentation() +type While = + { + Test: Expression + Body: Statement list + Else: Statement list + } + + static member Create(test, body, ?orelse) : Statement = + { + Test = test + Body = body + Else = defaultArg orelse [] + } |> While + + interface IPrint with + member x.Print(printer: Printer) = + printer.Print("while ") + printer.Print(x.Test) + printer.Print(":") + printer.PrintNewLine() + printer.PushIndentation() + printer.PrintStatements(x.Body) + printer.PopIndentation() /// A class definition. /// @@ -571,32 +905,43 @@ type While(test, body, orelse) = /// Name(id='decorator2', ctx=Load())])], /// type_ignores=[]) ///``` -type ClassDef(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc) = - inherit Statement() - - member _.Name: Identifier = name - member _.Bases: Expression list = defaultArg bases [] - member _.Keyword: Keyword list = defaultArg keywords [] - member _.Body: Statement list = defaultArg body [] - member _.DecoratorList: Expression list = defaultArg decoratorList [] - - override this.Print(printer) = - let (Identifier name) = name - printer.Print("class ", ?loc = loc) - printer.Print(name) - - match this.Bases with - | [] -> () - | xs -> - printer.Print("(") - printer.PrintCommaSeparatedList(this.Bases) - printer.Print(")") +type ClassDef = + { + Name: Identifier + Bases: Expression list + Keyword: Keyword list + Body: Statement list + DecoratorList: Expression list + Loc: SourceLocation option + } + static member Create(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc) : Statement = + { + Name= name + Bases = defaultArg bases [] + Keyword = defaultArg keywords [] + Body = defaultArg body [] + DecoratorList = defaultArg decoratorList [] + Loc = loc + } |> ClassDef + + interface IPrint with + member x.Print(printer) = + let (Identifier name) = x.Name + printer.Print("class ", ?loc = x.Loc) + printer.Print(name) - printer.Print(":") - printer.PrintNewLine() - printer.PushIndentation() - printer.PrintStatements(this.Body) - printer.PopIndentation() + match x.Bases with + | [] -> () + | xs -> + printer.Print("(") + printer.PrintCommaSeparatedList(x.Bases) + printer.Print(")") + + printer.Print(":") + printer.PrintNewLine() + printer.PushIndentation() + printer.PrintStatements(x.Body) + printer.PopIndentation() /// An if statement. test holds a single node, such as a Compare node. body and orelse each hold a list of nodes. /// @@ -630,14 +975,22 @@ type ClassDef(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc) = /// value=Constant(value=Ellipsis))])])], /// type_ignores=[]) /// ``` -type If(test, body, orelse) = - inherit Statement() - - member _.Test: Expression = test - member _.Body: Statement list = body - member _.Else: Statement list = orelse - - override _.Print(printer) = printer.Print("(If)") +type If = + { + Test: Expression + Body: Statement list + Else: Statement list + } + + static member Create(test, body, ?orelse) : Statement = + { + Test = test + Body = body + Else = defaultArg orelse [] + } |> If + + interface IPrint with + member _.Print(printer) = printer.Print("(If)") /// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone /// raise. cause is the optional part for y in raise x from y. @@ -651,13 +1004,19 @@ type If(test, body, orelse) = /// cause=Name(id='y', ctx=Load()))], /// type_ignores=[]) /// ``` -type Raise(exc, ?cause) = - inherit Statement() - - member _.Exception: Expression = exc - member _.Cause: Expression option = cause - - override _.Print(printer) = printer.Print("(Raise)") +type Raise = + { + Exception: Expression + Cause: Expression option + } + static member Create(exc, ?cause) : Statement = + { + Exception = exc + Cause = cause + } |> Raise + + interface IPrint with + member _.Print(printer) = printer.Print("(Raise)") /// A function definition. /// @@ -668,20 +1027,30 @@ type Raise(exc, ?cause) = /// applied last). /// - returns is the return annotation. /// - type_comment is an optional string with the type annotation as a comment. -type FunctionDef(name, args, body, ?decoratorList, ?returns, ?typeComment) = - - inherit Statement() - - member _.Name: Identifier = name - member _.Args: Arguments = args - member _.Body: Statement list = body - member _.DecoratorList: Expression list = defaultArg decoratorList [] - member _.Returns: Expression option = returns - member _.TypeComment: string option = typeComment - - override _.Print(printer) = - printer.PrintFunction(Some name, args, body, returns, isDeclaration = true) - printer.PrintNewLine() +type FunctionDef = + { + Name: Identifier + Args: Arguments + Body: Statement list + DecoratorList: Expression list + Returns: Expression option + TypeComment: string option + } + + static member Create(name, args, body, ?decoratorList, ?returns, ?typeComment) : Statement= + { + Name = name + Args = args + Body = body + DecoratorList = defaultArg decoratorList [] + Returns = returns + TypeComment = typeComment + } |> FunctionDef + + interface IPrint with + member x.Print(printer: Printer) = + printer.PrintFunction(Some x.Name, x.Args, x.Body, x.Returns, isDeclaration=true) + printer.PrintNewLine() /// global and nonlocal statements. names is a list of raw strings. /// @@ -697,12 +1066,18 @@ type FunctionDef(name, args, body, ?decoratorList, ?returns, ?typeComment) = /// type_ignores=[]) /// /// ``` -type Global(names) = - inherit Statement() +type Global = + { + Names: Identifier list + } - member _.Names: Identifier list = names + static member Create(names) = + { + Names = names + } - override _.Print(printer) = printer.Print("(Global)") + interface IPrint with + member x.Print(printer) = printer.Print("(Global)") /// global and nonlocal statements. names is a list of raw strings. /// @@ -717,38 +1092,19 @@ type Global(names) = /// 'z'])], /// type_ignores=[]) /// ````` -type NonLocal(names) = - inherit Statement() - - member _.Names: Identifier list = names - - override _.Print(printer) = printer.Print("(NonLocal)") - -/// A pass statement. -/// -/// ```py -/// >>> print(ast.dump(ast.parse('pass'), indent=4)) -/// Module( -/// body=[ -/// Pass()], -/// type_ignores=[]) -/// ``` -type Pass() = - inherit Statement() - - override _.Print(printer) = printer.Print("pass") - -/// The break statement. -type Break() = - inherit Statement() +type NonLocal = + { + Names: Identifier list + } - override _.Print(printer) = printer.Print("break") + static member Create(names) = + { + Names = names + } -/// The continue statement. -type Continue() = - inherit Statement() + interface IPrint with + member _.Print(printer: Printer) = printer.Print("(NonLocal)") - override _.Print(printer) = printer.Print("continue") /// An async function definition. /// @@ -759,18 +1115,28 @@ type Continue() = /// applied last). /// - returns is the return annotation. /// - type_comment is an optional string with the type annotation as a comment. -type AsyncFunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = - - inherit Statement() - - member _.Name: Identifier = name - member _.Args: Arguments = args - member _.Body: Statement list = body - member _.DecoratorList: Expression list = decoratorList - member _.Returns: Expression option = returns - member _.TypeComment: string option = typeComment - - override _.Print(printer) = printer.Print("(AsyncFunctionDef)") +type AsyncFunctionDef = + { + Name: Identifier + Args: Arguments + Body: Statement list + DecoratorList: Expression list + Returns: Expression option + TypeComment: string option + } + + static member Create(name, args, body, decoratorList, ?returns, ?typeComment) = + { + Name = name + Args = args + Body = body + DecoratorList = decoratorList + Returns = returns + TypeComment = typeComment + } + + interface IPrint with + member _.Print(printer: Printer) = printer.Print("(AsyncFunctionDef)") /// An import statement. names is a list of alias nodes. /// @@ -785,12 +1151,16 @@ type AsyncFunctionDef(name, args, body, decoratorList, ?returns, ?typeComment) = /// alias(name='z')])], /// type_ignores=[]) /// ````` -type Import(names) = - inherit Statement() +type Import = + { + Names: Alias list + } - member _.Names: Alias list = names + static member Create(names) : Statement = + Import { Names = names } - override _.Print(printer) = printer.Print("(Import)") + interface IPrint with + member _.Print(printer) = printer.Print("(Import)") /// Represents from x import y. module is a raw string of the ‘from’ name, without any leading dots, or None for /// statements such as from . import foo. level is an integer holding the level of the relative import (0 means absolute @@ -809,30 +1179,37 @@ type Import(names) = /// level=0)], /// type_ignores=[]) /// ``` -type ImportFrom(``module``, names, ?level) = - inherit Statement() - - member _.Module: Identifier option = ``module`` - member _.Names: Alias list = names - member _.Level: int option = level - - override this.Print(printer) = - let (Identifier path) = - this.Module - |> Option.defaultValue (Identifier ".") - - printer.Print("from ") - - printer.Print(printer.MakeImportPath(path)) - - printer.Print(" import ") - - if not (List.isEmpty names) then - if List.length names > 1 then - printer.Print("(") - printer.PrintCommaSeparatedList(names |> List.map (fun x -> x :> AST)) - if List.length names > 1 then - printer.Print(")") +type ImportFrom = + { + Module: Identifier option + Names: Alias list + Level: int option + } + + static member Create(``module``, names, ?level) : Statement = + { + Module = ``module`` + Names = names + Level = level + } + |> ImportFrom + + interface IPrint with + member x.Print(printer: Printer) = + let (Identifier path) = + x.Module + |> Option.defaultValue (Identifier ".") + + printer.Print("from ") + printer.Print(printer.MakeImportPath(path)) + printer.Print(" import ") + + if not (List.isEmpty x.Names) then + if List.length x.Names > 1 then + printer.Print("(") + printer.PrintCommaSeparatedList(x.Names |> List.map (fun x -> x :> IPrint)) + if List.length x.Names > 1 then + printer.Print(")") /// A return statement. /// @@ -844,13 +1221,18 @@ type ImportFrom(``module``, names, ?level) = /// value=Constant(value=4))], /// type_ignores=[]) /// ``` -type Return(?value) = - inherit Statement() - member _.Value: Expression option = value +type Return = + { + Value: Expression option + } + + static member Create(?value) : Statement = + Return { Value = value } - override this.Print(printer) = - printer.Print("return ") - printer.PrintOptional(this.Value) + interface IPrint with + member this.Print(printer) = + printer.Print("return ") + printer.PrintOptional(this.Value |> Option.map (fun x -> x :> IPrint )) //#endregion @@ -867,17 +1249,26 @@ type Return(?value) = /// attr='colour', /// ctx=Load())) /// ``` -type Attribute(value, attr, ctx) = - inherit Expression() - - member _.Value: Expression = value - member _.Attr: Identifier = attr - member _.Ctx: ExpressionContext = ctx - - override this.Print(printer) = - printer.Print(this.Value) - printer.Print(".") - printer.Print(this.Attr) +type Attribute = + { + Value: Expression + Attr: Identifier + Ctx: ExpressionContext + } + + + static member Create(value, attr, ctx) : Expression = + { + Value = value + Attr = attr + Ctx = ctx + } |> Attribute + + interface IPrint with + member this.Print(printer) = + printer.Print(this.Value) + printer.Print(".") + printer.Print(this.Attr) /// A subscript, such as l[1]. value is the subscripted object (usually sequence or mapping). slice is an index, slice /// or key. It can be a Tuple and contain a Slice. ctx is Load, Store or Del according to the action performed with the @@ -897,63 +1288,92 @@ type Attribute(value, attr, ctx) = /// ctx=Load()), /// ctx=Load())) /// ``` -type Subscript(value, slice, ctx) = - inherit Expression() - - member _.Value: Expression = value - member _.Slice: Expression = slice - member _.Ctx: ExpressionContext = ctx - - override this.Print(printer) = - printer.Print(this.Value) - printer.Print("[") - printer.Print(this.Slice) - printer.Print("]") - -type BinOp(left, op, right) = - inherit Expression() - - member _.Left: Expression = left - member _.Right: Expression = right - member _.Operator: Operator = op - - override this.Print(printer) = printer.PrintOperation(left, op, right) - -type Compare(left, ops, comparators) = - inherit Expression() - - member _.Left: Expression = left - member _.Comparators: Expression list = comparators - member _.Ops: ComparisonOperator list = ops - - override this.Print(printer) = - //printer.AddLocation(loc) - printer.ComplexExpressionWithParens(left) - - for op, comparator in List.zip this.Ops this.Comparators do - printer.Print(op) - printer.ComplexExpressionWithParens(comparator) +type Subscript = + { + Value: Expression + Slice: Expression + Ctx: ExpressionContext + } + + static member Create(value, slice, ctx) : Expression= + { + Value = value + Slice = slice + Ctx = ctx + } |> Subscript + + interface IPrint with + member this.Print(printer: Printer) = + printer.Print(this.Value) + printer.Print("[") + printer.Print(this.Slice) + printer.Print("]") + +type BinOp = + { + Left: Expression + Right: Expression + Operator: Operator + } + static member Create(left, op, right) : Expression = + { + Left = left + Right = right + Operator = op + } |> BinOp + + interface IPrint with + member this.Print(printer) = printer.PrintOperation(this.Left, this.Operator, this.Right) + +type Compare = + { + Left: Expression + Comparators: Expression list + Ops: ComparisonOperator list + } + + static member Create(left, ops, comparators) : Expression = + { + Left = left + Comparators = comparators + Ops = ops + } |> Compare + + interface IPrint with + member x.Print(printer) = + //printer.AddLocation(loc) + printer.ComplexExpressionWithParens(x.Left) + + for op, comparator in List.zip x.Ops x.Comparators do + printer.Print(op) + printer.ComplexExpressionWithParens(comparator) /// A unary operation. op is the operator, and operand any expression node. -type UnaryOp(op, operand, ?loc) = - inherit Expression() - - member _.Op: UnaryOperator = op - member _.Operand: Expression = operand - - override this.Print(printer) = - printer.AddLocation(loc) - - match op with - | :? USub - | :? UAdd - | :? Not - | :? Invert -> printer.Print(op) - | _ -> - printer.Print(op) - printer.Print(" ") - - printer.ComplexExpressionWithParens(operand) +type UnaryOp = + { + Op: UnaryOperator + Operand: Expression + Loc: SourceLocation option + } + + static member Create(op, operand, ?loc) : Expression = + { + Op = op + Operand = operand + Loc = loc + } |> UnaryOp + + interface IPrint with + override x.Print(printer) = + printer.AddLocation(x.Loc) + + match x.Op with + | USub + | UAdd + | Not + | Invert -> printer.Print(x.Op) + + printer.ComplexExpressionWithParens(x.Operand) /// A constant value. The value attribute of the Constant literal contains the Python object it represents. The values /// represented can be simple types such as a number, string or None, but also immutable container types (tuples and @@ -964,18 +1384,26 @@ type UnaryOp(op, operand, ?loc) = /// Expression( /// body=Constant(value=123)) /// ````` -type Constant(value: obj) = - inherit Expression() - - member _.Value: obj = value - - override _.Print(printer) = - match box value with - | :? string as str -> - printer.Print("\"") - printer.Print(string value) - printer.Print("\"") - | _ -> printer.Print(string value) +type Constant = + { + Value: obj + } + + static member Create(value: obj) : Expression = + { + Value = value + } |> Constant + + interface IPrint with + member x.Print(printer) = + match box x.Value with + | :? string as str -> + printer.Print("\"") + printer.Print(string x.Value) + printer.Print("\"") + | _ -> + printfn "*********** Value: %A" x.Value + printer.Print(string x.Value) /// Node representing a single formatting field in an f-string. If the string contains a single formatting field and /// nothing else the node can be isolated otherwise it appears in JoinedStr. @@ -988,14 +1416,22 @@ type Constant(value: obj) = /// - 97: !a ascii formatting /// - format_spec is a JoinedStr node representing the formatting of the value, or None if no format was specified. Both /// conversion and format_spec can be set at the same time. -type FormattedValue(value, ?conversion, ?formatSpec) = - inherit Expression() - - member _.Value: Expression = value - member _.Conversion: int option = conversion - member _.FormatSpec: Expression option = formatSpec - - override _.Print(printer) = printer.Print("(FormattedValue)") +type FormattedValue = + { + Value: Expression + Conversion: int option + FormatSpec: Expression option + } + + static member Create(value, ?conversion, ?formatSpec) = + { + Value = value + Conversion = conversion + FormatSpec = formatSpec + } + + interface IPrint with + member _.Print(printer) = printer.Print("(FormattedValue)") /// A function call. func is the function, which will often be a Name or Attribute object. Of the arguments: /// @@ -1022,90 +1458,105 @@ type FormattedValue(value, ?conversion, ?formatSpec) = /// keyword( /// value=Name(id='e', ctx=Load()))])) /// ``` -type Call(func, ?args, ?kw) = - inherit Expression() - - member _.Func: Expression = func - member _.Args: Expression list = defaultArg args [] - member _.Keywords: Keyword list = defaultArg kw [] - - override this.Print(printer) = - printer.Print(func) - printer.Print("(") - printer.PrintCommaSeparatedList(this.Args) - printer.PrintCommaSeparatedList(this.Keywords |> List.map (fun x -> x :> AST)) - printer.Print(")") - -type Emit(value, ?args) = - inherit Expression() - - member _.Value: string = value - member _.Args: Expression list = defaultArg args [] - - override this.Print(printer) = - let inline replace pattern (f: System.Text.RegularExpressions.Match -> string) input = - System.Text.RegularExpressions.Regex.Replace(input, pattern, f) - - let printSegment (printer: Printer) (value: string) segmentStart segmentEnd = - let segmentLength = segmentEnd - segmentStart - if segmentLength > 0 then - let segment = value.Substring(segmentStart, segmentLength) - let subSegments = System.Text.RegularExpressions.Regex.Split(segment, @"\r?\n") - for i = 1 to subSegments.Length do - let subSegment = - // Remove whitespace in front of new lines, - // indent will be automatically applied - if printer.Column = 0 then subSegments.[i - 1].TrimStart() - else subSegments.[i - 1] - if subSegment.Length > 0 then - printer.Print(subSegment) - if i < subSegments.Length then - printer.PrintNewLine() - - - // Macro transformations - // https://fable.io/docs/communicate/js-from-fable.html#Emit-when-F-is-not-enough - let value = - value - |> replace @"\$(\d+)\.\.\." (fun m -> - let rep = ResizeArray() - let i = int m.Groups.[1].Value - for j = i to this.Args.Length - 1 do - rep.Add("$" + string j) - String.concat ", " rep) - - |> replace @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" (fun m -> - let i = int m.Groups.[1].Value - match this.Args.[i] with - | :? Constant as b -> m.Groups.[2].Value - | _ -> m.Groups.[3].Value) - - |> replace @"\{\{([^\}]*\$(\d+).*?)\}\}" (fun m -> - let i = int m.Groups.[2].Value - match List.tryItem i this.Args with - | Some _ -> m.Groups.[1].Value - | None -> "") - - let matches = System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+") - if matches.Count > 0 then - for i = 0 to matches.Count - 1 do - let m = matches.[i] - - let segmentStart = - if i > 0 then matches.[i-1].Index + matches.[i-1].Length - else 0 - - printSegment printer value segmentStart m.Index - - let argIndex = int m.Value.[1..] - match List.tryItem argIndex this.Args with - | Some e -> printer.ComplexExpressionWithParens(e) - | None -> printer.Print("None") - - let lastMatch = matches.[matches.Count - 1] - printSegment printer value (lastMatch.Index + lastMatch.Length) value.Length - else - printSegment printer value 0 value.Length +type Call = + { + Func: Expression + Args: Expression list + Keywords: Keyword list + } + + static member Create(func, ?args, ?kw) : Expression = + { + Func = func + Args = defaultArg args [] + Keywords = defaultArg kw [] + } |> Call + + interface IPrint with + member x.Print(printer) = + printer.Print(x.Func) + printer.Print("(") + printer.PrintCommaSeparatedList(x.Args) + printer.PrintCommaSeparatedList(x.Keywords |> List.map (fun x -> x :> IPrint)) + printer.Print(")") + +type Emit = + { + Value: string + Args: Expression list + } + + static member Create(value, ?args) : Expression = + { + Value = value + Args = defaultArg args [] + } |> Emit + + interface IPrint with + member x.Print(printer) = + let inline replace pattern (f: System.Text.RegularExpressions.Match -> string) input = + System.Text.RegularExpressions.Regex.Replace(input, pattern, f) + + let printSegment (printer: Printer) (value: string) segmentStart segmentEnd = + let segmentLength = segmentEnd - segmentStart + if segmentLength > 0 then + let segment = value.Substring(segmentStart, segmentLength) + let subSegments = System.Text.RegularExpressions.Regex.Split(segment, @"\r?\n") + for i = 1 to subSegments.Length do + let subSegment = + // Remove whitespace in front of new lines, + // indent will be automatically applied + if printer.Column = 0 then subSegments.[i - 1].TrimStart() + else subSegments.[i - 1] + if subSegment.Length > 0 then + printer.Print(subSegment) + if i < subSegments.Length then + printer.PrintNewLine() + + + // Macro transformations + // https://fable.io/docs/communicate/js-from-fable.html#Emit-when-F-is-not-enough + let value = + x.Value + |> replace @"\$(\d+)\.\.\." (fun m -> + let rep = ResizeArray() + let i = int m.Groups.[1].Value + for j = i to x.Args.Length - 1 do + rep.Add("$" + string j) + String.concat ", " rep) + + |> replace @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" (fun m -> + let i = int m.Groups.[1].Value + match x.Args.[i] with + | Constant(c) -> m.Groups.[2].Value + | _ -> m.Groups.[3].Value) + + |> replace @"\{\{([^\}]*\$(\d+).*?)\}\}" (fun m -> + let i = int m.Groups.[2].Value + match List.tryItem i x.Args with + | Some _ -> m.Groups.[1].Value + | None -> "") + + let matches = System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+") + if matches.Count > 0 then + for i = 0 to matches.Count - 1 do + let m = matches.[i] + + let segmentStart = + if i > 0 then matches.[i-1].Index + matches.[i-1].Length + else 0 + + printSegment printer value segmentStart m.Index + + let argIndex = int m.Value.[1..] + match List.tryItem argIndex x.Args with + | Some e -> printer.ComplexExpressionWithParens(e) + | None -> printer.Print("None") + + let lastMatch = matches.[matches.Count - 1] + printSegment printer value (lastMatch.Index + lastMatch.Length) value.Length + else + printSegment printer value 0 value.Length /// lambda is a minimal function definition that can be used inside an expression. Unlike FunctionDef, body holds a @@ -1128,35 +1579,31 @@ type Emit(value, ?args) = /// body=Constant(value=Ellipsis)))], /// type_ignores=[]) /// ``` -type Lambda(args, body) = - - inherit Expression() - - member _.Args: Arguments = args - member _.Body: Statement list = body - - override _.Print(printer) = - printer.Print("lambda") - - if (List.isEmpty >> not) args.Args then - printer.Print(" ") - - printer.PrintCommaSeparatedList(args.Args |> List.map (fun arg -> arg :> AST)) - printer.Print(": ") - - for stmt in body do - printer.Print(stmt) - -/// A variable name. id holds the name as a string, and ctx is one of the following types. -type Name(id, ctx) = - inherit Expression() +type Lambda = + { + Args: Arguments + Body: Statement list + } + + static member Create(args, body) : Expression = + { + Args = args + Body = body + } |> Lambda + + interface IPrint with + member x.Print(printer: Printer) = + printer.Print("lambda") + + if (List.isEmpty >> not) x.Args.Args then + printer.Print(" ") + + printer.PrintCommaSeparatedList(x.Args.Args |> List.map (fun arg -> arg :> IPrint)) + printer.Print(": ") - member _.Id: Identifier = id - member _.Context: ExpressionContext = ctx + for stmt in x.Body do + printer.Print(stmt) - override _.Print(printer) = - let (Identifier name) = id - printer.Print(name) /// A tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an assignment target /// (i.e. (x,y)=something), and Load otherwise. @@ -1171,19 +1618,27 @@ type Name(id, ctx) = /// Constant(value=3)], /// ctx=Load())) ///``` -type Tuple(elts, ?loc) = - inherit Expression() - - member _.Elements: Expression list = elts - - override _.Print(printer) = - printer.Print("(", ?loc = loc) - printer.PrintCommaSeparatedList(elts) - - if elts.Length = 1 then - printer.Print(",") +type Tuple = + { + Elements: Expression list + Loc: SourceLocation option + } + + static member Create(elts, ?loc) : Expression = + { + Elements = elts + Loc = loc + } |> Tuple + + interface IPrint with + member x.Print(printer: Printer) = + printer.Print("(", ?loc = x.Loc) + printer.PrintCommaSeparatedList(x.Elements) + + if x.Elements.Length = 1 then + printer.Print(",") - printer.Print(")") + printer.Print(")") /// A list or tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an /// assignment target (i.e. (x,y)=something), and Load otherwise. @@ -1198,12 +1653,18 @@ type Tuple(elts, ?loc) = /// Constant(value=3)], /// ctx=Load())) ///``` -type List(elts) = - inherit Expression() +type List = + { + Elements: Expression list + } - member _.Elements: Expression list = elts + static member Create(elts) = + { + Elements = elts + } - override _.Print(printer) = printer.Print("(List)") + interface IPrint with + member _.Print(printer) = printer.Print("(List)") /// A set. elts holds a list of nodes representing the set’s elements. /// @@ -1217,11 +1678,10 @@ type List(elts) = /// Constant(value=3)])) /// ``` type Set(elts) = - inherit Expression() - member _.Elements: Expression list = elts - override _.Print(printer) = printer.Print("(Set)") + interface IPrint with + member _.Print(printer) = printer.Print("(Set)") /// A dictionary. keys and values hold lists of nodes representing the keys and the values respectively, in matching /// order (what would be returned when calling dictionary.keys() and dictionary.values()). @@ -1240,220 +1700,53 @@ type Set(elts) = /// Constant(value=1), /// Name(id='d', ctx=Load())])) /// ``` -type Dict(keys, values) = - inherit Expression() - - member _.Keys: Expression list = keys - member _.Values: Expression list = values - - override _.Print(printer) = - printer.Print("{") - printer.PrintNewLine() - printer.PushIndentation() - - let nodes = List.zip keys values |> List.mapi (fun i n -> (i, n)) - for i, (key, value) in nodes do - printer.Print("\"") - printer.Print(key) - printer.Print("\"") - printer.Print(": ") - printer.Print(value) - if i < nodes.Length - 1 then - printer.Print(",") - printer.PrintNewLine() - - printer.PrintNewLine() - printer.PopIndentation() - printer.Print("}") - -/// A yield expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back is not -/// used. -/// -/// ```py -/// >>> print(ast.dump(ast.parse('yield from x'), indent=4)) -/// Module( -/// body=[ -/// Expr( -/// value=YieldFrom( -/// value=Name(id='x', ctx=Load())))], -/// type_ignores=[]) -///``` -type Yield(?value) = - inherit Expression() - member _.Value: Expression option = value - - override _.Print(printer) = printer.Print("(Yield)") - -/// A yield from expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back -/// is not used. -/// -/// ```py -/// >>> print(ast.dump(ast.parse('yield from x'), indent=4)) -/// Module( -/// body=[ -/// Expr( -/// value=YieldFrom( -/// value=Name(id='x', ctx=Load())))], -/// type_ignores=[]) -///``` -type YieldFrom(?value) = - inherit Expression() - member _.Value: Expression option = value - - override _.Print(printer) = printer.Print("(YieldFrom)") - -//#endregion - -//#region Operators - -type Add() = - interface Operator with - member _.Print(printer: Printer) = printer.Print(" + ") - -type Sub() = - static member Pattern = "-" - - interface Operator with - member _.Print(printer: Printer) = printer.Print(" - ") - -type Mult() = - interface Operator with - member _.Print(printer: Printer) = printer.Print(" * ") - -type Div() = - interface Operator with - member _.Print(printer: Printer) = printer.Print(" / ") - -type FloorDiv() = - interface Operator with - member _.Print(printer: Printer) = printer.Print(" // ") - -type Mod() = - interface Operator with - member _.Print(printer: Printer) = printer.Print(" % ") - -type Pow() = - interface Operator with - member _.Print(printer: Printer) = printer.Print(" ** ") - -type LShift() = - interface Operator with - member _.Print(printer: Printer) = printer.Print(" << ") - -type RShift() = - interface Operator with - member _.Print(printer: Printer) = printer.Print(" >> ") - -type BitOr() = - interface Operator with - member _.Print(printer: Printer) = printer.Print(" | ") - -type BitXor() = - interface Operator with - member _.Print(printer: Printer) = printer.Print(" ^ ") - -type BitAnd() = - interface Operator with - member _.Print(printer: Printer) = printer.Print($" & ") - -type MatMult() = - interface Operator with - member _.Print(printer: Printer) = printer.Print($" @ ") - -//#endregion - -//#region Comparison operator tokens. - -type Eq() = - interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" == ") - -type NotEq() = - interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" != ") - -type Lt() = - interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" < ") - -type LtE() = - interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" <= ") - -type Gt() = - interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" > ") - -type GtE() = - interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" >= ") - -type Is() = - interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" is ") - -type IsNot() = - interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" is not ") - -type In() = - interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" in ") - -type NotIn() = - interface ComparisonOperator with - member _.Print(printer: Printer) = printer.Print($" not in ") - -//#endregion - -//#region Bool Operators - -type And = - inherit BoolOperator - -type Or = - inherit BoolOperator - -//#region - -//#region Unary Operators - -type Invert() = - interface UnaryOperator with - member this.Print(printer) = printer.Print "~" - -type Not() = - interface UnaryOperator with - member this.Print(printer) = printer.Print "not " - -type UAdd() = - interface UnaryOperator with - member this.Print(printer) = printer.Print "+" - -type USub() = - interface UnaryOperator with - member this.Print(printer) = printer.Print "-" - -//#endregion - -//#region Expression Context - -type Load() = - interface ExpressionContext - - interface AST with - member this.Print(printer) = () - -type Del()= - interface ExpressionContext - - interface AST with - member this.Print(printer) = () +type Dict = + { + Keys: Expression list + Values: Expression list + } + + static member Create(keys, values) : Expression = + { + Keys = keys + Values = values + } |> Dict + + interface IPrint with + member x.Print(printer: Printer) = + printer.Print("{") + printer.PrintNewLine() + printer.PushIndentation() -type Store() = - interface ExpressionContext + let nodes = List.zip x.Keys x.Values |> List.mapi (fun i n -> (i, n)) + for i, (key, value) in nodes do + printer.Print("\"") + printer.Print(key) + printer.Print("\"") + printer.Print(": ") + printer.Print(value) + if i < nodes.Length - 1 then + printer.Print(",") + printer.PrintNewLine() - interface AST with - member this.Print(printer) = () + printer.PrintNewLine() + printer.PopIndentation() + printer.Print("}") -//#endregion +/// A variable name. id holds the name as a string, and ctx is one of the following types. +type Name = + { + Id: Identifier + Context: ExpressionContext + } + + static member Create(id, ctx) : Expression = + { + Id = id + Context = ctx + } |> Name + + interface IPrint with + override x.Print(printer) = + let (Identifier name) = x.Id + printer.Print(name) diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index 7e28910d2f..cd2487be0d 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -84,7 +84,7 @@ type PrinterImpl(writer: Writer, map: SourceMapGenerator) = let run writer map (program: Module): Async = let printDeclWithExtraLine extraLine (printer: Printer) (decl: Statement) = - decl.Print(printer) + (decl :> IPrint).Print(printer) if printer.Column > 0 then //printer.Print(";") @@ -97,8 +97,8 @@ let run writer map (program: Module): Async = let imports, restDecls = program.Body |> List.splitWhile (function - | :? Import - | :? ImportFrom -> true + | Import _ + | ImportFrom _ -> true | _ -> false) for decl in imports do @@ -112,3 +112,4 @@ let run writer map (program: Module): Async = // TODO: Only flush every XXX lines? do! printer.Flush() } +// If the queryItemsHandler succeeds, the queryItems will be used in a request to CDF, and the resulting aggregates will be wrapped in an OK result-type. \ No newline at end of file From 6c86af2bcb28aaeb38a6bb13423291854b359387 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 21 Jan 2021 00:51:41 +0100 Subject: [PATCH 036/145] Fix assingments --- src/Fable.Transforms/Python/Babel2Python.fs | 59 ++- src/Fable.Transforms/Python/Python.fs | 406 +++++++++++--------- 2 files changed, 273 insertions(+), 192 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 1c9ecd1edf..e6f1483d1f 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -55,7 +55,10 @@ module Helpers = /// Replaces all '$' with '_' - let cleanNameAsPythonIdentifier (name: string) = name.Replace('$', '_') + let cleanNameAsPythonIdentifier (name: string) = + match name with + | "this" -> "self" // TODO: Babel should use ThisExpression to avoid this hack. + | _ -> name.Replace('$', '_') let rewriteFableImport moduleName = let _reFableLib = @@ -108,6 +111,7 @@ module Util = match body, returnStrategy with | [], ReturnStrategy.Return -> [ Return.Create() ] + | [], ReturnStrategy.NoReturn -> [ Pass ] | _ -> body let transformAsImports (com: IPythonCompiler) (ctx: Context) (imp: Babel.ImportDeclaration): Statement list = @@ -327,7 +331,6 @@ module Util = | :? Babel.StringLiteral as sl -> Constant.Create(value = sl.Value), [] | Babel.Patterns.Identifier (name, _, _, _) -> let name = Helpers.cleanNameAsPythonIdentifier name - Name.Create(id = Identifier name, ctx = Load), [] | :? Babel.NewExpression as ne -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, ne.Callee) @@ -360,7 +363,11 @@ module Util = |> List.map (fun expr -> com.TransformAsExpr(ctx, expr)) |> Helpers.unzipArgs - Emit.Create(value, args), stmts + match value with + | "void $0" -> args.[0], stmts + //| "raise %0" -> Raise.Create() + | _ -> + Emit.Create(value, args), stmts | :? Babel.MemberExpression as me -> let value, stmts = com.TransformAsExpr(ctx, me.Object) @@ -418,6 +425,13 @@ module Util = FunctionDef.Create(name = name, args = arguments, body = body) Name.Create(name, Load), [ func ] + | :? Babel.ConditionalExpression as ce -> + let test, stmts1 = com.TransformAsExpr(ctx, ce.Test) + let body, stmts2 = com.TransformAsExpr(ctx, ce.Consequent) + let orElse, stmts3 = com.TransformAsExpr(ctx, ce.Alternate) + + IfExp.Create(test, body, orElse), stmts1 @ stmts2 @ stmts3 + | :? Babel.NullLiteral -> Name.Create(Identifier("None"), ctx=Load), [] | _ -> failwith $"Unhandled value: {expr}" /// Transform Babel expressions as Python statements. @@ -434,16 +448,18 @@ module Util = let value, stmts = com.TransformAsExpr(ctx, ae.Right) let targets: Expression list = - let attr = - match ae.Left with - | :? Babel.Identifier as identifier -> Identifier(identifier.Name) - | :? Babel.MemberExpression as me -> - match me.Property with - | :? Babel.Identifier as id -> Identifier(id.Name) - | _ -> failwith "transformExpressionAsStatements: unknown property {me.Property}" - | _ -> failwith $"AssignmentExpression, unknow expression: {ae.Left}" + match ae.Left with + | :? Babel.Identifier as identifier -> + let target = Identifier(Helpers.cleanNameAsPythonIdentifier(identifier.Name)) + [ Name.Create(id = target, ctx=Store) ] + | :? Babel.MemberExpression as me -> + match me.Property with + | :? Babel.Identifier as id -> + let attr = Identifier(Helpers.cleanNameAsPythonIdentifier(id.Name)) + [ Attribute.Create(value = Name.Create(id = Identifier("self"), ctx = Load), attr = attr, ctx = Store) ] - [ Attribute.Create(value = Name.Create(id = Identifier("self"), ctx = Load), attr = attr, ctx = Store) ] + | _ -> failwith "transformExpressionAsStatements: unknown property {me.Property}" + | _ -> failwith $"AssignmentExpression, unknow expression: {ae.Left}" [ yield! stmts Assign.Create(targets = targets, value = value) ] @@ -474,7 +490,8 @@ module Util = | :? Babel.VariableDeclaration as vd -> [ for vd in vd.Declarations do let targets: Expression list = - [ Name.Create(id = Identifier(vd.Id.Name), ctx = Store) ] + let name = Helpers.cleanNameAsPythonIdentifier(vd.Id.Name) + [ Name.Create(id = Identifier(name), ctx = Store) ] match vd.Init with | Some value -> @@ -496,10 +513,14 @@ module Util = let body = com.TransformAsStatements(ctx, returnStrategy, iff.Consequent) + |> transformBody ReturnStrategy.NoReturn let orElse = match iff.Alternate with - | Some alt -> com.TransformAsStatements(ctx, returnStrategy, alt) + | Some alt -> + com.TransformAsStatements(ctx, returnStrategy, alt) + |> transformBody ReturnStrategy.NoReturn + | _ -> [] [ yield! stmts @@ -509,6 +530,7 @@ module Util = let body = com.TransformAsStatements(ctx, returnStrategy, ws.Body) + |> transformBody ReturnStrategy.NoReturn [ yield! stmts While.Create(test = expr, body = body, orelse = []) ] @@ -543,7 +565,8 @@ module Util = com.TransformAsExpr(ctx, decls.Init.Value) let targets: Expression list = - [ Name.Create(id = Identifier(decls.Id.Name), ctx = Store) ] + let name = Helpers.cleanNameAsPythonIdentifier(decls.Id.Name) + [ Name.Create(id = Identifier(name), ctx = Store) ] yield! stmts yield Assign.Create(targets = targets, value = value) @@ -551,7 +574,9 @@ module Util = let args = fd.Params |> List.ofArray - |> List.map (fun pattern -> Arg.Create(Identifier pattern.Name)) + |> List.map (fun pattern -> + let name = Helpers.cleanNameAsPythonIdentifier(pattern.Name) + Arg.Create(Identifier(name))) let arguments = Arguments.Create(args = args) @@ -559,7 +584,7 @@ module Util = com.TransformAsStatements(ctx, returnStrategy, fd.Body) let name = - Helpers.cleanNameAsPythonIdentifier (fd.Id.Name) + Helpers.cleanNameAsPythonIdentifier(fd.Id.Name) yield FunctionDef.Create(Identifier(name), arguments, body = body) | :? Babel.ClassDeclaration as cd -> yield! com.TransformAsClassDef(ctx, cd) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index ed2461116b..8000b40c4f 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -15,6 +15,7 @@ type Printer = module PrinterExtensions = type Printer with + member printer.Print(node: IPrint) = node.Print(printer) member printer.PrintBlock @@ -49,19 +50,19 @@ module PrinterExtensions = printfn $"hasNoSideEffects: {e}" match e with - | Constant(_) -> true - | Dict { Keys=keys }-> keys.IsEmpty + | Constant (_) -> true + | Dict { Keys = keys } -> keys.IsEmpty | _ -> false match stmt with - | Expr(expr) -> hasNoSideEffects expr.Value |> not + | Expr (expr) -> hasNoSideEffects expr.Value |> not | _ -> true member printer.PrintStatement(stmt: Statement, ?printSeparator) = - printfn "PrintStatement: %A" stmt - printer.Print(stmt) - printSeparator |> Option.iter (fun fn -> fn printer) + + printSeparator + |> Option.iter (fun fn -> fn printer) member printer.PrintStatements(statements: Statement list) = @@ -204,10 +205,10 @@ module PrinterExtensions = match expr with // | :? Undefined // | :? NullLiteral - | Constant(_) -> printer.Print(expr) + | Constant (_) -> printer.Print(expr) // | :? BooleanLiteral // | :? NumericLiteral - | Name(_) -> printer.Print(expr) + | Name (_) -> printer.Print(expr) // | :? MemberExpression // | :? CallExpression // | :? ThisExpression @@ -240,21 +241,22 @@ type AST = | Keyword of Keyword | Arg of Arg + interface IPrint with member x.Print(printer: Printer) = match x with - | Expression(ex) -> printer.Print(ex) - | Operator(op) -> printer.Print(op) - | BoolOperator(op) -> printer.Print(op) - | ComparisonOperator(op) -> printer.Print(op) - | UnaryOperator(op) -> printer.Print(op) - | ExpressionContext(ctx) -> printer.Print(ctx) - | Alias(al) -> printer.Print(al) - | Module(mod) -> printer.Print(mod) - | Arguments(arg) -> printer.Print(arg) - | Keyword(kw) -> printer.Print(kw) - | Arg(arg) -> printer.Print(arg) - | Statement(st) -> printer.Print(st) + | Expression (ex) -> printer.Print(ex) + | Operator (op) -> printer.Print(op) + | BoolOperator (op) -> printer.Print(op) + | ComparisonOperator (op) -> printer.Print(op) + | UnaryOperator (op) -> printer.Print(op) + | ExpressionContext (ctx) -> printer.Print(ctx) + | Alias (al) -> printer.Print(al) + | Module ``mod`` -> printer.Print(``mod``) + | Arguments (arg) -> printer.Print(arg) + | Keyword (kw) -> printer.Print(kw) + | Arg (arg) -> printer.Print(arg) + | Statement (st) -> printer.Print(st) type Expression = | Attribute of Attribute @@ -265,8 +267,9 @@ type Expression = | YieldFrom of Expression option /// A yield expression. Because these are expressions, they must be wrapped in a Expr node if the value sent back is /// not used. - | Yield of Expression option + | Yield of Expression option | Emit of Emit + | IfExp of IfExp | UnaryOp of UnaryOp | FormattedValue of FormattedValue | Constant of Constant @@ -278,6 +281,7 @@ type Expression = | Dict of Dict | Tuple of Tuple + // member val Lineno: int = 0 with get, set // member val ColOffset: int = 0 with get, set // member val EndLineno: int option = None with get, set @@ -285,21 +289,22 @@ type Expression = interface IPrint with member x.Print(printer: Printer) = match x with - | Attribute(ex) -> printer.Print(ex) - | Subscript(ex) -> printer.Print(ex) - | BinOp(ex) -> printer.Print(ex) - | Emit(ex) -> printer.Print(ex) - | UnaryOp(ex) -> printer.Print(ex) - | FormattedValue(ex) -> printer.Print(ex) - | Constant(ex) -> printer.Print(ex) - | Call(ex) -> printer.Print(ex) - | Lambda(ex) -> printer.Print(ex) - | Name(ex) -> printer.Print(ex) - | Yield(expr) -> printer.Print("(Yield)") - | YieldFrom(expr) -> printer.Print("(Yield)") - | Compare(cp) -> printer.Print(cp) - | Dict(di) -> printer.Print(di) - | Tuple(tu) -> printer.Print(tu) + | Attribute (ex) -> printer.Print(ex) + | Subscript (ex) -> printer.Print(ex) + | BinOp (ex) -> printer.Print(ex) + | Emit (ex) -> printer.Print(ex) + | UnaryOp (ex) -> printer.Print(ex) + | FormattedValue (ex) -> printer.Print(ex) + | Constant (ex) -> printer.Print(ex) + | IfExp(ex) -> printer.Print(ex) + | Call (ex) -> printer.Print(ex) + | Lambda (ex) -> printer.Print(ex) + | Name (ex) -> printer.Print(ex) + | Yield (expr) -> printer.Print("(Yield)") + | YieldFrom (expr) -> printer.Print("(Yield)") + | Compare (cp) -> printer.Print(cp) + | Dict (di) -> printer.Print(di) + | Tuple (tu) -> printer.Print(tu) type Operator = | Add @@ -316,6 +321,7 @@ type Operator = | BitAnd | MatMult + interface IPrint with member x.Print(printer: Printer) = let op = @@ -333,18 +339,21 @@ type Operator = | BitXor -> " ^ " | BitAnd -> $" & " | MatMult -> $" @ " + printer.Print(op) type BoolOperator = | And | Or + interface IPrint with member x.Print(printer: Printer) = let op = match x with | And -> "and" | Or -> "or" + printer.Print(op) @@ -360,6 +369,7 @@ type ComparisonOperator = | In | NotIn + interface IPrint with member x.Print(printer) = let op = @@ -374,6 +384,7 @@ type ComparisonOperator = | IsNot -> " is not " | In -> " in " | NotIn -> " not in " + printer.Print(op) type UnaryOperator = @@ -382,6 +393,7 @@ type UnaryOperator = | UAdd | USub + interface IPrint with member this.Print(printer) = let op = @@ -398,6 +410,7 @@ type ExpressionContext = | Del | Store + interface IPrint with member this.Print(printer) = () @@ -405,6 +418,7 @@ type Identifier = | Identifier of string + interface IPrint with member this.Print(printer: Printer) = let (Identifier id) = this @@ -431,6 +445,7 @@ type Statement = | Break | Continue + // member val Lineno: int = 0 with get, set // member val ColOffset: int = 0 with get, set // member val EndLineno: int option = None with get, set @@ -439,33 +454,32 @@ type Statement = interface IPrint with member x.Print(printer) = match x with - | FunctionDef(def) -> printer.Print(def) - | AsyncFunctionDef(def) -> printer.Print(def) - | Assign(st) -> printer.Print(st) - | Expr(st) -> printer.Print(st) - | For(st) -> printer.Print(st) - | AsyncFor(st) -> printer.Print(st) - | If(st) -> printer.Print(st) - | ClassDef(st) -> printer.Print(st) - | Raise(st) -> printer.Print(st) - | Global(st) -> printer.Print(st) - | NonLocal(st) -> printer.Print(st) + | FunctionDef (def) -> printer.Print(def) + | AsyncFunctionDef (def) -> printer.Print(def) + | Assign (st) -> printer.Print(st) + | Expr (st) -> printer.Print(st) + | For (st) -> printer.Print(st) + | AsyncFor (st) -> printer.Print(st) + | If (st) -> printer.Print(st) + | ClassDef (st) -> printer.Print(st) + | Raise (st) -> printer.Print(st) + | Global (st) -> printer.Print(st) + | NonLocal (st) -> printer.Print(st) | Pass -> printer.Print("pass") | Break -> printer.Print("break") | Continue -> printer.Print("continue") - | Return(rtn) -> printer.Print(rtn) - | Import(im) -> printer.Print(im) - | ImportFrom(im) -> printer.Print(im) - | While(wh) -> printer.Print(wh) - | Try(st) -> printer.Print(st) + | Return (rtn) -> printer.Print(rtn) + | Import (im) -> printer.Print(im) + | ImportFrom (im) -> printer.Print(im) + | While (wh) -> printer.Print(wh) + | Try (st) -> printer.Print(st) type Module = { Body: Statement list } - static member Create(body) = - { Body = body } + static member Create(body) = { Body = body } interface IPrint with member x.Print(printer: Printer) = printer.PrintStatements(x.Body) @@ -490,11 +504,7 @@ type Alias = AsName: Identifier option } - static member Create(name, asname) = - { - Name = name - AsName = asname - } + static member Create(name, asname) = { Name = name; AsName = asname } interface IPrint with member x.Print(printer) = @@ -524,14 +534,16 @@ type ExceptHandler = Body = defaultArg body [] Loc = loc } + interface IPrint with member x.Print(printer) = - printer.Print("except ", ?loc=x.Loc) + printer.Print("except ", ?loc = x.Loc) printer.PrintOptional(x.Type |> Option.map (fun t -> t :> IPrint)) printer.PrintOptional(" as ", x.Name |> Option.map (fun t -> t :> IPrint)) printer.Print(":") + match x.Body with - | [] -> printer.PrintBlock([Pass]) + | [] -> printer.PrintBlock([ Pass ]) | _ -> printer.PrintBlock(x.Body) @@ -546,24 +558,28 @@ type Try = Loc: SourceLocation option } - static member Create(body, ?handlers, ?orElse, ?finalBody, ?loc) : Statement = + static member Create(body, ?handlers, ?orElse, ?finalBody, ?loc): Statement = { Body = body Handlers = defaultArg handlers [] OrElse = defaultArg orElse [] FinalBody = defaultArg finalBody [] Loc = loc - } |> Try + } + |> Try interface IPrint with member x.Print(printer) = - printer.Print("try: ", ?loc=x.Loc) + printer.Print("try: ", ?loc = x.Loc) printer.PrintBlock(x.Body) + for handler in x.Handlers do printer.Print(handler) + if x.OrElse.Length > 0 then printer.Print("else: ") printer.PrintBlock(x.OrElse) + if x.FinalBody.Length > 0 then printer.Print("finally: ") printer.PrintBlock(x.FinalBody) @@ -704,12 +720,13 @@ type Assign = TypeComment: string option } - static member Create(targets, value, ?typeComment) : Statement = + static member Create(targets, value, ?typeComment): Statement = { Targets = targets Value = value TypeComment = typeComment - } |> Assign + } + |> Assign interface IPrint with member x.Print(printer) = @@ -741,10 +758,7 @@ type Expr = Value: Expression } - static member Create(value) : Statement = - { - Value = value - } |> Expr + static member Create(value): Statement = { Value = value } |> Expr interface IPrint with member x.Print(printer) = printer.Print(x.Value) @@ -852,12 +866,13 @@ type While = Else: Statement list } - static member Create(test, body, ?orelse) : Statement = + static member Create(test, body, ?orelse): Statement = { Test = test Body = body Else = defaultArg orelse [] - } |> While + } + |> While interface IPrint with member x.Print(printer: Printer) = @@ -914,15 +929,17 @@ type ClassDef = DecoratorList: Expression list Loc: SourceLocation option } - static member Create(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc) : Statement = + + static member Create(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc): Statement = { - Name= name + Name = name Bases = defaultArg bases [] Keyword = defaultArg keywords [] Body = defaultArg body [] DecoratorList = defaultArg decoratorList [] Loc = loc - } |> ClassDef + } + |> ClassDef interface IPrint with member x.Print(printer) = @@ -982,15 +999,22 @@ type If = Else: Statement list } - static member Create(test, body, ?orelse) : Statement = + static member Create(test, body, ?orelse): Statement = { Test = test Body = body Else = defaultArg orelse [] - } |> If + } + |> If interface IPrint with - member _.Print(printer) = printer.Print("(If)") + member x.Print(printer) = + printer.Print("if ") + printer.Print(x.Test) + printer.Print(":") + printer.PrintBlock(x.Body) + if not (List.isEmpty x.Else) then + printer.PrintBlock(x.Else) /// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone /// raise. cause is the optional part for y in raise x from y. @@ -1009,11 +1033,8 @@ type Raise = Exception: Expression Cause: Expression option } - static member Create(exc, ?cause) : Statement = - { - Exception = exc - Cause = cause - } |> Raise + + static member Create(exc, ?cause): Statement = { Exception = exc; Cause = cause } |> Raise interface IPrint with member _.Print(printer) = printer.Print("(Raise)") @@ -1037,7 +1058,7 @@ type FunctionDef = TypeComment: string option } - static member Create(name, args, body, ?decoratorList, ?returns, ?typeComment) : Statement= + static member Create(name, args, body, ?decoratorList, ?returns, ?typeComment): Statement = { Name = name Args = args @@ -1045,11 +1066,12 @@ type FunctionDef = DecoratorList = defaultArg decoratorList [] Returns = returns TypeComment = typeComment - } |> FunctionDef + } + |> FunctionDef interface IPrint with member x.Print(printer: Printer) = - printer.PrintFunction(Some x.Name, x.Args, x.Body, x.Returns, isDeclaration=true) + printer.PrintFunction(Some x.Name, x.Args, x.Body, x.Returns, isDeclaration = true) printer.PrintNewLine() /// global and nonlocal statements. names is a list of raw strings. @@ -1071,10 +1093,7 @@ type Global = Names: Identifier list } - static member Create(names) = - { - Names = names - } + static member Create(names) = { Names = names } interface IPrint with member x.Print(printer) = printer.Print("(Global)") @@ -1097,10 +1116,7 @@ type NonLocal = Names: Identifier list } - static member Create(names) = - { - Names = names - } + static member Create(names) = { Names = names } interface IPrint with member _.Print(printer: Printer) = printer.Print("(NonLocal)") @@ -1156,8 +1172,7 @@ type Import = Names: Alias list } - static member Create(names) : Statement = - Import { Names = names } + static member Create(names): Statement = Import { Names = names } interface IPrint with member _.Print(printer) = printer.Print("(Import)") @@ -1186,7 +1201,7 @@ type ImportFrom = Level: int option } - static member Create(``module``, names, ?level) : Statement = + static member Create(``module``, names, ?level): Statement = { Module = ``module`` Names = names @@ -1196,9 +1211,7 @@ type ImportFrom = interface IPrint with member x.Print(printer: Printer) = - let (Identifier path) = - x.Module - |> Option.defaultValue (Identifier ".") + let (Identifier path) = x.Module |> Option.defaultValue (Identifier ".") printer.Print("from ") printer.Print(printer.MakeImportPath(path)) @@ -1207,7 +1220,9 @@ type ImportFrom = if not (List.isEmpty x.Names) then if List.length x.Names > 1 then printer.Print("(") + printer.PrintCommaSeparatedList(x.Names |> List.map (fun x -> x :> IPrint)) + if List.length x.Names > 1 then printer.Print(")") @@ -1226,13 +1241,12 @@ type Return = Value: Expression option } - static member Create(?value) : Statement = - Return { Value = value } + static member Create(?value): Statement = Return { Value = value } interface IPrint with member this.Print(printer) = printer.Print("return ") - printer.PrintOptional(this.Value |> Option.map (fun x -> x :> IPrint )) + printer.PrintOptional(this.Value |> Option.map (fun x -> x :> IPrint)) //#endregion @@ -1257,12 +1271,13 @@ type Attribute = } - static member Create(value, attr, ctx) : Expression = + static member Create(value, attr, ctx): Expression = { Value = value Attr = attr Ctx = ctx - } |> Attribute + } + |> Attribute interface IPrint with member this.Print(printer) = @@ -1295,12 +1310,13 @@ type Subscript = Ctx: ExpressionContext } - static member Create(value, slice, ctx) : Expression= + static member Create(value, slice, ctx): Expression = { Value = value Slice = slice Ctx = ctx - } |> Subscript + } + |> Subscript interface IPrint with member this.Print(printer: Printer) = @@ -1315,12 +1331,14 @@ type BinOp = Right: Expression Operator: Operator } - static member Create(left, op, right) : Expression = + + static member Create(left, op, right): Expression = { Left = left Right = right Operator = op - } |> BinOp + } + |> BinOp interface IPrint with member this.Print(printer) = printer.PrintOperation(this.Left, this.Operator, this.Right) @@ -1332,12 +1350,13 @@ type Compare = Ops: ComparisonOperator list } - static member Create(left, ops, comparators) : Expression = + static member Create(left, ops, comparators): Expression = { Left = left Comparators = comparators Ops = ops - } |> Compare + } + |> Compare interface IPrint with member x.Print(printer) = @@ -1356,12 +1375,13 @@ type UnaryOp = Loc: SourceLocation option } - static member Create(op, operand, ?loc) : Expression = + static member Create(op, operand, ?loc): Expression = { Op = op Operand = operand Loc = loc - } |> UnaryOp + } + |> UnaryOp interface IPrint with override x.Print(printer) = @@ -1389,10 +1409,7 @@ type Constant = Value: obj } - static member Create(value: obj) : Expression = - { - Value = value - } |> Constant + static member Create(value: obj): Expression = { Value = value } |> Constant interface IPrint with member x.Print(printer) = @@ -1401,9 +1418,7 @@ type Constant = printer.Print("\"") printer.Print(string x.Value) printer.Print("\"") - | _ -> - printfn "*********** Value: %A" x.Value - printer.Print(string x.Value) + | _ -> printer.Print(string x.Value) /// Node representing a single formatting field in an f-string. If the string contains a single formatting field and /// nothing else the node can be isolated otherwise it appears in JoinedStr. @@ -1465,12 +1480,13 @@ type Call = Keywords: Keyword list } - static member Create(func, ?args, ?kw) : Expression = + static member Create(func, ?args, ?kw): Expression = { Func = func Args = defaultArg args [] Keywords = defaultArg kw [] - } |> Call + } + |> Call interface IPrint with member x.Print(printer) = @@ -1486,11 +1502,12 @@ type Emit = Args: Expression list } - static member Create(value, ?args) : Expression = + static member Create(value, ?args): Expression = { Value = value Args = defaultArg args [] - } |> Emit + } + |> Emit interface IPrint with member x.Print(printer) = @@ -1499,17 +1516,25 @@ type Emit = let printSegment (printer: Printer) (value: string) segmentStart segmentEnd = let segmentLength = segmentEnd - segmentStart + if segmentLength > 0 then let segment = value.Substring(segmentStart, segmentLength) - let subSegments = System.Text.RegularExpressions.Regex.Split(segment, @"\r?\n") + + let subSegments = + System.Text.RegularExpressions.Regex.Split(segment, @"\r?\n") + for i = 1 to subSegments.Length do let subSegment = // Remove whitespace in front of new lines, // indent will be automatically applied - if printer.Column = 0 then subSegments.[i - 1].TrimStart() - else subSegments.[i - 1] + if printer.Column = 0 then + subSegments.[i - 1].TrimStart() + else + subSegments.[i - 1] + if subSegment.Length > 0 then printer.Print(subSegment) + if i < subSegments.Length then printer.PrintNewLine() @@ -1518,37 +1543,52 @@ type Emit = // https://fable.io/docs/communicate/js-from-fable.html#Emit-when-F-is-not-enough let value = x.Value - |> replace @"\$(\d+)\.\.\." (fun m -> - let rep = ResizeArray() - let i = int m.Groups.[1].Value - for j = i to x.Args.Length - 1 do - rep.Add("$" + string j) - String.concat ", " rep) - - |> replace @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" (fun m -> - let i = int m.Groups.[1].Value - match x.Args.[i] with - | Constant(c) -> m.Groups.[2].Value - | _ -> m.Groups.[3].Value) - - |> replace @"\{\{([^\}]*\$(\d+).*?)\}\}" (fun m -> - let i = int m.Groups.[2].Value - match List.tryItem i x.Args with - | Some _ -> m.Groups.[1].Value - | None -> "") - - let matches = System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+") + |> replace + @"\$(\d+)\.\.\." + (fun m -> + let rep = ResizeArray() + let i = int m.Groups.[1].Value + + for j = i to x.Args.Length - 1 do + rep.Add("$" + string j) + + String.concat ", " rep) + + |> replace + @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" + (fun m -> + let i = int m.Groups.[1].Value + + match x.Args.[i] with + | Constant (c) -> m.Groups.[2].Value + | _ -> m.Groups.[3].Value) + + |> replace + @"\{\{([^\}]*\$(\d+).*?)\}\}" + (fun m -> + let i = int m.Groups.[2].Value + + match List.tryItem i x.Args with + | Some _ -> m.Groups.[1].Value + | None -> "") + + let matches = + System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+") + if matches.Count > 0 then for i = 0 to matches.Count - 1 do let m = matches.[i] let segmentStart = - if i > 0 then matches.[i-1].Index + matches.[i-1].Length - else 0 + if i > 0 then + matches.[i - 1].Index + matches.[i - 1].Length + else + 0 printSegment printer value segmentStart m.Index let argIndex = int m.Value.[1..] + match List.tryItem argIndex x.Args with | Some e -> printer.ComplexExpressionWithParens(e) | None -> printer.Print("None") @@ -1558,6 +1598,37 @@ type Emit = else printSegment printer value 0 value.Length +/// An expression such as a if b else c. Each field holds a single node, so in the following example, all three are Name nodes. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('a if b else c', mode='eval'), indent=4)) +/// Expression( +/// body=IfExp( +/// test=Name(id='b', ctx=Load()), +/// body=Name(id='a', ctx=Load()), +/// orelse=Name(id='c', ctx=Load()))) +/// ``` +type IfExp = + { + Test: Expression + Body: Expression + OrElse: Expression + } + + static member Create(test, body, orElse) : Expression = + { + Test = test + Body = body + OrElse = orElse + } |> IfExp + + interface IPrint with + member x.Print(printer: Printer) = + printer.Print(x.Body) + printer.Print(" if ") + printer.Print(x.Test) + printer.Print(" else ") + printer.Print(x.OrElse) /// lambda is a minimal function definition that can be used inside an expression. Unlike FunctionDef, body holds a /// single node. @@ -1585,11 +1656,7 @@ type Lambda = Body: Statement list } - static member Create(args, body) : Expression = - { - Args = args - Body = body - } |> Lambda + static member Create(args, body): Expression = { Args = args; Body = body } |> Lambda interface IPrint with member x.Print(printer: Printer) = @@ -1624,11 +1691,7 @@ type Tuple = Loc: SourceLocation option } - static member Create(elts, ?loc) : Expression = - { - Elements = elts - Loc = loc - } |> Tuple + static member Create(elts, ?loc): Expression = { Elements = elts; Loc = loc } |> Tuple interface IPrint with member x.Print(printer: Printer) = @@ -1658,10 +1721,7 @@ type List = Elements: Expression list } - static member Create(elts) = - { - Elements = elts - } + static member Create(elts) = { Elements = elts } interface IPrint with member _.Print(printer) = printer.Print("(List)") @@ -1677,7 +1737,7 @@ type List = /// Constant(value=2), /// Constant(value=3)])) /// ``` -type Set(elts) = +type Set (elts) = member _.Elements: Expression list = elts interface IPrint with @@ -1706,11 +1766,7 @@ type Dict = Values: Expression list } - static member Create(keys, values) : Expression = - { - Keys = keys - Values = values - } |> Dict + static member Create(keys, values): Expression = { Keys = keys; Values = values } |> Dict interface IPrint with member x.Print(printer: Printer) = @@ -1718,13 +1774,17 @@ type Dict = printer.PrintNewLine() printer.PushIndentation() - let nodes = List.zip x.Keys x.Values |> List.mapi (fun i n -> (i, n)) + let nodes = + List.zip x.Keys x.Values + |> List.mapi (fun i n -> (i, n)) + for i, (key, value) in nodes do printer.Print("\"") printer.Print(key) printer.Print("\"") printer.Print(": ") printer.Print(value) + if i < nodes.Length - 1 then printer.Print(",") printer.PrintNewLine() @@ -1740,11 +1800,7 @@ type Name = Context: ExpressionContext } - static member Create(id, ctx) : Expression = - { - Id = id - Context = ctx - } |> Name + static member Create(id, ctx): Expression = { Id = id; Context = ctx } |> Name interface IPrint with override x.Print(printer) = From 183dd84ea0e8954710b024ebe601fa170c7d59ba Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 22 Jan 2021 00:12:47 +0100 Subject: [PATCH 037/145] Fix build script. - Remove unused code --- build.fsx | 565 +++++++++++++++----------- src/Fable.Transforms/Python/Python.fs | 66 --- 2 files changed, 337 insertions(+), 294 deletions(-) diff --git a/build.fsx b/build.fsx index 9bb125d7c0..5d376875ad 100644 --- a/build.fsx +++ b/build.fsx @@ -7,6 +7,7 @@ open System.Text.RegularExpressions // Appveyor artifact let FABLE_BRANCH = "master" let APPVEYOR_REPL_ARTIFACT_URL_PARAMS = "?branch=" + FABLE_BRANCH //+ "&pr=false" + let APPVEYOR_REPL_ARTIFACT_URL = "https://ci.appveyor.com/api/projects/fable-compiler/Fable/artifacts/src/fable-standalone/fable-standalone.zip" + APPVEYOR_REPL_ARTIFACT_URL_PARAMS @@ -23,18 +24,19 @@ module Util = removeDirRecursive dir let updateVersionInFableTransforms version = - let filePath = "src/Fable.Transforms/Global/Compiler.fs" + let filePath = + "src/Fable.Transforms/Global/Compiler.fs" // printfn "VERSION %s" version Regex.Replace( readFile filePath, @"let \[] VERSION = "".*?""", - $"let [] VERSION = \"{version}\"") + $"let [] VERSION = \"{version}\"" + ) |> writeFile filePath let updatePkgVersionInFsproj projFile version = readFile projFile - |> replaceRegex Publish.NUGET_PACKAGE_VERSION (fun m -> - m.Groups.[1].Value + version + m.Groups.[3].Value) + |> replaceRegex Publish.NUGET_PACKAGE_VERSION (fun m -> m.Groups.[1].Value + version + m.Groups.[3].Value) |> writeFile projFile let runTSLint projectDir = @@ -44,28 +46,46 @@ module Util = run ("npm run tsc -- --project " + projectDir) let runFableWithArgs projectDir args = - run ("dotnet run -c Release -p src/Fable.Cli -- " + projectDir + " " + String.concat " " args) + run ( + "dotnet run -c Release -p src/Fable.Cli -- " + + projectDir + + " " + + String.concat " " args + ) let runFableWithArgsAsync projectDir args = - runAsync ("dotnet run -c Release -p src/Fable.Cli -- " + projectDir + " " + String.concat " " args) + runAsync ( + "dotnet run -c Release -p src/Fable.Cli -- " + + projectDir + + " " + + String.concat " " args + ) let runNpx command args = run ("npx " + command + " " + (String.concat " " args)) let runNpmScript script args = - run ("npm run " + script + " -- " + (String.concat " " args)) + run ( + "npm run " + + script + + " -- " + + (String.concat " " args) + ) let runNpmScriptAsync script args = - runAsync ("npm run " + script + " -- " + (String.concat " " args)) + runAsync ( + "npm run " + + script + + " -- " + + (String.concat " " args) + ) - let runFable projectDir = - runFableWithArgs projectDir [] + let runFable projectDir = runFableWithArgs projectDir [] let runMocha testDir = - runNpmScript "mocha" [$"{testDir} -r esm --reporter dot -t 10000"] + runNpmScript "mocha" [ $"{testDir} -r esm --reporter dot -t 10000" ] - let resolveDir dir = - __SOURCE_DIRECTORY__ dir + let resolveDir dir = __SOURCE_DIRECTORY__ dir open Util @@ -79,37 +99,50 @@ module Unused = // let concurrently(commands: string[]): unit = importDefault "concurrently" let downloadAndExtractTo (url: string) (targetDir: string) = - sprintf "npx download --extract --out %s \"%s\"" targetDir url |> run + sprintf "npx download --extract --out %s \"%s\"" targetDir url + |> run - let downloadStandalone() = + let downloadStandalone () = let targetDir = "src/fable-standalone/dist" - cleanDirs [targetDir] + cleanDirs [ targetDir ] downloadAndExtractTo APPVEYOR_REPL_ARTIFACT_URL targetDir - let coverage() = + let coverage () = // report converter // https://github.com/danielpalme/ReportGenerator // dotnet tool install dotnet-reportgenerator-globaltool --tool-path tools - if not (pathExists "./bin/tools/reportgenerator") && not (pathExists "./bin/tools/reportgenerator.exe") then + if not (pathExists "./bin/tools/reportgenerator") + && not (pathExists "./bin/tools/reportgenerator.exe") then runInDir "." "dotnet tool install dotnet-reportgenerator-globaltool --tool-path bin/tools" + let reportGen = - if pathExists "./bin/tools/reportgenerator" then "bin/tools/reportgenerator" - else "bin\\tools\\reportgenerator.exe" + if pathExists "./bin/tools/reportgenerator" then + "bin/tools/reportgenerator" + else + "bin\\tools\\reportgenerator.exe" // if not (pathExists "build/fable-library") then // buildLibrary() - cleanDirs ["build/tests"] + cleanDirs [ "build/tests" ] runFable "tests" // JS run "npx nyc mocha build/tests --require source-map-support/register --reporter dot -t 10000" - runInDir "." (reportGen + " \"-reports:build/coverage/nyc/lcov.info\" -reporttypes:Html \"-targetdir:build/coverage/nyc/html\" ") + + runInDir + "." + (reportGen + + " \"-reports:build/coverage/nyc/lcov.info\" -reporttypes:Html \"-targetdir:build/coverage/nyc/html\" ") // .NET //runInDir "tests/Main" "dotnet build /t:Collect_Coverage" - cleanDirs ["build/coverage/netcoreapp2.0/out"] - runInDir "." (reportGen + " \"-reports:build/coverage/netcoreapp2.0/coverage.xml\" -reporttypes:Html \"-targetdir:build/coverage/netcoreapp2.0/html\" ") + cleanDirs [ "build/coverage/netcoreapp2.0/out" ] + + runInDir + "." + (reportGen + + " \"-reports:build/coverage/netcoreapp2.0/coverage.xml\" -reporttypes:Html \"-targetdir:build/coverage/netcoreapp2.0/html\" ") // TARGETS --------------------------- @@ -118,53 +151,54 @@ let buildLibraryWithOptions (opts: {| watch: bool |}) = let projectDir = baseDir "src/fable-library" let buildDir = baseDir "build/fable-library" - let fableOpts = [ - "--outDir " + buildDir - "--fableLib " + buildDir - "--exclude Fable.Core" - "--define FX_NO_BIGINT" - "--define FABLE_LIBRARY" - if opts.watch then "--watch" - ] - - cleanDirs [buildDir] + + let fableOpts = + [ "--outDir " + buildDir + "--fableLib " + buildDir + "--exclude Fable.Core" + "--define FX_NO_BIGINT" + "--define FABLE_LIBRARY" + if opts.watch then "--watch" ] + + cleanDirs [ buildDir ] runInDir baseDir "npm install" + if opts.watch then - Async.Parallel [ - runNpmScriptAsync "tsc" [ - "--project " + projectDir - "--watch" - ] - runFableWithArgsAsync projectDir fableOpts - ] |> runAsyncWorkflow + Async.Parallel [ runNpmScriptAsync "tsc" [ "--project " + projectDir; "--watch" ] + runFableWithArgsAsync projectDir fableOpts ] + |> runAsyncWorkflow else runTSLint projectDir runTypeScript projectDir runFableWithArgs projectDir fableOpts -let buildLibrary() = buildLibraryWithOptions {| watch = false |} -let watchLibrary() = buildLibraryWithOptions {| watch = true |} +let buildLibrary () = + buildLibraryWithOptions {| watch = false |} + +let watchLibrary () = + buildLibraryWithOptions {| watch = true |} -let buildLibraryIfNotExists() = +let buildLibraryIfNotExists () = let baseDir = __SOURCE_DIRECTORY__ + if not (pathExists (baseDir "build/fable-library")) then - buildLibrary() + buildLibrary () -let buildLibraryTs() = +let buildLibraryTs () = let projectDir = "src/fable-library" let buildDirTs = "build/fable-library-ts" let buildDirJs = "build/fable-library-js" - cleanDirs [buildDirTs; buildDirJs] + cleanDirs [ buildDirTs; buildDirJs ] - runFableWithArgs projectDir [ - "--outDir " + buildDirTs - "--fableLib " + buildDirTs - "--typescript" - "--exclude Fable.Core" - "--define FX_NO_BIGINT" - "--define FABLE_LIBRARY" - ] + runFableWithArgs + projectDir + [ "--outDir " + buildDirTs + "--fableLib " + buildDirTs + "--typescript" + "--exclude Fable.Core" + "--define FX_NO_BIGINT" + "--define FABLE_LIBRARY" ] // TODO: cleanDirs [buildDirTs "fable-library"] // TODO: copy *.ts/*.js from projectDir to buildDir runInDir buildDirTs "npm run tsc -- --init --target es2020 --module es2020 --allowJs" @@ -172,27 +206,30 @@ let buildLibraryTs() = // Like testJs() but doesn't create bundles/packages for fable-standalone & friends // Mainly intended for CI -let testJsFast() = - runFableWithArgs "src/fable-standalone/src" [ - "--forcePkgs" - ] +let testJsFast () = + runFableWithArgs "src/fable-standalone/src" [ "--forcePkgs" ] - runFableWithArgs "src/fable-compiler-js/src" [ - "--exclude Fable.Core" - "--define LOCAL_TEST" - ] + runFableWithArgs + "src/fable-compiler-js/src" + [ "--exclude Fable.Core" + "--define LOCAL_TEST" ] let fableJs = "./src/fable-compiler-js/src/app.fs.js" let testProj = "tests/Main/Fable.Tests.fsproj" let buildDir = "build/tests-js" - run $"node --eval \"require('esm')(module)('{fableJs}')\" {fableJs} {testProj} {buildDir}" + run $"node --eval "require('esm')(module)('{fableJs}')" {fableJs} {testProj} {buildDir}" runMocha buildDir let buildStandalone (opts: {| minify: bool; watch: bool |}) = - buildLibraryIfNotExists() + buildLibraryIfNotExists () - printfn "Building standalone%s..." (if opts.minify then "" else " (no minification)") + printfn + "Building standalone%s..." + (if opts.minify then + "" + else + " (no minification)") let projectDir = "src/fable-standalone/src" let libraryDir = "build/fable-library" @@ -203,99 +240,110 @@ let buildStandalone (opts: {| minify: bool; watch: bool |}) = match opts.watch, opts.minify with | true, _ -> match args with - | _::rollupTarget::_ -> rollupTarget - | _ -> failwith "Pass the bundle output, e.g.: npm run build watch-standalone ../repl3/public/js/repl/bundle.min.js" + | _ :: rollupTarget :: _ -> rollupTarget + | _ -> + failwith + "Pass the bundle output, e.g.: npm run build watch-standalone ../repl3/public/js/repl/bundle.min.js" | false, true -> buildDir "bundle.js" | false, false -> distDir "bundle.min.js" - let rollupArgs = [ - buildDir "bundle/Main.js" - "-o " + rollupTarget - "--format umd" - "--name __FABLE_STANDALONE__" - ] + let rollupArgs = + [ buildDir "bundle/Main.js" + "-o " + rollupTarget + "--format umd" + "--name __FABLE_STANDALONE__" ] // cleanup if not opts.watch then - cleanDirs [buildDir; distDir] + cleanDirs [ buildDir; distDir ] makeDirRecursive distDir // build standalone bundle - runFableWithArgs projectDir [ - "--outDir " + buildDir "bundle" - if opts.watch then - "--watch" - "--run rollup" - yield! rollupArgs - "--watch" - ] + runFableWithArgs + projectDir + [ "--outDir " + buildDir "bundle" + if opts.watch then + "--watch" + "--run rollup" + yield! rollupArgs + "--watch" ] // build standalone worker - runFableWithArgs (projectDir + "/Worker") [ - "--outDir " + buildDir + "/worker" - ] + runFableWithArgs (projectDir + "/Worker") [ "--outDir " + buildDir + "/worker" ] // make standalone bundle dist runNpmScript "rollup" rollupArgs + if opts.minify then - runNpmScript "terser" [ - buildDir "bundle.js" - "-o " + distDir "bundle.min.js" - "--mangle" - "--compress" - ] + runNpmScript + "terser" + [ buildDir "bundle.js" + "-o " + distDir "bundle.min.js" + "--mangle" + "--compress" ] // make standalone worker dist - runNpmScript "rollup" [$"{buildDir}/worker/Worker.js -o {buildDir}/worker.js --format iife"] + runNpmScript "rollup" [ $"{buildDir}/worker/Worker.js -o {buildDir}/worker.js --format iife" ] // runNpx "webpack" [sprintf "--entry ./%s/worker.js --output ./%s/worker.min.js --config ./%s/../worker.config.js" buildDir distDir projectDir] - runNpmScript "terser" [$"{buildDir}/worker.js -o {distDir}/worker.min.js --mangle --compress"] + runNpmScript "terser" [ $"{buildDir}/worker.js -o {distDir}/worker.min.js --mangle --compress" ] // print bundle size - fileSizeInBytes (distDir "bundle.min.js") / 1000. |> printfn "Bundle size: %fKB" - fileSizeInBytes (distDir "worker.min.js") / 1000. |> printfn "Worker size: %fKB" + fileSizeInBytes (distDir "bundle.min.js") + / 1000. + |> printfn "Bundle size: %fKB" + + fileSizeInBytes (distDir "worker.min.js") + / 1000. + |> printfn "Worker size: %fKB" // Put fable-library files next to bundle let libraryTarget = distDir "fable-library" copyDirRecursive libraryDir libraryTarget - // These files will be used in the browser, so make sure the import paths include .js extension - // let reg = Regex(@"^import (.*"".*)("".*)$", RegexOptions.Multiline) - // getFullPathsInDirectoryRecursively libraryTarget - // |> Array.filter (fun file -> file.EndsWith(".js")) - // |> Array.iter (fun file -> - // reg.Replace(readFile file, fun m -> - // let fst = m.Groups.[1].Value - // if fst.EndsWith(".js") then m.Value - // else sprintf "import %s.js%s" fst m.Groups.[2].Value) - // |> writeFile file) - - // Bump version - // let compilerVersion = Publish.loadReleaseVersion "src/fable-compiler" - // let standaloneVersion = Publish.loadNpmVersion projectDir - // let (comMajor, comMinor, _, comPrerelease) = Publish.splitVersion compilerVersion - // let (staMajor, staMinor, staPatch, _) = Publish.splitVersion standaloneVersion - // Publish.bumpNpmVersion projectDir - // (if comMajor > staMajor || comMinor > staMinor then compilerVersion - // else sprintf "%i.%i.%i%s" staMajor staMinor (staPatch + 1) comPrerelease) - -let buildCompilerJs(minify: bool) = +// These files will be used in the browser, so make sure the import paths include .js extension +// let reg = Regex(@"^import (.*"".*)("".*)$", RegexOptions.Multiline) +// getFullPathsInDirectoryRecursively libraryTarget +// |> Array.filter (fun file -> file.EndsWith(".js")) +// |> Array.iter (fun file -> +// reg.Replace(readFile file, fun m -> +// let fst = m.Groups.[1].Value +// if fst.EndsWith(".js") then m.Value +// else sprintf "import %s.js%s" fst m.Groups.[2].Value) +// |> writeFile file) + +// Bump version +// let compilerVersion = Publish.loadReleaseVersion "src/fable-compiler" +// let standaloneVersion = Publish.loadNpmVersion projectDir +// let (comMajor, comMinor, _, comPrerelease) = Publish.splitVersion compilerVersion +// let (staMajor, staMinor, staPatch, _) = Publish.splitVersion standaloneVersion +// Publish.bumpNpmVersion projectDir +// (if comMajor > staMajor || comMinor > staMinor then compilerVersion +// else sprintf "%i.%i.%i%s" staMajor staMinor (staPatch + 1) comPrerelease) + +let buildCompilerJs (minify: bool) = let projectDir = "src/fable-compiler-js/src" let buildDir = "build/fable-compiler-js" let distDir = "src/fable-compiler-js/dist" if not (pathExists "build/fable-standalone") then - buildStandalone {|minify=minify; watch=false|} + buildStandalone {| minify = minify; watch = false |} - cleanDirs [buildDir; distDir] + cleanDirs [ buildDir; distDir ] makeDirRecursive distDir - runFableWithArgs projectDir [ - "--outDir " + buildDir - "--exclude Fable.Core" - ] + runFableWithArgs + projectDir + [ "--outDir " + buildDir + "--exclude Fable.Core" ] + + let rollupTarget = + if minify then + distDir "app.js" + else + distDir "app.min.js" - let rollupTarget = if minify then distDir "app.js" else distDir "app.min.js" run $"npx rollup {buildDir}/app.js -o {rollupTarget} --format umd --name Fable" + if minify then run $"npx terser {distDir}/app.js -o {distDir}/app.min.js --mangle --compress" @@ -304,14 +352,14 @@ let buildCompilerJs(minify: bool) = // Copy fable-metadata copyDirRecursive ("src/fable-metadata/lib") (distDir "fable-metadata") -let testJs(minify) = +let testJs (minify) = let fableDir = "src/fable-compiler-js" let buildDir = "build/tests-js" if not (pathExists "build/fable-compiler-js") then - buildCompilerJs(minify) + buildCompilerJs (minify) - cleanDirs [buildDir] + cleanDirs [ buildDir ] // Link fable-compiler-js to local packages runInDir fableDir "npm link ../fable-metadata" @@ -329,71 +377,79 @@ let testJs(minify) = runInDir fableDir "npm unlink ../fable-metadata && cd ../fable-metadata && npm unlink" runInDir fableDir "npm unlink ../fable-standalone && cd ../fable-standalone && npm unlink" -let testReact() = +let testReact () = runFableWithArgs "tests/React" [] runInDir "tests/React" "npm i && npm test" -let testIntegration() = +let testIntegration () = runInDir "tests/Integration" "dotnet run -c Release" -let test() = - buildLibraryIfNotExists() +let test () = + buildLibraryIfNotExists () let projectDir = "tests/Main" let buildDir = "build/tests" - cleanDirs [buildDir] - runFableWithArgs projectDir [ - "--outDir " + buildDir - "--exclude Fable.Core" - ] + cleanDirs [ buildDir ] + + runFableWithArgs + projectDir + [ "--outDir " + buildDir + "--exclude Fable.Core" ] runMocha buildDir runInDir projectDir "dotnet run" - testReact() + testReact () - testIntegration() + testIntegration () if envVarOrNone "APPVEYOR" |> Option.isSome then - testJsFast() + testJsFast () let buildLocalPackageWith pkgDir pkgCommand fsproj action = - let version = "3.0.0-local-build-" + DateTime.Now.ToString("yyyyMMdd-HHmm") + let version = + "3.0.0-local-build-" + + DateTime.Now.ToString("yyyyMMdd-HHmm") + action version updatePkgVersionInFsproj fsproj version run $"dotnet pack {fsproj} -p:Pack=true -c Release -o {pkgDir}" // Return install command - $"""dotnet {pkgCommand} --version "{version}" --add-source {fullPath pkgDir}""" + $"dotnet {pkgCommand} --version "{version}" --add-source {fullPath pkgDir}" let buildLocalPackage pkgDir = - buildLocalPackageWith pkgDir + buildLocalPackageWith + pkgDir "tool install fable" - (resolveDir "src/Fable.Cli/Fable.Cli.fsproj") (fun version -> - buildLibrary() + (resolveDir "src/Fable.Cli/Fable.Cli.fsproj") + (fun version -> + buildLibrary () updateVersionInFableTransforms version) -let testRepos() = - let repos = [ - "https://github.com/fable-compiler/fable-promise:master", "npm i && npm test" - "https://github.com/alfonsogarciacaro/Thoth.Json:nagareyama", "./fake.sh build -t MochaTest" - "https://github.com/alfonsogarciacaro/FSharp.Control.AsyncSeq:nagareyama", "cd tests/fable && npm i && npm test" - "https://github.com/alfonsogarciacaro/Fable.Extras:nagareyama", "dotnet paket restore && npm i && npm test" - "https://github.com/alfonsogarciacaro/Fable.Jester:nagareyama", "npm i && npm test" - "https://github.com/Zaid-Ajaj/Fable.SimpleJson:master", "npm i && npm run test-nagareyama" - ] - - let testDir = tempPath() "fable-repos" +let testRepos () = + let repos = + [ "https://github.com/fable-compiler/fable-promise:master", "npm i && npm test" + "https://github.com/alfonsogarciacaro/Thoth.Json:nagareyama", "./fake.sh build -t MochaTest" + "https://github.com/alfonsogarciacaro/FSharp.Control.AsyncSeq:nagareyama", + "cd tests/fable && npm i && npm test" + "https://github.com/alfonsogarciacaro/Fable.Extras:nagareyama", "dotnet paket restore && npm i && npm test" + "https://github.com/alfonsogarciacaro/Fable.Jester:nagareyama", "npm i && npm test" + "https://github.com/Zaid-Ajaj/Fable.SimpleJson:master", "npm i && npm run test-nagareyama" ] + + let testDir = tempPath () "fable-repos" printfn $"Cloning repos to: {testDir}" - cleanDirs [testDir] + cleanDirs [ testDir ] makeDirRecursive testDir let pkgInstallCmd = buildLocalPackage (testDir "pkg") for (repo, command) in repos do - let url, branch = let i = repo.LastIndexOf(":") in repo.[..i-1], repo.[i+1..] + let url, branch = + let i = repo.LastIndexOf(":") in repo.[..i - 1], repo.[i + 1..] + let name = url.[url.LastIndexOf("/") + 1..] runInDir testDir $"git clone {url} {name}" let repoDir = testDir name @@ -403,66 +459,78 @@ let testRepos() = runInDir repoDir "dotnet tool restore" runInDir repoDir command -let githubRelease() = +let githubRelease () = match envVarOrNone "GITHUB_USER", envVarOrNone "GITHUB_TOKEN" with | Some user, Some token -> async { try let! version, notes = Publish.loadReleaseVersionAndNotes "src/Fable.Cli" // TODO: escape single quotes - let notes = notes |> Array.map (sprintf "'%s'") |> String.concat "," + let notes = + notes + |> Array.map (sprintf "'%s'") + |> String.concat "," + run $"git commit -am \"Release {version}\" && git push" - runSilent $""" + + runSilent + $""" node --eval "require('ghreleases').create({{ user: '{user}', token: '{token}', }}, 'fable-compiler', 'Fable', {{ tag_name: '{version}', name: '{version}', - body: [{notes}].join('\n'), -}}, (err, res) => {{ - if (err != null) {{ - console.error(err) - }} -}})" -""" + body: [{notes}].join('\n'),}}, (err, res) => {{ if (err != null) {{ console.error(err) }}}})"""" + printfn "Github release %s created successfully" version - with ex -> - printfn "Github release failed: %s" ex.Message - } |> runAsyncWorkflow + with ex -> printfn "Github release failed: %s" ex.Message + } + |> runAsyncWorkflow | _ -> failwith "Expecting GITHUB_USER and GITHUB_TOKEN enviromental variables" let copyFcsRepo sourceDir = let targetDir = "src/fcs-fable" - cleanDirs [targetDir] + cleanDirs [ targetDir ] copyDirRecursive (sourceDir "fcs/fcs-fable") targetDir + [ "src/fsharp" - ; "src/fsharp/absil" - ; "src/fsharp/ilx" - ; "src/fsharp/service" - ; "src/fsharp/symbols" - ; "src/fsharp/utils" - ] |> List.iter (fun path -> - copyDirNonRecursive (sourceDir path) (targetDir path)) + "src/fsharp/absil" + "src/fsharp/ilx" + "src/fsharp/service" + "src/fsharp/symbols" + "src/fsharp/utils" ] + |> List.iter (fun path -> copyDirNonRecursive (sourceDir path) (targetDir path)) + removeFile (targetDir ".gitignore") let projPath = (targetDir "fcs-fable.fsproj") let projText = readFile projPath + let projText = - Regex.Replace(projText, + Regex.Replace( + projText, @"(\$\(MSBuildProjectDirectory\)).*?(<\/FSharpSourcesRoot>)", - "$1/src$2") + "$1/src$2" + ) // let projText = // Regex.Replace(projText, // @"artifacts\/bin\/FSharp.Core\/Release\/netstandard2.0", // "lib/fcs") projText |> writeFile projPath -let syncFcsRepo() = +let syncFcsRepo () = // FAKE is giving lots of problems with the dotnet SDK version, ignore it let cheatWithDotnetSdkVersion dir f = let path = dir "build.fsx" let script = readFile path - Regex.Replace(script, @"let dotnetExePath =[\s\S]*DotNetCli\.InstallDotNetSDK", "let dotnetExePath = \"dotnet\" //DotNetCli.InstallDotNetSDK") |> writeFile path + + Regex.Replace( + script, + @"let dotnetExePath =[\s\S]*DotNetCli\.InstallDotNetSDK", + "let dotnetExePath = \"dotnet\" //DotNetCli.InstallDotNetSDK" + ) + |> writeFile path + f () runInDir dir "git reset --hard" @@ -472,40 +540,63 @@ let syncFcsRepo() = // service_slim runInDir FCS_REPO_LOCAL ("git checkout " + FCS_REPO_SERVICE_SLIM_BRANCH) runInDir FCS_REPO_LOCAL "git pull" - cheatWithDotnetSdkVersion (FCS_REPO_LOCAL "fcs") (fun () -> - runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "") - copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.dll") "../fable/lib/fcs/" - copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.xml") "../fable/lib/fcs/" - copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.dll") "../fable/lib/fcs/" - copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.xml") "../fable/lib/fcs/" + cheatWithDotnetSdkVersion (FCS_REPO_LOCAL "fcs") (fun () -> runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "") + + copyFile + (FCS_REPO_LOCAL + "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.dll") + "../fable/lib/fcs/" + + copyFile + (FCS_REPO_LOCAL + "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.xml") + "../fable/lib/fcs/" + + copyFile + (FCS_REPO_LOCAL + "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.dll") + "../fable/lib/fcs/" + + copyFile + (FCS_REPO_LOCAL + "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.xml") + "../fable/lib/fcs/" // fcs-fable runInDir FCS_REPO_LOCAL ("git checkout " + FCS_REPO_FABLE_BRANCH) runInDir FCS_REPO_LOCAL "git pull" - cheatWithDotnetSdkVersion (FCS_REPO_LOCAL "fcs") (fun () -> - runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "CodeGen.Fable") + + cheatWithDotnetSdkVersion + (FCS_REPO_LOCAL "fcs") + (fun () -> runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "CodeGen.Fable") + copyFcsRepo FCS_REPO_LOCAL let packages = - ["Fable.AST", doNothing - "Fable.Core", doNothing - "Fable.Cli", (fun () -> - Publish.loadReleaseVersion "src/Fable.Cli" |> updateVersionInFableTransforms - buildLibrary()) - "fable-metadata", doNothing - "fable-publish-utils", doNothing - "fable-standalone", fun () -> buildStandalone {|minify=true; watch=false|} - "fable-compiler-js", fun () -> buildCompilerJs true - ] + [ "Fable.AST", doNothing + "Fable.Core", doNothing + "Fable.Cli", + (fun () -> + Publish.loadReleaseVersion "src/Fable.Cli" + |> updateVersionInFableTransforms + + buildLibrary ()) + "fable-metadata", doNothing + "fable-publish-utils", doNothing + "fable-standalone", (fun () -> buildStandalone {| minify = true; watch = false |}) + "fable-compiler-js", (fun () -> buildCompilerJs true) ] let publishPackages restArgs = let packages = match List.tryHead restArgs with - | Some pkg -> packages |> List.filter (fun (name,_) -> name = pkg) + | Some pkg -> + packages + |> List.filter (fun (name, _) -> name = pkg) | None -> packages + for (pkg, buildAction) in packages do if System.Char.IsUpper pkg.[0] then - pushNuget ("src" pkg pkg + ".fsproj") ["Pack", "true"] buildAction + pushNuget ("src" pkg pkg + ".fsproj") [ "Pack", "true" ] buildAction else pushNpm ("src" pkg) buildAction @@ -519,44 +610,62 @@ match argsLower with // |> List.singleton |> quicktest // | "download-standalone"::_ -> downloadStandalone() // | "coverage"::_ -> coverage() -| "test"::_ -> test() -| "test-js"::_ -> testJs(minify) -| "test-js-fast"::_ -> testJsFast() -| "test-react"::_ -> testReact() -| "test-integration"::_ -> testIntegration() -| "quicktest"::_ -> - buildLibraryIfNotExists() +| "test" :: _ -> test () +| "test-js" :: _ -> testJs (minify) +| "test-js-fast" :: _ -> testJsFast () +| "test-react" :: _ -> testReact () +| "test-integration" :: _ -> testIntegration () +| "quicktest" :: _ -> + buildLibraryIfNotExists () run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --forcePkgs --python" +| "jupyter" :: _ -> + buildLibraryIfNotExists () + + run + "dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src --exclude Fable.Core --forcePkgs --python" -| "run"::_ -> - buildLibraryIfNotExists() +| "run" :: _ -> + buildLibraryIfNotExists () // Don't take it from pattern matching as that one uses lowered args let restArgs = args |> List.skip 1 |> String.concat " " - run $"""dotnet run -c Release -p {resolveDir "src/Fable.Cli"} -- {restArgs}""" + run $"dotnet run -c Release -p {resolveDir "src/Fable.Cli"} -- {restArgs}" + +| "package" :: _ -> + let pkgInstallCmd = + buildLocalPackage (resolveDir "temp/pkg") -| "package"::_ -> - let pkgInstallCmd = buildLocalPackage (resolveDir "temp/pkg") printfn $"\nPackage has been created, use the following command to install it:\n {pkgInstallCmd}\n" -| "package-core"::_ -> - let pkgInstallCmd = buildLocalPackageWith (resolveDir "temp/pkg") "add package Fable.Core" (resolveDir "src/Fable.Core/Fable.Core.fsproj") ignore +| "package-core" :: _ -> + let pkgInstallCmd = + buildLocalPackageWith + (resolveDir "temp/pkg") + "add package Fable.Core" + (resolveDir "src/Fable.Core/Fable.Core.fsproj") + ignore + printfn $"\nFable.Core package has been created, use the following command to install it:\n {pkgInstallCmd}\n" -| ("watch-library")::_ -> watchLibrary() -| ("fable-library"|"library")::_ -> buildLibrary() -| ("fable-library-ts"|"library-ts")::_ -> buildLibraryTs() -| ("fable-compiler-js"|"compiler-js")::_ -> buildCompilerJs(minify) -| ("fable-standalone"|"standalone")::_ -> buildStandalone {|minify=minify; watch=false|} -| "watch-standalone"::_ -> buildStandalone {|minify=false; watch=true|} -| "publish"::restArgs -> publishPackages restArgs -| "github-release"::_ -> +| ("watch-library") :: _ -> watchLibrary () +| ("fable-library" +| "library") :: _ -> buildLibrary () +| ("fable-library-ts" +| "library-ts") :: _ -> buildLibraryTs () +| ("fable-compiler-js" +| "compiler-js") :: _ -> buildCompilerJs (minify) +| ("fable-standalone" +| "standalone") :: _ -> buildStandalone {| minify = minify; watch = false |} +| "watch-standalone" :: _ -> buildStandalone {| minify = false; watch = true |} +| "publish" :: restArgs -> publishPackages restArgs +| "github-release" :: _ -> publishPackages [] githubRelease () -| "sync-fcs-repo"::_ -> syncFcsRepo() -| "copy-fcs-repo"::_ -> copyFcsRepo "../fsharp" -| "test-repos"::_ -> testRepos() +| "sync-fcs-repo" :: _ -> syncFcsRepo () +| "copy-fcs-repo" :: _ -> copyFcsRepo "../fsharp" +| "test-repos" :: _ -> testRepos () | _ -> - printfn """Please pass a target name. Examples: + printfn + """Please pass a target name. Examples: - Use `test` to run tests: dotnet fsi build.fsx test diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 8000b40c4f..7c3d5f6f88 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -110,10 +110,6 @@ module PrinterExtensions = member printer.PrintCommaSeparatedList(nodes: Expression list) = printer.PrintList(nodes, (fun p x -> p.SequenceExpressionWithParens(x)), (fun p -> p.Print(", "))) - // member printer.PrintCommaSeparatedArray(nodes: #Node array) = -// printer.PrintArray(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) - - member printer.PrintFunction ( id: Identifier option, @@ -131,63 +127,6 @@ module PrinterExtensions = printer.Print(":") printer.PrintBlock(body, skipNewLineAtEnd = true) - // let areEqualPassedAndAppliedArgs (passedArgs: Pattern[]) (appliedAgs: Expression[]) = -// Array.zip passedArgs appliedAgs -// |> Array.forall (function -// | (:? Identifier as p), (:? Identifier as a) -> p.Name = a.Name -// | _ -> false) - - // let isDeclaration = defaultArg isDeclaration false -// let isArrow = defaultArg isArrow false - - // printer.AddLocation(loc) - - // // Check if we can remove the function -// let skipExpr = -// match body.Body with -// | [|:? ReturnStatement as r|] when not isDeclaration -> -// match r.Argument with -// | :? CallExpression as c when parameters.Length = c.Arguments.Length -> -// // To be sure we're not running side effects when deleting the function, -// // check the callee is an identifier (accept non-computed member expressions too?) -// match c.Callee with -// | :? Identifier when areEqualPassedAndAppliedArgs parameters c.Arguments -> -// Some c.Callee -// | _ -> None -// | _ -> None -// | _ -> None - - // match skipExpr with -// | Some e -> e.Print(printer) -// | None -> -// if false then //isArrow then -// // Remove parens if we only have one argument? (and no annotation) -// printer.PrintOptional(typeParameters) -// printer.Print("lambda-inline ") -// printer.PrintCommaSeparatedArray(parameters) -// printer.PrintOptional(returnType) -// printer.Print(": ") -// match body.Body with -// | [|:? ReturnStatement as r |] -> -// match r.Argument with -// | :? ObjectExpression as e -> printer.WithParens(e) -// | :? MemberExpression as e -> -// match e.Object with -// | :? ObjectExpression -> e.Print(printer, objectWithParens=true) -// | _ -> e.Print(printer) -// | _ -> printer.ComplexExpressionWithParens(r.Argument) -// | _ -> printer.PrintBlock(body.Body, skipNewLineAtEnd=true) -// else -// printer.Print("def ") -// printer.PrintOptional(id) -// printer.PrintOptional(typeParameters) -// printer.Print("(") -// printer.PrintCommaSeparatedArray(parameters) -// printer.Print(")") -// printer.PrintOptional(returnType) -// printer.Print(":") -// printer.PrintBlock(body.Body, skipNewLineAtEnd=true) - member printer.WithParens(expr: Expression) = printer.Print("(") printer.Print(expr) @@ -203,11 +142,7 @@ module PrinterExtensions = printfn "Expr: %A" expr match expr with - // | :? Undefined - // | :? NullLiteral | Constant (_) -> printer.Print(expr) - // | :? BooleanLiteral - // | :? NumericLiteral | Name (_) -> printer.Print(expr) // | :? MemberExpression // | :? CallExpression @@ -418,7 +353,6 @@ type Identifier = | Identifier of string - interface IPrint with member this.Print(printer: Printer) = let (Identifier id) = this From 7e7913a081aef80d70252dc421d496b2610e5cf1 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 23 Jan 2021 10:18:20 +0100 Subject: [PATCH 038/145] Initail support for switch statements --- src/Fable.Transforms/Python/Babel2Python.fs | 59 ++++++++++++--------- src/Fable.Transforms/Python/Python.fs | 16 ++++++ 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index e6f1483d1f..248d5125ad 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -12,6 +12,7 @@ open Fable.AST.Python type ReturnStrategy = | Return | NoReturn + | NoBreak type ITailCallOpportunity = abstract Label: string @@ -107,10 +108,13 @@ module Helpers = module Util = let transformBody (returnStrategy: ReturnStrategy) (body: Statement list) = let body = - body |> List.choose Helpers.isProductiveStatement + body + |> List.choose Helpers.isProductiveStatement + |> List.filter (fun x -> not (returnStrategy = ReturnStrategy.NoBreak && x = Break)) match body, returnStrategy with | [], ReturnStrategy.Return -> [ Return.Create() ] + | [], ReturnStrategy.NoBreak | [], ReturnStrategy.NoReturn -> [ Pass ] | _ -> body @@ -293,13 +297,12 @@ module Util = let arguments = Arguments.Create(args = args) - match afe.Body.Body.Length with - | 1 -> + let stmts = afe.Body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. + if stmts.Length = 1 && (stmts.[0] :? Babel.ReturnStatement) then let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, afe.Body) - Lambda.Create(arguments, body), [] - | _ -> + else let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, afe.Body) @@ -432,6 +435,9 @@ module Util = IfExp.Create(test, body, orElse), stmts1 @ stmts2 @ stmts3 | :? Babel.NullLiteral -> Name.Create(Identifier("None"), ctx=Load), [] + | :? Babel.SequenceExpression as se -> + let exprs, stmts = se.Expressions |> List.ofArray |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) |> Helpers.unzipArgs + exprs.[0], stmts // FIXME | _ -> failwith $"Unhandled value: {expr}" /// Transform Babel expressions as Python statements. @@ -548,6 +554,29 @@ module Util = | _ -> [] [ Try.Create(body=body, handlers=handlers, ?finalBody=finalBody) ] + | :? Babel.SwitchStatement as ss -> + let value, stmts = com.TransformAsExpr(ctx, ss.Discriminant) + let rec caser (cases: Babel.SwitchCase list) : Statement list option = + match cases with + | [] -> None + | case :: cases -> + let body = + case.Consequent + |> List.ofArray + |> List.collect (fun x -> com.TransformAsStatements(ctx, ReturnStrategy.NoBreak, x)) + let expr = + match case.Test with + | None -> Constant.Create(true) + | Some test -> + let test, st = com.TransformAsExpr(ctx, test) + Compare.Create(left=value, ops=[Eq], comparators=[test]) + [ If.Create(test=expr, body=body, ?orelse=caser cases) ] |> Some + + let result = ss.Cases |> List.ofArray |> caser + match result with + | Some ifStmt -> stmts @ ifStmt + | None -> [] + | :? Babel.BreakStatement -> [ Break ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" /// Transform Babel program to Python module. @@ -623,26 +652,6 @@ module Compiler = addWarning com [] range msg member _.GetImportExpr(ctx, selector, path, r) = - let cachedName = path + "::" + selector - // match imports.TryGetValue(cachedName) with - // | true, i -> - // match i.LocalIdent with - // | Some localIdent -> upcast Babel.Identifier(localIdent) - // | None -> upcast Babel.NullLiteral () - // | false, _ -> - // let localId = getIdentForImport ctx path selector - // let i = - // { Selector = - // if selector = Naming.placeholder then - // "`importMember` must be assigned to a variable" - // |> addError com [] r; selector - // else selector - // Path = path - // LocalIdent = localId } - // imports.Add(cachedName, i) - // match localId with - // | Some localId -> upcast Babel.Identifier(localId) - // | None -> upcast Babel.NullLiteral () failwith "Not implemented" member _.GetAllImports() = diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 7c3d5f6f88..eaa9e151e3 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -948,6 +948,7 @@ type If = printer.Print(":") printer.PrintBlock(x.Body) if not (List.isEmpty x.Else) then + printer.Print("else: ") printer.PrintBlock(x.Else) /// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone @@ -1277,6 +1278,21 @@ type BinOp = interface IPrint with member this.Print(printer) = printer.PrintOperation(this.Left, this.Operator, this.Right) +/// A comparison of two or more values. left is the first value in the comparison, ops the list of operators, and +/// comparators the list of values after the first element in the comparison. +/// +/// ```py +/// >>> print(ast.dump(ast.parse('1 <= a < 10', mode='eval'), indent=4)) +/// Expression( +/// body=Compare( +/// left=Constant(value=1), +/// ops=[ +/// LtE(), +/// Lt()], +/// comparators=[ +/// Name(id='a', ctx=Load()), +/// Constant(value=10)])) +/// ````` type Compare = { Left: Expression From 52dc5f6eb73df9509f6a2b9613c6a8d7cec1ec02 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 23 Jan 2021 11:41:40 +0100 Subject: [PATCH 039/145] Merge --- src/Fable.Transforms/Python/Babel2Python.fs | 34 +++++++++------ src/Fable.Transforms/Python/Python.fs | 47 ++++++++++++++++++--- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 248d5125ad..a97f1aec45 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -106,16 +106,17 @@ module Helpers = | _ -> Some stmt module Util = - let transformBody (returnStrategy: ReturnStrategy) (body: Statement list) = + let rec transformBody (returnStrategy: ReturnStrategy) (body: Statement list) = let body = body |> List.choose Helpers.isProductiveStatement - |> List.filter (fun x -> not (returnStrategy = ReturnStrategy.NoBreak && x = Break)) match body, returnStrategy with | [], ReturnStrategy.Return -> [ Return.Create() ] | [], ReturnStrategy.NoBreak | [], ReturnStrategy.NoReturn -> [ Pass ] + | xs, ReturnStrategy.NoBreak -> + xs |> List.filter (fun x -> x <> Break) |> transformBody ReturnStrategy.NoReturn | _ -> body let transformAsImports (com: IPythonCompiler) (ctx: Context) (imp: Babel.ImportDeclaration): Statement list = @@ -556,7 +557,7 @@ module Util = [ Try.Create(body=body, handlers=handlers, ?finalBody=finalBody) ] | :? Babel.SwitchStatement as ss -> let value, stmts = com.TransformAsExpr(ctx, ss.Discriminant) - let rec caser (cases: Babel.SwitchCase list) : Statement list option = + let rec ifThenElse (fallThrough: Expression option) (cases: Babel.SwitchCase list) : Statement list option = match cases with | [] -> None | case :: cases -> @@ -564,15 +565,24 @@ module Util = case.Consequent |> List.ofArray |> List.collect (fun x -> com.TransformAsStatements(ctx, ReturnStrategy.NoBreak, x)) - let expr = - match case.Test with - | None -> Constant.Create(true) - | Some test -> - let test, st = com.TransformAsExpr(ctx, test) - Compare.Create(left=value, ops=[Eq], comparators=[test]) - [ If.Create(test=expr, body=body, ?orelse=caser cases) ] |> Some - - let result = ss.Cases |> List.ofArray |> caser + match case.Test with + | None -> + body |> Some + | Some test -> + let test, st = com.TransformAsExpr(ctx, test) + let expr = Compare.Create(left=value, ops=[Eq], comparators=[test]) + let test = + match fallThrough with + | Some ft -> + BoolOp.Create(op=Or, values=[ft; expr]) + | _ -> expr + // Check for fallthrough + if body.IsEmpty then + ifThenElse (Some test) cases + else + [ If.Create(test=test, body=body, ?orelse=ifThenElse None cases) ] |> Some + + let result = ss.Cases |> List.ofArray |> ifThenElse None match result with | Some ifStmt -> stmts @ ifStmt | None -> [] diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index eaa9e151e3..0422b5b4fd 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -196,6 +196,7 @@ type AST = type Expression = | Attribute of Attribute | Subscript of Subscript + | BoolOp of BoolOp | BinOp of BinOp /// A yield from expression. Because these are expressions, they must be wrapped in a Expr node if the value sent /// back is not used. @@ -226,6 +227,7 @@ type Expression = match x with | Attribute (ex) -> printer.Print(ex) | Subscript (ex) -> printer.Print(ex) + | BoolOp (ex) -> printer.Print(ex) | BinOp (ex) -> printer.Print(ex) | Emit (ex) -> printer.Print(ex) | UnaryOp (ex) -> printer.Print(ex) @@ -286,8 +288,8 @@ type BoolOperator = member x.Print(printer: Printer) = let op = match x with - | And -> "and" - | Or -> "or" + | And -> " and " + | Or -> " or " printer.Print(op) @@ -943,13 +945,26 @@ type If = interface IPrint with member x.Print(printer) = + let rec printElse el = + match el with + | [] -> () + | [If iff ] -> + printer.Print("elif ") + printer.Print(iff.Test) + printer.Print(":") + printer.PrintBlock(iff.Body) + printElse iff.Else + | xs -> + printer.Print("else: ") + printer.PrintBlock(xs) + + printer.Print("if ") printer.Print(x.Test) printer.Print(":") printer.PrintBlock(x.Body) - if not (List.isEmpty x.Else) then - printer.Print("else: ") - printer.PrintBlock(x.Else) + printElse x.Else + /// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone /// raise. cause is the optional part for y in raise x from y. @@ -1278,6 +1293,28 @@ type BinOp = interface IPrint with member this.Print(printer) = printer.PrintOperation(this.Left, this.Operator, this.Right) + +type BoolOp = + { + Values: Expression list + Operator: BoolOperator + } + + static member Create(op, values): Expression = + { + Values = values + Operator = op + } + |> BoolOp + + interface IPrint with + + member this.Print(printer) = + for i, value in this.Values |> List.indexed do + printer.ComplexExpressionWithParens(value) + if i < this.Values.Length - 1 then + printer.Print(this.Operator) + /// A comparison of two or more values. left is the first value in the comparison, ops the list of operators, and /// comparators the list of values after the first element in the comparison. /// From 12c4816677bec6717c1b99b8c6ea6dc2c528cdf8 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 23 Jan 2021 12:41:40 +0100 Subject: [PATCH 040/145] Fixes for sequence expressions --- src/Fable.Transforms/Python/Babel2Python.fs | 374 +++++++++++--------- src/Fable.Transforms/Python/Python.fs | 36 +- 2 files changed, 234 insertions(+), 176 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index a97f1aec45..1576935795 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -20,17 +20,21 @@ type ITailCallOpportunity = abstract IsRecursiveRef: Fable.Expr -> bool type UsedNames = - { RootScope: HashSet - DeclarationScopes: HashSet - CurrentDeclarationScope: HashSet } + { + RootScope: HashSet + DeclarationScopes: HashSet + CurrentDeclarationScope: HashSet + } type Context = - { //UsedNames: UsedNames - DecisionTargets: (Fable.Ident list * Fable.Expr) list - HoistVars: Fable.Ident list -> bool - TailCallOpportunity: ITailCallOpportunity option - OptimizeTailCall: unit -> unit - ScopedTypeParams: Set } + { + //UsedNames: UsedNames + DecisionTargets: (Fable.Ident list * Fable.Expr) list + HoistVars: Fable.Ident list -> bool + TailCallOpportunity: ITailCallOpportunity option + OptimizeTailCall: unit -> unit + ScopedTypeParams: Set + } type IPythonCompiler = inherit Compiler @@ -107,21 +111,20 @@ module Helpers = module Util = let rec transformBody (returnStrategy: ReturnStrategy) (body: Statement list) = - let body = - body - |> List.choose Helpers.isProductiveStatement + let body = body |> List.choose Helpers.isProductiveStatement match body, returnStrategy with | [], ReturnStrategy.Return -> [ Return.Create() ] | [], ReturnStrategy.NoBreak | [], ReturnStrategy.NoReturn -> [ Pass ] | xs, ReturnStrategy.NoBreak -> - xs |> List.filter (fun x -> x <> Break) |> transformBody ReturnStrategy.NoReturn + xs + |> List.filter (fun x -> x <> Break) + |> transformBody ReturnStrategy.NoReturn | _ -> body let transformAsImports (com: IPythonCompiler) (ctx: Context) (imp: Babel.ImportDeclaration): Statement list = - let pymodule = - imp.Source.Value |> Helpers.rewriteFableImport + let pymodule = imp.Source.Value |> Helpers.rewriteFableImport printfn "Module: %A" pymodule @@ -171,11 +174,13 @@ module Util = importFroms.Add(alias) | _ -> failwith $"Unhandled import: {expr}" - [ if imports.Count > 0 then - Import.Create(imports |> List.ofSeq) + [ + if imports.Count > 0 then + Import.Create(imports |> List.ofSeq) - if importFroms.Count > 0 then - ImportFrom.Create(Some(Identifier(pymodule)), importFroms |> List.ofSeq) ] + if importFroms.Count > 0 then + ImportFrom.Create(Some(Identifier(pymodule)), importFroms |> List.ofSeq) + ] let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration): Statement list = @@ -191,46 +196,46 @@ module Util = | None -> [], [] let body: Statement list = - [ for mber in cls.Body.Body do - match mber with - | :? Babel.ClassMethod as cm -> - let self = Arg.Create(Identifier("self")) + [ + for mber in cls.Body.Body do + match mber with + | :? Babel.ClassMethod as cm -> + let self = Arg.Create(Identifier("self")) - let args = - cm.Params - |> List.ofArray - |> List.map (fun arg -> Arg.Create(Identifier(arg.Name))) + let args = + cm.Params + |> List.ofArray + |> List.map (fun arg -> Arg.Create(Identifier(arg.Name))) - let arguments = Arguments.Create(args = self :: args) + let arguments = Arguments.Create(args = self :: args) - match cm.Kind with - | "method" -> - let body = - com.TransformAsStatements(ctx, ReturnStrategy.Return, cm.Body) + match cm.Kind with + | "method" -> + let body = + com.TransformAsStatements(ctx, ReturnStrategy.Return, cm.Body) - let name = - match cm.Key with - | :? Babel.Identifier as id -> Identifier(id.Name) - | _ -> failwith "transformAsClassDef: Unknown key: {cm.Key}" + let name = + match cm.Key with + | :? Babel.Identifier as id -> Identifier(id.Name) + | _ -> failwith "transformAsClassDef: Unknown key: {cm.Key}" - FunctionDef.Create(name, arguments, body = body) - | "constructor" -> - let name = Identifier("__init__") + FunctionDef.Create(name, arguments, body = body) + | "constructor" -> + let name = Identifier("__init__") - let body = - com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, cm.Body) + let body = + com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, cm.Body) - FunctionDef.Create(name, arguments, body = body) - | _ -> failwith $"transformAsClassDef: Unknown kind: {cm.Kind}" - | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] + FunctionDef.Create(name, arguments, body = body) + | _ -> failwith $"transformAsClassDef: Unknown kind: {cm.Kind}" + | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" + ] printfn $"Body length: {body.Length}: ${body}" - let name = - Helpers.cleanNameAsPythonIdentifier (cls.Id.Value.Name) + let name = Helpers.cleanNameAsPythonIdentifier (cls.Id.Value.Name) - [ yield! stmts - ClassDef.Create(Identifier(name), body = body, bases = bases) ] + [ yield! stmts; ClassDef.Create(Identifier(name), body = body, bases = bases) ] /// Transform Babel expression as Python expression let rec transformAsExpr (com: IPythonCompiler) @@ -244,11 +249,9 @@ module Util = let left, leftStmts = com.TransformAsExpr(ctx, be.Left) let right, rightStmts = com.TransformAsExpr(ctx, be.Right) - let bin op = - BinOp.Create(left, op, right), leftStmts @ rightStmts + let bin op = BinOp.Create(left, op, right), leftStmts @ rightStmts - let cmp op = - Compare.Create(left, [ op ], [ right ]), leftStmts @ rightStmts + let cmp op = Compare.Create(left, [ op ], [ right ]), leftStmts @ rightStmts match be.Operator with | "+" -> Add |> bin @@ -299,9 +302,12 @@ module Util = let arguments = Arguments.Create(args = args) let stmts = afe.Body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. - if stmts.Length = 1 && (stmts.[0] :? Babel.ReturnStatement) then + + if stmts.Length = 1 + && (stmts.[0] :? Babel.ReturnStatement) then let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, afe.Body) + Lambda.Create(arguments, body), [] else let body = @@ -349,13 +355,15 @@ module Util = | :? Babel.Super -> Name.Create(Identifier("super().__init__"), ctx = Load), [] | :? Babel.ObjectExpression as oe -> let kv = - [ for prop in oe.Properties do - match prop with - | :? Babel.ObjectProperty as op -> - let key, _ = com.TransformAsExpr(ctx, op.Key) - let value, _ = com.TransformAsExpr(ctx, op.Value) - key, value - | _ -> failwith $"transformAsExpr: unhandled object expression property: {prop}" ] + [ + for prop in oe.Properties do + match prop with + | :? Babel.ObjectProperty as op -> + let key, _ = com.TransformAsExpr(ctx, op.Key) + let value, _ = com.TransformAsExpr(ctx, op.Value) + key, value + | _ -> failwith $"transformAsExpr: unhandled object expression property: {prop}" + ] let keys = kv |> List.map fst let values = kv |> List.map snd @@ -370,8 +378,7 @@ module Util = match value with | "void $0" -> args.[0], stmts //| "raise %0" -> Raise.Create() - | _ -> - Emit.Create(value, args), stmts + | _ -> Emit.Create(value, args), stmts | :? Babel.MemberExpression as me -> let value, stmts = com.TransformAsExpr(ctx, me.Object) @@ -435,10 +442,31 @@ module Util = let orElse, stmts3 = com.TransformAsExpr(ctx, ce.Alternate) IfExp.Create(test, body, orElse), stmts1 @ stmts2 @ stmts3 - | :? Babel.NullLiteral -> Name.Create(Identifier("None"), ctx=Load), [] + | :? Babel.NullLiteral -> Name.Create(Identifier("None"), ctx = Load), [] | :? Babel.SequenceExpression as se -> - let exprs, stmts = se.Expressions |> List.ofArray |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) |> Helpers.unzipArgs - exprs.[0], stmts // FIXME + // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments + let exprs, stmts = + se.Expressions + |> List.ofArray + |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) + |> Helpers.unzipArgs + + let body = + exprs + |> List.mapi + (fun i n -> + if i = exprs.Length - 1 then + Return.Create(n) // Return the last statement + else + Expr.Create(n)) + + let name = Helpers.getIdentifier ("lifted") + + let func = + FunctionDef.Create(name = name, args = Arguments.Create [], body = body) + + let name = Name.Create(name, Load) + Call.Create(name), stmts @ [ func ] | _ -> failwith $"Unhandled value: {expr}" /// Transform Babel expressions as Python statements. @@ -457,19 +485,27 @@ module Util = let targets: Expression list = match ae.Left with | :? Babel.Identifier as identifier -> - let target = Identifier(Helpers.cleanNameAsPythonIdentifier(identifier.Name)) - [ Name.Create(id = target, ctx=Store) ] + let target = + Identifier(Helpers.cleanNameAsPythonIdentifier (identifier.Name)) + + [ Name.Create(id = target, ctx = Store) ] | :? Babel.MemberExpression as me -> match me.Property with | :? Babel.Identifier as id -> - let attr = Identifier(Helpers.cleanNameAsPythonIdentifier(id.Name)) - [ Attribute.Create(value = Name.Create(id = Identifier("self"), ctx = Load), attr = attr, ctx = Store) ] + let attr = Identifier(Helpers.cleanNameAsPythonIdentifier (id.Name)) + + [ + Attribute.Create( + value = Name.Create(id = Identifier("self"), ctx = Load), + attr = attr, + ctx = Store + ) + ] | _ -> failwith "transformExpressionAsStatements: unknown property {me.Property}" | _ -> failwith $"AssignmentExpression, unknow expression: {ae.Left}" - [ yield! stmts - Assign.Create(targets = targets, value = value) ] + [ yield! stmts; Assign.Create(targets = targets, value = value) ] | _ -> failwith $"transformExpressionAsStatements: unknown expr: {expr}" @@ -484,8 +520,10 @@ module Util = match stmt with | :? Babel.BlockStatement as bl -> - [ for st in bl.Body do - yield! com.TransformAsStatements(ctx, returnStrategy, st) ] + [ + for st in bl.Body do + yield! com.TransformAsStatements(ctx, returnStrategy, st) + ] |> transformBody returnStrategy | :? Babel.ReturnStatement as rtn -> @@ -495,26 +533,30 @@ module Util = | ReturnStrategy.NoReturn -> stmts @ [ Expr.Create(expr) ] | _ -> stmts @ [ Return.Create(expr) ] | :? Babel.VariableDeclaration as vd -> - [ for vd in vd.Declarations do - let targets: Expression list = - let name = Helpers.cleanNameAsPythonIdentifier(vd.Id.Name) - [ Name.Create(id = Identifier(name), ctx = Store) ] - - match vd.Init with - | Some value -> - let expr, stmts = com.TransformAsExpr(ctx, value) - yield! stmts - Assign.Create(targets, expr) - | None -> () ] + [ + for vd in vd.Declarations do + let targets: Expression list = + let name = Helpers.cleanNameAsPythonIdentifier (vd.Id.Name) + [ Name.Create(id = Identifier(name), ctx = Store) ] + + match vd.Init with + | Some value -> + let expr, stmts = com.TransformAsExpr(ctx, value) + yield! stmts + Assign.Create(targets, expr) + | None -> () + ] | :? Babel.ExpressionStatement as es -> // Handle Babel expressions that we need to transforme here as Python statements match es.Expression with | :? Babel.AssignmentExpression -> com.TransformAsStatements(ctx, returnStrategy, es.Expression) | _ -> - [ let expr, stmts = com.TransformAsExpr(ctx, es.Expression) - yield! stmts - Expr.Create(expr) ] + [ + let expr, stmts = com.TransformAsExpr(ctx, es.Expression) + yield! stmts + Expr.Create(expr) + ] | :? Babel.IfStatement as iff -> let test, stmts = com.TransformAsExpr(ctx, iff.Test) @@ -530,8 +572,7 @@ module Util = | _ -> [] - [ yield! stmts - If.Create(test = test, body = body, orelse = orElse) ] + [ yield! stmts; If.Create(test = test, body = body, orelse = orElse) ] | :? Babel.WhileStatement as ws -> let expr, stmts = com.TransformAsExpr(ctx, ws.Test) @@ -539,25 +580,36 @@ module Util = com.TransformAsStatements(ctx, returnStrategy, ws.Body) |> transformBody ReturnStrategy.NoReturn - [ yield! stmts - While.Create(test = expr, body = body, orelse = []) ] + [ yield! stmts; While.Create(test = expr, body = body, orelse = []) ] | :? Babel.TryStatement as ts -> let body = com.TransformAsStatements(ctx, returnStrategy, ts.Block) - let finalBody = ts.Finalizer |> Option.map (fun f -> com.TransformAsStatements(ctx, returnStrategy, f)) + + let finalBody = + ts.Finalizer + |> Option.map (fun f -> com.TransformAsStatements(ctx, returnStrategy, f)) + let handlers = match ts.Handler with | Some cc -> let body = com.TransformAsStatements(ctx, returnStrategy, cc.Body) - let exn = Name.Create(Identifier("Exception"), ctx=Load) |> Some + + let exn = + Name.Create(Identifier("Exception"), ctx = Load) + |> Some + let identifier = Identifier(cc.Param.Name) - let handlers = [ ExceptHandler.Create(``type``=exn, name=identifier, body=body) ] + + let handlers = + [ ExceptHandler.Create(``type`` = exn, name = identifier, body = body) ] + handlers | _ -> [] - [ Try.Create(body=body, handlers=handlers, ?finalBody=finalBody) ] + [ Try.Create(body = body, handlers = handlers, ?finalBody = finalBody) ] | :? Babel.SwitchStatement as ss -> let value, stmts = com.TransformAsExpr(ctx, ss.Discriminant) - let rec ifThenElse (fallThrough: Expression option) (cases: Babel.SwitchCase list) : Statement list option = + + let rec ifThenElse (fallThrough: Expression option) (cases: Babel.SwitchCase list): Statement list option = match cases with | [] -> None | case :: cases -> @@ -565,24 +617,28 @@ module Util = case.Consequent |> List.ofArray |> List.collect (fun x -> com.TransformAsStatements(ctx, ReturnStrategy.NoBreak, x)) + match case.Test with - | None -> - body |> Some + | None -> body |> Some | Some test -> let test, st = com.TransformAsExpr(ctx, test) - let expr = Compare.Create(left=value, ops=[Eq], comparators=[test]) + + let expr = + Compare.Create(left = value, ops = [ Eq ], comparators = [ test ]) + let test = match fallThrough with - | Some ft -> - BoolOp.Create(op=Or, values=[ft; expr]) + | Some ft -> BoolOp.Create(op = Or, values = [ ft; expr ]) | _ -> expr // Check for fallthrough if body.IsEmpty then ifThenElse (Some test) cases else - [ If.Create(test=test, body=body, ?orelse=ifThenElse None cases) ] |> Some + [ If.Create(test = test, body = body, ?orelse = ifThenElse None cases) ] + |> Some let result = ss.Cases |> List.ofArray |> ifThenElse None + match result with | Some ifStmt -> stmts @ ifStmt | None -> [] @@ -594,47 +650,47 @@ module Util = let returnStrategy = ReturnStrategy.NoReturn let stmt: Statement list = - [ for md in body do - match md with - | :? Babel.ExportNamedDeclaration as decl -> - match decl.Declaration with - | :? Babel.VariableDeclaration as decl -> - for decls in decl.Declarations do - let value, stmts = - com.TransformAsExpr(ctx, decls.Init.Value) - - let targets: Expression list = - let name = Helpers.cleanNameAsPythonIdentifier(decls.Id.Name) - [ Name.Create(id = Identifier(name), ctx = Store) ] - - yield! stmts - yield Assign.Create(targets = targets, value = value) - | :? Babel.FunctionDeclaration as fd -> - let args = - fd.Params - |> List.ofArray - |> List.map (fun pattern -> - let name = Helpers.cleanNameAsPythonIdentifier(pattern.Name) - Arg.Create(Identifier(name))) - - let arguments = Arguments.Create(args = args) - - let body = - com.TransformAsStatements(ctx, returnStrategy, fd.Body) - - let name = - Helpers.cleanNameAsPythonIdentifier(fd.Id.Name) - - yield FunctionDef.Create(Identifier(name), arguments, body = body) - | :? Babel.ClassDeclaration as cd -> yield! com.TransformAsClassDef(ctx, cd) - | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" - - | :? Babel.ImportDeclaration as imp -> yield! com.TransformAsImports(ctx, imp) - | :? Babel.PrivateModuleDeclaration as pmd -> - yield! - com.TransformAsStatements(ctx, returnStrategy, pmd.Statement) - |> transformBody returnStrategy - | _ -> failwith $"Unknown module declaration: {md}" ] + [ + for md in body do + match md with + | :? Babel.ExportNamedDeclaration as decl -> + match decl.Declaration with + | :? Babel.VariableDeclaration as decl -> + for decls in decl.Declarations do + let value, stmts = com.TransformAsExpr(ctx, decls.Init.Value) + + let targets: Expression list = + let name = Helpers.cleanNameAsPythonIdentifier (decls.Id.Name) + [ Name.Create(id = Identifier(name), ctx = Store) ] + + yield! stmts + yield Assign.Create(targets = targets, value = value) + | :? Babel.FunctionDeclaration as fd -> + let args = + fd.Params + |> List.ofArray + |> List.map + (fun pattern -> + let name = Helpers.cleanNameAsPythonIdentifier (pattern.Name) + Arg.Create(Identifier(name))) + + let arguments = Arguments.Create(args = args) + + let body = com.TransformAsStatements(ctx, returnStrategy, fd.Body) + + let name = Helpers.cleanNameAsPythonIdentifier (fd.Id.Name) + + yield FunctionDef.Create(Identifier(name), arguments, body = body) + | :? Babel.ClassDeclaration as cd -> yield! com.TransformAsClassDef(ctx, cd) + | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" + + | :? Babel.ImportDeclaration as imp -> yield! com.TransformAsImports(ctx, imp) + | :? Babel.PrivateModuleDeclaration as pmd -> + yield! + com.TransformAsStatements(ctx, returnStrategy, pmd.Statement) + |> transformBody returnStrategy + | _ -> failwith $"Unknown module declaration: {md}" + ] let imports = com.GetAllImports() Module.Create(imports @ stmt) @@ -652,7 +708,8 @@ module Util = module Compiler = open Util - type PythonCompiler(com: Compiler) = + + type PythonCompiler (com: Compiler) = let onlyOnceWarnings = HashSet() let imports = Dictionary() @@ -661,21 +718,15 @@ module Compiler = if onlyOnceWarnings.Add(msg) then addWarning com [] range msg - member _.GetImportExpr(ctx, selector, path, r) = - failwith "Not implemented" + member _.GetImportExpr(ctx, selector, path, r) = failwith "Not implemented" - member _.GetAllImports() = - imports.Values - |> List.ofSeq - |> List.map Import + member _.GetAllImports() = imports.Values |> List.ofSeq |> List.map Import member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e - member bcom.TransformAsStatements(ctx, ret, e) = - transformExpressionAsStatements bcom ctx ret e + member bcom.TransformAsStatements(ctx, ret, e) = transformExpressionAsStatements bcom ctx ret e - member bcom.TransformAsStatements(ctx, ret, e) = - transformStatementAsStatements bcom ctx ret e + member bcom.TransformAsStatements(ctx, ret, e) = transformStatementAsStatements bcom ctx ret e member bcom.TransformAsClassDef(ctx, cls) = transformAsClassDef bcom ctx cls //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body @@ -690,8 +741,7 @@ module Compiler = member _.GetImplementationFile(fileName) = com.GetImplementationFile(fileName) member _.GetRootModule(fileName) = com.GetRootModule(fileName) - member _.GetOrAddInlineExpr(fullName, generate) = - com.GetOrAddInlineExpr(fullName, generate) + member _.GetOrAddInlineExpr(fullName, generate) = com.GetOrAddInlineExpr(fullName, generate) member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) @@ -704,10 +754,12 @@ module Compiler = let com = makeCompiler com :> IPythonCompiler let ctx = - { DecisionTargets = [] - HoistVars = fun _ -> false - TailCallOpportunity = None - OptimizeTailCall = fun () -> () - ScopedTypeParams = Set.empty } + { + DecisionTargets = [] + HoistVars = fun _ -> false + TailCallOpportunity = None + OptimizeTailCall = fun () -> () + ScopedTypeParams = Set.empty + } transformProgram com ctx program.Body diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 0422b5b4fd..e7bd7cf7fe 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -177,6 +177,7 @@ type AST = | Arg of Arg + interface IPrint with member x.Print(printer: Printer) = match x with @@ -218,6 +219,7 @@ type Expression = | Tuple of Tuple + // member val Lineno: int = 0 with get, set // member val ColOffset: int = 0 with get, set // member val EndLineno: int option = None with get, set @@ -233,7 +235,7 @@ type Expression = | UnaryOp (ex) -> printer.Print(ex) | FormattedValue (ex) -> printer.Print(ex) | Constant (ex) -> printer.Print(ex) - | IfExp(ex) -> printer.Print(ex) + | IfExp (ex) -> printer.Print(ex) | Call (ex) -> printer.Print(ex) | Lambda (ex) -> printer.Print(ex) | Name (ex) -> printer.Print(ex) @@ -259,6 +261,7 @@ type Operator = | MatMult + interface IPrint with member x.Print(printer: Printer) = let op = @@ -284,6 +287,7 @@ type BoolOperator = | Or + interface IPrint with member x.Print(printer: Printer) = let op = @@ -307,6 +311,7 @@ type ComparisonOperator = | NotIn + interface IPrint with member x.Print(printer) = let op = @@ -331,6 +336,7 @@ type UnaryOperator = | USub + interface IPrint with member this.Print(printer) = let op = @@ -348,6 +354,7 @@ type ExpressionContext = | Store + interface IPrint with member this.Print(printer) = () @@ -355,6 +362,7 @@ type Identifier = | Identifier of string + interface IPrint with member this.Print(printer: Printer) = let (Identifier id) = this @@ -382,6 +390,7 @@ type Statement = | Continue + // member val Lineno: int = 0 with get, set // member val ColOffset: int = 0 with get, set // member val EndLineno: int option = None with get, set @@ -536,7 +545,6 @@ type Arg = TypeComment: string option } - static member Create(arg, ?annotation, ?typeComment) = { Lineno = 0 @@ -945,10 +953,11 @@ type If = interface IPrint with member x.Print(printer) = - let rec printElse el = - match el with - | [] -> () - | [If iff ] -> + let rec printElse stmts = + match stmts with + | [] + | [ Pass ] -> () + | [ If iff ] -> printer.Print("elif ") printer.Print(iff.Test) printer.Print(":") @@ -1300,20 +1309,16 @@ type BoolOp = Operator: BoolOperator } - static member Create(op, values): Expression = - { - Values = values - Operator = op - } - |> BoolOp + static member Create(op, values): Expression = { Values = values; Operator = op } |> BoolOp interface IPrint with member this.Print(printer) = for i, value in this.Values |> List.indexed do printer.ComplexExpressionWithParens(value) + if i < this.Values.Length - 1 then - printer.Print(this.Operator) + printer.Print(this.Operator) /// A comparison of two or more values. left is the first value in the comparison, ops the list of operators, and /// comparators the list of values after the first element in the comparison. @@ -1602,12 +1607,13 @@ type IfExp = OrElse: Expression } - static member Create(test, body, orElse) : Expression = + static member Create(test, body, orElse): Expression = { Test = test Body = body OrElse = orElse - } |> IfExp + } + |> IfExp interface IPrint with member x.Print(printer: Printer) = From ac1fb28401e412de164d93ca718386b90987b0c7 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 23 Jan 2021 13:45:52 +0100 Subject: [PATCH 041/145] Fix exception message handling --- build.fsx | 563 ++++++++------------ src/Fable.Transforms/Python/Babel2Python.fs | 38 +- src/Fable.Transforms/Python/Python.fs | 5 +- 3 files changed, 252 insertions(+), 354 deletions(-) diff --git a/build.fsx b/build.fsx index 5d376875ad..d961ee4223 100644 --- a/build.fsx +++ b/build.fsx @@ -7,7 +7,6 @@ open System.Text.RegularExpressions // Appveyor artifact let FABLE_BRANCH = "master" let APPVEYOR_REPL_ARTIFACT_URL_PARAMS = "?branch=" + FABLE_BRANCH //+ "&pr=false" - let APPVEYOR_REPL_ARTIFACT_URL = "https://ci.appveyor.com/api/projects/fable-compiler/Fable/artifacts/src/fable-standalone/fable-standalone.zip" + APPVEYOR_REPL_ARTIFACT_URL_PARAMS @@ -24,19 +23,18 @@ module Util = removeDirRecursive dir let updateVersionInFableTransforms version = - let filePath = - "src/Fable.Transforms/Global/Compiler.fs" + let filePath = "src/Fable.Transforms/Global/Compiler.fs" // printfn "VERSION %s" version Regex.Replace( readFile filePath, @"let \[] VERSION = "".*?""", - $"let [] VERSION = \"{version}\"" - ) + $"let [] VERSION = \"{version}\"") |> writeFile filePath let updatePkgVersionInFsproj projFile version = readFile projFile - |> replaceRegex Publish.NUGET_PACKAGE_VERSION (fun m -> m.Groups.[1].Value + version + m.Groups.[3].Value) + |> replaceRegex Publish.NUGET_PACKAGE_VERSION (fun m -> + m.Groups.[1].Value + version + m.Groups.[3].Value) |> writeFile projFile let runTSLint projectDir = @@ -46,46 +44,28 @@ module Util = run ("npm run tsc -- --project " + projectDir) let runFableWithArgs projectDir args = - run ( - "dotnet run -c Release -p src/Fable.Cli -- " - + projectDir - + " " - + String.concat " " args - ) + run ("dotnet run -c Release -p src/Fable.Cli -- " + projectDir + " " + String.concat " " args) let runFableWithArgsAsync projectDir args = - runAsync ( - "dotnet run -c Release -p src/Fable.Cli -- " - + projectDir - + " " - + String.concat " " args - ) + runAsync ("dotnet run -c Release -p src/Fable.Cli -- " + projectDir + " " + String.concat " " args) let runNpx command args = run ("npx " + command + " " + (String.concat " " args)) let runNpmScript script args = - run ( - "npm run " - + script - + " -- " - + (String.concat " " args) - ) + run ("npm run " + script + " -- " + (String.concat " " args)) let runNpmScriptAsync script args = - runAsync ( - "npm run " - + script - + " -- " - + (String.concat " " args) - ) + runAsync ("npm run " + script + " -- " + (String.concat " " args)) - let runFable projectDir = runFableWithArgs projectDir [] + let runFable projectDir = + runFableWithArgs projectDir [] let runMocha testDir = - runNpmScript "mocha" [ $"{testDir} -r esm --reporter dot -t 10000" ] + runNpmScript "mocha" [$"{testDir} -r esm --reporter dot -t 10000"] - let resolveDir dir = __SOURCE_DIRECTORY__ dir + let resolveDir dir = + __SOURCE_DIRECTORY__ dir open Util @@ -99,50 +79,37 @@ module Unused = // let concurrently(commands: string[]): unit = importDefault "concurrently" let downloadAndExtractTo (url: string) (targetDir: string) = - sprintf "npx download --extract --out %s \"%s\"" targetDir url - |> run + sprintf "npx download --extract --out %s \"%s\"" targetDir url |> run - let downloadStandalone () = + let downloadStandalone() = let targetDir = "src/fable-standalone/dist" - cleanDirs [ targetDir ] + cleanDirs [targetDir] downloadAndExtractTo APPVEYOR_REPL_ARTIFACT_URL targetDir - let coverage () = + let coverage() = // report converter // https://github.com/danielpalme/ReportGenerator // dotnet tool install dotnet-reportgenerator-globaltool --tool-path tools - if not (pathExists "./bin/tools/reportgenerator") - && not (pathExists "./bin/tools/reportgenerator.exe") then + if not (pathExists "./bin/tools/reportgenerator") && not (pathExists "./bin/tools/reportgenerator.exe") then runInDir "." "dotnet tool install dotnet-reportgenerator-globaltool --tool-path bin/tools" - let reportGen = - if pathExists "./bin/tools/reportgenerator" then - "bin/tools/reportgenerator" - else - "bin\\tools\\reportgenerator.exe" + if pathExists "./bin/tools/reportgenerator" then "bin/tools/reportgenerator" + else "bin\\tools\\reportgenerator.exe" // if not (pathExists "build/fable-library") then // buildLibrary() - cleanDirs [ "build/tests" ] + cleanDirs ["build/tests"] runFable "tests" // JS run "npx nyc mocha build/tests --require source-map-support/register --reporter dot -t 10000" - - runInDir - "." - (reportGen - + " \"-reports:build/coverage/nyc/lcov.info\" -reporttypes:Html \"-targetdir:build/coverage/nyc/html\" ") + runInDir "." (reportGen + " \"-reports:build/coverage/nyc/lcov.info\" -reporttypes:Html \"-targetdir:build/coverage/nyc/html\" ") // .NET //runInDir "tests/Main" "dotnet build /t:Collect_Coverage" - cleanDirs [ "build/coverage/netcoreapp2.0/out" ] - - runInDir - "." - (reportGen - + " \"-reports:build/coverage/netcoreapp2.0/coverage.xml\" -reporttypes:Html \"-targetdir:build/coverage/netcoreapp2.0/html\" ") + cleanDirs ["build/coverage/netcoreapp2.0/out"] + runInDir "." (reportGen + " \"-reports:build/coverage/netcoreapp2.0/coverage.xml\" -reporttypes:Html \"-targetdir:build/coverage/netcoreapp2.0/html\" ") // TARGETS --------------------------- @@ -151,54 +118,53 @@ let buildLibraryWithOptions (opts: {| watch: bool |}) = let projectDir = baseDir "src/fable-library" let buildDir = baseDir "build/fable-library" - - let fableOpts = - [ "--outDir " + buildDir - "--fableLib " + buildDir - "--exclude Fable.Core" - "--define FX_NO_BIGINT" - "--define FABLE_LIBRARY" - if opts.watch then "--watch" ] - - cleanDirs [ buildDir ] + let fableOpts = [ + "--outDir " + buildDir + "--fableLib " + buildDir + "--exclude Fable.Core" + "--define FX_NO_BIGINT" + "--define FABLE_LIBRARY" + if opts.watch then "--watch" + ] + + cleanDirs [buildDir] runInDir baseDir "npm install" - if opts.watch then - Async.Parallel [ runNpmScriptAsync "tsc" [ "--project " + projectDir; "--watch" ] - runFableWithArgsAsync projectDir fableOpts ] - |> runAsyncWorkflow + Async.Parallel [ + runNpmScriptAsync "tsc" [ + "--project " + projectDir + "--watch" + ] + runFableWithArgsAsync projectDir fableOpts + ] |> runAsyncWorkflow else runTSLint projectDir runTypeScript projectDir runFableWithArgs projectDir fableOpts -let buildLibrary () = - buildLibraryWithOptions {| watch = false |} - -let watchLibrary () = - buildLibraryWithOptions {| watch = true |} +let buildLibrary() = buildLibraryWithOptions {| watch = false |} +let watchLibrary() = buildLibraryWithOptions {| watch = true |} -let buildLibraryIfNotExists () = +let buildLibraryIfNotExists() = let baseDir = __SOURCE_DIRECTORY__ - if not (pathExists (baseDir "build/fable-library")) then - buildLibrary () + buildLibrary() -let buildLibraryTs () = +let buildLibraryTs() = let projectDir = "src/fable-library" let buildDirTs = "build/fable-library-ts" let buildDirJs = "build/fable-library-js" - cleanDirs [ buildDirTs; buildDirJs ] + cleanDirs [buildDirTs; buildDirJs] - runFableWithArgs - projectDir - [ "--outDir " + buildDirTs - "--fableLib " + buildDirTs - "--typescript" - "--exclude Fable.Core" - "--define FX_NO_BIGINT" - "--define FABLE_LIBRARY" ] + runFableWithArgs projectDir [ + "--outDir " + buildDirTs + "--fableLib " + buildDirTs + "--typescript" + "--exclude Fable.Core" + "--define FX_NO_BIGINT" + "--define FABLE_LIBRARY" + ] // TODO: cleanDirs [buildDirTs "fable-library"] // TODO: copy *.ts/*.js from projectDir to buildDir runInDir buildDirTs "npm run tsc -- --init --target es2020 --module es2020 --allowJs" @@ -206,30 +172,27 @@ let buildLibraryTs () = // Like testJs() but doesn't create bundles/packages for fable-standalone & friends // Mainly intended for CI -let testJsFast () = - runFableWithArgs "src/fable-standalone/src" [ "--forcePkgs" ] +let testJsFast() = + runFableWithArgs "src/fable-standalone/src" [ + "--forcePkgs" + ] - runFableWithArgs - "src/fable-compiler-js/src" - [ "--exclude Fable.Core" - "--define LOCAL_TEST" ] + runFableWithArgs "src/fable-compiler-js/src" [ + "--exclude Fable.Core" + "--define LOCAL_TEST" + ] let fableJs = "./src/fable-compiler-js/src/app.fs.js" let testProj = "tests/Main/Fable.Tests.fsproj" let buildDir = "build/tests-js" - run $"node --eval "require('esm')(module)('{fableJs}')" {fableJs} {testProj} {buildDir}" + run $"node --eval \"require('esm')(module)('{fableJs}')\" {fableJs} {testProj} {buildDir}" runMocha buildDir let buildStandalone (opts: {| minify: bool; watch: bool |}) = - buildLibraryIfNotExists () + buildLibraryIfNotExists() - printfn - "Building standalone%s..." - (if opts.minify then - "" - else - " (no minification)") + printfn "Building standalone%s..." (if opts.minify then "" else " (no minification)") let projectDir = "src/fable-standalone/src" let libraryDir = "build/fable-library" @@ -240,110 +203,99 @@ let buildStandalone (opts: {| minify: bool; watch: bool |}) = match opts.watch, opts.minify with | true, _ -> match args with - | _ :: rollupTarget :: _ -> rollupTarget - | _ -> - failwith - "Pass the bundle output, e.g.: npm run build watch-standalone ../repl3/public/js/repl/bundle.min.js" + | _::rollupTarget::_ -> rollupTarget + | _ -> failwith "Pass the bundle output, e.g.: npm run build watch-standalone ../repl3/public/js/repl/bundle.min.js" | false, true -> buildDir "bundle.js" | false, false -> distDir "bundle.min.js" - let rollupArgs = - [ buildDir "bundle/Main.js" - "-o " + rollupTarget - "--format umd" - "--name __FABLE_STANDALONE__" ] + let rollupArgs = [ + buildDir "bundle/Main.js" + "-o " + rollupTarget + "--format umd" + "--name __FABLE_STANDALONE__" + ] // cleanup if not opts.watch then - cleanDirs [ buildDir; distDir ] + cleanDirs [buildDir; distDir] makeDirRecursive distDir // build standalone bundle - runFableWithArgs - projectDir - [ "--outDir " + buildDir "bundle" - if opts.watch then - "--watch" - "--run rollup" - yield! rollupArgs - "--watch" ] + runFableWithArgs projectDir [ + "--outDir " + buildDir "bundle" + if opts.watch then + "--watch" + "--run rollup" + yield! rollupArgs + "--watch" + ] // build standalone worker - runFableWithArgs (projectDir + "/Worker") [ "--outDir " + buildDir + "/worker" ] + runFableWithArgs (projectDir + "/Worker") [ + "--outDir " + buildDir + "/worker" + ] // make standalone bundle dist runNpmScript "rollup" rollupArgs - if opts.minify then - runNpmScript - "terser" - [ buildDir "bundle.js" - "-o " + distDir "bundle.min.js" - "--mangle" - "--compress" ] + runNpmScript "terser" [ + buildDir "bundle.js" + "-o " + distDir "bundle.min.js" + "--mangle" + "--compress" + ] // make standalone worker dist - runNpmScript "rollup" [ $"{buildDir}/worker/Worker.js -o {buildDir}/worker.js --format iife" ] + runNpmScript "rollup" [$"{buildDir}/worker/Worker.js -o {buildDir}/worker.js --format iife"] // runNpx "webpack" [sprintf "--entry ./%s/worker.js --output ./%s/worker.min.js --config ./%s/../worker.config.js" buildDir distDir projectDir] - runNpmScript "terser" [ $"{buildDir}/worker.js -o {distDir}/worker.min.js --mangle --compress" ] + runNpmScript "terser" [$"{buildDir}/worker.js -o {distDir}/worker.min.js --mangle --compress"] // print bundle size - fileSizeInBytes (distDir "bundle.min.js") - / 1000. - |> printfn "Bundle size: %fKB" - - fileSizeInBytes (distDir "worker.min.js") - / 1000. - |> printfn "Worker size: %fKB" + fileSizeInBytes (distDir "bundle.min.js") / 1000. |> printfn "Bundle size: %fKB" + fileSizeInBytes (distDir "worker.min.js") / 1000. |> printfn "Worker size: %fKB" // Put fable-library files next to bundle let libraryTarget = distDir "fable-library" copyDirRecursive libraryDir libraryTarget -// These files will be used in the browser, so make sure the import paths include .js extension -// let reg = Regex(@"^import (.*"".*)("".*)$", RegexOptions.Multiline) -// getFullPathsInDirectoryRecursively libraryTarget -// |> Array.filter (fun file -> file.EndsWith(".js")) -// |> Array.iter (fun file -> -// reg.Replace(readFile file, fun m -> -// let fst = m.Groups.[1].Value -// if fst.EndsWith(".js") then m.Value -// else sprintf "import %s.js%s" fst m.Groups.[2].Value) -// |> writeFile file) - -// Bump version -// let compilerVersion = Publish.loadReleaseVersion "src/fable-compiler" -// let standaloneVersion = Publish.loadNpmVersion projectDir -// let (comMajor, comMinor, _, comPrerelease) = Publish.splitVersion compilerVersion -// let (staMajor, staMinor, staPatch, _) = Publish.splitVersion standaloneVersion -// Publish.bumpNpmVersion projectDir -// (if comMajor > staMajor || comMinor > staMinor then compilerVersion -// else sprintf "%i.%i.%i%s" staMajor staMinor (staPatch + 1) comPrerelease) - -let buildCompilerJs (minify: bool) = + // These files will be used in the browser, so make sure the import paths include .js extension + // let reg = Regex(@"^import (.*"".*)("".*)$", RegexOptions.Multiline) + // getFullPathsInDirectoryRecursively libraryTarget + // |> Array.filter (fun file -> file.EndsWith(".js")) + // |> Array.iter (fun file -> + // reg.Replace(readFile file, fun m -> + // let fst = m.Groups.[1].Value + // if fst.EndsWith(".js") then m.Value + // else sprintf "import %s.js%s" fst m.Groups.[2].Value) + // |> writeFile file) + + // Bump version + // let compilerVersion = Publish.loadReleaseVersion "src/fable-compiler" + // let standaloneVersion = Publish.loadNpmVersion projectDir + // let (comMajor, comMinor, _, comPrerelease) = Publish.splitVersion compilerVersion + // let (staMajor, staMinor, staPatch, _) = Publish.splitVersion standaloneVersion + // Publish.bumpNpmVersion projectDir + // (if comMajor > staMajor || comMinor > staMinor then compilerVersion + // else sprintf "%i.%i.%i%s" staMajor staMinor (staPatch + 1) comPrerelease) + +let buildCompilerJs(minify: bool) = let projectDir = "src/fable-compiler-js/src" let buildDir = "build/fable-compiler-js" let distDir = "src/fable-compiler-js/dist" if not (pathExists "build/fable-standalone") then - buildStandalone {| minify = minify; watch = false |} + buildStandalone {|minify=minify; watch=false|} - cleanDirs [ buildDir; distDir ] + cleanDirs [buildDir; distDir] makeDirRecursive distDir - runFableWithArgs - projectDir - [ "--outDir " + buildDir - "--exclude Fable.Core" ] - - let rollupTarget = - if minify then - distDir "app.js" - else - distDir "app.min.js" + runFableWithArgs projectDir [ + "--outDir " + buildDir + "--exclude Fable.Core" + ] + let rollupTarget = if minify then distDir "app.js" else distDir "app.min.js" run $"npx rollup {buildDir}/app.js -o {rollupTarget} --format umd --name Fable" - if minify then run $"npx terser {distDir}/app.js -o {distDir}/app.min.js --mangle --compress" @@ -352,14 +304,14 @@ let buildCompilerJs (minify: bool) = // Copy fable-metadata copyDirRecursive ("src/fable-metadata/lib") (distDir "fable-metadata") -let testJs (minify) = +let testJs(minify) = let fableDir = "src/fable-compiler-js" let buildDir = "build/tests-js" if not (pathExists "build/fable-compiler-js") then - buildCompilerJs (minify) + buildCompilerJs(minify) - cleanDirs [ buildDir ] + cleanDirs [buildDir] // Link fable-compiler-js to local packages runInDir fableDir "npm link ../fable-metadata" @@ -377,79 +329,71 @@ let testJs (minify) = runInDir fableDir "npm unlink ../fable-metadata && cd ../fable-metadata && npm unlink" runInDir fableDir "npm unlink ../fable-standalone && cd ../fable-standalone && npm unlink" -let testReact () = +let testReact() = runFableWithArgs "tests/React" [] runInDir "tests/React" "npm i && npm test" -let testIntegration () = +let testIntegration() = runInDir "tests/Integration" "dotnet run -c Release" -let test () = - buildLibraryIfNotExists () +let test() = + buildLibraryIfNotExists() let projectDir = "tests/Main" let buildDir = "build/tests" - cleanDirs [ buildDir ] - - runFableWithArgs - projectDir - [ "--outDir " + buildDir - "--exclude Fable.Core" ] + cleanDirs [buildDir] + runFableWithArgs projectDir [ + "--outDir " + buildDir + "--exclude Fable.Core" + ] runMocha buildDir runInDir projectDir "dotnet run" - testReact () + testReact() - testIntegration () + testIntegration() if envVarOrNone "APPVEYOR" |> Option.isSome then - testJsFast () + testJsFast() let buildLocalPackageWith pkgDir pkgCommand fsproj action = - let version = - "3.0.0-local-build-" - + DateTime.Now.ToString("yyyyMMdd-HHmm") - + let version = "3.0.0-local-build-" + DateTime.Now.ToString("yyyyMMdd-HHmm") action version updatePkgVersionInFsproj fsproj version run $"dotnet pack {fsproj} -p:Pack=true -c Release -o {pkgDir}" // Return install command - $"dotnet {pkgCommand} --version "{version}" --add-source {fullPath pkgDir}" + $"""dotnet {pkgCommand} --version "{version}" --add-source {fullPath pkgDir}""" let buildLocalPackage pkgDir = - buildLocalPackageWith - pkgDir + buildLocalPackageWith pkgDir "tool install fable" - (resolveDir "src/Fable.Cli/Fable.Cli.fsproj") - (fun version -> - buildLibrary () + (resolveDir "src/Fable.Cli/Fable.Cli.fsproj") (fun version -> + buildLibrary() updateVersionInFableTransforms version) -let testRepos () = - let repos = - [ "https://github.com/fable-compiler/fable-promise:master", "npm i && npm test" - "https://github.com/alfonsogarciacaro/Thoth.Json:nagareyama", "./fake.sh build -t MochaTest" - "https://github.com/alfonsogarciacaro/FSharp.Control.AsyncSeq:nagareyama", - "cd tests/fable && npm i && npm test" - "https://github.com/alfonsogarciacaro/Fable.Extras:nagareyama", "dotnet paket restore && npm i && npm test" - "https://github.com/alfonsogarciacaro/Fable.Jester:nagareyama", "npm i && npm test" - "https://github.com/Zaid-Ajaj/Fable.SimpleJson:master", "npm i && npm run test-nagareyama" ] - - let testDir = tempPath () "fable-repos" +let testRepos() = + let repos = [ + "https://github.com/fable-compiler/fable-promise:master", "npm i && npm test" + "https://github.com/alfonsogarciacaro/Thoth.Json:nagareyama", "./fake.sh build -t MochaTest" + "https://github.com/alfonsogarciacaro/FSharp.Control.AsyncSeq:nagareyama", "cd tests/fable && npm i && npm test" + "https://github.com/alfonsogarciacaro/Fable.Extras:nagareyama", "dotnet paket restore && npm i && npm test" + "https://github.com/alfonsogarciacaro/Fable.Jester:nagareyama", "npm i && npm test" + "https://github.com/Zaid-Ajaj/Fable.SimpleJson:master", "npm i && npm run test-nagareyama" + ] + + let testDir = tempPath() "fable-repos" printfn $"Cloning repos to: {testDir}" - cleanDirs [ testDir ] + cleanDirs [testDir] makeDirRecursive testDir let pkgInstallCmd = buildLocalPackage (testDir "pkg") for (repo, command) in repos do - let url, branch = - let i = repo.LastIndexOf(":") in repo.[..i - 1], repo.[i + 1..] - + let url, branch = let i = repo.LastIndexOf(":") in repo.[..i-1], repo.[i+1..] let name = url.[url.LastIndexOf("/") + 1..] runInDir testDir $"git clone {url} {name}" let repoDir = testDir name @@ -459,78 +403,66 @@ let testRepos () = runInDir repoDir "dotnet tool restore" runInDir repoDir command -let githubRelease () = +let githubRelease() = match envVarOrNone "GITHUB_USER", envVarOrNone "GITHUB_TOKEN" with | Some user, Some token -> async { try let! version, notes = Publish.loadReleaseVersionAndNotes "src/Fable.Cli" // TODO: escape single quotes - let notes = - notes - |> Array.map (sprintf "'%s'") - |> String.concat "," - + let notes = notes |> Array.map (sprintf "'%s'") |> String.concat "," run $"git commit -am \"Release {version}\" && git push" - - runSilent - $""" + runSilent $""" node --eval "require('ghreleases').create({{ user: '{user}', token: '{token}', }}, 'fable-compiler', 'Fable', {{ tag_name: '{version}', name: '{version}', - body: [{notes}].join('\n'),}}, (err, res) => {{ if (err != null) {{ console.error(err) }}}})"""" - + body: [{notes}].join('\n'), +}}, (err, res) => {{ + if (err != null) {{ + console.error(err) + }} +}})" +""" printfn "Github release %s created successfully" version - with ex -> printfn "Github release failed: %s" ex.Message - } - |> runAsyncWorkflow + with ex -> + printfn "Github release failed: %s" ex.Message + } |> runAsyncWorkflow | _ -> failwith "Expecting GITHUB_USER and GITHUB_TOKEN enviromental variables" let copyFcsRepo sourceDir = let targetDir = "src/fcs-fable" - cleanDirs [ targetDir ] + cleanDirs [targetDir] copyDirRecursive (sourceDir "fcs/fcs-fable") targetDir - [ "src/fsharp" - "src/fsharp/absil" - "src/fsharp/ilx" - "src/fsharp/service" - "src/fsharp/symbols" - "src/fsharp/utils" ] - |> List.iter (fun path -> copyDirNonRecursive (sourceDir path) (targetDir path)) - + ; "src/fsharp/absil" + ; "src/fsharp/ilx" + ; "src/fsharp/service" + ; "src/fsharp/symbols" + ; "src/fsharp/utils" + ] |> List.iter (fun path -> + copyDirNonRecursive (sourceDir path) (targetDir path)) removeFile (targetDir ".gitignore") let projPath = (targetDir "fcs-fable.fsproj") let projText = readFile projPath - let projText = - Regex.Replace( - projText, + Regex.Replace(projText, @"(\$\(MSBuildProjectDirectory\)).*?(<\/FSharpSourcesRoot>)", - "$1/src$2" - ) + "$1/src$2") // let projText = // Regex.Replace(projText, // @"artifacts\/bin\/FSharp.Core\/Release\/netstandard2.0", // "lib/fcs") projText |> writeFile projPath -let syncFcsRepo () = +let syncFcsRepo() = // FAKE is giving lots of problems with the dotnet SDK version, ignore it let cheatWithDotnetSdkVersion dir f = let path = dir "build.fsx" let script = readFile path - - Regex.Replace( - script, - @"let dotnetExePath =[\s\S]*DotNetCli\.InstallDotNetSDK", - "let dotnetExePath = \"dotnet\" //DotNetCli.InstallDotNetSDK" - ) - |> writeFile path - + Regex.Replace(script, @"let dotnetExePath =[\s\S]*DotNetCli\.InstallDotNetSDK", "let dotnetExePath = \"dotnet\" //DotNetCli.InstallDotNetSDK") |> writeFile path f () runInDir dir "git reset --hard" @@ -540,63 +472,40 @@ let syncFcsRepo () = // service_slim runInDir FCS_REPO_LOCAL ("git checkout " + FCS_REPO_SERVICE_SLIM_BRANCH) runInDir FCS_REPO_LOCAL "git pull" - cheatWithDotnetSdkVersion (FCS_REPO_LOCAL "fcs") (fun () -> runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "") - - copyFile - (FCS_REPO_LOCAL - "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.dll") - "../fable/lib/fcs/" - - copyFile - (FCS_REPO_LOCAL - "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.xml") - "../fable/lib/fcs/" - - copyFile - (FCS_REPO_LOCAL - "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.dll") - "../fable/lib/fcs/" - - copyFile - (FCS_REPO_LOCAL - "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.xml") - "../fable/lib/fcs/" + cheatWithDotnetSdkVersion (FCS_REPO_LOCAL "fcs") (fun () -> + runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "") + copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.dll") "../fable/lib/fcs/" + copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.xml") "../fable/lib/fcs/" + copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.dll") "../fable/lib/fcs/" + copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.xml") "../fable/lib/fcs/" // fcs-fable runInDir FCS_REPO_LOCAL ("git checkout " + FCS_REPO_FABLE_BRANCH) runInDir FCS_REPO_LOCAL "git pull" - - cheatWithDotnetSdkVersion - (FCS_REPO_LOCAL "fcs") - (fun () -> runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "CodeGen.Fable") - + cheatWithDotnetSdkVersion (FCS_REPO_LOCAL "fcs") (fun () -> + runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "CodeGen.Fable") copyFcsRepo FCS_REPO_LOCAL let packages = - [ "Fable.AST", doNothing - "Fable.Core", doNothing - "Fable.Cli", - (fun () -> - Publish.loadReleaseVersion "src/Fable.Cli" - |> updateVersionInFableTransforms - - buildLibrary ()) - "fable-metadata", doNothing - "fable-publish-utils", doNothing - "fable-standalone", (fun () -> buildStandalone {| minify = true; watch = false |}) - "fable-compiler-js", (fun () -> buildCompilerJs true) ] + ["Fable.AST", doNothing + "Fable.Core", doNothing + "Fable.Cli", (fun () -> + Publish.loadReleaseVersion "src/Fable.Cli" |> updateVersionInFableTransforms + buildLibrary()) + "fable-metadata", doNothing + "fable-publish-utils", doNothing + "fable-standalone", fun () -> buildStandalone {|minify=true; watch=false|} + "fable-compiler-js", fun () -> buildCompilerJs true + ] let publishPackages restArgs = let packages = match List.tryHead restArgs with - | Some pkg -> - packages - |> List.filter (fun (name, _) -> name = pkg) + | Some pkg -> packages |> List.filter (fun (name,_) -> name = pkg) | None -> packages - for (pkg, buildAction) in packages do if System.Char.IsUpper pkg.[0] then - pushNuget ("src" pkg pkg + ".fsproj") [ "Pack", "true" ] buildAction + pushNuget ("src" pkg pkg + ".fsproj") ["Pack", "true"] buildAction else pushNpm ("src" pkg) buildAction @@ -610,62 +519,46 @@ match argsLower with // |> List.singleton |> quicktest // | "download-standalone"::_ -> downloadStandalone() // | "coverage"::_ -> coverage() -| "test" :: _ -> test () -| "test-js" :: _ -> testJs (minify) -| "test-js-fast" :: _ -> testJsFast () -| "test-react" :: _ -> testReact () -| "test-integration" :: _ -> testIntegration () +| "test"::_ -> test() +| "test-js"::_ -> testJs(minify) +| "test-js-fast"::_ -> testJsFast() +| "test-react"::_ -> testReact() +| "test-integration"::_ -> testIntegration() | "quicktest" :: _ -> buildLibraryIfNotExists () run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --forcePkgs --python" | "jupyter" :: _ -> buildLibraryIfNotExists () - - run - "dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src --exclude Fable.Core --forcePkgs --python" - -| "run" :: _ -> - buildLibraryIfNotExists () + run "dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src --exclude Fable.Core --forcePkgs --python" +| "run"::_ -> + buildLibraryIfNotExists() // Don't take it from pattern matching as that one uses lowered args let restArgs = args |> List.skip 1 |> String.concat " " - run $"dotnet run -c Release -p {resolveDir "src/Fable.Cli"} -- {restArgs}" - -| "package" :: _ -> - let pkgInstallCmd = - buildLocalPackage (resolveDir "temp/pkg") + run $"""dotnet run -c Release -p {resolveDir "src/Fable.Cli"} -- {restArgs}""" +| "package"::_ -> + let pkgInstallCmd = buildLocalPackage (resolveDir "temp/pkg") printfn $"\nPackage has been created, use the following command to install it:\n {pkgInstallCmd}\n" -| "package-core" :: _ -> - let pkgInstallCmd = - buildLocalPackageWith - (resolveDir "temp/pkg") - "add package Fable.Core" - (resolveDir "src/Fable.Core/Fable.Core.fsproj") - ignore - +| "package-core"::_ -> + let pkgInstallCmd = buildLocalPackageWith (resolveDir "temp/pkg") "add package Fable.Core" (resolveDir "src/Fable.Core/Fable.Core.fsproj") ignore printfn $"\nFable.Core package has been created, use the following command to install it:\n {pkgInstallCmd}\n" -| ("watch-library") :: _ -> watchLibrary () -| ("fable-library" -| "library") :: _ -> buildLibrary () -| ("fable-library-ts" -| "library-ts") :: _ -> buildLibraryTs () -| ("fable-compiler-js" -| "compiler-js") :: _ -> buildCompilerJs (minify) -| ("fable-standalone" -| "standalone") :: _ -> buildStandalone {| minify = minify; watch = false |} -| "watch-standalone" :: _ -> buildStandalone {| minify = false; watch = true |} -| "publish" :: restArgs -> publishPackages restArgs -| "github-release" :: _ -> +| ("watch-library")::_ -> watchLibrary() +| ("fable-library"|"library")::_ -> buildLibrary() +| ("fable-library-ts"|"library-ts")::_ -> buildLibraryTs() +| ("fable-compiler-js"|"compiler-js")::_ -> buildCompilerJs(minify) +| ("fable-standalone"|"standalone")::_ -> buildStandalone {|minify=minify; watch=false|} +| "watch-standalone"::_ -> buildStandalone {|minify=false; watch=true|} +| "publish"::restArgs -> publishPackages restArgs +| "github-release"::_ -> publishPackages [] githubRelease () -| "sync-fcs-repo" :: _ -> syncFcsRepo () -| "copy-fcs-repo" :: _ -> copyFcsRepo "../fsharp" -| "test-repos" :: _ -> testRepos () +| "sync-fcs-repo"::_ -> syncFcsRepo() +| "copy-fcs-repo"::_ -> copyFcsRepo "../fsharp" +| "test-repos"::_ -> testRepos() | _ -> - printfn - """Please pass a target name. Examples: + printfn """Please pass a target name. Examples: - Use `test` to run tests: dotnet fsi build.fsx test @@ -680,4 +573,4 @@ match argsLower with dotnet fsi build.fsx quicktest """ -printfn "Build finished successfully" +printfn "Build finished successfully" \ No newline at end of file diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 1576935795..0e710579f4 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -12,7 +12,7 @@ open Fable.AST.Python type ReturnStrategy = | Return | NoReturn - | NoBreak + | NoBreak // Used in switch statement blocks type ITailCallOpportunity = abstract Label: string @@ -34,6 +34,8 @@ type Context = TailCallOpportunity: ITailCallOpportunity option OptimizeTailCall: unit -> unit ScopedTypeParams: Set + + } type IPythonCompiler = @@ -303,17 +305,16 @@ module Util = let stmts = afe.Body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. - if stmts.Length = 1 - && (stmts.[0] :? Babel.ReturnStatement) then - let body = - com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, afe.Body) - - Lambda.Create(arguments, body), [] - else + match stmts with + | [| stmt |] when (stmt :? Babel.ReturnStatement) -> + let rtn = stmt :?> Babel.ReturnStatement + let body, stmts = com.TransformAsExpr(ctx, rtn.Argument) + Lambda.Create(arguments, body), stmts + | _ -> let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, afe.Body) - let name = Helpers.getIdentifier ("lifted") + let name = Helpers.getIdentifier "lifted" let func = FunctionDef.Create(name = name, args = arguments, body = body) @@ -420,12 +421,11 @@ module Util = let arguments = Arguments.Create(args = args) - match fe.Body.Body.Length with - | 1 -> - let body = - com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, fe.Body) - - Lambda.Create(arguments, body), [] + match fe.Body.Body with + | [| stmt |] when (stmt :? Babel.ExpressionStatement) -> + let expr = stmt :?> Babel.ExpressionStatement + let body, stmts = com.TransformAsExpr(ctx, expr.Expression) + Lambda.Create(arguments, body), stmts | _ -> let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, fe.Body) @@ -597,8 +597,14 @@ module Util = Name.Create(Identifier("Exception"), ctx = Load) |> Some + // Insert a ex.message = str(ex) for all aliased exceptions. let identifier = Identifier(cc.Param.Name) - + let idName = Name.Create(identifier, Load) + let message = Identifier("message") + let trg = Attribute.Create(idName, message, Store) + let value = Call.Create(Name.Create(Identifier("str"), Load), [idName]) + let msg = Assign.Create([trg], value) + let body = msg :: body let handlers = [ ExceptHandler.Create(``type`` = exn, name = identifier, body = body) ] diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index e7bd7cf7fe..59c04b69f4 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -1646,7 +1646,7 @@ type IfExp = type Lambda = { Args: Arguments - Body: Statement list + Body: Expression } static member Create(args, body): Expression = { Args = args; Body = body } |> Lambda @@ -1661,8 +1661,7 @@ type Lambda = printer.PrintCommaSeparatedList(x.Args.Args |> List.map (fun arg -> arg :> IPrint)) printer.Print(": ") - for stmt in x.Body do - printer.Print(stmt) + printer.Print(x.Body) /// A tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an assignment target From 175623788cc1c19df6ca032bb02e855d6afb763e Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 23 Jan 2021 16:45:21 +0100 Subject: [PATCH 042/145] Fix argument annotations --- src/Fable.Transforms/Python/Babel2Python.fs | 14 +++++++++----- src/Fable.Transforms/Python/Python.fs | 5 +++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 0e710579f4..a82aae1ebd 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -65,6 +65,7 @@ module Helpers = let cleanNameAsPythonIdentifier (name: string) = match name with | "this" -> "self" // TODO: Babel should use ThisExpression to avoid this hack. + | "async" -> "asyncio" | _ -> name.Replace('$', '_') let rewriteFableImport moduleName = @@ -72,10 +73,8 @@ module Helpers = Regex(".*\/fable-library[\.0-9]*\/(?[^\/]*)\.js", RegexOptions.Compiled) let m = _reFableLib.Match(moduleName) - printfn "Count: %d" m.Groups.Count - if m.Groups.Count > 1 then - let pymodule = m.Groups.["module"].Value.ToLower() + let pymodule = m.Groups.["module"].Value.ToLower() |> cleanNameAsPythonIdentifier let moduleName = String.concat "." [ "fable"; pymodule ] @@ -301,7 +300,12 @@ module Util = |> List.ofArray |> List.map (fun pattern -> Arg.Create(Identifier pattern.Name)) - let arguments = Arguments.Create(args = args) + let arguments = + let args = + match args with + | [] -> [ Arg.Create(Identifier("_"), Name.Create(Identifier("None"), Load)) ] // Need to receive unit + | _ -> args + Arguments.Create(args = args) let stmts = afe.Body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. @@ -653,7 +657,7 @@ module Util = /// Transform Babel program to Python module. let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = - let returnStrategy = ReturnStrategy.NoReturn + let returnStrategy = ReturnStrategy.Return let stmt: Statement list = [ diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 59c04b69f4..ddebc3de71 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -561,6 +561,11 @@ type Arg = member x.Print(printer) = let (Identifier name) = x.Arg printer.Print(name) + match x.Annotation with + | Some ann -> + printer.Print (" = ") + printer.Print(ann) + | _ -> () type Keyword = { From b2a8ab637236cf5e678580f836a07f298a29ccc5 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 23 Jan 2021 20:48:06 +0100 Subject: [PATCH 043/145] Fix lifting out of object properties --- src/Fable.Transforms/Python/Babel2Python.fs | 28 +++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index a82aae1ebd..411303de16 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -359,20 +359,34 @@ module Util = Call.Create(func, args), stmts @ stmtArgs | :? Babel.Super -> Name.Create(Identifier("super().__init__"), ctx = Load), [] | :? Babel.ObjectExpression as oe -> - let kv = + let keys, values, stmts = [ for prop in oe.Properties do match prop with | :? Babel.ObjectProperty as op -> - let key, _ = com.TransformAsExpr(ctx, op.Key) - let value, _ = com.TransformAsExpr(ctx, op.Value) - key, value + let key, stmts1 = com.TransformAsExpr(ctx, op.Key) + let value, stmts2 = com.TransformAsExpr(ctx, op.Value) + key, value, stmts1 @ stmts2 + | :? Babel.ObjectMethod as om -> + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, om.Body) + let key, stmts = com.TransformAsExpr(ctx, om.Key) + let args = + om.Params + |> List.ofArray + |> List.map (fun pattern -> Arg.Create(Identifier pattern.Name)) + + let arguments = Arguments.Create(args = args) + let name = Helpers.getIdentifier "lifted" + + let func = + FunctionDef.Create(name = name, args = arguments, body = body) + key, Name.Create(name, Load), stmts @ [func] + | _ -> failwith $"transformAsExpr: unhandled object expression property: {prop}" ] + |> List.unzip3 - let keys = kv |> List.map fst - let values = kv |> List.map snd - Dict.Create(keys = keys, values = values), [] + Dict.Create(keys = keys, values = values), stmts |> List.collect id | Babel.Patterns.EmitExpression (value, args, _) -> let args, stmts = args From 4215d9506d895fa06d6542ee87ef8fedaf615570 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 25 Jan 2021 23:35:52 +0100 Subject: [PATCH 044/145] Handle varargs --- src/Fable.Transforms/Python/Babel2Python.fs | 119 ++++++++++++-------- src/Fable.Transforms/Python/Python.fs | 38 ++++++- 2 files changed, 109 insertions(+), 48 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 411303de16..f371922204 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -47,7 +47,7 @@ type IPythonCompiler = abstract TransformAsStatements: Context * ReturnStrategy * Babel.Expression -> Statement list abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement list abstract TransformAsImports: Context * Babel.ImportDeclaration -> Statement list - //abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> (Pattern array) * BlockStatement + abstract TransformFunction: Context * Babel.Identifier * Babel.Pattern array * Babel.BlockStatement -> Statement abstract WarnOnlyOnce: string * ?range:SourceLocation -> unit @@ -203,12 +203,26 @@ module Util = | :? Babel.ClassMethod as cm -> let self = Arg.Create(Identifier("self")) - let args = + let parms = cm.Params |> List.ofArray - |> List.map (fun arg -> Arg.Create(Identifier(arg.Name))) - let arguments = Arguments.Create(args = self :: args) + let args = + parms + |> List.choose (function + | :? Babel.Identifier as id -> + Arg.Create(Identifier(id.Name)) |> Some + | _ -> None) + + let varargs = + parms + |> List.choose (function + | :? Babel.RestElement as rest -> + Arg.Create(Identifier(rest.Argument.Name)) |> Some + | _ -> None) + |> List.tryHead + + let arguments = Arguments.Create(args = self :: args, ?vararg=varargs) match cm.Kind with | "method" -> @@ -218,7 +232,7 @@ module Util = let name = match cm.Key with | :? Babel.Identifier as id -> Identifier(id.Name) - | _ -> failwith "transformAsClassDef: Unknown key: {cm.Key}" + | _ -> failwith $"transformAsClassDef: Unknown key: {cm.Key}" FunctionDef.Create(name, arguments, body = body) | "constructor" -> @@ -233,10 +247,25 @@ module Util = ] printfn $"Body length: {body.Length}: ${body}" - let name = Helpers.cleanNameAsPythonIdentifier (cls.Id.Value.Name) [ yield! stmts; ClassDef.Create(Identifier(name), body = body, bases = bases) ] + + let transformAsFunction (com: IPythonCompiler) (ctx: Context) (name: Babel.Identifier) (parms: Babel.Pattern array) (body: Babel.BlockStatement) = + let args = + parms + |> List.ofArray + |> List.map + (fun pattern -> + let name = Helpers.cleanNameAsPythonIdentifier (pattern.Name) + Arg.Create(Identifier(name))) + + let arguments = Arguments.Create(args = args) + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) + let name = Helpers.cleanNameAsPythonIdentifier (name.Name) + + FunctionDef.Create(Identifier(name), arguments, body = body) + /// Transform Babel expression as Python expression let rec transformAsExpr (com: IPythonCompiler) @@ -246,34 +275,46 @@ module Util = printfn $"transformAsExpr: {expr}" match expr with + | :? Babel.AssignmentExpression as ae -> + let left, leftStmts = com.TransformAsExpr(ctx, ae.Left) + let right, rightStmts = com.TransformAsExpr(ctx, ae.Right) + match ae.Operator with + | "=" -> + NamedExpr.Create(left, right), leftStmts @ rightStmts + | _ -> failwith $"Unsuppored assingment expression: {ae.Operator}" + | :? Babel.BinaryExpression as be -> let left, leftStmts = com.TransformAsExpr(ctx, be.Left) let right, rightStmts = com.TransformAsExpr(ctx, be.Right) - let bin op = BinOp.Create(left, op, right), leftStmts @ rightStmts - - let cmp op = Compare.Create(left, [ op ], [ right ]), leftStmts @ rightStmts + let toBinOp op = BinOp.Create(left, op, right), leftStmts @ rightStmts + let toCompare op = Compare.Create(left, [ op ], [ right ]), leftStmts @ rightStmts + let toCall name = + let func = Name.Create(Identifier(name), Load) + let args = [left; right] + Call.Create(func, args),leftStmts @ rightStmts match be.Operator with - | "+" -> Add |> bin - | "-" -> Sub |> bin - | "*" -> Mult |> bin - | "/" -> Div |> bin - | "%" -> Mod |> bin - | "**" -> Pow |> bin - | "<<" -> LShift |> bin - | ">>" -> RShift |> bin - | "|" -> BitOr |> bin - | "^" -> BitXor |> bin - | "&" -> BitAnd |> bin + | "+" -> Add |> toBinOp + | "-" -> Sub |> toBinOp + | "*" -> Mult |> toBinOp + | "/" -> Div |> toBinOp + | "%" -> Mod |> toBinOp + | "**" -> Pow |> toBinOp + | "<<" -> LShift |> toBinOp + | ">>" -> RShift |> toBinOp + | "|" -> BitOr |> toBinOp + | "^" -> BitXor |> toBinOp + | "&" -> BitAnd |> toBinOp | "===" - | "==" -> Eq |> cmp + | "==" -> Eq |> toCompare | "!==" - | "!=" -> NotEq |> cmp - | ">" -> Gt |> cmp - | ">=" -> GtE |> cmp - | "<" -> Lt |> cmp - | "<=" -> LtE |> cmp + | "!=" -> NotEq |> toCompare + | ">" -> Gt |> toCompare + | ">=" -> GtE |> toCompare + | "<" -> Lt |> toCompare + | "<=" -> LtE |> toCompare + | "isinstance" -> toCall "isinstance" | _ -> failwith $"Unknown operator: {be.Operator}" | :? Babel.UnaryExpression as ue -> @@ -485,7 +526,7 @@ module Util = let name = Name.Create(name, Load) Call.Create(name), stmts @ [ func ] - | _ -> failwith $"Unhandled value: {expr}" + | _ -> failwith $"transformAsExpr: Unhandled value: {expr}" /// Transform Babel expressions as Python statements. let rec transformExpressionAsStatements @@ -520,8 +561,8 @@ module Util = ) ] - | _ -> failwith "transformExpressionAsStatements: unknown property {me.Property}" - | _ -> failwith $"AssignmentExpression, unknow expression: {ae.Left}" + | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" + | _ -> failwith $"AssignmentExpression, unknown expression: {ae.Left}" [ yield! stmts; Assign.Create(targets = targets, value = value) ] @@ -667,6 +708,9 @@ module Util = | Some ifStmt -> stmts @ ifStmt | None -> [] | :? Babel.BreakStatement -> [ Break ] + | :? Babel.FunctionDeclaration as fd -> + [ com.TransformFunction(ctx, fd.Id, fd.Params, fd.Body) ] + | :? Babel.ClassDeclaration as cd -> com.TransformAsClassDef(ctx, cd) | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" /// Transform Babel program to Python module. @@ -690,21 +734,8 @@ module Util = yield! stmts yield Assign.Create(targets = targets, value = value) | :? Babel.FunctionDeclaration as fd -> - let args = - fd.Params - |> List.ofArray - |> List.map - (fun pattern -> - let name = Helpers.cleanNameAsPythonIdentifier (pattern.Name) - Arg.Create(Identifier(name))) - - let arguments = Arguments.Create(args = args) - - let body = com.TransformAsStatements(ctx, returnStrategy, fd.Body) - - let name = Helpers.cleanNameAsPythonIdentifier (fd.Id.Name) + yield com.TransformFunction(ctx, fd.Id, fd.Params, fd.Body) - yield FunctionDef.Create(Identifier(name), arguments, body = body) | :? Babel.ClassDeclaration as cd -> yield! com.TransformAsClassDef(ctx, cd) | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" @@ -753,7 +784,7 @@ module Compiler = member bcom.TransformAsStatements(ctx, ret, e) = transformStatementAsStatements bcom ctx ret e member bcom.TransformAsClassDef(ctx, cls) = transformAsClassDef bcom ctx cls - //member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body + member bcom.TransformFunction(ctx, name, args, body) = transformAsFunction bcom ctx name args body member bcom.TransformAsImports(ctx, imp) = transformAsImports bcom ctx imp interface Compiler with diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index ddebc3de71..d332d22e0e 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -121,7 +121,7 @@ module PrinterExtensions = printer.Print("def ") printer.PrintOptional(id |> Option.map (fun x -> x :> IPrint)) printer.Print("(") - printer.PrintCommaSeparatedList(args.Args |> List.map (fun arg -> arg :> IPrint)) + printer.Print(args) printer.Print(")") printer.PrintOptional(returnType |> Option.map (fun x -> x :> IPrint)) printer.Print(":") @@ -213,6 +213,7 @@ type Expression = | Call of Call | Compare of Compare | Lambda of Lambda + | NamedExpr of NamedExpr /// A variable name. id holds the name as a string, and ctx is one of the following types. | Name of Name | Dict of Dict @@ -238,6 +239,7 @@ type Expression = | IfExp (ex) -> printer.Print(ex) | Call (ex) -> printer.Print(ex) | Lambda (ex) -> printer.Print(ex) + | NamedExpr (ex) -> printer.Print(ex) | Name (ex) -> printer.Print(ex) | Yield (expr) -> printer.Print("(Yield)") | YieldFrom (expr) -> printer.Print("(Yield)") @@ -562,8 +564,8 @@ type Arg = let (Identifier name) = x.Arg printer.Print(name) match x.Annotation with - | Some ann -> - printer.Print (" = ") + | Some ann -> + printer.Print("=") printer.Print(ann) | _ -> () @@ -627,7 +629,17 @@ type Arguments = } interface IPrint with - member _.Print(printer) = printer.Print("(Arguments)") + member x.Print(printer) = + match x.Args, x.VarArg with + | [], Some vararg -> + printer.Print("*") + printer.Print(vararg) + | args, Some vararg -> + printer.PrintCommaSeparatedList(args |> List.map (fun arg -> arg :> IPrint)) + printer.Print(", *") + printer.Print(vararg) + | args, None -> + printer.PrintCommaSeparatedList(args |> List.map (fun arg -> arg :> IPrint)) //#region Statements @@ -1249,6 +1261,24 @@ type Attribute = printer.Print(".") printer.Print(this.Attr) +type NamedExpr = + { + Target: Expression + Value: Expression + } + static member Create(target, value) = + { + Target = target + Value = value + } + |> NamedExpr + + interface IPrint with + member this.Print(printer) = + printer.Print(this.Target) + printer.Print(" :=") + printer.Print(this.Value) + /// A subscript, such as l[1]. value is the subscripted object (usually sequence or mapping). slice is an index, slice /// or key. It can be a Tuple and contain a Slice. ctx is Load, Store or Del according to the action performed with the /// subscript. From 627289f6eeeccdb963180deb18ff95400a7e0705 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 31 Jan 2021 08:03:08 +0100 Subject: [PATCH 045/145] Revert changes --- src/Fable.Transforms/Global/Babel.fs | 49 +--------------------------- 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index f02b24d1ef..90d5a2e955 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -242,7 +242,6 @@ type ModuleDeclaration = inherit Node type EmitExpression(value, args, ?loc) = member _.Value: string = value member _.Args: Expression array = args - member _.Loc: SourceLocation option = loc interface Expression with member _.Print(printer) = printer.AddLocation(loc) @@ -340,7 +339,6 @@ type Identifier(name, ?optional, ?typeAnnotation, ?loc) = member _.Name: string = name member _.Optional: bool option = optional member _.TypeAnnotation: TypeAnnotation option = typeAnnotation - member _.Loc: SourceLocation option = loc interface PatternExpression with member _.Name = name member _.Print(printer) = @@ -359,7 +357,6 @@ type RegExpLiteral(pattern, flags_, ?loc) = | RegexSticky -> "y") |> Seq.fold (+) "" member _.Pattern: string = pattern member _.Flags: string = flags - member _.Loc: SourceLocation option = loc interface Literal with member _.Print(printer) = printer.Print("/", ?loc=loc) @@ -684,7 +681,6 @@ type FunctionDeclaration(``params``, body, id, ?returnType, ?typeParameters, ?lo member _.Id: Identifier = id member _.ReturnType: TypeAnnotation option = returnType member _.TypeParameters: TypeParameterDeclaration option = typeParameters - member _.Loc: SourceLocation option = loc interface Declaration with member _.Print(printer) = printer.PrintFunction(Some id, ``params``, body, typeParameters, returnType, loc, isDeclaration=true) @@ -903,7 +899,6 @@ type CallExpression(callee, arguments, ?loc) = member _.Callee: Expression = callee // member _.Arguments: Choice array = arguments member _.Arguments: Expression array = arguments - member _.Loc: SourceLocation option = loc interface Expression with member _.Print(printer) = printer.AddLocation(loc) @@ -1212,9 +1207,7 @@ type ImportSpecifier = inherit Node /// If it is a basic named import, such as in import {foo} from "mod", both imported and local are equivalent Identifier nodes; in this case an Identifier node representing foo. /// If it is an aliased import, such as in import {foo as bar} from "mod", the imported field is an Identifier node representing foo, and the local field is an Identifier node representing bar. type ImportMemberSpecifier(local: Identifier, imported) = - member _.Local: Identifier = local member _.Imported: Identifier = imported - interface ImportSpecifier with member this.Print(printer) = // Don't print the braces, this will be done in the import declaration @@ -1225,16 +1218,12 @@ type ImportMemberSpecifier(local: Identifier, imported) = /// A default import specifier, e.g., foo in import foo from "mod". type ImportDefaultSpecifier(local) = - member _.Local: Identifier = local - interface ImportSpecifier with member _.Print(printer) = printer.Print(local) /// A namespace import specifier, e.g., * as foo in import * as foo from "mod". type ImportNamespaceSpecifier(local) = - member _.Local: Identifier = local - interface ImportSpecifier with member _.Print(printer) = printer.Print("* as ") @@ -1550,40 +1539,4 @@ type InterfaceDeclaration(id, body, ?extends_, ?typeParameters, ?implements_) = printer.Print(" implements ") printer.PrintArray(implements, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) printer.Print(" ") - printer.Print(body) - -module Patterns = - let (|EmitExpression|_|) (expr: Expression) = - match expr with - | :? EmitExpression as ex -> Some (ex.Value, ex.Args, ex.Loc) - | _ -> None - - let (|Identifier|_|) (expr: Expression) = - match expr with - | :? Identifier as ex -> Some (ex.Name, ex.Optional, ex.TypeAnnotation, ex.Loc) - | _ -> None - - let (|RegExpLiteral|_|) (literal: Literal) = - match literal with - | :? RegExpLiteral as lit -> Some (lit.Pattern, lit.Flags, lit.Loc) - | _ -> None - - let (|CallExpression|_|) (expr: Expression) = - match expr with - | :? CallExpression as ex -> Some (ex.Callee, ex.Arguments, ex.Loc) - | _ -> None - - let (|ObjectTypeAnnotation|_|) (typ: TypeAnnotationInfo) = - match typ with - | :? ObjectTypeAnnotation as ann -> Some (ann.Properties, ann.Indexers, ann.CallProperties, ann.InternalSlots, ann.Exact) - | _ -> None - - let (|InterfaceExtends|_|) (node: Node) = - match node with - | :? InterfaceExtends as n -> Some (n.Id, n.TypeParameters) - | _ -> None - - let (|InterfaceDeclaration|_|) (decl: Declaration) = - match decl with - | :? InterfaceDeclaration as decl -> Some (decl.Id, decl.Body, decl.Extends, decl.Implements, decl.TypeParameters) - | _ -> None + printer.Print(body) \ No newline at end of file From b37affeffeb52b4bdfe59c69ebbd575d91cbbf21 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 31 Jan 2021 14:12:47 +0100 Subject: [PATCH 046/145] Refactor to babel unions --- src/Fable.Transforms/Fable2Babel.fs | 2 +- src/Fable.Transforms/Python/Babel2Python.fs | 176 ++++++++++---------- src/Fable.Transforms/Python/Python.fs | 8 +- 3 files changed, 91 insertions(+), 95 deletions(-) diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 9e101da2d3..c41ccc5a7b 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -749,7 +749,7 @@ module Util = CallExpression.AsExpr(funcExpr, List.toArray args, ?loc=r) let callFunctionWithThisContext r funcExpr (args: Expression list) = - let args = (Identifier.AsExpr("this"))::args |> List.toArray + let args = thisExpr::args |> List.toArray CallExpression.AsExpr(get None funcExpr "call", args, ?loc=r) let emitExpression range (txt: string) args = diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index f371922204..1bbe865262 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -34,8 +34,6 @@ type Context = TailCallOpportunity: ITailCallOpportunity option OptimizeTailCall: unit -> unit ScopedTypeParams: Set - - } type IPythonCompiler = @@ -43,8 +41,9 @@ type IPythonCompiler = abstract GetAllImports: unit -> Statement list abstract GetImportExpr: Context * selector:string * path:string * SourceLocation option -> Expression abstract TransformAsExpr: Context * Babel.Expression -> Expression * Statement list - abstract TransformAsStatements: Context * ReturnStrategy * Babel.Statement -> Statement list abstract TransformAsStatements: Context * ReturnStrategy * Babel.Expression -> Statement list + abstract TransformAsStatements: Context * ReturnStrategy * Babel.Statement -> Statement list + abstract TransformAsStatements: Context * ReturnStrategy * Babel.BlockStatement -> Statement list abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement list abstract TransformAsImports: Context * Babel.ImportDeclaration -> Statement list abstract TransformFunction: Context * Babel.Identifier * Babel.Pattern array * Babel.BlockStatement -> Statement @@ -97,13 +96,13 @@ module Helpers = printfn $"hasNoSideEffects: {e}" match e with - | Constant (_) -> true - | Dict ({ Keys = keys }) -> keys.IsEmpty // Empty object - | Name (_) -> true // E.g `void 0` is translated to Name(None) + | Constant _ -> true + | Dict { Keys = keys } -> keys.IsEmpty // Empty object + | Name _ -> true // E.g `void 0` is translated to Name(None) | _ -> false match stmt with - | Expr (expr) -> + | Expr expr -> if hasNoSideEffects expr.Value then None else @@ -134,7 +133,7 @@ module Util = for expr in imp.Specifiers do match expr with - | :? Babel.ImportMemberSpecifier as im -> + | Babel.ImportMemberSpecifier(im) -> printfn "ImportMemberSpecifier" let alias = @@ -147,7 +146,7 @@ module Util = ) importFroms.Add(alias) - | :? Babel.ImportDefaultSpecifier as ids -> + | Babel.ImportDefaultSpecifier(ids) -> printfn "ImportDefaultSpecifier" let alias = @@ -160,7 +159,7 @@ module Util = ) imports.Add(alias) - | :? Babel.ImportNamespaceSpecifier as ins -> + | Babel.ImportNamespaceSpecifier(ins) -> printfn "ImportNamespaceSpecifier: %A" (ins.Local.Name, ins.Local.Name) let alias = @@ -173,8 +172,6 @@ module Util = ) importFroms.Add(alias) - | _ -> failwith $"Unhandled import: {expr}" - [ if imports.Count > 0 then Import.Create(imports |> List.ofSeq) @@ -200,7 +197,7 @@ module Util = [ for mber in cls.Body.Body do match mber with - | :? Babel.ClassMethod as cm -> + | Babel.ClassMethod(cm) -> let self = Arg.Create(Identifier("self")) let parms = @@ -210,14 +207,14 @@ module Util = let args = parms |> List.choose (function - | :? Babel.Identifier as id -> + | Babel.IdentifierPattern(id) -> Arg.Create(Identifier(id.Name)) |> Some | _ -> None) let varargs = parms |> List.choose (function - | :? Babel.RestElement as rest -> + | Babel.RestElement(rest) -> Arg.Create(Identifier(rest.Argument.Name)) |> Some | _ -> None) |> List.tryHead @@ -227,11 +224,11 @@ module Util = match cm.Kind with | "method" -> let body = - com.TransformAsStatements(ctx, ReturnStrategy.Return, cm.Body) + com.TransformAsStatements(ctx, ReturnStrategy.Return, cm.Body |> Babel.BlockStatement) let name = match cm.Key with - | :? Babel.Identifier as id -> Identifier(id.Name) + | Babel.Identifier(id) -> Identifier(id.Name) | _ -> failwith $"transformAsClassDef: Unknown key: {cm.Key}" FunctionDef.Create(name, arguments, body = body) @@ -239,7 +236,7 @@ module Util = let name = Identifier("__init__") let body = - com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, cm.Body) + com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, cm.Body |> Babel.BlockStatement) FunctionDef.Create(name, arguments, body = body) | _ -> failwith $"transformAsClassDef: Unknown kind: {cm.Kind}" @@ -261,7 +258,7 @@ module Util = Arg.Create(Identifier(name))) let arguments = Arguments.Create(args = args) - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Babel.BlockStatement) let name = Helpers.cleanNameAsPythonIdentifier (name.Name) FunctionDef.Create(Identifier(name), arguments, body = body) @@ -275,7 +272,7 @@ module Util = printfn $"transformAsExpr: {expr}" match expr with - | :? Babel.AssignmentExpression as ae -> + | Babel.AssignmentExpression(ae) -> let left, leftStmts = com.TransformAsExpr(ctx, ae.Left) let right, rightStmts = com.TransformAsExpr(ctx, ae.Right) match ae.Operator with @@ -283,7 +280,7 @@ module Util = NamedExpr.Create(left, right), leftStmts @ rightStmts | _ -> failwith $"Unsuppored assingment expression: {ae.Operator}" - | :? Babel.BinaryExpression as be -> + | Babel.BinaryExpression(be) -> let left, leftStmts = com.TransformAsExpr(ctx, be.Left) let right, rightStmts = com.TransformAsExpr(ctx, be.Right) @@ -317,7 +314,7 @@ module Util = | "isinstance" -> toCall "isinstance" | _ -> failwith $"Unknown operator: {be.Operator}" - | :? Babel.UnaryExpression as ue -> + | Babel.UnaryExpression(ue) -> let op = match ue.Operator with | "-" -> USub |> Some @@ -335,7 +332,7 @@ module Util = // TODO: Should be Contant(value=None) but we cannot create that in F# Name.Create(id = Identifier("None"), ctx = Load), stmts - | :? Babel.ArrowFunctionExpression as afe -> + | Babel.ArrowFunctionExpression(afe) -> let args = afe.Params |> List.ofArray @@ -349,10 +346,8 @@ module Util = Arguments.Create(args = args) let stmts = afe.Body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. - match stmts with - | [| stmt |] when (stmt :? Babel.ReturnStatement) -> - let rtn = stmt :?> Babel.ReturnStatement + | [| Babel.ReturnStatement(rtn) |] -> let body, stmts = com.TransformAsExpr(ctx, rtn.Argument) Lambda.Create(arguments, body), stmts | _ -> @@ -365,7 +360,7 @@ module Util = FunctionDef.Create(name = name, args = arguments, body = body) Name.Create(name, Load), [ func ] - | Babel.Patterns.CallExpression (callee, args, loc) -> // FIXME: use transformAsCall + | Babel.CallExpression { Callee=callee; Arguments=args } -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, callee) let args, stmtArgs = @@ -375,7 +370,7 @@ module Util = |> Helpers.unzipArgs Call.Create(func, args), stmts @ stmtArgs - | :? Babel.ArrayExpression as ae -> + | Babel.ArrayExpression(ae) -> let elems, stmts = ae.Elements |> List.ofArray @@ -383,12 +378,12 @@ module Util = |> Helpers.unzipArgs Tuple.Create(elems), stmts - | :? Babel.NumericLiteral as nl -> Constant.Create(value = nl.Value), [] - | :? Babel.StringLiteral as sl -> Constant.Create(value = sl.Value), [] - | Babel.Patterns.Identifier (name, _, _, _) -> - let name = Helpers.cleanNameAsPythonIdentifier name - Name.Create(id = Identifier name, ctx = Load), [] - | :? Babel.NewExpression as ne -> // FIXME: use transformAsCall + | Babel.Literal(Babel.NumericLiteral(nl)) -> Constant.Create(value = nl.Value), [] + | Babel.Literal(Babel.StringLiteral(sl)) -> Constant.Create(value = sl.Value), [] + // | Babel.Pattern(Babel.IdentifierPattern { Name=name }) -> + // let name = Helpers.cleanNameAsPythonIdentifier name + // Name.Create(id = Identifier name, ctx = Load), [] + | Babel.NewExpression(ne) -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, ne.Callee) let args, stmtArgs = @@ -398,17 +393,17 @@ module Util = |> Helpers.unzipArgs Call.Create(func, args), stmts @ stmtArgs - | :? Babel.Super -> Name.Create(Identifier("super().__init__"), ctx = Load), [] - | :? Babel.ObjectExpression as oe -> + | Babel.Super(se) -> Name.Create(Identifier("super().__init__"), ctx = Load), [] + | Babel.ObjectExpression({Properties=properties}) -> let keys, values, stmts = [ - for prop in oe.Properties do + for prop in properties do match prop with - | :? Babel.ObjectProperty as op -> + | Babel.ObjectProperty(op) -> let key, stmts1 = com.TransformAsExpr(ctx, op.Key) let value, stmts2 = com.TransformAsExpr(ctx, op.Value) key, value, stmts1 @ stmts2 - | :? Babel.ObjectMethod as om -> + | Babel.ObjectMethod(om) -> let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, om.Body) let key, stmts = com.TransformAsExpr(ctx, om.Key) let args = @@ -423,12 +418,11 @@ module Util = FunctionDef.Create(name = name, args = arguments, body = body) key, Name.Create(name, Load), stmts @ [func] - | _ -> failwith $"transformAsExpr: unhandled object expression property: {prop}" ] |> List.unzip3 Dict.Create(keys = keys, values = values), stmts |> List.collect id - | Babel.Patterns.EmitExpression (value, args, _) -> + | Babel.EmitExpression { Value=value; Args=args} -> let args, stmts = args |> List.ofArray @@ -439,21 +433,21 @@ module Util = | "void $0" -> args.[0], stmts //| "raise %0" -> Raise.Create() | _ -> Emit.Create(value, args), stmts - | :? Babel.MemberExpression as me -> + | Babel.MemberExpression(me) -> let value, stmts = com.TransformAsExpr(ctx, me.Object) if me.Computed then let attr = match me.Property with - | :? Babel.NumericLiteral as nl -> Constant.Create(nl.Value) - | :? Babel.StringLiteral as literal -> Constant.Create(literal.Value) + | Babel.Literal(Babel.NumericLiteral(nl)) -> Constant.Create(nl.Value) + | Babel.Literal(Babel.StringLiteral(literal)) -> Constant.Create(literal.Value) | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" Subscript.Create(value = value, slice = attr, ctx = Load), stmts else let attr = match me.Property with - | Babel.Patterns.Identifier (name, _, _, _) -> Identifier(name) + | Babel.Identifier { Name=name } -> Identifier(name) | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" let value = @@ -471,8 +465,8 @@ module Util = | _ -> value Attribute.Create(value = value, attr = attr, ctx = Load), stmts - | :? Babel.BooleanLiteral as bl -> Constant.Create(value = bl.Value), [] - | :? Babel.FunctionExpression as fe -> + | Babel.Literal(Babel.BooleanLiteral(bl)) -> Constant.Create(value = bl.Value), [] + | Babel.FunctionExpression(fe) -> let args = fe.Params |> List.ofArray @@ -481,8 +475,7 @@ module Util = let arguments = Arguments.Create(args = args) match fe.Body.Body with - | [| stmt |] when (stmt :? Babel.ExpressionStatement) -> - let expr = stmt :?> Babel.ExpressionStatement + | [| Babel.ExpressionStatement(expr) |] -> let body, stmts = com.TransformAsExpr(ctx, expr.Expression) Lambda.Create(arguments, body), stmts | _ -> @@ -495,14 +488,14 @@ module Util = FunctionDef.Create(name = name, args = arguments, body = body) Name.Create(name, Load), [ func ] - | :? Babel.ConditionalExpression as ce -> + | Babel.ConditionalExpression(ce) -> let test, stmts1 = com.TransformAsExpr(ctx, ce.Test) let body, stmts2 = com.TransformAsExpr(ctx, ce.Consequent) let orElse, stmts3 = com.TransformAsExpr(ctx, ce.Alternate) IfExp.Create(test, body, orElse), stmts1 @ stmts2 @ stmts3 - | :? Babel.NullLiteral -> Name.Create(Identifier("None"), ctx = Load), [] - | :? Babel.SequenceExpression as se -> + | Babel.Literal(Babel.NullLiteral(nl)) -> Name.Create(Identifier("None"), ctx = Load), [] + | Babel.SequenceExpression(se) -> // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments let exprs, stmts = se.Expressions @@ -535,24 +528,24 @@ module Util = (returnStrategy: ReturnStrategy) (expr: Babel.Expression) : Statement list = + printfn $"transformExpressionAsStatements: {expr}" match expr with - | :? Babel.AssignmentExpression as ae -> + | Babel.AssignmentExpression(ae) -> let value, stmts = com.TransformAsExpr(ctx, ae.Right) let targets: Expression list = match ae.Left with - | :? Babel.Identifier as identifier -> + | Babel.Identifier(identifier) -> let target = Identifier(Helpers.cleanNameAsPythonIdentifier (identifier.Name)) [ Name.Create(id = target, ctx = Store) ] - | :? Babel.MemberExpression as me -> - match me.Property with - | :? Babel.Identifier as id -> + | Babel.MemberExpression({Property=property}) -> + match property with + | Babel.Identifier(id) -> let attr = Identifier(Helpers.cleanNameAsPythonIdentifier (id.Name)) - [ Attribute.Create( value = Name.Create(id = Identifier("self"), ctx = Load), @@ -560,12 +553,9 @@ module Util = ctx = Store ) ] - - | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" + | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" | _ -> failwith $"AssignmentExpression, unknown expression: {ae.Left}" - [ yield! stmts; Assign.Create(targets = targets, value = value) ] - | _ -> failwith $"transformExpressionAsStatements: unknown expr: {expr}" /// Transform Babel statement as Python statements. @@ -578,20 +568,20 @@ module Util = printfn $"transformStatementAsStatements: {stmt}, returnStrategy: {returnStrategy}" match stmt with - | :? Babel.BlockStatement as bl -> + | Babel.BlockStatement(bl) -> [ for st in bl.Body do yield! com.TransformAsStatements(ctx, returnStrategy, st) ] |> transformBody returnStrategy - | :? Babel.ReturnStatement as rtn -> + | Babel.ReturnStatement(rtn) -> let expr, stmts = transformAsExpr com ctx rtn.Argument match returnStrategy with | ReturnStrategy.NoReturn -> stmts @ [ Expr.Create(expr) ] | _ -> stmts @ [ Return.Create(expr) ] - | :? Babel.VariableDeclaration as vd -> + | Babel.Declaration(Babel.VariableDeclaration(vd)) -> [ for vd in vd.Declarations do let targets: Expression list = @@ -605,18 +595,17 @@ module Util = Assign.Create(targets, expr) | None -> () ] - - | :? Babel.ExpressionStatement as es -> + | Babel.ExpressionStatement(es) -> // Handle Babel expressions that we need to transforme here as Python statements match es.Expression with - | :? Babel.AssignmentExpression -> com.TransformAsStatements(ctx, returnStrategy, es.Expression) + | Babel.AssignmentExpression(ae) -> com.TransformAsStatements(ctx, returnStrategy, ae |> Babel.AssignmentExpression) | _ -> [ let expr, stmts = com.TransformAsExpr(ctx, es.Expression) yield! stmts Expr.Create(expr) ] - | :? Babel.IfStatement as iff -> + | Babel.IfStatement(iff) -> let test, stmts = com.TransformAsExpr(ctx, iff.Test) let body = @@ -632,7 +621,7 @@ module Util = | _ -> [] [ yield! stmts; If.Create(test = test, body = body, orelse = orElse) ] - | :? Babel.WhileStatement as ws -> + | Babel.WhileStatement(ws) -> let expr, stmts = com.TransformAsExpr(ctx, ws.Test) let body = @@ -640,7 +629,7 @@ module Util = |> transformBody ReturnStrategy.NoReturn [ yield! stmts; While.Create(test = expr, body = body, orelse = []) ] - | :? Babel.TryStatement as ts -> + | Babel.TryStatement(ts) -> let body = com.TransformAsStatements(ctx, returnStrategy, ts.Block) let finalBody = @@ -671,7 +660,7 @@ module Util = | _ -> [] [ Try.Create(body = body, handlers = handlers, ?finalBody = finalBody) ] - | :? Babel.SwitchStatement as ss -> + | Babel.SwitchStatement(ss) -> let value, stmts = com.TransformAsExpr(ctx, ss.Discriminant) let rec ifThenElse (fallThrough: Expression option) (cases: Babel.SwitchCase list): Statement list option = @@ -703,16 +692,31 @@ module Util = |> Some let result = ss.Cases |> List.ofArray |> ifThenElse None - match result with | Some ifStmt -> stmts @ ifStmt | None -> [] - | :? Babel.BreakStatement -> [ Break ] - | :? Babel.FunctionDeclaration as fd -> + | Babel.BreakStatement(_) -> [ Break ] + | Babel.Declaration(Babel.FunctionDeclaration(fd)) -> [ com.TransformFunction(ctx, fd.Id, fd.Params, fd.Body) ] - | :? Babel.ClassDeclaration as cd -> com.TransformAsClassDef(ctx, cd) + | Babel.Declaration(Babel.ClassDeclaration(cd)) -> com.TransformAsClassDef(ctx, cd) + // | :? Babel.ForStatement as fs -> + // let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, fs.Body) + // let test = fs.Test + // For.Create(body=body) | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" + let transformBlockStatementAsStatements + (com: IPythonCompiler) + (ctx: Context) + (returnStrategy: ReturnStrategy) + (stmt: Babel.BlockStatement) + : list = + + [ + for stmt in stmt.Body do + yield! com.TransformAsStatements(ctx, returnStrategy, stmt) + ] + /// Transform Babel program to Python module. let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = let returnStrategy = ReturnStrategy.Return @@ -721,9 +725,9 @@ module Util = [ for md in body do match md with - | :? Babel.ExportNamedDeclaration as decl -> + | Babel.ExportNamedDeclaration(decl) -> match decl.Declaration with - | :? Babel.VariableDeclaration as decl -> + | Babel.VariableDeclaration(decl) -> for decls in decl.Declarations do let value, stmts = com.TransformAsExpr(ctx, decls.Init.Value) @@ -733,14 +737,14 @@ module Util = yield! stmts yield Assign.Create(targets = targets, value = value) - | :? Babel.FunctionDeclaration as fd -> + | Babel.FunctionDeclaration(fd) -> yield com.TransformFunction(ctx, fd.Id, fd.Params, fd.Body) - | :? Babel.ClassDeclaration as cd -> yield! com.TransformAsClassDef(ctx, cd) + | Babel.ClassDeclaration(cd) -> yield! com.TransformAsClassDef(ctx, cd) | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" - | :? Babel.ImportDeclaration as imp -> yield! com.TransformAsImports(ctx, imp) - | :? Babel.PrivateModuleDeclaration as pmd -> + | Babel.ImportDeclaration(imp) -> yield! com.TransformAsImports(ctx, imp) + | Babel.PrivateModuleDeclaration(pmd) -> yield! com.TransformAsStatements(ctx, returnStrategy, pmd.Statement) |> transformBody returnStrategy @@ -774,15 +778,12 @@ module Compiler = addWarning com [] range msg member _.GetImportExpr(ctx, selector, path, r) = failwith "Not implemented" - member _.GetAllImports() = imports.Values |> List.ofSeq |> List.map Import member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e - member bcom.TransformAsStatements(ctx, ret, e) = transformExpressionAsStatements bcom ctx ret e - member bcom.TransformAsStatements(ctx, ret, e) = transformStatementAsStatements bcom ctx ret e - + member bcom.TransformAsStatements(ctx, ret, e) = transformBlockStatementAsStatements bcom ctx ret e member bcom.TransformAsClassDef(ctx, cls) = transformAsClassDef bcom ctx cls member bcom.TransformFunction(ctx, name, args, body) = transformAsFunction bcom ctx name args body member bcom.TransformAsImports(ctx, imp) = transformAsImports bcom ctx imp @@ -795,11 +796,8 @@ module Compiler = member _.GetEntity(fullName) = com.GetEntity(fullName) member _.GetImplementationFile(fileName) = com.GetImplementationFile(fileName) member _.GetRootModule(fileName) = com.GetRootModule(fileName) - member _.GetOrAddInlineExpr(fullName, generate) = com.GetOrAddInlineExpr(fullName, generate) - member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) - member _.AddLog(msg, severity, ?range, ?fileName: string, ?tag: string) = com.AddLog(msg, severity, ?range = range, ?fileName = fileName, ?tag = tag) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index d332d22e0e..c0abc5c9cd 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -176,8 +176,6 @@ type AST = | Keyword of Keyword | Arg of Arg - - interface IPrint with member x.Print(printer: Printer) = match x with @@ -759,12 +757,12 @@ type For = TypeComment: string option } - static member Create(target, iter, body, orelse, ?typeComment) = + static member Create(target, iter, ?body, ?orelse, ?typeComment) = { Target = target Iterator = iter - Body = body - Else = orelse + Body = defaultArg body [] + Else = defaultArg orelse [] TypeComment = typeComment } From dc1f822177b61e71d9160cb99b13edcc1fd37336 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 31 Jan 2021 14:49:42 +0100 Subject: [PATCH 047/145] Simplify pattern matching --- src/Fable.Transforms/Python/Babel2Python.fs | 108 +++++++++----------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 1bbe865262..fa3d126432 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -159,14 +159,14 @@ module Util = ) imports.Add(alias) - | Babel.ImportNamespaceSpecifier(ins) -> - printfn "ImportNamespaceSpecifier: %A" (ins.Local.Name, ins.Local.Name) + | Babel.ImportNamespaceSpecifier { Local = { Name = name } } -> + printfn "ImportNamespaceSpecifier: %A" (name, name) let alias = Alias.Create( Identifier(pymodule), - if pymodule <> ins.Local.Name then - Identifier(ins.Local.Name) |> Some + if pymodule <> name then + Identifier(name) |> Some else None ) @@ -272,17 +272,16 @@ module Util = printfn $"transformAsExpr: {expr}" match expr with - | Babel.AssignmentExpression(ae) -> - let left, leftStmts = com.TransformAsExpr(ctx, ae.Left) - let right, rightStmts = com.TransformAsExpr(ctx, ae.Right) - match ae.Operator with - | "=" -> - NamedExpr.Create(left, right), leftStmts @ rightStmts - | _ -> failwith $"Unsuppored assingment expression: {ae.Operator}" - - | Babel.BinaryExpression(be) -> - let left, leftStmts = com.TransformAsExpr(ctx, be.Left) - let right, rightStmts = com.TransformAsExpr(ctx, be.Right) + | Babel.AssignmentExpression { Left=left; Operator=operator; Right=right } -> + let left, leftStmts = com.TransformAsExpr(ctx, left) + let right, rightStmts = com.TransformAsExpr(ctx, right) + match operator with + | "=" -> NamedExpr.Create(left, right), leftStmts @ rightStmts + | _ -> failwith $"Unsuppored assingment expression: {operator}" + + | Babel.BinaryExpression { Left=left; Operator=operator; Right=right } -> + let left, leftStmts = com.TransformAsExpr(ctx, left) + let right, rightStmts = com.TransformAsExpr(ctx, right) let toBinOp op = BinOp.Create(left, op, right), leftStmts @ rightStmts let toCompare op = Compare.Create(left, [ op ], [ right ]), leftStmts @ rightStmts @@ -291,7 +290,7 @@ module Util = let args = [left; right] Call.Create(func, args),leftStmts @ rightStmts - match be.Operator with + match operator with | "+" -> Add |> toBinOp | "-" -> Sub |> toBinOp | "*" -> Mult |> toBinOp @@ -312,19 +311,19 @@ module Util = | "<" -> Lt |> toCompare | "<=" -> LtE |> toCompare | "isinstance" -> toCall "isinstance" - | _ -> failwith $"Unknown operator: {be.Operator}" + | _ -> failwith $"Unknown operator: {operator}" - | Babel.UnaryExpression(ue) -> + | Babel.UnaryExpression { Operator=operator; Argument=arg } -> let op = - match ue.Operator with + match operator with | "-" -> USub |> Some | "+" -> UAdd |> Some | "~" -> Invert |> Some | "!" -> Not |> Some | "void" -> None - | _ -> failwith $"Unhandled unary operator: {ue.Operator}" + | _ -> failwith $"Unhandled unary operator: {operator}" - let operand, stmts = com.TransformAsExpr(ctx, ue.Argument) + let operand, stmts = com.TransformAsExpr(ctx, arg) match op with | Some op -> UnaryOp.Create(op, operand), stmts @@ -332,9 +331,9 @@ module Util = // TODO: Should be Contant(value=None) but we cannot create that in F# Name.Create(id = Identifier("None"), ctx = Load), stmts - | Babel.ArrowFunctionExpression(afe) -> + | Babel.ArrowFunctionExpression { Params=parms; Body=body} -> let args = - afe.Params + parms |> List.ofArray |> List.map (fun pattern -> Arg.Create(Identifier pattern.Name)) @@ -345,15 +344,13 @@ module Util = | _ -> args Arguments.Create(args = args) - let stmts = afe.Body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. + let stmts = body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. match stmts with | [| Babel.ReturnStatement(rtn) |] -> let body, stmts = com.TransformAsExpr(ctx, rtn.Argument) Lambda.Create(arguments, body), stmts | _ -> - let body = - com.TransformAsStatements(ctx, ReturnStrategy.Return, afe.Body) - + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) let name = Helpers.getIdentifier "lifted" let func = @@ -380,21 +377,21 @@ module Util = Tuple.Create(elems), stmts | Babel.Literal(Babel.NumericLiteral(nl)) -> Constant.Create(value = nl.Value), [] | Babel.Literal(Babel.StringLiteral(sl)) -> Constant.Create(value = sl.Value), [] - // | Babel.Pattern(Babel.IdentifierPattern { Name=name }) -> - // let name = Helpers.cleanNameAsPythonIdentifier name - // Name.Create(id = Identifier name, ctx = Load), [] - | Babel.NewExpression(ne) -> // FIXME: use transformAsCall - let func, stmts = com.TransformAsExpr(ctx, ne.Callee) + | Babel.Identifier { Name=name } -> + let name = Helpers.cleanNameAsPythonIdentifier name + Name.Create(id = Identifier name, ctx = Load), [] + | Babel.NewExpression { Callee=callee; Arguments=args} -> // FIXME: use transformAsCall + let func, stmts = com.TransformAsExpr(ctx, callee) let args, stmtArgs = - ne.Arguments + args |> List.ofArray |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) |> Helpers.unzipArgs Call.Create(func, args), stmts @ stmtArgs | Babel.Super(se) -> Name.Create(Identifier("super().__init__"), ctx = Load), [] - | Babel.ObjectExpression({Properties=properties}) -> + | Babel.ObjectExpression { Properties=properties } -> let keys, values, stmts = [ for prop in properties do @@ -422,7 +419,7 @@ module Util = |> List.unzip3 Dict.Create(keys = keys, values = values), stmts |> List.collect id - | Babel.EmitExpression { Value=value; Args=args} -> + | Babel.EmitExpression { Value=value; Args=args } -> let args, stmts = args |> List.ofArray @@ -465,7 +462,7 @@ module Util = | _ -> value Attribute.Create(value = value, attr = attr, ctx = Load), stmts - | Babel.Literal(Babel.BooleanLiteral(bl)) -> Constant.Create(value = bl.Value), [] + | Babel.Literal(Babel.BooleanLiteral { Value=value }) -> Constant.Create(value = value), [] | Babel.FunctionExpression(fe) -> let args = fe.Params @@ -488,17 +485,17 @@ module Util = FunctionDef.Create(name = name, args = arguments, body = body) Name.Create(name, Load), [ func ] - | Babel.ConditionalExpression(ce) -> - let test, stmts1 = com.TransformAsExpr(ctx, ce.Test) - let body, stmts2 = com.TransformAsExpr(ctx, ce.Consequent) - let orElse, stmts3 = com.TransformAsExpr(ctx, ce.Alternate) + | Babel.ConditionalExpression { Test=test; Consequent=consequent; Alternate=alternate } -> + let test, stmts1 = com.TransformAsExpr(ctx, test) + let body, stmts2 = com.TransformAsExpr(ctx, consequent) + let orElse, stmts3 = com.TransformAsExpr(ctx, alternate) IfExp.Create(test, body, orElse), stmts1 @ stmts2 @ stmts3 | Babel.Literal(Babel.NullLiteral(nl)) -> Name.Create(Identifier("None"), ctx = Load), [] - | Babel.SequenceExpression(se) -> + | Babel.SequenceExpression { Expressions=exprs } -> // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments let exprs, stmts = - se.Expressions + exprs |> List.ofArray |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) |> Helpers.unzipArgs @@ -532,11 +529,11 @@ module Util = printfn $"transformExpressionAsStatements: {expr}" match expr with - | Babel.AssignmentExpression(ae) -> - let value, stmts = com.TransformAsExpr(ctx, ae.Right) + | Babel.AssignmentExpression { Left=left; Right=right } -> + let value, stmts = com.TransformAsExpr(ctx, right) let targets: Expression list = - match ae.Left with + match left with | Babel.Identifier(identifier) -> let target = Identifier(Helpers.cleanNameAsPythonIdentifier (identifier.Name)) @@ -554,7 +551,7 @@ module Util = ) ] | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" - | _ -> failwith $"AssignmentExpression, unknown expression: {ae.Left}" + | _ -> failwith $"AssignmentExpression, unknown expression: {left}" [ yield! stmts; Assign.Create(targets = targets, value = value) ] | _ -> failwith $"transformExpressionAsStatements: unknown expr: {expr}" @@ -568,15 +565,12 @@ module Util = printfn $"transformStatementAsStatements: {stmt}, returnStrategy: {returnStrategy}" match stmt with - | Babel.BlockStatement(bl) -> - [ - for st in bl.Body do - yield! com.TransformAsStatements(ctx, returnStrategy, st) - ] + | Babel.BlockStatement(bs) -> + [ yield! com.TransformAsStatements(ctx, returnStrategy, bs) ] |> transformBody returnStrategy - | Babel.ReturnStatement(rtn) -> - let expr, stmts = transformAsExpr com ctx rtn.Argument + | Babel.ReturnStatement { Argument=arg } -> + let expr, stmts = transformAsExpr com ctx arg match returnStrategy with | ReturnStrategy.NoReturn -> stmts @ [ Expr.Create(expr) ] @@ -605,15 +599,15 @@ module Util = yield! stmts Expr.Create(expr) ] - | Babel.IfStatement(iff) -> - let test, stmts = com.TransformAsExpr(ctx, iff.Test) + | Babel.IfStatement { Test=test; Consequent=consequent; Alternate=alternate } -> + let test, stmts = com.TransformAsExpr(ctx, test) let body = - com.TransformAsStatements(ctx, returnStrategy, iff.Consequent) + com.TransformAsStatements(ctx, returnStrategy, consequent) |> transformBody ReturnStrategy.NoReturn let orElse = - match iff.Alternate with + match alternate with | Some alt -> com.TransformAsStatements(ctx, returnStrategy, alt) |> transformBody ReturnStrategy.NoReturn From c6761835ddade3ab06449ce1289baf1c5ff74469 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 31 Jan 2021 15:03:28 +0100 Subject: [PATCH 048/145] Pattern matching fixes --- src/Fable.Transforms/Python/Babel2Python.fs | 40 ++++++++++----------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index fa3d126432..1b7bfbad2c 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -430,22 +430,22 @@ module Util = | "void $0" -> args.[0], stmts //| "raise %0" -> Raise.Create() | _ -> Emit.Create(value, args), stmts - | Babel.MemberExpression(me) -> - let value, stmts = com.TransformAsExpr(ctx, me.Object) + | Babel.MemberExpression { Computed=computed; Object=object; Property=property } -> + let value, stmts = com.TransformAsExpr(ctx, object) - if me.Computed then + if computed then let attr = - match me.Property with + match property with | Babel.Literal(Babel.NumericLiteral(nl)) -> Constant.Create(nl.Value) | Babel.Literal(Babel.StringLiteral(literal)) -> Constant.Create(literal.Value) - | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" + | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" Subscript.Create(value = value, slice = attr, ctx = Load), stmts else let attr = - match me.Property with + match property with | Babel.Identifier { Name=name } -> Identifier(name) - | _ -> failwith $"transformExpressionAsStatements: unknown property {me.Property}" + | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" let value = match value with @@ -589,13 +589,13 @@ module Util = Assign.Create(targets, expr) | None -> () ] - | Babel.ExpressionStatement(es) -> + | Babel.ExpressionStatement { Expression=expression } -> // Handle Babel expressions that we need to transforme here as Python statements - match es.Expression with + match expression with | Babel.AssignmentExpression(ae) -> com.TransformAsStatements(ctx, returnStrategy, ae |> Babel.AssignmentExpression) | _ -> [ - let expr, stmts = com.TransformAsExpr(ctx, es.Expression) + let expr, stmts = com.TransformAsExpr(ctx, expression) yield! stmts Expr.Create(expr) ] @@ -615,11 +615,11 @@ module Util = | _ -> [] [ yield! stmts; If.Create(test = test, body = body, orelse = orElse) ] - | Babel.WhileStatement(ws) -> - let expr, stmts = com.TransformAsExpr(ctx, ws.Test) + | Babel.WhileStatement { Test=test; Body=body } -> + let expr, stmts = com.TransformAsExpr(ctx, test) let body = - com.TransformAsStatements(ctx, returnStrategy, ws.Body) + com.TransformAsStatements(ctx, returnStrategy, body) |> transformBody ReturnStrategy.NoReturn [ yield! stmts; While.Create(test = expr, body = body, orelse = []) ] @@ -654,8 +654,8 @@ module Util = | _ -> [] [ Try.Create(body = body, handlers = handlers, ?finalBody = finalBody) ] - | Babel.SwitchStatement(ss) -> - let value, stmts = com.TransformAsExpr(ctx, ss.Discriminant) + | Babel.SwitchStatement { Discriminant=discriminant; Cases=cases } -> + let value, stmts = com.TransformAsExpr(ctx, discriminant) let rec ifThenElse (fallThrough: Expression option) (cases: Babel.SwitchCase list): Statement list option = match cases with @@ -685,7 +685,7 @@ module Util = [ If.Create(test = test, body = body, ?orelse = ifThenElse None cases) ] |> Some - let result = ss.Cases |> List.ofArray |> ifThenElse None + let result = cases |> List.ofArray |> ifThenElse None match result with | Some ifStmt -> stmts @ ifStmt | None -> [] @@ -703,13 +703,11 @@ module Util = (com: IPythonCompiler) (ctx: Context) (returnStrategy: ReturnStrategy) - (stmt: Babel.BlockStatement) + (block: Babel.BlockStatement) : list = - [ - for stmt in stmt.Body do - yield! com.TransformAsStatements(ctx, returnStrategy, stmt) - ] + [ for stmt in block.Body do + yield! transformStatementAsStatements com ctx returnStrategy stmt ] /// Transform Babel program to Python module. let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = From 5c5d948ec0086cefaf3cd2f4c4b325bef13a93b8 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 31 Jan 2021 16:59:50 +0100 Subject: [PATCH 049/145] Add simple for-loops --- src/Fable.Transforms/Python/Babel2Python.fs | 18 ++- src/Fable.Transforms/Python/Python.fs | 141 ++++++++++--------- src/Fable.Transforms/Python/PythonPrinter.fs | 2 +- 3 files changed, 88 insertions(+), 73 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 1b7bfbad2c..d6adfefa62 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -653,7 +653,7 @@ module Util = handlers | _ -> [] - [ Try.Create(body = body, handlers = handlers, ?finalBody = finalBody) ] + [ Try.AsStatement(body = body, handlers = handlers, ?finalBody = finalBody) ] | Babel.SwitchStatement { Discriminant=discriminant; Cases=cases } -> let value, stmts = com.TransformAsExpr(ctx, discriminant) @@ -693,10 +693,18 @@ module Util = | Babel.Declaration(Babel.FunctionDeclaration(fd)) -> [ com.TransformFunction(ctx, fd.Id, fd.Params, fd.Body) ] | Babel.Declaration(Babel.ClassDeclaration(cd)) -> com.TransformAsClassDef(ctx, cd) - // | :? Babel.ForStatement as fs -> - // let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, fs.Body) - // let test = fs.Test - // For.Create(body=body) + | Babel.ForStatement + { Init=Some({ Declarations= [| { Id=id; Init=Some(init)} |] }) + Test=Some(Babel.BinaryExpression { Left=left; Right=right; Operator="<="}) + Body=body } -> + let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) + let name = Name.Create(Identifier id.Name, Load) + let start, _ = com.TransformAsExpr(ctx, init) + let stop, _ = com.TransformAsExpr(ctx, right) + let iter = Call.Create(Name.Create(Identifier "range", Load), args=[start; stop]) + [ For.AsStatement(target=name, iter=iter, body=body) ] + | Babel.LabeledStatement { Body=body } -> com.TransformAsStatements(ctx, returnStrategy, body) + | Babel.ContinueStatement(_) -> [ Continue ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" let transformBlockStatementAsStatements diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index c0abc5c9cd..9f8c44e657 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -16,7 +16,7 @@ type Printer = module PrinterExtensions = type Printer with - member printer.Print(node: IPrint) = node.Print(printer) + member printer.Print(node: IPrintable) = node.Print(printer) member printer.PrintBlock ( @@ -77,14 +77,14 @@ module PrinterExtensions = ?skipNewLineAtEnd = skipNewLineAtEnd ) - member printer.PrintOptional(before: string, node: IPrint option) = + member printer.PrintOptional(before: string, node: #IPrintable option) = match node with | None -> () | Some node -> printer.Print(before) node.Print(printer) - member printer.PrintOptional(before: string, node: IPrint option, after: string) = + member printer.PrintOptional(before: string, node: IPrintable option, after: string) = match node with | None -> () | Some node -> @@ -92,7 +92,7 @@ module PrinterExtensions = node.Print(printer) printer.Print(after) - member printer.PrintOptional(node: IPrint option) = + member printer.PrintOptional(node: #IPrintable option) = match node with | None -> () | Some node -> node.Print(printer) @@ -104,7 +104,7 @@ module PrinterExtensions = if i < nodes.Length - 1 then printSeparator printer - member printer.PrintCommaSeparatedList(nodes: IPrint list) = + member printer.PrintCommaSeparatedList(nodes: #IPrintable list) = printer.PrintList(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) member printer.PrintCommaSeparatedList(nodes: Expression list) = @@ -119,11 +119,11 @@ module PrinterExtensions = ?isDeclaration ) = printer.Print("def ") - printer.PrintOptional(id |> Option.map (fun x -> x :> IPrint)) + printer.PrintOptional(id) printer.Print("(") printer.Print(args) printer.Print(")") - printer.PrintOptional(returnType |> Option.map (fun x -> x :> IPrint)) + printer.PrintOptional(returnType) printer.Print(":") printer.PrintBlock(body, skipNewLineAtEnd = true) @@ -159,7 +159,7 @@ module PrinterExtensions = printer.Print(operator) printer.ComplexExpressionWithParens(right) -type IPrint = +type IPrintable = abstract Print: Printer -> unit type AST = @@ -176,7 +176,7 @@ type AST = | Keyword of Keyword | Arg of Arg - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = match x with | Expression (ex) -> printer.Print(ex) @@ -223,7 +223,7 @@ type Expression = // member val ColOffset: int = 0 with get, set // member val EndLineno: int option = None with get, set // member val EndColOffset: int option = None with get, set - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = match x with | Attribute (ex) -> printer.Print(ex) @@ -262,7 +262,7 @@ type Operator = - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = let op = match x with @@ -288,7 +288,7 @@ type BoolOperator = - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = let op = match x with @@ -312,7 +312,7 @@ type ComparisonOperator = - interface IPrint with + interface IPrintable with member x.Print(printer) = let op = match x with @@ -337,7 +337,7 @@ type UnaryOperator = - interface IPrint with + interface IPrintable with member this.Print(printer) = let op = match this with @@ -355,7 +355,7 @@ type ExpressionContext = - interface IPrint with + interface IPrintable with member this.Print(printer) = () type Identifier = @@ -363,7 +363,7 @@ type Identifier = - interface IPrint with + interface IPrintable with member this.Print(printer: Printer) = let (Identifier id) = this printer.Print(id) @@ -396,7 +396,7 @@ type Statement = // member val EndLineno: int option = None with get, set // member val EndColOffset: int option = None with get, set - interface IPrint with + interface IPrintable with member x.Print(printer) = match x with | FunctionDef (def) -> printer.Print(def) @@ -426,7 +426,7 @@ type Module = static member Create(body) = { Body = body } - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = printer.PrintStatements(x.Body) /// Both parameters are raw strings of the names. asname can be None if the regular name is to be used. @@ -451,7 +451,7 @@ type Alias = static member Create(name, asname) = { Name = name; AsName = asname } - interface IPrint with + interface IPrintable with member x.Print(printer) = printer.Print(x.Name) @@ -480,11 +480,11 @@ type ExceptHandler = Loc = loc } - interface IPrint with + interface IPrintable with member x.Print(printer) = printer.Print("except ", ?loc = x.Loc) - printer.PrintOptional(x.Type |> Option.map (fun t -> t :> IPrint)) - printer.PrintOptional(" as ", x.Name |> Option.map (fun t -> t :> IPrint)) + printer.PrintOptional(x.Type) + printer.PrintOptional(" as ", x.Name) printer.Print(":") match x.Body with @@ -503,7 +503,7 @@ type Try = Loc: SourceLocation option } - static member Create(body, ?handlers, ?orElse, ?finalBody, ?loc): Statement = + static member Create(body, ?handlers, ?orElse, ?finalBody, ?loc) = { Body = body Handlers = defaultArg handlers [] @@ -511,9 +511,10 @@ type Try = FinalBody = defaultArg finalBody [] Loc = loc } - |> Try + static member AsStatement(body, ?handlers, ?orElse, ?finalBody, ?loc): Statement = + Try.Create(body, ?handlers=handlers, ?orElse=orElse, ?finalBody=finalBody, ?loc=loc) |> Try - interface IPrint with + interface IPrintable with member x.Print(printer) = printer.Print("try: ", ?loc = x.Loc) printer.PrintBlock(x.Body) @@ -557,7 +558,7 @@ type Arg = TypeComment = typeComment } - interface IPrint with + interface IPrintable with member x.Print(printer) = let (Identifier name) = x.Arg printer.Print(name) @@ -589,7 +590,7 @@ type Keyword = Value = value } - interface IPrint with + interface IPrintable with member x.Print(printer) = let (Identifier name) = x.Arg printer.Print(name) @@ -626,18 +627,18 @@ type Arguments = Defaults = defaultArg defaults [] } - interface IPrint with + interface IPrintable with member x.Print(printer) = match x.Args, x.VarArg with | [], Some vararg -> printer.Print("*") printer.Print(vararg) | args, Some vararg -> - printer.PrintCommaSeparatedList(args |> List.map (fun arg -> arg :> IPrint)) + printer.PrintCommaSeparatedList(args) printer.Print(", *") printer.Print(vararg) | args, None -> - printer.PrintCommaSeparatedList(args |> List.map (fun arg -> arg :> IPrint)) + printer.PrintCommaSeparatedList(args) //#region Statements @@ -687,7 +688,7 @@ type Assign = } |> Assign - interface IPrint with + interface IPrintable with member x.Print(printer) = printfn "Assign: %A" (x.Targets, x.Value) //printer.PrintOperation(targets.[0], "=", value, None) @@ -719,7 +720,7 @@ type Expr = static member Create(value): Statement = { Value = value } |> Expr - interface IPrint with + interface IPrintable with member x.Print(printer) = printer.Print(x.Value) /// A for loop. target holds the variable(s) the loop assigns to, as a single Name, Tuple or List node. iter holds the @@ -766,9 +767,15 @@ type For = TypeComment = typeComment } - interface IPrint with + static member AsStatement(target, iter, ?body, ?orelse, ?typeComment) : Statement = + For.Create(target, iter, ?body=body, ?orelse=orelse, ?typeComment=typeComment) + |> For + + interface IPrintable with member x.Print(printer: Printer) = printer.Print("for ") + printer.Print(x.Target) + printer.Print(" in ") printer.Print(x.Iterator) printer.Print(":") printer.PrintNewLine() @@ -794,7 +801,7 @@ type AsyncFor = TypeComment = typeComment } - interface IPrint with + interface IPrintable with member _.Print(printer) = printer.Print("(AsyncFor)") /// A while loop. test holds the condition, such as a Compare node. @@ -833,7 +840,7 @@ type While = } |> While - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = printer.Print("while ") printer.Print(x.Test) @@ -900,7 +907,7 @@ type ClassDef = } |> ClassDef - interface IPrint with + interface IPrintable with member x.Print(printer) = let (Identifier name) = x.Name printer.Print("class ", ?loc = x.Loc) @@ -966,7 +973,7 @@ type If = } |> If - interface IPrint with + interface IPrintable with member x.Print(printer) = let rec printElse stmts = match stmts with @@ -1010,7 +1017,7 @@ type Raise = static member Create(exc, ?cause): Statement = { Exception = exc; Cause = cause } |> Raise - interface IPrint with + interface IPrintable with member _.Print(printer) = printer.Print("(Raise)") /// A function definition. @@ -1043,7 +1050,7 @@ type FunctionDef = } |> FunctionDef - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = printer.PrintFunction(Some x.Name, x.Args, x.Body, x.Returns, isDeclaration = true) printer.PrintNewLine() @@ -1069,7 +1076,7 @@ type Global = static member Create(names) = { Names = names } - interface IPrint with + interface IPrintable with member x.Print(printer) = printer.Print("(Global)") /// global and nonlocal statements. names is a list of raw strings. @@ -1092,7 +1099,7 @@ type NonLocal = static member Create(names) = { Names = names } - interface IPrint with + interface IPrintable with member _.Print(printer: Printer) = printer.Print("(NonLocal)") @@ -1125,7 +1132,7 @@ type AsyncFunctionDef = TypeComment = typeComment } - interface IPrint with + interface IPrintable with member _.Print(printer: Printer) = printer.Print("(AsyncFunctionDef)") /// An import statement. names is a list of alias nodes. @@ -1148,7 +1155,7 @@ type Import = static member Create(names): Statement = Import { Names = names } - interface IPrint with + interface IPrintable with member _.Print(printer) = printer.Print("(Import)") /// Represents from x import y. module is a raw string of the ‘from’ name, without any leading dots, or None for @@ -1183,7 +1190,7 @@ type ImportFrom = } |> ImportFrom - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = let (Identifier path) = x.Module |> Option.defaultValue (Identifier ".") @@ -1195,7 +1202,7 @@ type ImportFrom = if List.length x.Names > 1 then printer.Print("(") - printer.PrintCommaSeparatedList(x.Names |> List.map (fun x -> x :> IPrint)) + printer.PrintCommaSeparatedList(x.Names) if List.length x.Names > 1 then printer.Print(")") @@ -1217,10 +1224,10 @@ type Return = static member Create(?value): Statement = Return { Value = value } - interface IPrint with + interface IPrintable with member this.Print(printer) = printer.Print("return ") - printer.PrintOptional(this.Value |> Option.map (fun x -> x :> IPrint)) + printer.PrintOptional(this.Value) //#endregion @@ -1253,7 +1260,7 @@ type Attribute = } |> Attribute - interface IPrint with + interface IPrintable with member this.Print(printer) = printer.Print(this.Value) printer.Print(".") @@ -1271,7 +1278,7 @@ type NamedExpr = } |> NamedExpr - interface IPrint with + interface IPrintable with member this.Print(printer) = printer.Print(this.Target) printer.Print(" :=") @@ -1310,7 +1317,7 @@ type Subscript = } |> Subscript - interface IPrint with + interface IPrintable with member this.Print(printer: Printer) = printer.Print(this.Value) printer.Print("[") @@ -1332,7 +1339,7 @@ type BinOp = } |> BinOp - interface IPrint with + interface IPrintable with member this.Print(printer) = printer.PrintOperation(this.Left, this.Operator, this.Right) @@ -1344,7 +1351,7 @@ type BoolOp = static member Create(op, values): Expression = { Values = values; Operator = op } |> BoolOp - interface IPrint with + interface IPrintable with member this.Print(printer) = for i, value in this.Values |> List.indexed do @@ -1383,7 +1390,7 @@ type Compare = } |> Compare - interface IPrint with + interface IPrintable with member x.Print(printer) = //printer.AddLocation(loc) printer.ComplexExpressionWithParens(x.Left) @@ -1408,7 +1415,7 @@ type UnaryOp = } |> UnaryOp - interface IPrint with + interface IPrintable with override x.Print(printer) = printer.AddLocation(x.Loc) @@ -1436,7 +1443,7 @@ type Constant = static member Create(value: obj): Expression = { Value = value } |> Constant - interface IPrint with + interface IPrintable with member x.Print(printer) = match box x.Value with | :? string as str -> @@ -1470,7 +1477,7 @@ type FormattedValue = FormatSpec = formatSpec } - interface IPrint with + interface IPrintable with member _.Print(printer) = printer.Print("(FormattedValue)") /// A function call. func is the function, which will often be a Name or Attribute object. Of the arguments: @@ -1513,12 +1520,12 @@ type Call = } |> Call - interface IPrint with + interface IPrintable with member x.Print(printer) = printer.Print(x.Func) printer.Print("(") printer.PrintCommaSeparatedList(x.Args) - printer.PrintCommaSeparatedList(x.Keywords |> List.map (fun x -> x :> IPrint)) + printer.PrintCommaSeparatedList(x.Keywords) printer.Print(")") type Emit = @@ -1534,7 +1541,7 @@ type Emit = } |> Emit - interface IPrint with + interface IPrintable with member x.Print(printer) = let inline replace pattern (f: System.Text.RegularExpressions.Match -> string) input = System.Text.RegularExpressions.Regex.Replace(input, pattern, f) @@ -1648,7 +1655,7 @@ type IfExp = } |> IfExp - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = printer.Print(x.Body) printer.Print(" if ") @@ -1684,14 +1691,14 @@ type Lambda = static member Create(args, body): Expression = { Args = args; Body = body } |> Lambda - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = printer.Print("lambda") if (List.isEmpty >> not) x.Args.Args then printer.Print(" ") - printer.PrintCommaSeparatedList(x.Args.Args |> List.map (fun arg -> arg :> IPrint)) + printer.PrintCommaSeparatedList(x.Args.Args) printer.Print(": ") printer.Print(x.Body) @@ -1718,7 +1725,7 @@ type Tuple = static member Create(elts, ?loc): Expression = { Elements = elts; Loc = loc } |> Tuple - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = printer.Print("(", ?loc = x.Loc) printer.PrintCommaSeparatedList(x.Elements) @@ -1748,7 +1755,7 @@ type List = static member Create(elts) = { Elements = elts } - interface IPrint with + interface IPrintable with member _.Print(printer) = printer.Print("(List)") /// A set. elts holds a list of nodes representing the set’s elements. @@ -1765,7 +1772,7 @@ type List = type Set (elts) = member _.Elements: Expression list = elts - interface IPrint with + interface IPrintable with member _.Print(printer) = printer.Print("(Set)") /// A dictionary. keys and values hold lists of nodes representing the keys and the values respectively, in matching @@ -1793,7 +1800,7 @@ type Dict = static member Create(keys, values): Expression = { Keys = keys; Values = values } |> Dict - interface IPrint with + interface IPrintable with member x.Print(printer: Printer) = printer.Print("{") printer.PrintNewLine() @@ -1827,7 +1834,7 @@ type Name = static member Create(id, ctx): Expression = { Id = id; Context = ctx } |> Name - interface IPrint with + interface IPrintable with override x.Print(printer) = let (Identifier name) = x.Id printer.Print(name) diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index cd2487be0d..96fb27a10e 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -84,7 +84,7 @@ type PrinterImpl(writer: Writer, map: SourceMapGenerator) = let run writer map (program: Module): Async = let printDeclWithExtraLine extraLine (printer: Printer) (decl: Statement) = - (decl :> IPrint).Print(printer) + (decl :> IPrintable).Print(printer) if printer.Column > 0 then //printer.Print(";") From c63973ec6a7fb5c4b3626c3e85b9cd93fd428fc5 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 31 Jan 2021 17:40:09 +0100 Subject: [PATCH 050/145] Cleanup --- src/Fable.Transforms/Python/Babel2Python.fs | 8 +++--- src/Fable.Transforms/Python/Python.fs | 27 ++++----------------- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index d6adfefa62..1714cc0099 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -698,11 +698,11 @@ module Util = Test=Some(Babel.BinaryExpression { Left=left; Right=right; Operator="<="}) Body=body } -> let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) - let name = Name.Create(Identifier id.Name, Load) - let start, _ = com.TransformAsExpr(ctx, init) - let stop, _ = com.TransformAsExpr(ctx, right) + let target = Name.Create(Identifier id.Name, Load) + let start, stmts1 = com.TransformAsExpr(ctx, init) + let stop, stmts2 = com.TransformAsExpr(ctx, right) let iter = Call.Create(Name.Create(Identifier "range", Load), args=[start; stop]) - [ For.AsStatement(target=name, iter=iter, body=body) ] + stmts1 @ stmts2 @ [ For.AsStatement(target=target, iter=iter, body=body) ] | Babel.LabeledStatement { Body=body } -> com.TransformAsStatements(ctx, returnStrategy, body) | Babel.ContinueStatement(_) -> [ Continue ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 9f8c44e657..4913ddaf0f 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -184,7 +184,7 @@ type AST = | BoolOperator (op) -> printer.Print(op) | ComparisonOperator (op) -> printer.Print(op) | UnaryOperator (op) -> printer.Print(op) - | ExpressionContext (ctx) -> printer.Print(ctx) + | ExpressionContext (_) -> () | Alias (al) -> printer.Print(al) | Module ``mod`` -> printer.Print(``mod``) | Arguments (arg) -> printer.Print(arg) @@ -260,8 +260,6 @@ type Operator = | BitAnd | MatMult - - interface IPrintable with member x.Print(printer: Printer) = let op = @@ -286,8 +284,6 @@ type BoolOperator = | And | Or - - interface IPrintable with member x.Print(printer: Printer) = let op = @@ -310,8 +306,6 @@ type ComparisonOperator = | In | NotIn - - interface IPrintable with member x.Print(printer) = let op = @@ -335,8 +329,6 @@ type UnaryOperator = | UAdd | USub - - interface IPrintable with member this.Print(printer) = let op = @@ -354,15 +346,9 @@ type ExpressionContext = | Store - - interface IPrintable with - member this.Print(printer) = () - type Identifier = | Identifier of string - - interface IPrintable with member this.Print(printer: Printer) = let (Identifier id) = this @@ -389,8 +375,6 @@ type Statement = | Break | Continue - - // member val Lineno: int = 0 with get, set // member val ColOffset: int = 0 with get, set // member val EndLineno: int option = None with get, set @@ -491,7 +475,6 @@ type ExceptHandler = | [] -> printer.PrintBlock([ Pass ]) | _ -> printer.PrintBlock(x.Body) - /// try blocks. All attributes are list of nodes to execute, except for handlers, which is a list of ExceptHandler /// nodes. type Try = @@ -979,12 +962,12 @@ type If = match stmts with | [] | [ Pass ] -> () - | [ If iff ] -> + | [ If { Test=test; Body=body; Else=els } ] -> printer.Print("elif ") - printer.Print(iff.Test) + printer.Print(test) printer.Print(":") - printer.PrintBlock(iff.Body) - printElse iff.Else + printer.PrintBlock(body) + printElse els | xs -> printer.Print("else: ") printer.PrintBlock(xs) From ebd986820ba64e43c80aa65ecbf050e81bc52a15 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 31 Jan 2021 23:08:40 +0100 Subject: [PATCH 051/145] Fix for length / str --- src/Fable.Transforms/Python/Babel2Python.fs | 77 +++++++++++--------- src/Fable.Transforms/Python/Python.fs | 44 +++++------ src/Fable.Transforms/Python/PythonPrinter.fs | 1 - 3 files changed, 65 insertions(+), 57 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 1714cc0099..5b99ca179a 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -430,38 +430,46 @@ module Util = | "void $0" -> args.[0], stmts //| "raise %0" -> Raise.Create() | _ -> Emit.Create(value, args), stmts - | Babel.MemberExpression { Computed=computed; Object=object; Property=property } -> + | Babel.MemberExpression { Computed=true; Object=object; Property=Babel.Literal(literal) } -> let value, stmts = com.TransformAsExpr(ctx, object) - if computed then - let attr = - match property with - | Babel.Literal(Babel.NumericLiteral(nl)) -> Constant.Create(nl.Value) - | Babel.Literal(Babel.StringLiteral(literal)) -> Constant.Create(literal.Value) - | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" + let attr = + match literal with + | Babel.NumericLiteral(nl) -> Constant.Create(nl.Value) + | Babel.StringLiteral(literal) -> Constant.Create(literal.Value) + | _ -> failwith $"transformExpressionAsStatements: unknown literal {literal}" - Subscript.Create(value = value, slice = attr, ctx = Load), stmts - else - let attr = - match property with - | Babel.Identifier { Name=name } -> Identifier(name) - | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" + Subscript.Create(value = value, slice = attr, ctx = Load), stmts + | Babel.MemberExpression { Computed=false; Object=object; Property=Babel.Identifier { Name="length" } } -> + let value, stmts = com.TransformAsExpr(ctx, object) + let func = Name.Create(Identifier "len", Load) + Call.Create(func, [value]), stmts + | Babel.MemberExpression { Computed=false; Object=object; Property=Babel.Identifier { Name="message" } } -> + let value, stmts = com.TransformAsExpr(ctx, object) + let func = Name.Create(Identifier "str", Load) + Call.Create(func, [value]), stmts + | Babel.MemberExpression { Computed=false; Object=object; Property=property } -> + let value, stmts = com.TransformAsExpr(ctx, object) + let attr = + match property with + | Babel.Identifier { Name=name } -> Identifier(name) + | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" + + let value = + match value with + | Name { Id = Identifier (id); Context = ctx } -> + // TODO: Need to make this more generic and robust + let id = + if id = "Math" then + //com.imports.Add("math", ) + "math" + else + id + + Name.Create(id = Identifier(id), ctx = ctx) + | _ -> value - let value = - match value with - | Name { Id = Identifier (id); Context = ctx } -> - // TODO: Need to make this more generic and robust - let id = - if id = "Math" then - //com.imports.Add("math", ) - "math" - else - id - - Name.Create(id = Identifier(id), ctx = ctx) - | _ -> value - - Attribute.Create(value = value, attr = attr, ctx = Load), stmts + Attribute.Create(value = value, attr = attr, ctx = Load), stmts | Babel.Literal(Babel.BooleanLiteral { Value=value }) -> Constant.Create(value = value), [] | Babel.FunctionExpression(fe) -> let args = @@ -641,12 +649,12 @@ module Util = // Insert a ex.message = str(ex) for all aliased exceptions. let identifier = Identifier(cc.Param.Name) - let idName = Name.Create(identifier, Load) - let message = Identifier("message") - let trg = Attribute.Create(idName, message, Store) - let value = Call.Create(Name.Create(Identifier("str"), Load), [idName]) - let msg = Assign.Create([trg], value) - let body = msg :: body + // let idName = Name.Create(identifier, Load) + // let message = Identifier("message") + // let trg = Attribute.Create(idName, message, Store) + // let value = Call.Create(Name.Create(Identifier("str"), Load), [idName]) + // let msg = Assign.Create([trg], value) + // let body = msg :: body let handlers = [ ExceptHandler.Create(``type`` = exn, name = identifier, body = body) ] @@ -701,6 +709,7 @@ module Util = let target = Name.Create(Identifier id.Name, Load) let start, stmts1 = com.TransformAsExpr(ctx, init) let stop, stmts2 = com.TransformAsExpr(ctx, right) + let stop = BinOp.Create(stop, Add, Constant.Create(1)) // Python `range` has exclusive end. let iter = Call.Create(Name.Create(Identifier "range", Load), args=[start; stop]) stmts1 @ stmts2 @ [ For.AsStatement(target=target, iter=iter, body=body) ] | Babel.LabeledStatement { Body=body } -> com.TransformAsStatements(ctx, returnStrategy, body) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 4913ddaf0f..4089edaa54 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -355,25 +355,25 @@ type Identifier = printer.Print(id) type Statement = - | Assign of Assign - | Import of Import + | AsyncFunctionDef of AsyncFunctionDef + | FunctionDef of FunctionDef | ImportFrom of ImportFrom - | Expr of Expr - | For of For | AsyncFor of AsyncFor - | If of If | ClassDef of ClassDef - | Raise of Raise - | Global of Global | NonLocal of NonLocal - | AsyncFunctionDef of AsyncFunctionDef - | FunctionDef of FunctionDef + | Global of Global | Return of Return + | Assign of Assign + | Import of Import + | Raise of Raise | While of While + | Expr of Expr | Try of Try - | Pass - | Break + | For of For + | If of If | Continue + | Break + | Pass // member val Lineno: int = 0 with get, set // member val ColOffset: int = 0 with get, set @@ -383,25 +383,25 @@ type Statement = interface IPrintable with member x.Print(printer) = match x with - | FunctionDef (def) -> printer.Print(def) | AsyncFunctionDef (def) -> printer.Print(def) + | FunctionDef (def) -> printer.Print(def) + | ImportFrom (im) -> printer.Print(im) + | NonLocal (st) -> printer.Print(st) + | ClassDef (st) -> printer.Print(st) + | AsyncFor (st) -> printer.Print(st) + | Return (rtn) -> printer.Print(rtn) + | Global (st) -> printer.Print(st) + | Import (im) -> printer.Print(im) | Assign (st) -> printer.Print(st) + | While (wh) -> printer.Print(wh) + | Raise (st) -> printer.Print(st) | Expr (st) -> printer.Print(st) | For (st) -> printer.Print(st) - | AsyncFor (st) -> printer.Print(st) + | Try (st) -> printer.Print(st) | If (st) -> printer.Print(st) - | ClassDef (st) -> printer.Print(st) - | Raise (st) -> printer.Print(st) - | Global (st) -> printer.Print(st) - | NonLocal (st) -> printer.Print(st) | Pass -> printer.Print("pass") | Break -> printer.Print("break") | Continue -> printer.Print("continue") - | Return (rtn) -> printer.Print(rtn) - | Import (im) -> printer.Print(im) - | ImportFrom (im) -> printer.Print(im) - | While (wh) -> printer.Print(wh) - | Try (st) -> printer.Print(st) type Module = { diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index 96fb27a10e..3d3cce2a8f 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -112,4 +112,3 @@ let run writer map (program: Module): Async = // TODO: Only flush every XXX lines? do! printer.Flush() } -// If the queryItemsHandler succeeds, the queryItems will be used in a request to CDF, and the resulting aggregates will be wrapped in an OK result-type. \ No newline at end of file From 559d79e18fff7d1e597f981ebdcf7dd761c27eb2 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 9 Feb 2021 23:53:06 +0100 Subject: [PATCH 052/145] Refactor for latest Fable --- src/Fable.Transforms/Python/Babel2Python.fs | 360 ++++++++++---------- 1 file changed, 188 insertions(+), 172 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 5b99ca179a..2cf93ef51f 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -7,6 +7,7 @@ open System.Text.RegularExpressions open Fable open Fable.AST open Fable.AST.Python +open Fable.AST.Babel [] type ReturnStrategy = @@ -38,15 +39,15 @@ type Context = type IPythonCompiler = inherit Compiler - abstract GetAllImports: unit -> Statement list - abstract GetImportExpr: Context * selector:string * path:string * SourceLocation option -> Expression - abstract TransformAsExpr: Context * Babel.Expression -> Expression * Statement list - abstract TransformAsStatements: Context * ReturnStrategy * Babel.Expression -> Statement list - abstract TransformAsStatements: Context * ReturnStrategy * Babel.Statement -> Statement list - abstract TransformAsStatements: Context * ReturnStrategy * Babel.BlockStatement -> Statement list - abstract TransformAsClassDef: Context * Babel.ClassDeclaration -> Statement list - abstract TransformAsImports: Context * Babel.ImportDeclaration -> Statement list - abstract TransformFunction: Context * Babel.Identifier * Babel.Pattern array * Babel.BlockStatement -> Statement + abstract GetAllImports: unit -> Python.Statement list + abstract GetImportExpr: Context * selector:string * path:string * SourceLocation option -> Python.Expression + abstract TransformAsExpr: Context * Babel.Expression -> Python.Expression * Python.Statement list + abstract TransformAsStatements: Context * ReturnStrategy * Babel.Expression -> Python.Statement list + abstract TransformAsStatements: Context * ReturnStrategy * Babel.Statement -> Python.Statement list + abstract TransformAsStatements: Context * ReturnStrategy * Babel.BlockStatement -> Python.Statement list + abstract TransformAsClassDef: Context * Babel.ClassBody * Babel.Identifier option * Babel.Expression option * Babel.ClassImplements array option * Babel.TypeParameterInstantiation option * Babel.TypeParameterDeclaration option * SourceLocation option -> Python.Statement list + abstract TransformAsImports: Context * Babel.ImportSpecifier array * Babel.StringLiteral -> Python.Statement list + abstract TransformFunction: Context * Babel.Identifier * Babel.Pattern array * Babel.BlockStatement -> Python.Statement abstract WarnOnlyOnce: string * ?range:SourceLocation -> unit @@ -54,10 +55,10 @@ type IPythonCompiler = module Helpers = let index = (Seq.initInfinite id).GetEnumerator() - let getIdentifier (name: string): Identifier = + let getIdentifier (name: string): Python.Identifier = do index.MoveNext() |> ignore let idx = index.Current.ToString() - Identifier($"{name}_{idx}") + Python.Identifier($"{name}_{idx}") /// Replaces all '$' with '_' @@ -84,15 +85,15 @@ module Helpers = printfn "moduleName: %s" moduleName moduleName - let unzipArgs (args: (Expression * Statement list) list): Expression list * Statement list = + let unzipArgs (args: (Python.Expression * Python.Statement list) list): Python.Expression list * Python.Statement list = let stmts = args |> List.map snd |> List.collect id let args = args |> List.map fst args, stmts /// A few statements in the generated Babel AST do not produce any effect, and will not be printet. But they are /// left in the AST and we need to skip them since they are not valid for Python (either). - let isProductiveStatement (stmt: Statement) = - let rec hasNoSideEffects (e: Expression) = + let isProductiveStatement (stmt: Python.Statement) = + let rec hasNoSideEffects (e: Python.Expression) = printfn $"hasNoSideEffects: {e}" match e with @@ -110,7 +111,7 @@ module Helpers = | _ -> Some stmt module Util = - let rec transformBody (returnStrategy: ReturnStrategy) (body: Statement list) = + let rec transformBody (returnStrategy: ReturnStrategy) (body: Python.Statement list) = let body = body |> List.choose Helpers.isProductiveStatement match body, returnStrategy with @@ -123,50 +124,51 @@ module Util = |> transformBody ReturnStrategy.NoReturn | _ -> body - let transformAsImports (com: IPythonCompiler) (ctx: Context) (imp: Babel.ImportDeclaration): Statement list = - let pymodule = imp.Source.Value |> Helpers.rewriteFableImport + let transformAsImports (com: IPythonCompiler) (ctx: Context) (specifiers: Babel.ImportSpecifier array) (source: Babel.StringLiteral) : Python.Statement list = + let (StringLiteral.StringLiteral(value=value)) = source + let pymodule = value |> Helpers.rewriteFableImport printfn "Module: %A" pymodule let imports: ResizeArray = ResizeArray() let importFroms = ResizeArray() - for expr in imp.Specifiers do + for expr in specifiers do match expr with - | Babel.ImportMemberSpecifier(im) -> + | Babel.ImportMemberSpecifier(local, imported) -> printfn "ImportMemberSpecifier" let alias = Alias.Create( - Identifier(im.Imported.Name), - if im.Imported.Name <> im.Local.Name then - Identifier(im.Local.Name) |> Some + Python.Identifier(imported.Name), + if imported.Name <> local.Name then + Python.Identifier(local.Name) |> Some else None ) importFroms.Add(alias) - | Babel.ImportDefaultSpecifier(ids) -> + | Babel.ImportDefaultSpecifier(local) -> printfn "ImportDefaultSpecifier" let alias = Alias.Create( - Identifier(pymodule), - if ids.Local.Name <> pymodule then - Identifier(ids.Local.Name) |> Some + Python.Identifier(pymodule), + if local.Name <> pymodule then + Python.Identifier(local.Name) |> Some else None ) imports.Add(alias) - | Babel.ImportNamespaceSpecifier { Local = { Name = name } } -> + | Babel.ImportNamespaceSpecifier(Identifier(name = name)) -> printfn "ImportNamespaceSpecifier: %A" (name, name) let alias = Alias.Create( - Identifier(pymodule), + Python.Identifier(pymodule), if pymodule <> name then - Identifier(name) |> Some + Python.Identifier(name) |> Some else None ) @@ -177,76 +179,87 @@ module Util = Import.Create(imports |> List.ofSeq) if importFroms.Count > 0 then - ImportFrom.Create(Some(Identifier(pymodule)), importFroms |> List.ofSeq) + ImportFrom.Create(Some(Python.Identifier(pymodule)), importFroms |> List.ofSeq) ] - let transformAsClassDef (com: IPythonCompiler) ctx (cls: Babel.ClassDeclaration): Statement list = + let transformAsClassDef + (com: IPythonCompiler) + (ctx: Context) + (body: Babel.ClassBody) + (id: Babel.Identifier option) + (superClass: Babel.Expression option) + (implements: Babel.ClassImplements array option) + (superTypeParameters: Babel.TypeParameterInstantiation option) + (typeParameters: Babel.TypeParameterDeclaration option) + (loc: SourceLocation option) + : Python.Statement list = printfn $"transformAsClassDef" let bases, stmts = let entries = - cls.SuperClass + superClass |> Option.map (fun expr -> com.TransformAsExpr(ctx, expr)) match entries with | Some (expr, stmts) -> [ expr ], stmts | None -> [], [] - let body: Statement list = + let body: Python.Statement list = [ - for mber in cls.Body.Body do + let (ClassBody(body=body)) = body + for mber in body do match mber with - | Babel.ClassMethod(cm) -> - let self = Arg.Create(Identifier("self")) + | Babel.ClassMember.ClassMethod(kind, key, ``params``, body, computed, ``static``, ``abstract``, returnType, typeParameters, loc) -> + let self = Arg.Create(Python.Identifier("self")) let parms = - cm.Params + ``params`` |> List.ofArray let args = parms |> List.choose (function - | Babel.IdentifierPattern(id) -> - Arg.Create(Identifier(id.Name)) |> Some + | Pattern.Identifier(id) -> + Arg.Create(Python.Identifier(id.Name)) |> Some | _ -> None) let varargs = parms |> List.choose (function - | Babel.RestElement(rest) -> - Arg.Create(Identifier(rest.Argument.Name)) |> Some + | Pattern.RestElement(argument=argument) -> + Arg.Create(Python.Identifier(argument.Name)) |> Some | _ -> None) |> List.tryHead let arguments = Arguments.Create(args = self :: args, ?vararg=varargs) - match cm.Kind with + match kind with | "method" -> let body = - com.TransformAsStatements(ctx, ReturnStrategy.Return, cm.Body |> Babel.BlockStatement) + com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Statement.BlockStatement) let name = - match cm.Key with - | Babel.Identifier(id) -> Identifier(id.Name) - | _ -> failwith $"transformAsClassDef: Unknown key: {cm.Key}" + match key with + | Expression.Identifier(id) -> Python.Identifier(id.Name) + | _ -> failwith $"transformAsClassDef: Unknown key: {key}" FunctionDef.Create(name, arguments, body = body) | "constructor" -> - let name = Identifier("__init__") + let name = Python.Identifier("__init__") let body = - com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, cm.Body |> Babel.BlockStatement) + com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body |> Statement.BlockStatement) FunctionDef.Create(name, arguments, body = body) - | _ -> failwith $"transformAsClassDef: Unknown kind: {cm.Kind}" + | _ -> failwith $"transformAsClassDef: Unknown kind: {kind}" | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] printfn $"Body length: {body.Length}: ${body}" - let name = Helpers.cleanNameAsPythonIdentifier (cls.Id.Value.Name) + let name = Helpers.cleanNameAsPythonIdentifier (id.Value.Name) - [ yield! stmts; ClassDef.Create(Identifier(name), body = body, bases = bases) ] + [ yield! stmts; ClassDef.Create(Python.Identifier(name), body = body, bases = bases) ] let transformAsFunction (com: IPythonCompiler) (ctx: Context) (name: Babel.Identifier) (parms: Babel.Pattern array) (body: Babel.BlockStatement) = let args = @@ -255,38 +268,38 @@ module Util = |> List.map (fun pattern -> let name = Helpers.cleanNameAsPythonIdentifier (pattern.Name) - Arg.Create(Identifier(name))) + Arg.Create(Python.Identifier(name))) let arguments = Arguments.Create(args = args) - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Babel.BlockStatement) + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Statement.BlockStatement) let name = Helpers.cleanNameAsPythonIdentifier (name.Name) - FunctionDef.Create(Identifier(name), arguments, body = body) + FunctionDef.Create(Python.Identifier(name), arguments, body = body) /// Transform Babel expression as Python expression let rec transformAsExpr (com: IPythonCompiler) (ctx: Context) (expr: Babel.Expression) - : Expression * list = + : Python.Expression * list = printfn $"transformAsExpr: {expr}" match expr with - | Babel.AssignmentExpression { Left=left; Operator=operator; Right=right } -> + | Expression.AssignmentExpression(left=left; operator=operator; right=right) -> let left, leftStmts = com.TransformAsExpr(ctx, left) let right, rightStmts = com.TransformAsExpr(ctx, right) match operator with | "=" -> NamedExpr.Create(left, right), leftStmts @ rightStmts | _ -> failwith $"Unsuppored assingment expression: {operator}" - | Babel.BinaryExpression { Left=left; Operator=operator; Right=right } -> + | Expression.BinaryExpression(left=left; operator=operator; right=right) -> let left, leftStmts = com.TransformAsExpr(ctx, left) let right, rightStmts = com.TransformAsExpr(ctx, right) let toBinOp op = BinOp.Create(left, op, right), leftStmts @ rightStmts let toCompare op = Compare.Create(left, [ op ], [ right ]), leftStmts @ rightStmts let toCall name = - let func = Name.Create(Identifier(name), Load) + let func = Name.Create(Python.Identifier(name), Load) let args = [left; right] Call.Create(func, args),leftStmts @ rightStmts @@ -313,7 +326,7 @@ module Util = | "isinstance" -> toCall "isinstance" | _ -> failwith $"Unknown operator: {operator}" - | Babel.UnaryExpression { Operator=operator; Argument=arg } -> + | Expression.UnaryExpression(operator=operator; argument=arg) -> let op = match operator with | "-" -> USub |> Some @@ -329,25 +342,25 @@ module Util = | Some op -> UnaryOp.Create(op, operand), stmts | _ -> // TODO: Should be Contant(value=None) but we cannot create that in F# - Name.Create(id = Identifier("None"), ctx = Load), stmts + Name.Create(id = Python.Identifier("None"), ctx = Load), stmts - | Babel.ArrowFunctionExpression { Params=parms; Body=body} -> + | Expression.ArrowFunctionExpression(``params``=parms; body=body) -> let args = parms |> List.ofArray - |> List.map (fun pattern -> Arg.Create(Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.Create(Python.Identifier pattern.Name)) let arguments = let args = match args with - | [] -> [ Arg.Create(Identifier("_"), Name.Create(Identifier("None"), Load)) ] // Need to receive unit + | [] -> [ Arg.Create(Python.Identifier("_"), Name.Create(Python.Identifier("None"), Load)) ] // Need to receive unit | _ -> args Arguments.Create(args = args) let stmts = body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. match stmts with - | [| Babel.ReturnStatement(rtn) |] -> - let body, stmts = com.TransformAsExpr(ctx, rtn.Argument) + | [| Statement.ReturnStatement(argument=argument) |] -> + let body, stmts = com.TransformAsExpr(ctx, argument) Lambda.Create(arguments, body), stmts | _ -> let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) @@ -357,7 +370,7 @@ module Util = FunctionDef.Create(name = name, args = arguments, body = body) Name.Create(name, Load), [ func ] - | Babel.CallExpression { Callee=callee; Arguments=args } -> // FIXME: use transformAsCall + | Expression.CallExpression(callee=callee; arguments=args) -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, callee) let args, stmtArgs = @@ -367,20 +380,20 @@ module Util = |> Helpers.unzipArgs Call.Create(func, args), stmts @ stmtArgs - | Babel.ArrayExpression(ae) -> + | Expression.ArrayExpression(elements=elements) -> let elems, stmts = - ae.Elements + elements |> List.ofArray |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) |> Helpers.unzipArgs Tuple.Create(elems), stmts - | Babel.Literal(Babel.NumericLiteral(nl)) -> Constant.Create(value = nl.Value), [] - | Babel.Literal(Babel.StringLiteral(sl)) -> Constant.Create(value = sl.Value), [] - | Babel.Identifier { Name=name } -> + | Expression.Literal(Literal.NumericLiteral(value=value)) -> Constant.Create(value = value), [] + | Expression.Literal(Literal.StringLiteral(StringLiteral.StringLiteral(value=value))) -> Constant.Create(value = value), [] + | Expression.Identifier(Identifier(name=name)) -> let name = Helpers.cleanNameAsPythonIdentifier name - Name.Create(id = Identifier name, ctx = Load), [] - | Babel.NewExpression { Callee=callee; Arguments=args} -> // FIXME: use transformAsCall + Name.Create(id = Python.Identifier name, ctx = Load), [] + | Expression.NewExpression(callee=callee; arguments=args) -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, callee) let args, stmtArgs = @@ -390,23 +403,23 @@ module Util = |> Helpers.unzipArgs Call.Create(func, args), stmts @ stmtArgs - | Babel.Super(se) -> Name.Create(Identifier("super().__init__"), ctx = Load), [] - | Babel.ObjectExpression { Properties=properties } -> + | Expression.Super(se) -> Name.Create(Python.Identifier("super().__init__"), ctx = Load), [] + | Expression.ObjectExpression(properties=properties) -> let keys, values, stmts = [ for prop in properties do match prop with - | Babel.ObjectProperty(op) -> - let key, stmts1 = com.TransformAsExpr(ctx, op.Key) - let value, stmts2 = com.TransformAsExpr(ctx, op.Value) + | ObjectProperty(key=key; value=value) -> + let key, stmts1 = com.TransformAsExpr(ctx, key) + let value, stmts2 = com.TransformAsExpr(ctx, value) key, value, stmts1 @ stmts2 - | Babel.ObjectMethod(om) -> - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, om.Body) - let key, stmts = com.TransformAsExpr(ctx, om.Key) + | Babel.ObjectMethod(key=key; ``params``=parms; body=body) -> + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) + let key, stmts = com.TransformAsExpr(ctx, key) let args = - om.Params + parms |> List.ofArray - |> List.map (fun pattern -> Arg.Create(Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.Create(Python.Identifier pattern.Name)) let arguments = Arguments.Create(args = args) let name = Helpers.getIdentifier "lifted" @@ -419,7 +432,7 @@ module Util = |> List.unzip3 Dict.Create(keys = keys, values = values), stmts |> List.collect id - | Babel.EmitExpression { Value=value; Args=args } -> + | Expression.EmitExpression(value=value; args=args) -> let args, stmts = args |> List.ofArray @@ -430,34 +443,34 @@ module Util = | "void $0" -> args.[0], stmts //| "raise %0" -> Raise.Create() | _ -> Emit.Create(value, args), stmts - | Babel.MemberExpression { Computed=true; Object=object; Property=Babel.Literal(literal) } -> + | Expression.MemberExpression(computed=true; object=object; property=Expression.Literal(literal)) -> let value, stmts = com.TransformAsExpr(ctx, object) let attr = match literal with - | Babel.NumericLiteral(nl) -> Constant.Create(nl.Value) - | Babel.StringLiteral(literal) -> Constant.Create(literal.Value) + | Literal.NumericLiteral(value=value) -> Constant.Create(value) + | Literal.StringLiteral(StringLiteral.StringLiteral(value=value)) -> Constant.Create(value) | _ -> failwith $"transformExpressionAsStatements: unknown literal {literal}" Subscript.Create(value = value, slice = attr, ctx = Load), stmts - | Babel.MemberExpression { Computed=false; Object=object; Property=Babel.Identifier { Name="length" } } -> + | Expression.MemberExpression(computed=false; object=object; property=Expression.Identifier(Identifier(name="length"))) -> let value, stmts = com.TransformAsExpr(ctx, object) - let func = Name.Create(Identifier "len", Load) + let func = Name.Create(Python.Identifier "len", Load) Call.Create(func, [value]), stmts - | Babel.MemberExpression { Computed=false; Object=object; Property=Babel.Identifier { Name="message" } } -> + | Expression.MemberExpression(computed=false; object=object; property=Expression.Identifier(Identifier(name="message"))) -> let value, stmts = com.TransformAsExpr(ctx, object) - let func = Name.Create(Identifier "str", Load) + let func = Name.Create(Python.Identifier "str", Load) Call.Create(func, [value]), stmts - | Babel.MemberExpression { Computed=false; Object=object; Property=property } -> + | Expression.MemberExpression(computed=false; object=object; property=property) -> let value, stmts = com.TransformAsExpr(ctx, object) let attr = match property with - | Babel.Identifier { Name=name } -> Identifier(name) + | Expression.Identifier(Identifier(name=name)) -> Python.Identifier(name) | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" let value = match value with - | Name { Id = Identifier (id); Context = ctx } -> + | Name { Id = Python.Identifier (id); Context = ctx } -> // TODO: Need to make this more generic and robust let id = if id = "Math" then @@ -466,26 +479,26 @@ module Util = else id - Name.Create(id = Identifier(id), ctx = ctx) + Name.Create(id = Python.Identifier(id), ctx = ctx) | _ -> value Attribute.Create(value = value, attr = attr, ctx = Load), stmts - | Babel.Literal(Babel.BooleanLiteral { Value=value }) -> Constant.Create(value = value), [] - | Babel.FunctionExpression(fe) -> + | Expression.Literal(Literal.BooleanLiteral(value=value)) -> Constant.Create(value = value), [] + | Expression.FunctionExpression(``params``=parms; body=body) -> let args = - fe.Params + parms |> List.ofArray - |> List.map (fun pattern -> Arg.Create(Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.Create(Python.Identifier pattern.Name)) let arguments = Arguments.Create(args = args) - match fe.Body.Body with - | [| Babel.ExpressionStatement(expr) |] -> - let body, stmts = com.TransformAsExpr(ctx, expr.Expression) + match body.Body with + | [| Statement.ExpressionStatement(expr) |] -> + let body, stmts = com.TransformAsExpr(ctx, expr) Lambda.Create(arguments, body), stmts | _ -> let body = - com.TransformAsStatements(ctx, ReturnStrategy.Return, fe.Body) + com.TransformAsStatements(ctx, ReturnStrategy.Return, body) let name = Helpers.getIdentifier "lifted" @@ -493,14 +506,14 @@ module Util = FunctionDef.Create(name = name, args = arguments, body = body) Name.Create(name, Load), [ func ] - | Babel.ConditionalExpression { Test=test; Consequent=consequent; Alternate=alternate } -> + | Expression.ConditionalExpression(test=test; consequent=consequent; alternate=alternate) -> let test, stmts1 = com.TransformAsExpr(ctx, test) let body, stmts2 = com.TransformAsExpr(ctx, consequent) let orElse, stmts3 = com.TransformAsExpr(ctx, alternate) IfExp.Create(test, body, orElse), stmts1 @ stmts2 @ stmts3 - | Babel.Literal(Babel.NullLiteral(nl)) -> Name.Create(Identifier("None"), ctx = Load), [] - | Babel.SequenceExpression { Expressions=exprs } -> + | Expression.Literal(Literal.NullLiteral(nl)) -> Name.Create(Python.Identifier("None"), ctx = Load), [] + | Expression.SequenceExpression(expressions=exprs) -> // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments let exprs, stmts = exprs @@ -532,28 +545,28 @@ module Util = (ctx: Context) (returnStrategy: ReturnStrategy) (expr: Babel.Expression) - : Statement list = + : Python.Statement list = printfn $"transformExpressionAsStatements: {expr}" match expr with - | Babel.AssignmentExpression { Left=left; Right=right } -> + | Expression.AssignmentExpression(left=left; right=right) -> let value, stmts = com.TransformAsExpr(ctx, right) - let targets: Expression list = + let targets: Python.Expression list = match left with - | Babel.Identifier(identifier) -> + | Expression.Identifier(Identifier(name=name)) -> let target = - Identifier(Helpers.cleanNameAsPythonIdentifier (identifier.Name)) + Python.Identifier(Helpers.cleanNameAsPythonIdentifier (name)) [ Name.Create(id = target, ctx = Store) ] - | Babel.MemberExpression({Property=property}) -> + | Expression.MemberExpression(property=property) -> match property with - | Babel.Identifier(id) -> - let attr = Identifier(Helpers.cleanNameAsPythonIdentifier (id.Name)) + | Expression.Identifier(id) -> + let attr = Python.Identifier(Helpers.cleanNameAsPythonIdentifier (id.Name)) [ Attribute.Create( - value = Name.Create(id = Identifier("self"), ctx = Load), + value = Name.Create(id = Python.Identifier("self"), ctx = Load), attr = attr, ctx = Store ) @@ -569,45 +582,45 @@ module Util = (ctx: Context) (returnStrategy: ReturnStrategy) (stmt: Babel.Statement) - : list = + : Python.Statement list = printfn $"transformStatementAsStatements: {stmt}, returnStrategy: {returnStrategy}" match stmt with - | Babel.BlockStatement(bs) -> + | Statement.BlockStatement(bs) -> [ yield! com.TransformAsStatements(ctx, returnStrategy, bs) ] |> transformBody returnStrategy - | Babel.ReturnStatement { Argument=arg } -> + | Statement.ReturnStatement(argument=arg) -> let expr, stmts = transformAsExpr com ctx arg match returnStrategy with | ReturnStrategy.NoReturn -> stmts @ [ Expr.Create(expr) ] | _ -> stmts @ [ Return.Create(expr) ] - | Babel.Declaration(Babel.VariableDeclaration(vd)) -> + | Statement.Declaration(Declaration.VariableDeclaration(VariableDeclaration(declarations=declarations))) -> [ - for vd in vd.Declarations do - let targets: Expression list = - let name = Helpers.cleanNameAsPythonIdentifier (vd.Id.Name) - [ Name.Create(id = Identifier(name), ctx = Store) ] + for (VariableDeclarator(id=id; init=init)) in declarations do + let targets: Python.Expression list = + let name = Helpers.cleanNameAsPythonIdentifier (id.Name) + [ Name.Create(id = Python.Identifier(name), ctx = Store) ] - match vd.Init with + match init with | Some value -> let expr, stmts = com.TransformAsExpr(ctx, value) yield! stmts Assign.Create(targets, expr) | None -> () ] - | Babel.ExpressionStatement { Expression=expression } -> + | Statement.ExpressionStatement(expr=expression) -> // Handle Babel expressions that we need to transforme here as Python statements match expression with - | Babel.AssignmentExpression(ae) -> com.TransformAsStatements(ctx, returnStrategy, ae |> Babel.AssignmentExpression) + | Expression.AssignmentExpression(_) -> com.TransformAsStatements(ctx, returnStrategy, expression) | _ -> [ let expr, stmts = com.TransformAsExpr(ctx, expression) yield! stmts Expr.Create(expr) ] - | Babel.IfStatement { Test=test; Consequent=consequent; Alternate=alternate } -> + | Statement.IfStatement(test=test; consequent=consequent; alternate=alternate) -> let test, stmts = com.TransformAsExpr(ctx, test) let body = @@ -623,7 +636,7 @@ module Util = | _ -> [] [ yield! stmts; If.Create(test = test, body = body, orelse = orElse) ] - | Babel.WhileStatement { Test=test; Body=body } -> + | Statement.WhileStatement(test=test; body=body) -> let expr, stmts = com.TransformAsExpr(ctx, test) let body = @@ -631,24 +644,24 @@ module Util = |> transformBody ReturnStrategy.NoReturn [ yield! stmts; While.Create(test = expr, body = body, orelse = []) ] - | Babel.TryStatement(ts) -> - let body = com.TransformAsStatements(ctx, returnStrategy, ts.Block) + | Statement.TryStatement(block=block; handler=handler; finalizer=finalizer) -> + let body = com.TransformAsStatements(ctx, returnStrategy, block) let finalBody = - ts.Finalizer + finalizer |> Option.map (fun f -> com.TransformAsStatements(ctx, returnStrategy, f)) let handlers = - match ts.Handler with - | Some cc -> - let body = com.TransformAsStatements(ctx, returnStrategy, cc.Body) + match handler with + | Some (CatchClause(``param``=parm; body=body)) -> + let body = com.TransformAsStatements(ctx, returnStrategy, body) let exn = - Name.Create(Identifier("Exception"), ctx = Load) + Name.Create(Python.Identifier("Exception"), ctx = Load) |> Some // Insert a ex.message = str(ex) for all aliased exceptions. - let identifier = Identifier(cc.Param.Name) + let identifier = Python.Identifier(parm.Name) // let idName = Name.Create(identifier, Load) // let message = Identifier("message") // let trg = Attribute.Create(idName, message, Store) @@ -662,19 +675,19 @@ module Util = | _ -> [] [ Try.AsStatement(body = body, handlers = handlers, ?finalBody = finalBody) ] - | Babel.SwitchStatement { Discriminant=discriminant; Cases=cases } -> + | Statement.SwitchStatement(discriminant=discriminant; cases=cases) -> let value, stmts = com.TransformAsExpr(ctx, discriminant) - let rec ifThenElse (fallThrough: Expression option) (cases: Babel.SwitchCase list): Statement list option = + let rec ifThenElse (fallThrough: Python.Expression option) (cases: Babel.SwitchCase list): Python.Statement list option = match cases with | [] -> None - | case :: cases -> + | SwitchCase(test=test; consequent=consequent) :: cases -> let body = - case.Consequent + consequent |> List.ofArray |> List.collect (fun x -> com.TransformAsStatements(ctx, ReturnStrategy.NoBreak, x)) - match case.Test with + match test with | None -> body |> Some | Some test -> let test, st = com.TransformAsExpr(ctx, test) @@ -697,23 +710,24 @@ module Util = match result with | Some ifStmt -> stmts @ ifStmt | None -> [] - | Babel.BreakStatement(_) -> [ Break ] - | Babel.Declaration(Babel.FunctionDeclaration(fd)) -> - [ com.TransformFunction(ctx, fd.Id, fd.Params, fd.Body) ] - | Babel.Declaration(Babel.ClassDeclaration(cd)) -> com.TransformAsClassDef(ctx, cd) - | Babel.ForStatement - { Init=Some({ Declarations= [| { Id=id; Init=Some(init)} |] }) - Test=Some(Babel.BinaryExpression { Left=left; Right=right; Operator="<="}) - Body=body } -> + | Statement.BreakStatement(_) -> [ Break ] + | Statement.Declaration(Declaration.FunctionDeclaration(``params``=parms; id=id; body=body)) -> + [ com.TransformFunction(ctx, id, parms, body) ] + | Statement.Declaration(Declaration.ClassDeclaration(body, id, superClass, implements, superTypeParameters, typeParameters, loc)) -> + transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc + | Statement.ForStatement + (init=Some(VariableDeclaration(declarations= [| VariableDeclarator(id=id; init=Some(init)) |] )) + test=Some(Expression.BinaryExpression(left=left; right=right; operator="<=")) + body=body) -> let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) - let target = Name.Create(Identifier id.Name, Load) + let target = Name.Create(Python.Identifier id.Name, Load) let start, stmts1 = com.TransformAsExpr(ctx, init) let stop, stmts2 = com.TransformAsExpr(ctx, right) let stop = BinOp.Create(stop, Add, Constant.Create(1)) // Python `range` has exclusive end. - let iter = Call.Create(Name.Create(Identifier "range", Load), args=[start; stop]) + let iter = Call.Create(Name.Create(Python.Identifier "range", Load), args=[start; stop]) stmts1 @ stmts2 @ [ For.AsStatement(target=target, iter=iter, body=body) ] - | Babel.LabeledStatement { Body=body } -> com.TransformAsStatements(ctx, returnStrategy, body) - | Babel.ContinueStatement(_) -> [ Continue ] + | Statement.LabeledStatement(body=body) -> com.TransformAsStatements(ctx, returnStrategy, body) + | Statement.ContinueStatement(_) -> [ Continue ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" let transformBlockStatementAsStatements @@ -721,7 +735,7 @@ module Util = (ctx: Context) (returnStrategy: ReturnStrategy) (block: Babel.BlockStatement) - : list = + : Python.Statement list = [ for stmt in block.Body do yield! transformStatementAsStatements com ctx returnStrategy stmt ] @@ -730,32 +744,33 @@ module Util = let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = let returnStrategy = ReturnStrategy.Return - let stmt: Statement list = + let stmt: Python.Statement list = [ for md in body do match md with | Babel.ExportNamedDeclaration(decl) -> - match decl.Declaration with - | Babel.VariableDeclaration(decl) -> - for decls in decl.Declarations do - let value, stmts = com.TransformAsExpr(ctx, decls.Init.Value) + match decl with + | Babel.VariableDeclaration(VariableDeclaration(declarations=declarations)) -> + for (VariableDeclarator(id, init)) in declarations do + let value, stmts = com.TransformAsExpr(ctx, init.Value) - let targets: Expression list = - let name = Helpers.cleanNameAsPythonIdentifier (decls.Id.Name) - [ Name.Create(id = Identifier(name), ctx = Store) ] + let targets: Python.Expression list = + let name = Helpers.cleanNameAsPythonIdentifier (id.Name) + [ Name.Create(id = Python.Identifier(name), ctx = Store) ] yield! stmts yield Assign.Create(targets = targets, value = value) - | Babel.FunctionDeclaration(fd) -> - yield com.TransformFunction(ctx, fd.Id, fd.Params, fd.Body) + | Babel.FunctionDeclaration(``params``=``params``; body=body; id=id) -> + yield com.TransformFunction(ctx, id, ``params``, body) - | Babel.ClassDeclaration(cd) -> yield! com.TransformAsClassDef(ctx, cd) - | _ -> failwith $"Unhandled Declaration: {decl.Declaration}" + | Babel.ClassDeclaration(body, id, superClass, implements, superTypeParameters, typeParameters, loc) -> + yield! transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc + | _ -> failwith $"Unhandled Declaration: {decl}" - | Babel.ImportDeclaration(imp) -> yield! com.TransformAsImports(ctx, imp) - | Babel.PrivateModuleDeclaration(pmd) -> + | Babel.ImportDeclaration(specifiers, source) -> yield! com.TransformAsImports(ctx, specifiers, source) + | Babel.PrivateModuleDeclaration(statement=statement) -> yield! - com.TransformAsStatements(ctx, returnStrategy, pmd.Statement) + com.TransformAsStatements(ctx, returnStrategy, statement) |> transformBody returnStrategy | _ -> failwith $"Unknown module declaration: {md}" ] @@ -793,9 +808,9 @@ module Compiler = member bcom.TransformAsStatements(ctx, ret, e) = transformExpressionAsStatements bcom ctx ret e member bcom.TransformAsStatements(ctx, ret, e) = transformStatementAsStatements bcom ctx ret e member bcom.TransformAsStatements(ctx, ret, e) = transformBlockStatementAsStatements bcom ctx ret e - member bcom.TransformAsClassDef(ctx, cls) = transformAsClassDef bcom ctx cls + member bcom.TransformAsClassDef(ctx, body, id, superClass, implements, superTypeParameters, typeParameters, loc) = transformAsClassDef bcom ctx body id superClass implements superTypeParameters typeParameters loc member bcom.TransformFunction(ctx, name, args, body) = transformAsFunction bcom ctx name args body - member bcom.TransformAsImports(ctx, imp) = transformAsImports bcom ctx imp + member bcom.TransformAsImports(ctx, specifiers, source) = transformAsImports bcom ctx specifiers source interface Compiler with member _.Options = com.Options @@ -824,4 +839,5 @@ module Compiler = ScopedTypeParams = Set.empty } - transformProgram com ctx program.Body + let (Program body) = program + transformProgram com ctx body From ce1741eb98b3bdb7624b912c67415e4e86ba59f4 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 11 Feb 2021 07:06:39 +0100 Subject: [PATCH 053/145] Refactor Python printer --- src/Fable.Transforms/Fable2Babel.fs | 4 - src/Fable.Transforms/Global/Babel.fs | 4 +- src/Fable.Transforms/Python/Babel2Python.fs | 2 +- src/Fable.Transforms/Python/Python.fs | 713 +------------------ src/Fable.Transforms/Python/PythonPrinter.fs | 668 ++++++++++++++++- 5 files changed, 672 insertions(+), 719 deletions(-) diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 3ae93c4174..9ba05c1e31 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -750,11 +750,7 @@ module Util = let callFunctionWithThisContext r funcExpr (args: Expression list) = let args = thisExpr::args |> List.toArray -<<<<<<< HEAD - CallExpression.AsExpr(get None funcExpr "call", args, ?loc=r) -======= Expression.callExpression(get None funcExpr "call", args, ?loc=r) ->>>>>>> 97093df8228558dbae07e8266eff178dd75eaf83 let emitExpression range (txt: string) args = EmitExpression (txt, List.toArray args, ?loc=range) diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index 94e2d28278..375c2e3912 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -113,6 +113,8 @@ type Statement = | TryStatement of block: BlockStatement * handler: CatchClause option * finalizer: BlockStatement option * loc: SourceLocation option | ForStatement of body: BlockStatement * init: VariableDeclaration option * test: Expression option * update: Expression option * loc: SourceLocation option + + /// Note that declarations are considered statements; this is because declarations can appear in any statement context. type Declaration = | ClassDeclaration of @@ -348,7 +350,7 @@ type ClassMember = key: Expression * ``params``: Pattern array * body: BlockStatement * - Computed: bool * + computed: bool * ``static``: bool option * ``abstract``: bool option * returnType: TypeAnnotation option * diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 2cf93ef51f..2a920747d3 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -125,7 +125,7 @@ module Util = | _ -> body let transformAsImports (com: IPythonCompiler) (ctx: Context) (specifiers: Babel.ImportSpecifier array) (source: Babel.StringLiteral) : Python.Statement list = - let (StringLiteral.StringLiteral(value=value)) = source + let (StringLiteral(value=value)) = source let pymodule = value |> Helpers.rewriteFableImport printfn "Module: %A" pymodule diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 4089edaa54..c6b4284e1a 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -1,166 +1,6 @@ namespace rec Fable.AST.Python open Fable.AST -open PrinterExtensions - -type Printer = - abstract Line: int - abstract Column: int - abstract PushIndentation: unit -> unit - abstract PopIndentation: unit -> unit - abstract Print: string * ?loc:SourceLocation -> unit - abstract PrintNewLine: unit -> unit - abstract AddLocation: SourceLocation option -> unit - abstract MakeImportPath: string -> string - -module PrinterExtensions = - type Printer with - - member printer.Print(node: IPrintable) = node.Print(printer) - - member printer.PrintBlock - ( - nodes: 'a list, - printNode: Printer -> 'a -> unit, - printSeparator: Printer -> unit, - ?skipNewLineAtEnd - ) = - let skipNewLineAtEnd = defaultArg skipNewLineAtEnd false - printer.Print("") - printer.PrintNewLine() - printer.PushIndentation() - - for node in nodes do - printNode printer node - printSeparator printer - - printer.PopIndentation() - printer.Print("") - - if not skipNewLineAtEnd then - printer.PrintNewLine() - - member printer.PrintStatementSeparator() = - if printer.Column > 0 then - printer.Print("") - printer.PrintNewLine() - - member _.IsProductiveStatement(stmt: Statement) = - let rec hasNoSideEffects(e: Expression) = - printfn $"hasNoSideEffects: {e}" - - match e with - | Constant (_) -> true - | Dict { Keys = keys } -> keys.IsEmpty - | _ -> false - - match stmt with - | Expr (expr) -> hasNoSideEffects expr.Value |> not - | _ -> true - - member printer.PrintStatement(stmt: Statement, ?printSeparator) = - printer.Print(stmt) - - printSeparator - |> Option.iter (fun fn -> fn printer) - - member printer.PrintStatements(statements: Statement list) = - - for stmt in statements do - printer.PrintStatement(stmt, (fun p -> p.PrintStatementSeparator())) - - member printer.PrintBlock(nodes: Statement list, ?skipNewLineAtEnd) = - printer.PrintBlock( - nodes, - (fun p s -> p.PrintStatement(s)), - (fun p -> p.PrintStatementSeparator()), - ?skipNewLineAtEnd = skipNewLineAtEnd - ) - - member printer.PrintOptional(before: string, node: #IPrintable option) = - match node with - | None -> () - | Some node -> - printer.Print(before) - node.Print(printer) - - member printer.PrintOptional(before: string, node: IPrintable option, after: string) = - match node with - | None -> () - | Some node -> - printer.Print(before) - node.Print(printer) - printer.Print(after) - - member printer.PrintOptional(node: #IPrintable option) = - match node with - | None -> () - | Some node -> node.Print(printer) - - member printer.PrintList(nodes: 'a list, printNode: Printer -> 'a -> unit, printSeparator: Printer -> unit) = - for i = 0 to nodes.Length - 1 do - printNode printer nodes.[i] - - if i < nodes.Length - 1 then - printSeparator printer - - member printer.PrintCommaSeparatedList(nodes: #IPrintable list) = - printer.PrintList(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) - - member printer.PrintCommaSeparatedList(nodes: Expression list) = - printer.PrintList(nodes, (fun p x -> p.SequenceExpressionWithParens(x)), (fun p -> p.Print(", "))) - - member printer.PrintFunction - ( - id: Identifier option, - args: Arguments, - body: Statement list, - returnType: Expression option, - ?isDeclaration - ) = - printer.Print("def ") - printer.PrintOptional(id) - printer.Print("(") - printer.Print(args) - printer.Print(")") - printer.PrintOptional(returnType) - printer.Print(":") - printer.PrintBlock(body, skipNewLineAtEnd = true) - - member printer.WithParens(expr: Expression) = - printer.Print("(") - printer.Print(expr) - printer.Print(")") - - member printer.SequenceExpressionWithParens(expr: Expression) = - match expr with - //| :? SequenceExpression -> printer.WithParens(expr) - | _ -> printer.Print(expr) - - /// Surround with parens anything that can potentially conflict with operator precedence - member printer.ComplexExpressionWithParens(expr: Expression) = - printfn "Expr: %A" expr - - match expr with - | Constant (_) -> printer.Print(expr) - | Name (_) -> printer.Print(expr) - // | :? MemberExpression - // | :? CallExpression - // | :? ThisExpression - // | :? Super - // | :? SpreadElement - // | :? ArrayExpression - // | :? ObjectExpression -> expr.Print(printer) - | _ -> printer.WithParens(expr) - - member printer.PrintOperation(left, operator, right, ?loc) = - printer.AddLocation(loc) - printer.ComplexExpressionWithParens(left) - printer.Print(operator) - printer.ComplexExpressionWithParens(right) - -type IPrintable = - abstract Print: Printer -> unit type AST = | Expression of Expression @@ -176,22 +16,6 @@ type AST = | Keyword of Keyword | Arg of Arg - interface IPrintable with - member x.Print(printer: Printer) = - match x with - | Expression (ex) -> printer.Print(ex) - | Operator (op) -> printer.Print(op) - | BoolOperator (op) -> printer.Print(op) - | ComparisonOperator (op) -> printer.Print(op) - | UnaryOperator (op) -> printer.Print(op) - | ExpressionContext (_) -> () - | Alias (al) -> printer.Print(al) - | Module ``mod`` -> printer.Print(``mod``) - | Arguments (arg) -> printer.Print(arg) - | Keyword (kw) -> printer.Print(kw) - | Arg (arg) -> printer.Print(arg) - | Statement (st) -> printer.Print(st) - type Expression = | Attribute of Attribute | Subscript of Subscript @@ -217,34 +41,10 @@ type Expression = | Dict of Dict | Tuple of Tuple - - // member val Lineno: int = 0 with get, set // member val ColOffset: int = 0 with get, set // member val EndLineno: int option = None with get, set // member val EndColOffset: int option = None with get, set - interface IPrintable with - member x.Print(printer: Printer) = - match x with - | Attribute (ex) -> printer.Print(ex) - | Subscript (ex) -> printer.Print(ex) - | BoolOp (ex) -> printer.Print(ex) - | BinOp (ex) -> printer.Print(ex) - | Emit (ex) -> printer.Print(ex) - | UnaryOp (ex) -> printer.Print(ex) - | FormattedValue (ex) -> printer.Print(ex) - | Constant (ex) -> printer.Print(ex) - | IfExp (ex) -> printer.Print(ex) - | Call (ex) -> printer.Print(ex) - | Lambda (ex) -> printer.Print(ex) - | NamedExpr (ex) -> printer.Print(ex) - | Name (ex) -> printer.Print(ex) - | Yield (expr) -> printer.Print("(Yield)") - | YieldFrom (expr) -> printer.Print("(Yield)") - | Compare (cp) -> printer.Print(cp) - | Dict (di) -> printer.Print(di) - | Tuple (tu) -> printer.Print(tu) - type Operator = | Add | Sub @@ -260,40 +60,10 @@ type Operator = | BitAnd | MatMult - interface IPrintable with - member x.Print(printer: Printer) = - let op = - match x with - | Add -> " + " - | Sub -> " - " - | Mult -> " * " - | Div -> " / " - | FloorDiv -> " // " - | Mod -> " % " - | Pow -> " ** " - | LShift -> " << " - | RShift -> " >> " - | BitOr -> " | " - | BitXor -> " ^ " - | BitAnd -> $" & " - | MatMult -> $" @ " - - printer.Print(op) - type BoolOperator = | And | Or - interface IPrintable with - member x.Print(printer: Printer) = - let op = - match x with - | And -> " and " - | Or -> " or " - - printer.Print(op) - - type ComparisonOperator = | Eq | NotEq @@ -306,54 +76,20 @@ type ComparisonOperator = | In | NotIn - interface IPrintable with - member x.Print(printer) = - let op = - match x with - | Eq -> " == " - | NotEq -> " != " - | Lt -> " < " - | LtE -> " <= " - | Gt -> " > " - | GtE -> " >= " - | Is -> " is " - | IsNot -> " is not " - | In -> " in " - | NotIn -> " not in " - - printer.Print(op) - type UnaryOperator = | Invert | Not | UAdd | USub - interface IPrintable with - member this.Print(printer) = - let op = - match this with - | Invert -> "~" - | Not -> "not " - | UAdd -> "+" - | USub -> "-" - - printer.Print(op) - type ExpressionContext = | Load | Del | Store - type Identifier = | Identifier of string - interface IPrintable with - member this.Print(printer: Printer) = - let (Identifier id) = this - printer.Print(id) - type Statement = | AsyncFunctionDef of AsyncFunctionDef | FunctionDef of FunctionDef @@ -380,29 +116,6 @@ type Statement = // member val EndLineno: int option = None with get, set // member val EndColOffset: int option = None with get, set - interface IPrintable with - member x.Print(printer) = - match x with - | AsyncFunctionDef (def) -> printer.Print(def) - | FunctionDef (def) -> printer.Print(def) - | ImportFrom (im) -> printer.Print(im) - | NonLocal (st) -> printer.Print(st) - | ClassDef (st) -> printer.Print(st) - | AsyncFor (st) -> printer.Print(st) - | Return (rtn) -> printer.Print(rtn) - | Global (st) -> printer.Print(st) - | Import (im) -> printer.Print(im) - | Assign (st) -> printer.Print(st) - | While (wh) -> printer.Print(wh) - | Raise (st) -> printer.Print(st) - | Expr (st) -> printer.Print(st) - | For (st) -> printer.Print(st) - | Try (st) -> printer.Print(st) - | If (st) -> printer.Print(st) - | Pass -> printer.Print("pass") - | Break -> printer.Print("break") - | Continue -> printer.Print("continue") - type Module = { Body: Statement list @@ -410,9 +123,6 @@ type Module = static member Create(body) = { Body = body } - interface IPrintable with - member x.Print(printer: Printer) = printer.PrintStatements(x.Body) - /// Both parameters are raw strings of the names. asname can be None if the regular name is to be used. /// /// ```py @@ -435,16 +145,6 @@ type Alias = static member Create(name, asname) = { Name = name; AsName = asname } - interface IPrintable with - member x.Print(printer) = - printer.Print(x.Name) - - match x.AsName with - | Some (Identifier alias) -> - printer.Print(" as ") - printer.Print(alias) - | _ -> () - /// A single except clause. type is the exception type it will match, typically a Name node (or None for a catch-all /// except: clause). name is a raw string for the name to hold the exception, or None if the clause doesn’t have as foo. /// body is a list of nodes. @@ -464,17 +164,6 @@ type ExceptHandler = Loc = loc } - interface IPrintable with - member x.Print(printer) = - printer.Print("except ", ?loc = x.Loc) - printer.PrintOptional(x.Type) - printer.PrintOptional(" as ", x.Name) - printer.Print(":") - - match x.Body with - | [] -> printer.PrintBlock([ Pass ]) - | _ -> printer.PrintBlock(x.Body) - /// try blocks. All attributes are list of nodes to execute, except for handlers, which is a list of ExceptHandler /// nodes. type Try = @@ -497,21 +186,6 @@ type Try = static member AsStatement(body, ?handlers, ?orElse, ?finalBody, ?loc): Statement = Try.Create(body, ?handlers=handlers, ?orElse=orElse, ?finalBody=finalBody, ?loc=loc) |> Try - interface IPrintable with - member x.Print(printer) = - printer.Print("try: ", ?loc = x.Loc) - printer.PrintBlock(x.Body) - - for handler in x.Handlers do - printer.Print(handler) - - if x.OrElse.Length > 0 then - printer.Print("else: ") - printer.PrintBlock(x.OrElse) - - if x.FinalBody.Length > 0 then - printer.Print("finally: ") - printer.PrintBlock(x.FinalBody) /// A single argument in a list. arg is a raw string of the argument name, annotation is its annotation, such as a Str /// or Name node. @@ -541,16 +215,6 @@ type Arg = TypeComment = typeComment } - interface IPrintable with - member x.Print(printer) = - let (Identifier name) = x.Arg - printer.Print(name) - match x.Annotation with - | Some ann -> - printer.Print("=") - printer.Print(ann) - | _ -> () - type Keyword = { Lineno: int @@ -573,13 +237,6 @@ type Keyword = Value = value } - interface IPrintable with - member x.Print(printer) = - let (Identifier name) = x.Arg - printer.Print(name) - printer.Print(" = ") - printer.Print(x.Value) - /// The arguments for a function. /// /// - posonlyargs, args and kwonlyargs are lists of arg nodes. @@ -610,19 +267,6 @@ type Arguments = Defaults = defaultArg defaults [] } - interface IPrintable with - member x.Print(printer) = - match x.Args, x.VarArg with - | [], Some vararg -> - printer.Print("*") - printer.Print(vararg) - | args, Some vararg -> - printer.PrintCommaSeparatedList(args) - printer.Print(", *") - printer.Print(vararg) - | args, None -> - printer.PrintCommaSeparatedList(args) - //#region Statements /// An assignment. targets is a list of nodes, and value is a single node. @@ -671,17 +315,6 @@ type Assign = } |> Assign - interface IPrintable with - member x.Print(printer) = - printfn "Assign: %A" (x.Targets, x.Value) - //printer.PrintOperation(targets.[0], "=", value, None) - - for target in x.Targets do - printer.Print(target) - printer.Print(" = ") - - printer.Print(x.Value) - /// When an expression, such as a function call, appears as a statement by itself with its return value not used or /// stored, it is wrapped in this container. value holds one of the other nodes in this section, a Constant, a Name, a /// Lambda, a Yield or YieldFrom node. @@ -703,9 +336,6 @@ type Expr = static member Create(value): Statement = { Value = value } |> Expr - interface IPrintable with - member x.Print(printer) = printer.Print(x.Value) - /// A for loop. target holds the variable(s) the loop assigns to, as a single Name, Tuple or List node. iter holds the /// item to be looped over, again as a single node. body and orelse contain lists of nodes to execute. Those in orelse /// are executed if the loop finishes normally, rather than via a break statement. @@ -754,18 +384,6 @@ type For = For.Create(target, iter, ?body=body, ?orelse=orelse, ?typeComment=typeComment) |> For - interface IPrintable with - member x.Print(printer: Printer) = - printer.Print("for ") - printer.Print(x.Target) - printer.Print(" in ") - printer.Print(x.Iterator) - printer.Print(":") - printer.PrintNewLine() - printer.PushIndentation() - printer.PrintStatements(x.Body) - printer.PopIndentation() - type AsyncFor = { Target: Expression @@ -784,9 +402,6 @@ type AsyncFor = TypeComment = typeComment } - interface IPrintable with - member _.Print(printer) = printer.Print("(AsyncFor)") - /// A while loop. test holds the condition, such as a Compare node. /// /// ```py @@ -823,16 +438,6 @@ type While = } |> While - interface IPrintable with - member x.Print(printer: Printer) = - printer.Print("while ") - printer.Print(x.Test) - printer.Print(":") - printer.PrintNewLine() - printer.PushIndentation() - printer.PrintStatements(x.Body) - printer.PopIndentation() - /// A class definition. /// /// - name is a raw string for the class name @@ -890,25 +495,6 @@ type ClassDef = } |> ClassDef - interface IPrintable with - member x.Print(printer) = - let (Identifier name) = x.Name - printer.Print("class ", ?loc = x.Loc) - printer.Print(name) - - match x.Bases with - | [] -> () - | xs -> - printer.Print("(") - printer.PrintCommaSeparatedList(x.Bases) - printer.Print(")") - - printer.Print(":") - printer.PrintNewLine() - printer.PushIndentation() - printer.PrintStatements(x.Body) - printer.PopIndentation() - /// An if statement. test holds a single node, such as a Compare node. body and orelse each hold a list of nodes. /// /// elif clauses don’t have a special representation in the AST, but rather appear as extra If nodes within the orelse @@ -956,30 +542,6 @@ type If = } |> If - interface IPrintable with - member x.Print(printer) = - let rec printElse stmts = - match stmts with - | [] - | [ Pass ] -> () - | [ If { Test=test; Body=body; Else=els } ] -> - printer.Print("elif ") - printer.Print(test) - printer.Print(":") - printer.PrintBlock(body) - printElse els - | xs -> - printer.Print("else: ") - printer.PrintBlock(xs) - - - printer.Print("if ") - printer.Print(x.Test) - printer.Print(":") - printer.PrintBlock(x.Body) - printElse x.Else - - /// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone /// raise. cause is the optional part for y in raise x from y. /// @@ -1000,9 +562,6 @@ type Raise = static member Create(exc, ?cause): Statement = { Exception = exc; Cause = cause } |> Raise - interface IPrintable with - member _.Print(printer) = printer.Print("(Raise)") - /// A function definition. /// /// - name is a raw string of the function name. @@ -1033,11 +592,6 @@ type FunctionDef = } |> FunctionDef - interface IPrintable with - member x.Print(printer: Printer) = - printer.PrintFunction(Some x.Name, x.Args, x.Body, x.Returns, isDeclaration = true) - printer.PrintNewLine() - /// global and nonlocal statements. names is a list of raw strings. /// /// ```py @@ -1059,9 +613,6 @@ type Global = static member Create(names) = { Names = names } - interface IPrintable with - member x.Print(printer) = printer.Print("(Global)") - /// global and nonlocal statements. names is a list of raw strings. /// /// ```py @@ -1082,10 +633,6 @@ type NonLocal = static member Create(names) = { Names = names } - interface IPrintable with - member _.Print(printer: Printer) = printer.Print("(NonLocal)") - - /// An async function definition. /// /// - name is a raw string of the function name. @@ -1115,9 +662,6 @@ type AsyncFunctionDef = TypeComment = typeComment } - interface IPrintable with - member _.Print(printer: Printer) = printer.Print("(AsyncFunctionDef)") - /// An import statement. names is a list of alias nodes. /// /// ```py @@ -1138,9 +682,6 @@ type Import = static member Create(names): Statement = Import { Names = names } - interface IPrintable with - member _.Print(printer) = printer.Print("(Import)") - /// Represents from x import y. module is a raw string of the ‘from’ name, without any leading dots, or None for /// statements such as from . import foo. level is an integer holding the level of the relative import (0 means absolute /// import). @@ -1173,23 +714,6 @@ type ImportFrom = } |> ImportFrom - interface IPrintable with - member x.Print(printer: Printer) = - let (Identifier path) = x.Module |> Option.defaultValue (Identifier ".") - - printer.Print("from ") - printer.Print(printer.MakeImportPath(path)) - printer.Print(" import ") - - if not (List.isEmpty x.Names) then - if List.length x.Names > 1 then - printer.Print("(") - - printer.PrintCommaSeparatedList(x.Names) - - if List.length x.Names > 1 then - printer.Print(")") - /// A return statement. /// /// ```py @@ -1207,11 +731,6 @@ type Return = static member Create(?value): Statement = Return { Value = value } - interface IPrintable with - member this.Print(printer) = - printer.Print("return ") - printer.PrintOptional(this.Value) - //#endregion //#region Expressions @@ -1234,7 +753,6 @@ type Attribute = Ctx: ExpressionContext } - static member Create(value, attr, ctx): Expression = { Value = value @@ -1243,12 +761,6 @@ type Attribute = } |> Attribute - interface IPrintable with - member this.Print(printer) = - printer.Print(this.Value) - printer.Print(".") - printer.Print(this.Attr) - type NamedExpr = { Target: Expression @@ -1261,12 +773,6 @@ type NamedExpr = } |> NamedExpr - interface IPrintable with - member this.Print(printer) = - printer.Print(this.Target) - printer.Print(" :=") - printer.Print(this.Value) - /// A subscript, such as l[1]. value is the subscripted object (usually sequence or mapping). slice is an index, slice /// or key. It can be a Tuple and contain a Slice. ctx is Load, Store or Del according to the action performed with the /// subscript. @@ -1300,13 +806,6 @@ type Subscript = } |> Subscript - interface IPrintable with - member this.Print(printer: Printer) = - printer.Print(this.Value) - printer.Print("[") - printer.Print(this.Slice) - printer.Print("]") - type BinOp = { Left: Expression @@ -1322,10 +821,6 @@ type BinOp = } |> BinOp - interface IPrintable with - member this.Print(printer) = printer.PrintOperation(this.Left, this.Operator, this.Right) - - type BoolOp = { Values: Expression list @@ -1334,15 +829,6 @@ type BoolOp = static member Create(op, values): Expression = { Values = values; Operator = op } |> BoolOp - interface IPrintable with - - member this.Print(printer) = - for i, value in this.Values |> List.indexed do - printer.ComplexExpressionWithParens(value) - - if i < this.Values.Length - 1 then - printer.Print(this.Operator) - /// A comparison of two or more values. left is the first value in the comparison, ops the list of operators, and /// comparators the list of values after the first element in the comparison. /// @@ -1373,15 +859,6 @@ type Compare = } |> Compare - interface IPrintable with - member x.Print(printer) = - //printer.AddLocation(loc) - printer.ComplexExpressionWithParens(x.Left) - - for op, comparator in List.zip x.Ops x.Comparators do - printer.Print(op) - printer.ComplexExpressionWithParens(comparator) - /// A unary operation. op is the operator, and operand any expression node. type UnaryOp = { @@ -1398,18 +875,6 @@ type UnaryOp = } |> UnaryOp - interface IPrintable with - override x.Print(printer) = - printer.AddLocation(x.Loc) - - match x.Op with - | USub - | UAdd - | Not - | Invert -> printer.Print(x.Op) - - printer.ComplexExpressionWithParens(x.Operand) - /// A constant value. The value attribute of the Constant literal contains the Python object it represents. The values /// represented can be simple types such as a number, string or None, but also immutable container types (tuples and /// frozensets) if all of their elements are constant. @@ -1426,15 +891,6 @@ type Constant = static member Create(value: obj): Expression = { Value = value } |> Constant - interface IPrintable with - member x.Print(printer) = - match box x.Value with - | :? string as str -> - printer.Print("\"") - printer.Print(string x.Value) - printer.Print("\"") - | _ -> printer.Print(string x.Value) - /// Node representing a single formatting field in an f-string. If the string contains a single formatting field and /// nothing else the node can be isolated otherwise it appears in JoinedStr. /// @@ -1460,9 +916,6 @@ type FormattedValue = FormatSpec = formatSpec } - interface IPrintable with - member _.Print(printer) = printer.Print("(FormattedValue)") - /// A function call. func is the function, which will often be a Name or Attribute object. Of the arguments: /// /// args holds a list of the arguments passed by position. @@ -1503,14 +956,6 @@ type Call = } |> Call - interface IPrintable with - member x.Print(printer) = - printer.Print(x.Func) - printer.Print("(") - printer.PrintCommaSeparatedList(x.Args) - printer.PrintCommaSeparatedList(x.Keywords) - printer.Print(")") - type Emit = { Value: string @@ -1524,95 +969,6 @@ type Emit = } |> Emit - interface IPrintable with - member x.Print(printer) = - let inline replace pattern (f: System.Text.RegularExpressions.Match -> string) input = - System.Text.RegularExpressions.Regex.Replace(input, pattern, f) - - let printSegment (printer: Printer) (value: string) segmentStart segmentEnd = - let segmentLength = segmentEnd - segmentStart - - if segmentLength > 0 then - let segment = value.Substring(segmentStart, segmentLength) - - let subSegments = - System.Text.RegularExpressions.Regex.Split(segment, @"\r?\n") - - for i = 1 to subSegments.Length do - let subSegment = - // Remove whitespace in front of new lines, - // indent will be automatically applied - if printer.Column = 0 then - subSegments.[i - 1].TrimStart() - else - subSegments.[i - 1] - - if subSegment.Length > 0 then - printer.Print(subSegment) - - if i < subSegments.Length then - printer.PrintNewLine() - - - // Macro transformations - // https://fable.io/docs/communicate/js-from-fable.html#Emit-when-F-is-not-enough - let value = - x.Value - |> replace - @"\$(\d+)\.\.\." - (fun m -> - let rep = ResizeArray() - let i = int m.Groups.[1].Value - - for j = i to x.Args.Length - 1 do - rep.Add("$" + string j) - - String.concat ", " rep) - - |> replace - @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" - (fun m -> - let i = int m.Groups.[1].Value - - match x.Args.[i] with - | Constant (c) -> m.Groups.[2].Value - | _ -> m.Groups.[3].Value) - - |> replace - @"\{\{([^\}]*\$(\d+).*?)\}\}" - (fun m -> - let i = int m.Groups.[2].Value - - match List.tryItem i x.Args with - | Some _ -> m.Groups.[1].Value - | None -> "") - - let matches = - System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+") - - if matches.Count > 0 then - for i = 0 to matches.Count - 1 do - let m = matches.[i] - - let segmentStart = - if i > 0 then - matches.[i - 1].Index + matches.[i - 1].Length - else - 0 - - printSegment printer value segmentStart m.Index - - let argIndex = int m.Value.[1..] - - match List.tryItem argIndex x.Args with - | Some e -> printer.ComplexExpressionWithParens(e) - | None -> printer.Print("None") - - let lastMatch = matches.[matches.Count - 1] - printSegment printer value (lastMatch.Index + lastMatch.Length) value.Length - else - printSegment printer value 0 value.Length - /// An expression such as a if b else c. Each field holds a single node, so in the following example, all three are Name nodes. /// /// ```py @@ -1638,14 +994,6 @@ type IfExp = } |> IfExp - interface IPrintable with - member x.Print(printer: Printer) = - printer.Print(x.Body) - printer.Print(" if ") - printer.Print(x.Test) - printer.Print(" else ") - printer.Print(x.OrElse) - /// lambda is a minimal function definition that can be used inside an expression. Unlike FunctionDef, body holds a /// single node. /// @@ -1674,19 +1022,6 @@ type Lambda = static member Create(args, body): Expression = { Args = args; Body = body } |> Lambda - interface IPrintable with - member x.Print(printer: Printer) = - printer.Print("lambda") - - if (List.isEmpty >> not) x.Args.Args then - printer.Print(" ") - - printer.PrintCommaSeparatedList(x.Args.Args) - printer.Print(": ") - - printer.Print(x.Body) - - /// A tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an assignment target /// (i.e. (x,y)=something), and Load otherwise. /// @@ -1708,16 +1043,6 @@ type Tuple = static member Create(elts, ?loc): Expression = { Elements = elts; Loc = loc } |> Tuple - interface IPrintable with - member x.Print(printer: Printer) = - printer.Print("(", ?loc = x.Loc) - printer.PrintCommaSeparatedList(x.Elements) - - if x.Elements.Length = 1 then - printer.Print(",") - - printer.Print(")") - /// A list or tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an /// assignment target (i.e. (x,y)=something), and Load otherwise. /// @@ -1738,9 +1063,6 @@ type List = static member Create(elts) = { Elements = elts } - interface IPrintable with - member _.Print(printer) = printer.Print("(List)") - /// A set. elts holds a list of nodes representing the set’s elements. /// /// ```py @@ -1755,9 +1077,6 @@ type List = type Set (elts) = member _.Elements: Expression list = elts - interface IPrintable with - member _.Print(printer) = printer.Print("(Set)") - /// A dictionary. keys and values hold lists of nodes representing the keys and the values respectively, in matching /// order (what would be returned when calling dictionary.keys() and dictionary.values()). /// @@ -1783,31 +1102,6 @@ type Dict = static member Create(keys, values): Expression = { Keys = keys; Values = values } |> Dict - interface IPrintable with - member x.Print(printer: Printer) = - printer.Print("{") - printer.PrintNewLine() - printer.PushIndentation() - - let nodes = - List.zip x.Keys x.Values - |> List.mapi (fun i n -> (i, n)) - - for i, (key, value) in nodes do - printer.Print("\"") - printer.Print(key) - printer.Print("\"") - printer.Print(": ") - printer.Print(value) - - if i < nodes.Length - 1 then - printer.Print(",") - printer.PrintNewLine() - - printer.PrintNewLine() - printer.PopIndentation() - printer.Print("}") - /// A variable name. id holds the name as a string, and ctx is one of the following types. type Name = { @@ -1815,9 +1109,4 @@ type Name = Context: ExpressionContext } - static member Create(id, ctx): Expression = { Id = id; Context = ctx } |> Name - - interface IPrintable with - override x.Print(printer) = - let (Identifier name) = x.Id - printer.Print(name) + static member Create(id, ctx): Expression = { Id = id; Context = ctx } |> Name \ No newline at end of file diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index 3d3cce2a8f..6a5b9dc938 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -1,3 +1,4 @@ +// fsharplint:disable InterfaceNames module Fable.Transforms.PythonPrinter open System @@ -19,6 +20,16 @@ type Writer = abstract MakeImportPath: string -> string abstract Write: string -> Async +type Printer = + abstract Line: int + abstract Column: int + abstract PushIndentation: unit -> unit + abstract PopIndentation: unit -> unit + abstract Print: string * ?loc:SourceLocation -> unit + abstract PrintNewLine: unit -> unit + abstract AddLocation: SourceLocation option -> unit + abstract MakeImportPath: string -> string + type PrinterImpl(writer: Writer, map: SourceMapGenerator) = // TODO: We can make this configurable later let indentSpaces = " " @@ -81,10 +92,665 @@ type PrinterImpl(writer: Writer, map: SourceMapGenerator) = member this.MakeImportPath(path) = writer.MakeImportPath(path) + + +module PrinterExtensions = + type Printer with + + member printer.Print(stmt: Statement) = + match stmt with + | AsyncFunctionDef (def) -> printer.Print(def) + | FunctionDef (def) -> printer.Print(def) + | ImportFrom (im) -> printer.Print(im) + | NonLocal (st) -> printer.Print(st) + | ClassDef (st) -> printer.Print(st) + | AsyncFor (st) -> printer.Print(st) + | Return (rtn) -> printer.Print(rtn) + | Global (st) -> printer.Print(st) + | Import (im) -> printer.Print(im) + | Assign (st) -> printer.Print(st) + | While (wh) -> printer.Print(wh) + | Raise (st) -> printer.Print(st) + | Expr (st) -> printer.Print(st) + | For (st) -> printer.Print(st) + | Try (st) -> printer.Print(st) + | If (st) -> printer.Print(st) + | Pass -> printer.Print("pass") + | Break -> printer.Print("break") + | Continue -> printer.Print("continue") + + member printer.Print(node: Try) = + printer.Print("try: ", ?loc = node.Loc) + printer.PrintBlock(node.Body) + + for handler in node.Handlers do + printer.Print(handler) + + if node.OrElse.Length > 0 then + printer.Print("else: ") + printer.PrintBlock(node.OrElse) + + if node.FinalBody.Length > 0 then + printer.Print("finally: ") + printer.PrintBlock(node.FinalBody) + + member printer.Print(arg: Arg) = + let (Identifier name) = arg.Arg + printer.Print(name) + match arg.Annotation with + | Some ann -> + printer.Print("=") + printer.Print(ann) + | _ -> () + + member printer.Print(kw: Keyword) = + let (Identifier name) = kw.Arg + printer.Print(name) + printer.Print(" = ") + printer.Print(kw.Value) + + member printer.Print(arguments: Arguments) = + match arguments.Args, arguments.VarArg with + | [], Some vararg -> + printer.Print("*") + printer.Print(vararg) + | args, Some vararg -> + printer.PrintCommaSeparatedList(args) + printer.Print(", *") + printer.Print(vararg) + | args, None -> + printer.PrintCommaSeparatedList(args) + + member printer.Print(assign: Assign) = + printfn "Assign: %A" (assign.Targets, assign.Value) + //printer.PrintOperation(targets.[0], "=", value, None) + + for target in assign.Targets do + printer.Print(target) + printer.Print(" = ") + + printer.Print(assign.Value) + + member printer.Print(expr: Expr) = printer.Print(expr.Value) + + member printer.Print(forIn: For) = + printer.Print("for ") + printer.Print(forIn.Target) + printer.Print(" in ") + printer.Print(forIn.Iterator) + printer.Print(":") + printer.PrintNewLine() + printer.PushIndentation() + printer.PrintStatements(forIn.Body) + printer.PopIndentation() + + member printer.Print(asyncFor: AsyncFor) = printer.Print("(AsyncFor)") + + member printer.Print(wh: While) = + printer.Print("while ") + printer.Print(wh.Test) + printer.Print(":") + printer.PrintNewLine() + printer.PushIndentation() + printer.PrintStatements(wh.Body) + printer.PopIndentation() + + member printer.Print(cd: ClassDef) = + let (Identifier name) = cd.Name + printer.Print("class ", ?loc = cd.Loc) + printer.Print(name) + + match cd.Bases with + | [] -> () + | xs -> + printer.Print("(") + printer.PrintCommaSeparatedList(cd.Bases) + printer.Print(")") + + printer.Print(":") + printer.PrintNewLine() + printer.PushIndentation() + printer.PrintStatements(cd.Body) + printer.PopIndentation() + + member printer.Print(ifElse: If) = + let rec printElse stmts = + match stmts with + | [] + | [ Pass ] -> () + | [ If { Test=test; Body=body; Else=els } ] -> + printer.Print("elif ") + printer.Print(test) + printer.Print(":") + printer.PrintBlock(body) + printElse els + | xs -> + printer.Print("else: ") + printer.PrintBlock(xs) + + + printer.Print("if ") + printer.Print(ifElse.Test) + printer.Print(":") + printer.PrintBlock(ifElse.Body) + printElse ifElse.Else + + member printer.Print(ri: Raise) = printer.Print("(Raise)") + + member printer.Print(func: FunctionDef) = + printer.PrintFunction(Some func.Name, func.Args, func.Body, func.Returns, isDeclaration = true) + printer.PrintNewLine() + + member printer.Print(gl: Global) = printer.Print("(Global)") + + member printer.Print(nl: NonLocal) = printer.Print("(NonLocal)") + + member printer.Print(af: AsyncFunctionDef) = printer.Print("(AsyncFunctionDef)") + + member printer.Print(im: Import) = printer.Print("(Import)") + + member printer.Print(im: ImportFrom) = + let (Identifier path) = im.Module |> Option.defaultValue (Identifier ".") + + printer.Print("from ") + printer.Print(printer.MakeImportPath(path)) + printer.Print(" import ") + + if not (List.isEmpty im.Names) then + if List.length im.Names > 1 then + printer.Print("(") + + printer.PrintCommaSeparatedList(im.Names) + + if List.length im.Names > 1 then + printer.Print(")") + + member printer.Print(node: Return) = + printer.Print("return ") + printer.PrintOptional(node.Value) + + member printer.Print(node: Attribute) = + printer.Print(node.Value) + printer.Print(".") + printer.Print(node.Attr) + + member printer.Print(ne: NamedExpr) = + printer.Print(ne.Target) + printer.Print(" :=") + printer.Print(ne.Value) + + member printer.Print(node: Subscript) = + printer.Print(node.Value) + printer.Print("[") + printer.Print(node.Slice) + printer.Print("]") + + member printer.Print(node: BinOp) = printer.PrintOperation(node.Left, node.Operator, node.Right) + + member printer.Print(node: BoolOp) = + for i, value in node.Values |> List.indexed do + printer.ComplexExpressionWithParens(value) + + if i < node.Values.Length - 1 then + printer.Print(node.Operator) + + member printer.Print(node: Compare) = + //printer.AddLocation(loc) + printer.ComplexExpressionWithParens(node.Left) + + for op, comparator in List.zip node.Ops node.Comparators do + printer.Print(op) + printer.ComplexExpressionWithParens(comparator) + + member printer.Print(node: UnaryOp) = + printer.AddLocation(node.Loc) + + match node.Op with + | USub + | UAdd + | Not + | Invert -> printer.Print(node.Op) + + printer.ComplexExpressionWithParens(node.Operand) + + member printer.Print(node: Constant) = + match box node.Value with + | :? string as str -> + printer.Print("\"") + printer.Print(string node.Value) + printer.Print("\"") + | _ -> printer.Print(string node.Value) + + member printer.Print(node: FormattedValue) = printer.Print("(FormattedValue)") + + member printer.Print(node: Call) = + printer.Print(node.Func) + printer.Print("(") + printer.PrintCommaSeparatedList(node.Args) + printer.PrintCommaSeparatedList(node.Keywords) + printer.Print(")") + + member printer.Print(node: Emit) = + let inline replace pattern (f: System.Text.RegularExpressions.Match -> string) input = + System.Text.RegularExpressions.Regex.Replace(input, pattern, f) + + let printSegment (printer: Printer) (value: string) segmentStart segmentEnd = + let segmentLength = segmentEnd - segmentStart + + if segmentLength > 0 then + let segment = value.Substring(segmentStart, segmentLength) + + let subSegments = + System.Text.RegularExpressions.Regex.Split(segment, @"\r?\n") + + for i = 1 to subSegments.Length do + let subSegment = + // Remove whitespace in front of new lines, + // indent will be automatically applied + if printer.Column = 0 then + subSegments.[i - 1].TrimStart() + else + subSegments.[i - 1] + + if subSegment.Length > 0 then + printer.Print(subSegment) + + if i < subSegments.Length then + printer.PrintNewLine() + + // Macro transformations + // https://fable.io/docs/communicate/js-from-fable.html#Emit-when-F-is-not-enough + let value = + node.Value + |> replace + @"\$(\d+)\.\.\." + (fun m -> + let rep = ResizeArray() + let i = int m.Groups.[1].Value + + for j = i to node.Args.Length - 1 do + rep.Add("$" + string j) + + String.concat ", " rep) + + |> replace + @"\{\{\s*\$(\d+)\s*\?(.*?)\:(.*?)\}\}" + (fun m -> + let i = int m.Groups.[1].Value + + match node.Args.[i] with + | Constant (c) -> m.Groups.[2].Value + | _ -> m.Groups.[3].Value) + + |> replace + @"\{\{([^\}]*\$(\d+).*?)\}\}" + (fun m -> + let i = int m.Groups.[2].Value + + match List.tryItem i node.Args with + | Some _ -> m.Groups.[1].Value + | None -> "") + + let matches = + System.Text.RegularExpressions.Regex.Matches(value, @"\$\d+") + + if matches.Count > 0 then + for i = 0 to matches.Count - 1 do + let m = matches.[i] + + let segmentStart = + if i > 0 then + matches.[i - 1].Index + matches.[i - 1].Length + else + 0 + + printSegment printer value segmentStart m.Index + + let argIndex = int m.Value.[1..] + + match List.tryItem argIndex node.Args with + | Some e -> printer.ComplexExpressionWithParens(e) + | None -> printer.Print("None") + + let lastMatch = matches.[matches.Count - 1] + printSegment printer value (lastMatch.Index + lastMatch.Length) value.Length + else + printSegment printer value 0 value.Length + + member printer.Print(node: IfExp) = + printer.Print(node.Body) + printer.Print(" if ") + printer.Print(node.Test) + printer.Print(" else ") + printer.Print(node.OrElse) + + member printer.Print(node: Lambda) = + printer.Print("lambda") + + if (List.isEmpty >> not) node.Args.Args then + printer.Print(" ") + + printer.PrintCommaSeparatedList(node.Args.Args) + printer.Print(": ") + + printer.Print(node.Body) + + + member printer.Print(node: Tuple) = + printer.Print("(", ?loc = node.Loc) + printer.PrintCommaSeparatedList(node.Elements) + + if node.Elements.Length = 1 then + printer.Print(",") + + printer.Print(")") + + member printer.Print(node: List) = printer.Print("(List)") + + member printer.Print(node: Set) = printer.Print("(Set)") + + member printer.Print(node: Dict) = + printer.Print("{") + printer.PrintNewLine() + printer.PushIndentation() + + let nodes = + List.zip node.Keys node.Values + |> List.mapi (fun i n -> (i, n)) + + for i, (key, value) in nodes do + printer.Print("\"") + printer.Print(key) + printer.Print("\"") + printer.Print(": ") + printer.Print(value) + + if i < nodes.Length - 1 then + printer.Print(",") + printer.PrintNewLine() + + printer.PrintNewLine() + printer.PopIndentation() + printer.Print("}") + + member printer.Print(node: Name) = + let (Identifier name) = node.Id + printer.Print(name) + + member printer.Print(node: ExceptHandler) = + printer.Print("except ", ?loc = node.Loc) + printer.PrintOptional(node.Type) + printer.PrintOptional(" as ", node.Name) + printer.Print(":") + + match node.Body with + | [] -> printer.PrintBlock([ Pass ]) + | _ -> printer.PrintBlock(node.Body) + + member printer.Print(node: Alias) = + printer.Print(node.Name) + + match node.AsName with + | Some (Identifier alias) -> + printer.Print(" as ") + printer.Print(alias) + | _ -> () + + member printer.Print(node: Module) = printer.PrintStatements(node.Body) + + member printer.Print(node: Identifier) = + let (Identifier id) = node + printer.Print(id) + + member printer.Print(node: UnaryOperator) = + let op = + match node with + | Invert -> "~" + | Not -> "not " + | UAdd -> "+" + | USub -> "-" + + printer.Print(op) + + member printer.Print(node: ComparisonOperator) = + let op = + match node with + | Eq -> " == " + | NotEq -> " != " + | Lt -> " < " + | LtE -> " <= " + | Gt -> " > " + | GtE -> " >= " + | Is -> " is " + | IsNot -> " is not " + | In -> " in " + | NotIn -> " not in " + + printer.Print(op) + + member printer.Print(node: BoolOperator) = + let op = + match node with + | And -> " and " + | Or -> " or " + + printer.Print(op) + + member printer.Print(node: Operator) = + let op = + match node with + | Add -> " + " + | Sub -> " - " + | Mult -> " * " + | Div -> " / " + | FloorDiv -> " // " + | Mod -> " % " + | Pow -> " ** " + | LShift -> " << " + | RShift -> " >> " + | BitOr -> " | " + | BitXor -> " ^ " + | BitAnd -> $" & " + | MatMult -> $" @ " + + printer.Print(op) + + member printer.Print(node: Expression) = + match node with + | Attribute (ex) -> printer.Print(ex) + | Subscript (ex) -> printer.Print(ex) + | BoolOp (ex) -> printer.Print(ex) + | BinOp (ex) -> printer.Print(ex) + | Emit (ex) -> printer.Print(ex) + | UnaryOp (ex) -> printer.Print(ex) + | FormattedValue (ex) -> printer.Print(ex) + | Constant (ex) -> printer.Print(ex) + | IfExp (ex) -> printer.Print(ex) + | Call (ex) -> printer.Print(ex) + | Lambda (ex) -> printer.Print(ex) + | NamedExpr (ex) -> printer.Print(ex) + | Name (ex) -> printer.Print(ex) + | Yield (expr) -> printer.Print("(Yield)") + | YieldFrom (expr) -> printer.Print("(Yield)") + | Compare (cp) -> printer.Print(cp) + | Dict (di) -> printer.Print(di) + | Tuple (tu) -> printer.Print(tu) + + + member printer.Print(node: AST) = + match node with + | Expression (ex) -> printer.Print(ex) + | Operator (op) -> printer.Print(op) + | BoolOperator (op) -> printer.Print(op) + | ComparisonOperator (op) -> printer.Print(op) + | UnaryOperator (op) -> printer.Print(op) + | ExpressionContext (_) -> () + | Alias (al) -> printer.Print(al) + | Module ``mod`` -> printer.Print(``mod``) + | Arguments (arg) -> printer.Print(arg) + | Keyword (kw) -> printer.Print(kw) + | Arg (arg) -> printer.Print(arg) + | Statement (st) -> printer.Print(st) + + member printer.PrintBlock + ( + nodes: 'a list, + printNode: Printer -> 'a -> unit, + printSeparator: Printer -> unit, + ?skipNewLineAtEnd + ) = + let skipNewLineAtEnd = defaultArg skipNewLineAtEnd false + printer.Print("") + printer.PrintNewLine() + printer.PushIndentation() + + for node in nodes do + printNode printer node + printSeparator printer + + printer.PopIndentation() + printer.Print("") + + if not skipNewLineAtEnd then + printer.PrintNewLine() + + member printer.PrintStatementSeparator() = + if printer.Column > 0 then + printer.Print("") + printer.PrintNewLine() + + member _.IsProductiveStatement(stmt: Statement) = + let rec hasNoSideEffects(e: Expression) = + printfn $"hasNoSideEffects: {e}" + + match e with + | Constant (_) -> true + | Dict { Keys = keys } -> keys.IsEmpty + | _ -> false + + match stmt with + | Expr (expr) -> hasNoSideEffects expr.Value |> not + | _ -> true + + member printer.PrintStatement(stmt: Statement, ?printSeparator) = + printer.Print(stmt) + + printSeparator + |> Option.iter (fun fn -> fn printer) + + member printer.PrintStatements(statements: Statement list) = + + for stmt in statements do + printer.PrintStatement(stmt, (fun p -> p.PrintStatementSeparator())) + + member printer.PrintBlock(nodes: Statement list, ?skipNewLineAtEnd) = + printer.PrintBlock( + nodes, + (fun p s -> p.PrintStatement(s)), + (fun p -> p.PrintStatementSeparator()), + ?skipNewLineAtEnd = skipNewLineAtEnd + ) + + member printer.PrintOptional(before: string, node: Identifier option) = + match node with + | None -> () + | Some node -> + printer.Print(before) + printer.Print(node) + + member printer.PrintOptional(before: string, node: AST option, after: string) = + match node with + | None -> () + | Some node -> + printer.Print(before) + printer.Print(node) + printer.Print(after) + + member printer.PrintOptional(node: AST option) = + match node with + | None -> () + | Some node -> printer.Print(node) + + member printer.PrintOptional(node: Expression option) = + printer.PrintOptional(node |> Option.map Expression) + member printer.PrintOptional(node: Identifier option) = + match node with + | None -> () + | Some node -> printer.Print(node) + + member printer.PrintList(nodes: 'a list, printNode: Printer -> 'a -> unit, printSeparator: Printer -> unit) = + for i = 0 to nodes.Length - 1 do + printNode printer nodes.[i] + + if i < nodes.Length - 1 then + printSeparator printer + + member printer.PrintCommaSeparatedList(nodes: AST list) = + printer.PrintList(nodes, (fun p x -> p.Print(x)), (fun p -> p.Print(", "))) + + member printer.PrintCommaSeparatedList(nodes: Expression list) = + printer.PrintList(nodes, (fun p x -> p.SequenceExpressionWithParens(x)), (fun p -> p.Print(", "))) + member printer.PrintCommaSeparatedList(nodes: Arg list) = + printer.PrintCommaSeparatedList(nodes |> List.map Arg) + member printer.PrintCommaSeparatedList(nodes: Keyword list) = + printer.PrintCommaSeparatedList(nodes |> List.map Keyword) + member printer.PrintCommaSeparatedList(nodes: Alias list) = + printer.PrintCommaSeparatedList(nodes |> List.map Alias) + + member printer.PrintFunction + ( + id: Identifier option, + args: Arguments, + body: Statement list, + returnType: Expression option, + ?isDeclaration + ) = + printer.Print("def ") + printer.PrintOptional(id) + printer.Print("(") + printer.Print(args) + printer.Print(")") + printer.PrintOptional(returnType) + printer.Print(":") + printer.PrintBlock(body, skipNewLineAtEnd = true) + + member printer.WithParens(expr: Expression) = + printer.Print("(") + printer.Print(expr) + printer.Print(")") + + member printer.SequenceExpressionWithParens(expr: Expression) = + match expr with + //| :? SequenceExpression -> printer.WithParens(expr) + | _ -> printer.Print(expr) + + /// Surround with parens anything that can potentially conflict with operator precedence + member printer.ComplexExpressionWithParens(expr: Expression) = + printfn "Expr: %A" expr + + match expr with + | Constant (_) -> printer.Print(expr) + | Name (_) -> printer.Print(expr) + // | :? MemberExpression + // | :? CallExpression + // | :? ThisExpression + // | :? Super + // | :? SpreadElement + // | :? ArrayExpression + // | :? ObjectExpression -> expr.Print(printer) + | _ -> printer.WithParens(expr) + + member printer.PrintOperation(left, operator, right, ?loc) = + printer.AddLocation(loc) + printer.ComplexExpressionWithParens(left) + printer.Print(operator) + printer.ComplexExpressionWithParens(right) + +open PrinterExtensions let run writer map (program: Module): Async = let printDeclWithExtraLine extraLine (printer: Printer) (decl: Statement) = - (decl :> IPrintable).Print(printer) + printer.Print(decl) if printer.Column > 0 then //printer.Print(";") From d8189e8a5b1d2d2e26f09b191dbc85a685b87a20 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 11 Feb 2021 21:57:59 +0100 Subject: [PATCH 054/145] Refactored Python AST extensions --- src/Fable.Transforms/Python/Babel2Python.fs | 218 +++++---- src/Fable.Transforms/Python/Python.fs | 473 ++++++++++---------- 2 files changed, 362 insertions(+), 329 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 2a920747d3..bdea07a29e 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -40,7 +40,7 @@ type Context = type IPythonCompiler = inherit Compiler abstract GetAllImports: unit -> Python.Statement list - abstract GetImportExpr: Context * selector:string * path:string * SourceLocation option -> Python.Expression + abstract GetImportExpr: Context * name:string * moduleName:string * SourceLocation option -> Python.Identifier option abstract TransformAsExpr: Context * Babel.Expression -> Python.Expression * Python.Statement list abstract TransformAsStatements: Context * ReturnStrategy * Babel.Expression -> Python.Statement list abstract TransformAsStatements: Context * ReturnStrategy * Babel.Statement -> Python.Statement list @@ -51,7 +51,6 @@ type IPythonCompiler = abstract WarnOnlyOnce: string * ?range:SourceLocation -> unit - module Helpers = let index = (Seq.initInfinite id).GetEnumerator() @@ -111,11 +110,18 @@ module Helpers = | _ -> Some stmt module Util = + let makeImportTypeId (com: IPythonCompiler) ctx moduleName typeName = + let expr = com.GetImportExpr(ctx, typeName, getLibPath com moduleName, None) + match expr with + | Some(id) -> id + | _ -> Python.Identifier typeName + + let rec transformBody (returnStrategy: ReturnStrategy) (body: Python.Statement list) = let body = body |> List.choose Helpers.isProductiveStatement match body, returnStrategy with - | [], ReturnStrategy.Return -> [ Return.Create() ] + | [], ReturnStrategy.Return -> [ Statement.return'() ] | [], ReturnStrategy.NoBreak | [], ReturnStrategy.NoReturn -> [ Pass ] | xs, ReturnStrategy.NoBreak -> @@ -139,7 +145,7 @@ module Util = printfn "ImportMemberSpecifier" let alias = - Alias.Create( + Alias.alias( Python.Identifier(imported.Name), if imported.Name <> local.Name then Python.Identifier(local.Name) |> Some @@ -152,7 +158,7 @@ module Util = printfn "ImportDefaultSpecifier" let alias = - Alias.Create( + Alias.alias( Python.Identifier(pymodule), if local.Name <> pymodule then Python.Identifier(local.Name) |> Some @@ -165,7 +171,7 @@ module Util = printfn "ImportNamespaceSpecifier: %A" (name, name) let alias = - Alias.Create( + Alias.alias( Python.Identifier(pymodule), if pymodule <> name then Python.Identifier(name) |> Some @@ -176,10 +182,10 @@ module Util = importFroms.Add(alias) [ if imports.Count > 0 then - Import.Create(imports |> List.ofSeq) + Statement.import(imports |> List.ofSeq) if importFroms.Count > 0 then - ImportFrom.Create(Some(Python.Identifier(pymodule)), importFroms |> List.ofSeq) + Statement.importFrom(Some(Python.Identifier(pymodule)), importFroms |> List.ofSeq) ] @@ -211,7 +217,7 @@ module Util = for mber in body do match mber with | Babel.ClassMember.ClassMethod(kind, key, ``params``, body, computed, ``static``, ``abstract``, returnType, typeParameters, loc) -> - let self = Arg.Create(Python.Identifier("self")) + let self = Arg.arg(Python.Identifier("self")) let parms = ``params`` @@ -221,18 +227,18 @@ module Util = parms |> List.choose (function | Pattern.Identifier(id) -> - Arg.Create(Python.Identifier(id.Name)) |> Some + Arg.arg(Python.Identifier(id.Name)) |> Some | _ -> None) let varargs = parms |> List.choose (function | Pattern.RestElement(argument=argument) -> - Arg.Create(Python.Identifier(argument.Name)) |> Some + Arg.arg(Python.Identifier(argument.Name)) |> Some | _ -> None) |> List.tryHead - let arguments = Arguments.Create(args = self :: args, ?vararg=varargs) + let arguments = Arguments.arguments(args = self :: args, ?vararg=varargs) match kind with | "method" -> @@ -259,7 +265,7 @@ module Util = printfn $"Body length: {body.Length}: ${body}" let name = Helpers.cleanNameAsPythonIdentifier (id.Value.Name) - [ yield! stmts; ClassDef.Create(Python.Identifier(name), body = body, bases = bases) ] + [ yield! stmts; Statement.classDef(Python.Identifier(name), body = body, bases = bases) ] let transformAsFunction (com: IPythonCompiler) (ctx: Context) (name: Babel.Identifier) (parms: Babel.Pattern array) (body: Babel.BlockStatement) = let args = @@ -268,9 +274,9 @@ module Util = |> List.map (fun pattern -> let name = Helpers.cleanNameAsPythonIdentifier (pattern.Name) - Arg.Create(Python.Identifier(name))) + Arg.arg(Python.Identifier(name))) - let arguments = Arguments.Create(args = args) + let arguments = Arguments.arguments(args = args) let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Statement.BlockStatement) let name = Helpers.cleanNameAsPythonIdentifier (name.Name) @@ -289,19 +295,19 @@ module Util = let left, leftStmts = com.TransformAsExpr(ctx, left) let right, rightStmts = com.TransformAsExpr(ctx, right) match operator with - | "=" -> NamedExpr.Create(left, right), leftStmts @ rightStmts + | "=" -> Expression.namedExpr(left, right), leftStmts @ rightStmts | _ -> failwith $"Unsuppored assingment expression: {operator}" | Expression.BinaryExpression(left=left; operator=operator; right=right) -> let left, leftStmts = com.TransformAsExpr(ctx, left) let right, rightStmts = com.TransformAsExpr(ctx, right) - let toBinOp op = BinOp.Create(left, op, right), leftStmts @ rightStmts - let toCompare op = Compare.Create(left, [ op ], [ right ]), leftStmts @ rightStmts + let toBinOp op = Expression.binOp(left, op, right), leftStmts @ rightStmts + let toCompare op = Expression.compare(left, [ op ], [ right ]), leftStmts @ rightStmts let toCall name = - let func = Name.Create(Python.Identifier(name), Load) + let func = Expression.name(Python.Identifier(name)) let args = [left; right] - Call.Create(func, args),leftStmts @ rightStmts + Expression.call(func, args),leftStmts @ rightStmts match operator with | "+" -> Add |> toBinOp @@ -339,29 +345,29 @@ module Util = let operand, stmts = com.TransformAsExpr(ctx, arg) match op with - | Some op -> UnaryOp.Create(op, operand), stmts + | Some op -> Expression.unaryOp(op, operand), stmts | _ -> // TODO: Should be Contant(value=None) but we cannot create that in F# - Name.Create(id = Python.Identifier("None"), ctx = Load), stmts + Expression.name(id = Python.Identifier("None")), stmts | Expression.ArrowFunctionExpression(``params``=parms; body=body) -> let args = parms |> List.ofArray - |> List.map (fun pattern -> Arg.Create(Python.Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.arg(Python.Identifier pattern.Name)) let arguments = let args = match args with - | [] -> [ Arg.Create(Python.Identifier("_"), Name.Create(Python.Identifier("None"), Load)) ] // Need to receive unit + | [] -> [ Arg.arg(Python.Identifier("_"), Expression.name(Python.Identifier("None"))) ] // Need to receive unit | _ -> args - Arguments.Create(args = args) + Arguments.arguments(args = args) let stmts = body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. match stmts with | [| Statement.ReturnStatement(argument=argument) |] -> let body, stmts = com.TransformAsExpr(ctx, argument) - Lambda.Create(arguments, body), stmts + Expression.lambda(arguments, body), stmts | _ -> let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) let name = Helpers.getIdentifier "lifted" @@ -369,7 +375,7 @@ module Util = let func = FunctionDef.Create(name = name, args = arguments, body = body) - Name.Create(name, Load), [ func ] + Expression.name(name), [ func ] | Expression.CallExpression(callee=callee; arguments=args) -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, callee) @@ -379,7 +385,7 @@ module Util = |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) |> Helpers.unzipArgs - Call.Create(func, args), stmts @ stmtArgs + Expression.call(func, args), stmts @ stmtArgs | Expression.ArrayExpression(elements=elements) -> let elems, stmts = elements @@ -387,12 +393,12 @@ module Util = |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) |> Helpers.unzipArgs - Tuple.Create(elems), stmts - | Expression.Literal(Literal.NumericLiteral(value=value)) -> Constant.Create(value = value), [] - | Expression.Literal(Literal.StringLiteral(StringLiteral.StringLiteral(value=value))) -> Constant.Create(value = value), [] + Expression.tuple(elems), stmts + | Expression.Literal(Literal.NumericLiteral(value=value)) -> Expression.constant(value = value), [] + | Expression.Literal(Literal.StringLiteral(StringLiteral.StringLiteral(value=value))) -> Expression.constant(value = value), [] | Expression.Identifier(Identifier(name=name)) -> let name = Helpers.cleanNameAsPythonIdentifier name - Name.Create(id = Python.Identifier name, ctx = Load), [] + Expression.name(id = Python.Identifier name), [] | Expression.NewExpression(callee=callee; arguments=args) -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, callee) @@ -402,8 +408,8 @@ module Util = |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) |> Helpers.unzipArgs - Call.Create(func, args), stmts @ stmtArgs - | Expression.Super(se) -> Name.Create(Python.Identifier("super().__init__"), ctx = Load), [] + Expression.call(func, args), stmts @ stmtArgs + | Expression.Super(se) -> Expression.name(Python.Identifier("super().__init__")), [] | Expression.ObjectExpression(properties=properties) -> let keys, values, stmts = [ @@ -419,19 +425,19 @@ module Util = let args = parms |> List.ofArray - |> List.map (fun pattern -> Arg.Create(Python.Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.arg(Python.Identifier pattern.Name)) - let arguments = Arguments.Create(args = args) + let arguments = Arguments.arguments(args = args) let name = Helpers.getIdentifier "lifted" let func = FunctionDef.Create(name = name, args = arguments, body = body) - key, Name.Create(name, Load), stmts @ [func] + key, Expression.name(name), stmts @ [func] ] |> List.unzip3 - Dict.Create(keys = keys, values = values), stmts |> List.collect id + Expression.dict(keys = keys, values = values), stmts |> List.collect id | Expression.EmitExpression(value=value; args=args) -> let args, stmts = args @@ -442,25 +448,29 @@ module Util = match value with | "void $0" -> args.[0], stmts //| "raise %0" -> Raise.Create() - | _ -> Emit.Create(value, args), stmts + | _ -> Expression.emit(value, args), stmts | Expression.MemberExpression(computed=true; object=object; property=Expression.Literal(literal)) -> let value, stmts = com.TransformAsExpr(ctx, object) let attr = match literal with - | Literal.NumericLiteral(value=value) -> Constant.Create(value) - | Literal.StringLiteral(StringLiteral.StringLiteral(value=value)) -> Constant.Create(value) + | Literal.NumericLiteral(value=value) -> Expression.constant(value) + | Literal.StringLiteral(StringLiteral.StringLiteral(value=value)) -> Expression.constant(value) | _ -> failwith $"transformExpressionAsStatements: unknown literal {literal}" - Subscript.Create(value = value, slice = attr, ctx = Load), stmts + Expression.subscript(value = value, slice = attr, ctx = Load), stmts + | Expression.MemberExpression(computed=false; object=object; property=Expression.Identifier(Identifier(name="indexOf"))) -> + let value, stmts = com.TransformAsExpr(ctx, object) + let attr = Python.Identifier "index" + Expression.attribute(value = value, attr = attr, ctx = Load), stmts | Expression.MemberExpression(computed=false; object=object; property=Expression.Identifier(Identifier(name="length"))) -> let value, stmts = com.TransformAsExpr(ctx, object) - let func = Name.Create(Python.Identifier "len", Load) - Call.Create(func, [value]), stmts + let func = Expression.name(Python.Identifier "len") + Expression.call(func, [value]), stmts | Expression.MemberExpression(computed=false; object=object; property=Expression.Identifier(Identifier(name="message"))) -> let value, stmts = com.TransformAsExpr(ctx, object) - let func = Name.Create(Python.Identifier "str", Load) - Call.Create(func, [value]), stmts + let func = Expression.name(Python.Identifier "str") + Expression.call(func, [value]), stmts | Expression.MemberExpression(computed=false; object=object; property=property) -> let value, stmts = com.TransformAsExpr(ctx, object) let attr = @@ -479,23 +489,23 @@ module Util = else id - Name.Create(id = Python.Identifier(id), ctx = ctx) + Expression.name(id = Python.Identifier(id), ctx = ctx) | _ -> value - Attribute.Create(value = value, attr = attr, ctx = Load), stmts - | Expression.Literal(Literal.BooleanLiteral(value=value)) -> Constant.Create(value = value), [] + Expression.attribute(value = value, attr = attr, ctx = Load), stmts + | Expression.Literal(Literal.BooleanLiteral(value=value)) -> Expression.constant(value = value), [] | Expression.FunctionExpression(``params``=parms; body=body) -> let args = parms |> List.ofArray - |> List.map (fun pattern -> Arg.Create(Python.Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.arg(Python.Identifier pattern.Name)) - let arguments = Arguments.Create(args = args) + let arguments = Arguments.arguments(args = args) match body.Body with | [| Statement.ExpressionStatement(expr) |] -> let body, stmts = com.TransformAsExpr(ctx, expr) - Lambda.Create(arguments, body), stmts + Expression.lambda(arguments, body), stmts | _ -> let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) @@ -505,14 +515,14 @@ module Util = let func = FunctionDef.Create(name = name, args = arguments, body = body) - Name.Create(name, Load), [ func ] + Expression.name(name), [ func ] | Expression.ConditionalExpression(test=test; consequent=consequent; alternate=alternate) -> let test, stmts1 = com.TransformAsExpr(ctx, test) let body, stmts2 = com.TransformAsExpr(ctx, consequent) let orElse, stmts3 = com.TransformAsExpr(ctx, alternate) - IfExp.Create(test, body, orElse), stmts1 @ stmts2 @ stmts3 - | Expression.Literal(Literal.NullLiteral(nl)) -> Name.Create(Python.Identifier("None"), ctx = Load), [] + Expression.ifExp(test, body, orElse), stmts1 @ stmts2 @ stmts3 + | Expression.Literal(Literal.NullLiteral(nl)) -> Expression.name(Python.Identifier("None")), [] | Expression.SequenceExpression(expressions=exprs) -> // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments let exprs, stmts = @@ -526,17 +536,17 @@ module Util = |> List.mapi (fun i n -> if i = exprs.Length - 1 then - Return.Create(n) // Return the last statement + Statement.return'(n) // Return the last statement else - Expr.Create(n)) + Statement.expr(n)) let name = Helpers.getIdentifier ("lifted") let func = - FunctionDef.Create(name = name, args = Arguments.Create [], body = body) + FunctionDef.Create(name = name, args = Arguments.arguments [], body = body) - let name = Name.Create(name, Load) - Call.Create(name), stmts @ [ func ] + let name = Expression.name(name) + Expression.call(name), stmts @ [ func ] | _ -> failwith $"transformAsExpr: Unhandled value: {expr}" /// Transform Babel expressions as Python statements. @@ -559,21 +569,21 @@ module Util = let target = Python.Identifier(Helpers.cleanNameAsPythonIdentifier (name)) - [ Name.Create(id = target, ctx = Store) ] + [ Expression.name(id = target, ctx = Store) ] | Expression.MemberExpression(property=property) -> match property with | Expression.Identifier(id) -> let attr = Python.Identifier(Helpers.cleanNameAsPythonIdentifier (id.Name)) [ - Attribute.Create( - value = Name.Create(id = Python.Identifier("self"), ctx = Load), + Expression.attribute( + value = Expression.name(id = Python.Identifier("self")), attr = attr, ctx = Store ) ] | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" | _ -> failwith $"AssignmentExpression, unknown expression: {left}" - [ yield! stmts; Assign.Create(targets = targets, value = value) ] + [ yield! stmts; Statement.assign(targets = targets, value = value) ] | _ -> failwith $"transformExpressionAsStatements: unknown expr: {expr}" /// Transform Babel statement as Python statements. @@ -594,20 +604,20 @@ module Util = let expr, stmts = transformAsExpr com ctx arg match returnStrategy with - | ReturnStrategy.NoReturn -> stmts @ [ Expr.Create(expr) ] - | _ -> stmts @ [ Return.Create(expr) ] + | ReturnStrategy.NoReturn -> stmts @ [ Statement.expr(expr) ] + | _ -> stmts @ [ Statement.return'(expr) ] | Statement.Declaration(Declaration.VariableDeclaration(VariableDeclaration(declarations=declarations))) -> [ for (VariableDeclarator(id=id; init=init)) in declarations do let targets: Python.Expression list = let name = Helpers.cleanNameAsPythonIdentifier (id.Name) - [ Name.Create(id = Python.Identifier(name), ctx = Store) ] + [ Expression.name(id = Python.Identifier(name), ctx = Store) ] match init with | Some value -> let expr, stmts = com.TransformAsExpr(ctx, value) yield! stmts - Assign.Create(targets, expr) + Statement.assign(targets, expr) | None -> () ] | Statement.ExpressionStatement(expr=expression) -> @@ -618,7 +628,7 @@ module Util = [ let expr, stmts = com.TransformAsExpr(ctx, expression) yield! stmts - Expr.Create(expr) + Statement.expr(expr) ] | Statement.IfStatement(test=test; consequent=consequent; alternate=alternate) -> let test, stmts = com.TransformAsExpr(ctx, test) @@ -635,7 +645,7 @@ module Util = | _ -> [] - [ yield! stmts; If.Create(test = test, body = body, orelse = orElse) ] + [ yield! stmts; Statement.if'(test = test, body = body, orelse = orElse) ] | Statement.WhileStatement(test=test; body=body) -> let expr, stmts = com.TransformAsExpr(ctx, test) @@ -643,7 +653,7 @@ module Util = com.TransformAsStatements(ctx, returnStrategy, body) |> transformBody ReturnStrategy.NoReturn - [ yield! stmts; While.Create(test = expr, body = body, orelse = []) ] + [ yield! stmts; Statement.while'(test = expr, body = body, orelse = []) ] | Statement.TryStatement(block=block; handler=handler; finalizer=finalizer) -> let body = com.TransformAsStatements(ctx, returnStrategy, block) @@ -657,7 +667,7 @@ module Util = let body = com.TransformAsStatements(ctx, returnStrategy, body) let exn = - Name.Create(Python.Identifier("Exception"), ctx = Load) + Expression.name(Python.Identifier("Exception")) |> Some // Insert a ex.message = str(ex) for all aliased exceptions. @@ -669,12 +679,12 @@ module Util = // let msg = Assign.Create([trg], value) // let body = msg :: body let handlers = - [ ExceptHandler.Create(``type`` = exn, name = identifier, body = body) ] + [ ExceptHandler.exceptHandler(``type`` = exn, name = identifier, body = body) ] handlers | _ -> [] - [ Try.AsStatement(body = body, handlers = handlers, ?finalBody = finalBody) ] + [ Statement.try'(body = body, handlers = handlers, ?finalBody = finalBody) ] | Statement.SwitchStatement(discriminant=discriminant; cases=cases) -> let value, stmts = com.TransformAsExpr(ctx, discriminant) @@ -693,17 +703,17 @@ module Util = let test, st = com.TransformAsExpr(ctx, test) let expr = - Compare.Create(left = value, ops = [ Eq ], comparators = [ test ]) + Expression.compare(left = value, ops = [ Eq ], comparators = [ test ]) let test = match fallThrough with - | Some ft -> BoolOp.Create(op = Or, values = [ ft; expr ]) + | Some ft -> Expression.boolOp(op = Or, values = [ ft; expr ]) | _ -> expr // Check for fallthrough if body.IsEmpty then ifThenElse (Some test) cases else - [ If.Create(test = test, body = body, ?orelse = ifThenElse None cases) ] + [ Statement.if'(test = test, body = body, ?orelse = ifThenElse None cases) ] |> Some let result = cases |> List.ofArray |> ifThenElse None @@ -720,12 +730,12 @@ module Util = test=Some(Expression.BinaryExpression(left=left; right=right; operator="<=")) body=body) -> let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) - let target = Name.Create(Python.Identifier id.Name, Load) + let target = Expression.name(Python.Identifier id.Name) let start, stmts1 = com.TransformAsExpr(ctx, init) let stop, stmts2 = com.TransformAsExpr(ctx, right) - let stop = BinOp.Create(stop, Add, Constant.Create(1)) // Python `range` has exclusive end. - let iter = Call.Create(Name.Create(Python.Identifier "range", Load), args=[start; stop]) - stmts1 @ stmts2 @ [ For.AsStatement(target=target, iter=iter, body=body) ] + let stop = Expression.binOp(stop, Add, Expression.constant(1)) // Python `range` has exclusive end. + let iter = Expression.call(Expression.name(Python.Identifier "range"), args=[start; stop]) + stmts1 @ stmts2 @ [ Statement.for'(target=target, iter=iter, body=body) ] | Statement.LabeledStatement(body=body) -> com.TransformAsStatements(ctx, returnStrategy, body) | Statement.ContinueStatement(_) -> [ Continue ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" @@ -756,10 +766,10 @@ module Util = let targets: Python.Expression list = let name = Helpers.cleanNameAsPythonIdentifier (id.Name) - [ Name.Create(id = Python.Identifier(name), ctx = Store) ] + [ Expression.name(id = Python.Identifier(name), ctx = Store) ] yield! stmts - yield Assign.Create(targets = targets, value = value) + yield Statement.assign(targets = targets, value = value) | Babel.FunctionDeclaration(``params``=``params``; body=body; id=id) -> yield com.TransformFunction(ctx, id, ``params``, body) @@ -776,33 +786,55 @@ module Util = ] let imports = com.GetAllImports() - Module.Create(imports @ stmt) + Module.module'(imports @ stmt) - let getIdentForImport (ctx: Context) (path: string) (selector: string) = - if String.IsNullOrEmpty selector then + let getIdentForImport (ctx: Context) (moduleName: string) (name: string) = + if String.IsNullOrEmpty name then None else - match selector with + match name with | "*" - | "default" -> Path.GetFileNameWithoutExtension(path) - | _ -> selector + | "default" -> Path.GetFileNameWithoutExtension(moduleName) + | _ -> name //|> getUniqueNameInRootScope ctx - |> Some + |> Python.Identifier |> Some module Compiler = open Util type PythonCompiler (com: Compiler) = let onlyOnceWarnings = HashSet() - let imports = Dictionary() + let imports = Dictionary() interface IPythonCompiler with member _.WarnOnlyOnce(msg, ?range) = if onlyOnceWarnings.Add(msg) then addWarning com [] range msg - member _.GetImportExpr(ctx, selector, path, r) = failwith "Not implemented" - member _.GetAllImports() = imports.Values |> List.ofSeq |> List.map Import + member _.GetImportExpr(ctx, name, moduleName, r) = + let cachedName = moduleName + "::" + name + match imports.TryGetValue(cachedName) with + | true, { Names = [ { AsName=localIdent}] } -> + match localIdent with + | Some localIdent -> localIdent |> Some + | None -> None + | _ -> + let localId = getIdentForImport ctx moduleName name + let nameId = + if name = Naming.placeholder then + "`importMember` must be assigned to a variable" + |> addError com [] r; (name |> Python.Identifier) + else name |> Python.Identifier + let i = + ImportFrom.importFrom( + Python.Identifier moduleName |> Some, + [ Alias.alias(nameId, localId) ]) + imports.Add(cachedName, i) + match localId with + | Some localId -> localId |> Some + | None -> None + + member _.GetAllImports() = imports.Values |> List.ofSeq |> List.map ImportFrom member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e member bcom.TransformAsStatements(ctx, ret, e) = transformExpressionAsStatements bcom ctx ret e diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index c6b4284e1a..2f9703dace 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -1,3 +1,4 @@ +// fsharplint:disable MemberNames InterfaceNames namespace rec Fable.AST.Python open Fable.AST @@ -91,25 +92,25 @@ type Identifier = | Identifier of string type Statement = - | AsyncFunctionDef of AsyncFunctionDef - | FunctionDef of FunctionDef - | ImportFrom of ImportFrom - | AsyncFor of AsyncFor - | ClassDef of ClassDef - | NonLocal of NonLocal - | Global of Global - | Return of Return - | Assign of Assign - | Import of Import - | Raise of Raise - | While of While - | Expr of Expr - | Try of Try - | For of For - | If of If - | Continue - | Break | Pass + | Break + | Continue + | If of If + | For of For + | Try of Try + | Expr of Expr + | While of While + | Raise of Raise + | Import of Import + | Assign of Assign + | Return of Return + | Global of Global + | NonLocal of NonLocal + | ClassDef of ClassDef + | AsyncFor of AsyncFor + | ImportFrom of ImportFrom + | FunctionDef of FunctionDef + | AsyncFunctionDef of AsyncFunctionDef // member val Lineno: int = 0 with get, set // member val ColOffset: int = 0 with get, set @@ -121,8 +122,6 @@ type Module = Body: Statement list } - static member Create(body) = { Body = body } - /// Both parameters are raw strings of the names. asname can be None if the regular name is to be used. /// /// ```py @@ -143,8 +142,6 @@ type Alias = AsName: Identifier option } - static member Create(name, asname) = { Name = name; AsName = asname } - /// A single except clause. type is the exception type it will match, typically a Name node (or None for a catch-all /// except: clause). name is a raw string for the name to hold the exception, or None if the clause doesn’t have as foo. /// body is a list of nodes. @@ -156,14 +153,6 @@ type ExceptHandler = Loc: SourceLocation option } - static member Create(``type``, ?name, ?body, ?loc) = - { - Type = ``type`` - Name = name - Body = defaultArg body [] - Loc = loc - } - /// try blocks. All attributes are list of nodes to execute, except for handlers, which is a list of ExceptHandler /// nodes. type Try = @@ -175,18 +164,6 @@ type Try = Loc: SourceLocation option } - static member Create(body, ?handlers, ?orElse, ?finalBody, ?loc) = - { - Body = body - Handlers = defaultArg handlers [] - OrElse = defaultArg orElse [] - FinalBody = defaultArg finalBody [] - Loc = loc - } - static member AsStatement(body, ?handlers, ?orElse, ?finalBody, ?loc): Statement = - Try.Create(body, ?handlers=handlers, ?orElse=orElse, ?finalBody=finalBody, ?loc=loc) |> Try - - /// A single argument in a list. arg is a raw string of the argument name, annotation is its annotation, such as a Str /// or Name node. /// @@ -203,18 +180,6 @@ type Arg = TypeComment: string option } - static member Create(arg, ?annotation, ?typeComment) = - { - Lineno = 0 - ColOffset = 0 - EndLineno = None - EndColOffset = None - - Arg = arg - Annotation = annotation - TypeComment = typeComment - } - type Keyword = { Lineno: int @@ -226,17 +191,6 @@ type Keyword = Value: Expression } - static member Create(arg, value) = - { - Lineno = 0 - ColOffset = 0 - EndLineno = None - EndColOffset = None - - Arg = arg - Value = value - } - /// The arguments for a function. /// /// - posonlyargs, args and kwonlyargs are lists of arg nodes. @@ -256,17 +210,6 @@ type Arguments = Defaults: Expression list } - static member Create(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ?defaults) = - { - PosOnlyArgs = defaultArg posonlyargs [] - Args = defaultArg args [] - VarArg = vararg - KwOnlyArgs = defaultArg kwonlyargs [] - KwDefaults = defaultArg kwDefaults [] - KwArg = kwarg - Defaults = defaultArg defaults [] - } - //#region Statements /// An assignment. targets is a list of nodes, and value is a single node. @@ -307,14 +250,6 @@ type Assign = TypeComment: string option } - static member Create(targets, value, ?typeComment): Statement = - { - Targets = targets - Value = value - TypeComment = typeComment - } - |> Assign - /// When an expression, such as a function call, appears as a statement by itself with its return value not used or /// stored, it is wrapped in this container. value holds one of the other nodes in this section, a Constant, a Name, a /// Lambda, a Yield or YieldFrom node. @@ -334,8 +269,6 @@ type Expr = Value: Expression } - static member Create(value): Statement = { Value = value } |> Expr - /// A for loop. target holds the variable(s) the loop assigns to, as a single Name, Tuple or List node. iter holds the /// item to be looped over, again as a single node. body and orelse contain lists of nodes to execute. Those in orelse /// are executed if the loop finishes normally, rather than via a break statement. @@ -371,19 +304,6 @@ type For = TypeComment: string option } - static member Create(target, iter, ?body, ?orelse, ?typeComment) = - { - Target = target - Iterator = iter - Body = defaultArg body [] - Else = defaultArg orelse [] - TypeComment = typeComment - } - - static member AsStatement(target, iter, ?body, ?orelse, ?typeComment) : Statement = - For.Create(target, iter, ?body=body, ?orelse=orelse, ?typeComment=typeComment) - |> For - type AsyncFor = { Target: Expression @@ -393,15 +313,6 @@ type AsyncFor = TypeComment: string option } - static member Create(target, iter, body, ?orelse, ?typeComment) = - { - Target = target - Iterator = iter - Body = body - Else = defaultArg orelse [] - TypeComment = typeComment - } - /// A while loop. test holds the condition, such as a Compare node. /// /// ```py @@ -430,14 +341,6 @@ type While = Else: Statement list } - static member Create(test, body, ?orelse): Statement = - { - Test = test - Body = body - Else = defaultArg orelse [] - } - |> While - /// A class definition. /// /// - name is a raw string for the class name @@ -484,17 +387,6 @@ type ClassDef = Loc: SourceLocation option } - static member Create(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc): Statement = - { - Name = name - Bases = defaultArg bases [] - Keyword = defaultArg keywords [] - Body = defaultArg body [] - DecoratorList = defaultArg decoratorList [] - Loc = loc - } - |> ClassDef - /// An if statement. test holds a single node, such as a Compare node. body and orelse each hold a list of nodes. /// /// elif clauses don’t have a special representation in the AST, but rather appear as extra If nodes within the orelse @@ -534,14 +426,6 @@ type If = Else: Statement list } - static member Create(test, body, ?orelse): Statement = - { - Test = test - Body = body - Else = defaultArg orelse [] - } - |> If - /// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone /// raise. cause is the optional part for y in raise x from y. /// @@ -680,7 +564,6 @@ type Import = Names: Alias list } - static member Create(names): Statement = Import { Names = names } /// Represents from x import y. module is a raw string of the ‘from’ name, without any leading dots, or None for /// statements such as from . import foo. level is an integer holding the level of the relative import (0 means absolute @@ -706,13 +589,6 @@ type ImportFrom = Level: int option } - static member Create(``module``, names, ?level): Statement = - { - Module = ``module`` - Names = names - Level = level - } - |> ImportFrom /// A return statement. /// @@ -729,8 +605,6 @@ type Return = Value: Expression option } - static member Create(?value): Statement = Return { Value = value } - //#endregion //#region Expressions @@ -753,25 +627,11 @@ type Attribute = Ctx: ExpressionContext } - static member Create(value, attr, ctx): Expression = - { - Value = value - Attr = attr - Ctx = ctx - } - |> Attribute - type NamedExpr = { Target: Expression Value: Expression } - static member Create(target, value) = - { - Target = target - Value = value - } - |> NamedExpr /// A subscript, such as l[1]. value is the subscripted object (usually sequence or mapping). slice is an index, slice /// or key. It can be a Tuple and contain a Slice. ctx is Load, Store or Del according to the action performed with the @@ -798,14 +658,6 @@ type Subscript = Ctx: ExpressionContext } - static member Create(value, slice, ctx): Expression = - { - Value = value - Slice = slice - Ctx = ctx - } - |> Subscript - type BinOp = { Left: Expression @@ -813,22 +665,12 @@ type BinOp = Operator: Operator } - static member Create(left, op, right): Expression = - { - Left = left - Right = right - Operator = op - } - |> BinOp - type BoolOp = { Values: Expression list Operator: BoolOperator } - static member Create(op, values): Expression = { Values = values; Operator = op } |> BoolOp - /// A comparison of two or more values. left is the first value in the comparison, ops the list of operators, and /// comparators the list of values after the first element in the comparison. /// @@ -851,14 +693,6 @@ type Compare = Ops: ComparisonOperator list } - static member Create(left, ops, comparators): Expression = - { - Left = left - Comparators = comparators - Ops = ops - } - |> Compare - /// A unary operation. op is the operator, and operand any expression node. type UnaryOp = { @@ -867,14 +701,6 @@ type UnaryOp = Loc: SourceLocation option } - static member Create(op, operand, ?loc): Expression = - { - Op = op - Operand = operand - Loc = loc - } - |> UnaryOp - /// A constant value. The value attribute of the Constant literal contains the Python object it represents. The values /// represented can be simple types such as a number, string or None, but also immutable container types (tuples and /// frozensets) if all of their elements are constant. @@ -889,8 +715,6 @@ type Constant = Value: obj } - static member Create(value: obj): Expression = { Value = value } |> Constant - /// Node representing a single formatting field in an f-string. If the string contains a single formatting field and /// nothing else the node can be isolated otherwise it appears in JoinedStr. /// @@ -909,13 +733,6 @@ type FormattedValue = FormatSpec: Expression option } - static member Create(value, ?conversion, ?formatSpec) = - { - Value = value - Conversion = conversion - FormatSpec = formatSpec - } - /// A function call. func is the function, which will often be a Name or Attribute object. Of the arguments: /// /// args holds a list of the arguments passed by position. @@ -948,27 +765,12 @@ type Call = Keywords: Keyword list } - static member Create(func, ?args, ?kw): Expression = - { - Func = func - Args = defaultArg args [] - Keywords = defaultArg kw [] - } - |> Call - type Emit = { Value: string Args: Expression list } - static member Create(value, ?args): Expression = - { - Value = value - Args = defaultArg args [] - } - |> Emit - /// An expression such as a if b else c. Each field holds a single node, so in the following example, all three are Name nodes. /// /// ```py @@ -986,14 +788,6 @@ type IfExp = OrElse: Expression } - static member Create(test, body, orElse): Expression = - { - Test = test - Body = body - OrElse = orElse - } - |> IfExp - /// lambda is a minimal function definition that can be used inside an expression. Unlike FunctionDef, body holds a /// single node. /// @@ -1020,8 +814,6 @@ type Lambda = Body: Expression } - static member Create(args, body): Expression = { Args = args; Body = body } |> Lambda - /// A tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an assignment target /// (i.e. (x,y)=something), and Load otherwise. /// @@ -1041,8 +833,6 @@ type Tuple = Loc: SourceLocation option } - static member Create(elts, ?loc): Expression = { Elements = elts; Loc = loc } |> Tuple - /// A list or tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an /// assignment target (i.e. (x,y)=something), and Load otherwise. /// @@ -1061,8 +851,6 @@ type List = Elements: Expression list } - static member Create(elts) = { Elements = elts } - /// A set. elts holds a list of nodes representing the set’s elements. /// /// ```py @@ -1074,8 +862,10 @@ type List = /// Constant(value=2), /// Constant(value=3)])) /// ``` -type Set (elts) = - member _.Elements: Expression list = elts +type Set = + { + Elements: Expression list + } /// A dictionary. keys and values hold lists of nodes representing the keys and the values respectively, in matching /// order (what would be returned when calling dictionary.keys() and dictionary.values()). @@ -1100,8 +890,6 @@ type Dict = Values: Expression list } - static member Create(keys, values): Expression = { Keys = keys; Values = values } |> Dict - /// A variable name. id holds the name as a string, and ctx is one of the following types. type Name = { @@ -1109,4 +897,217 @@ type Name = Context: ExpressionContext } - static member Create(id, ctx): Expression = { Id = id; Context = ctx } |> Name \ No newline at end of file +[] +module PythonExtensions = + type Statement with + static member import(names): Statement = Import { Names = names } + static member expr(value): Statement = { Expr.Value = value } |> Expr + static member try'(body, ?handlers, ?orElse, ?finalBody, ?loc): Statement = + Try.try'(body, ?handlers=handlers, ?orElse=orElse, ?finalBody=finalBody, ?loc=loc) |> Try + static member classDef(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc): Statement = + { + Name = name + Bases = defaultArg bases [] + Keyword = defaultArg keywords [] + Body = defaultArg body [] + DecoratorList = defaultArg decoratorList [] + Loc = loc + } + |> ClassDef + static member assign(targets, value, ?typeComment): Statement = + { + Targets = targets + Value = value + TypeComment = typeComment + } + |> Assign + static member return'(?value): Statement = Return { Value = value } + static member for'(target, iter, ?body, ?orelse, ?typeComment) : Statement = + For.for'(target, iter, ?body=body, ?orelse=orelse, ?typeComment=typeComment) + |> For + static member while'(test, body, ?orelse): Statement = + { + While.Test = test + Body = body + Else = defaultArg orelse [] + } + |> While + static member if'(test, body, ?orelse): Statement = + { + Test = test + Body = body + Else = defaultArg orelse [] + } + |> If + static member importFrom(``module``, names, ?level) = + ImportFrom.importFrom(``module``, names, ?level=level) |> ImportFrom + + type Expression with + static member name(id, ?ctx): Expression = { Id = id; Context = defaultArg ctx Load } |> Name + static member dict(keys, values): Expression = { Keys = keys; Values = values } |> Dict + static member tuple(elts, ?loc): Expression = { Elements = elts; Loc = loc } |> Tuple + static member ifExp(test, body, orElse): Expression = + { + Test = test + Body = body + OrElse = orElse + } + |> IfExp + static member lambda(args, body): Expression = { Args = args; Body = body } |> Lambda + static member emit(value, ?args): Expression = + { + Value = value + Args = defaultArg args [] + } + |> Emit + static member call(func, ?args, ?kw): Expression = + { + Func = func + Args = defaultArg args [] + Keywords = defaultArg kw [] + } + |> Call + static member compare(left, ops, comparators): Expression = + { + Left = left + Comparators = comparators + Ops = ops + } + |> Compare + static member attribute(value, attr, ctx): Expression = + { + Value = value + Attr = attr + Ctx = ctx + } + |> Attribute + static member unaryOp(op, operand, ?loc): Expression = + { + Op = op + Operand = operand + Loc = loc + } + |> UnaryOp + static member namedExpr(target, value) = + { + Target = target + Value = value + } + |> NamedExpr + static member subscript(value, slice, ctx): Expression = + { + Value = value + Slice = slice + Ctx = ctx + } + |> Subscript + static member binOp(left, op, right): Expression = + { + Left = left + Right = right + Operator = op + } + |> BinOp + static member boolOp(op, values): Expression = { Values = values; Operator = op } |> BoolOp + static member constant(value: obj): Expression = { Value = value } |> Constant + + + type List with + static member list(elts) = { Elements = elts } + + type ExceptHandler with + static member exceptHandler(``type``, ?name, ?body, ?loc) = + { + Type = ``type`` + Name = name + Body = defaultArg body [] + Loc = loc + } + type Alias with + static member alias(name, asname) = { Name = name; AsName = asname } + + type Try with + static member try'(body, ?handlers, ?orElse, ?finalBody, ?loc) = + { + Body = body + Handlers = defaultArg handlers [] + OrElse = defaultArg orElse [] + FinalBody = defaultArg finalBody [] + Loc = loc + } + + type FormattedValue with + static member formattedValue(value, ?conversion, ?formatSpec) = + { + Value = value + Conversion = conversion + FormatSpec = formatSpec + } + + type Module with + static member module'(body) = { Body = body } + + type Arg with + static member arg(arg, ?annotation, ?typeComment) = + { + Lineno = 0 + ColOffset = 0 + EndLineno = None + EndColOffset = None + + Arg = arg + Annotation = annotation + TypeComment = typeComment + } + + type Keyword with + static member keyword(arg, value) = + { + Lineno = 0 + ColOffset = 0 + EndLineno = None + EndColOffset = None + + Arg = arg + Value = value + } + + type Arguments with + static member arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ?defaults) = + { + PosOnlyArgs = defaultArg posonlyargs [] + Args = defaultArg args [] + VarArg = vararg + KwOnlyArgs = defaultArg kwonlyargs [] + KwDefaults = defaultArg kwDefaults [] + KwArg = kwarg + Defaults = defaultArg defaults [] + } + + type For with + static member for'(target, iter, ?body, ?orelse, ?typeComment) = + { + Target = target + Iterator = iter + Body = defaultArg body [] + Else = defaultArg orelse [] + TypeComment = typeComment + } + + type AsyncFor with + static member asyncFor(target, iter, body, ?orelse, ?typeComment) = + { + Target = target + Iterator = iter + Body = body + Else = defaultArg orelse [] + TypeComment = typeComment + } + + type ImportFrom with + static member importFrom(``module``, names, ?level) = + { + Module = ``module`` + Names = names + Level = level + } From 4209a4be001e505f9029c45cf51f1a21bba653a2 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 14 Feb 2021 09:50:45 +0100 Subject: [PATCH 055/145] Better handling of babel member expressions --- src/Fable.Transforms/Python/Babel2Python.fs | 711 +++++++++++--------- src/Fable.Transforms/Python/Python.fs | 646 ++++++++---------- 2 files changed, 665 insertions(+), 692 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index bdea07a29e..9c932e2e69 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -13,7 +13,7 @@ open Fable.AST.Babel type ReturnStrategy = | Return | NoReturn - | NoBreak // Used in switch statement blocks + | NoBreak // Used in switch statement blocks type ITailCallOpportunity = abstract Label: string @@ -21,35 +21,46 @@ type ITailCallOpportunity = abstract IsRecursiveRef: Fable.Expr -> bool type UsedNames = - { - RootScope: HashSet - DeclarationScopes: HashSet - CurrentDeclarationScope: HashSet - } + { RootScope: HashSet + DeclarationScopes: HashSet + CurrentDeclarationScope: HashSet } type Context = { - //UsedNames: UsedNames - DecisionTargets: (Fable.Ident list * Fable.Expr) list - HoistVars: Fable.Ident list -> bool - TailCallOpportunity: ITailCallOpportunity option - OptimizeTailCall: unit -> unit - ScopedTypeParams: Set - } + //UsedNames: UsedNames + DecisionTargets: (Fable.Ident list * Fable.Expr) list + HoistVars: Fable.Ident list -> bool + TailCallOpportunity: ITailCallOpportunity option + OptimizeTailCall: unit -> unit + ScopedTypeParams: Set } type IPythonCompiler = inherit Compiler abstract GetAllImports: unit -> Python.Statement list - abstract GetImportExpr: Context * name:string * moduleName:string * SourceLocation option -> Python.Identifier option + + abstract GetImportExpr: Context * name: string * moduleName: string * SourceLocation option -> Python.Identifier option + abstract TransformAsExpr: Context * Babel.Expression -> Python.Expression * Python.Statement list abstract TransformAsStatements: Context * ReturnStrategy * Babel.Expression -> Python.Statement list abstract TransformAsStatements: Context * ReturnStrategy * Babel.Statement -> Python.Statement list abstract TransformAsStatements: Context * ReturnStrategy * Babel.BlockStatement -> Python.Statement list - abstract TransformAsClassDef: Context * Babel.ClassBody * Babel.Identifier option * Babel.Expression option * Babel.ClassImplements array option * Babel.TypeParameterInstantiation option * Babel.TypeParameterDeclaration option * SourceLocation option -> Python.Statement list + + abstract TransformAsClassDef: + Context + * Babel.ClassBody + * Babel.Identifier option + * Babel.Expression option + * Babel.ClassImplements array option + * Babel.TypeParameterInstantiation option + * Babel.TypeParameterDeclaration option + * SourceLocation option -> + Python.Statement list + abstract TransformAsImports: Context * Babel.ImportSpecifier array * Babel.StringLiteral -> Python.Statement list + abstract TransformFunction: Context * Babel.Identifier * Babel.Pattern array * Babel.BlockStatement -> Python.Statement - abstract WarnOnlyOnce: string * ?range:SourceLocation -> unit + abstract WarnOnlyOnce: string * ?range: SourceLocation -> unit module Helpers = let index = (Seq.initInfinite id).GetEnumerator() @@ -65,15 +76,18 @@ module Helpers = match name with | "this" -> "self" // TODO: Babel should use ThisExpression to avoid this hack. | "async" -> "asyncio" - | _ -> name.Replace('$', '_') + | _ -> name.Replace('$', '_').Replace('.', '_') let rewriteFableImport moduleName = let _reFableLib = Regex(".*\/fable-library[\.0-9]*\/(?[^\/]*)\.js", RegexOptions.Compiled) let m = _reFableLib.Match(moduleName) + if m.Groups.Count > 1 then - let pymodule = m.Groups.["module"].Value.ToLower() |> cleanNameAsPythonIdentifier + let pymodule = + m.Groups.["module"].Value.ToLower() + |> cleanNameAsPythonIdentifier let moduleName = String.concat "." [ "fable"; pymodule ] @@ -111,17 +125,18 @@ module Helpers = module Util = let makeImportTypeId (com: IPythonCompiler) ctx moduleName typeName = - let expr = com.GetImportExpr(ctx, typeName, getLibPath com moduleName, None) + let expr = + com.GetImportExpr(ctx, typeName, getLibPath com moduleName, None) + match expr with - | Some(id) -> id + | Some (id) -> id | _ -> Python.Identifier typeName - let rec transformBody (returnStrategy: ReturnStrategy) (body: Python.Statement list) = let body = body |> List.choose Helpers.isProductiveStatement match body, returnStrategy with - | [], ReturnStrategy.Return -> [ Statement.return'() ] + | [], ReturnStrategy.Return -> [ Statement.return' () ] | [], ReturnStrategy.NoBreak | [], ReturnStrategy.NoReturn -> [ Pass ] | xs, ReturnStrategy.NoBreak -> @@ -130,8 +145,13 @@ module Util = |> transformBody ReturnStrategy.NoReturn | _ -> body - let transformAsImports (com: IPythonCompiler) (ctx: Context) (specifiers: Babel.ImportSpecifier array) (source: Babel.StringLiteral) : Python.Statement list = - let (StringLiteral(value=value)) = source + let transformAsImports + (com: IPythonCompiler) + (ctx: Context) + (specifiers: Babel.ImportSpecifier array) + (source: Babel.StringLiteral) + : Python.Statement list = + let (StringLiteral (value = value)) = source let pymodule = value |> Helpers.rewriteFableImport printfn "Module: %A" pymodule @@ -141,11 +161,11 @@ module Util = for expr in specifiers do match expr with - | Babel.ImportMemberSpecifier(local, imported) -> + | Babel.ImportMemberSpecifier (local, imported) -> printfn "ImportMemberSpecifier" let alias = - Alias.alias( + Alias.alias ( Python.Identifier(imported.Name), if imported.Name <> local.Name then Python.Identifier(local.Name) |> Some @@ -154,11 +174,11 @@ module Util = ) importFroms.Add(alias) - | Babel.ImportDefaultSpecifier(local) -> + | Babel.ImportDefaultSpecifier (local) -> printfn "ImportDefaultSpecifier" let alias = - Alias.alias( + Alias.alias ( Python.Identifier(pymodule), if local.Name <> pymodule then Python.Identifier(local.Name) |> Some @@ -167,11 +187,11 @@ module Util = ) imports.Add(alias) - | Babel.ImportNamespaceSpecifier(Identifier(name = name)) -> + | Babel.ImportNamespaceSpecifier (Identifier (name = name)) -> printfn "ImportNamespaceSpecifier: %A" (name, name) let alias = - Alias.alias( + Alias.alias ( Python.Identifier(pymodule), if pymodule <> name then Python.Identifier(name) |> Some @@ -180,14 +200,12 @@ module Util = ) importFroms.Add(alias) - [ - if imports.Count > 0 then - Statement.import(imports |> List.ofSeq) - if importFroms.Count > 0 then - Statement.importFrom(Some(Python.Identifier(pymodule)), importFroms |> List.ofSeq) - ] + [ if imports.Count > 0 then + Statement.import (imports |> List.ofSeq) + if importFroms.Count > 0 then + Statement.importFrom (Some(Python.Identifier(pymodule)), importFroms |> List.ofSeq) ] let transformAsClassDef (com: IPythonCompiler) @@ -212,102 +230,110 @@ module Util = | None -> [], [] let body: Python.Statement list = - [ - let (ClassBody(body=body)) = body - for mber in body do - match mber with - | Babel.ClassMember.ClassMethod(kind, key, ``params``, body, computed, ``static``, ``abstract``, returnType, typeParameters, loc) -> - let self = Arg.arg(Python.Identifier("self")) - - let parms = - ``params`` - |> List.ofArray - - let args = - parms - |> List.choose (function - | Pattern.Identifier(id) -> - Arg.arg(Python.Identifier(id.Name)) |> Some - | _ -> None) - - let varargs = - parms - |> List.choose (function - | Pattern.RestElement(argument=argument) -> - Arg.arg(Python.Identifier(argument.Name)) |> Some - | _ -> None) - |> List.tryHead - - let arguments = Arguments.arguments(args = self :: args, ?vararg=varargs) - - match kind with - | "method" -> - let body = - com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Statement.BlockStatement) - - let name = - match key with - | Expression.Identifier(id) -> Python.Identifier(id.Name) - | _ -> failwith $"transformAsClassDef: Unknown key: {key}" - - FunctionDef.Create(name, arguments, body = body) - | "constructor" -> - let name = Python.Identifier("__init__") - - let body = - com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body |> Statement.BlockStatement) - - FunctionDef.Create(name, arguments, body = body) - | _ -> failwith $"transformAsClassDef: Unknown kind: {kind}" - | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" - ] + [ let (ClassBody (body = body)) = body + + for mber in body do + match mber with + | Babel.ClassMember.ClassMethod (kind, key, ``params``, body, computed, ``static``, ``abstract``, returnType, typeParameters, loc) -> + let self = Arg.arg (Python.Identifier("self")) + + let parms = ``params`` |> List.ofArray + + let args = + parms + |> List.choose + (function + | Pattern.Identifier (id) -> Arg.arg (Python.Identifier(id.Name)) |> Some + | _ -> None) + + let varargs = + parms + |> List.choose + (function + | Pattern.RestElement (argument = argument) -> Arg.arg (Python.Identifier(argument.Name)) |> Some + | _ -> None) + |> List.tryHead + + let arguments = Arguments.arguments (args = self :: args, ?vararg = varargs) + + match kind with + | "method" -> + let body = + com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Statement.BlockStatement) + + let name = + match key with + | Expression.Identifier (id) -> Python.Identifier(id.Name) + | Expression.Literal(Literal.StringLiteral(StringLiteral(value=name))) -> + let name = Helpers.cleanNameAsPythonIdentifier(name) + Python.Identifier(name) + | _ -> failwith $"transformAsClassDef: Unknown key: {key}" + + FunctionDef.Create(name, arguments, body = body) + | "constructor" -> + let name = Python.Identifier("__init__") + + let body = + com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body |> Statement.BlockStatement) + + FunctionDef.Create(name, arguments, body = body) + | _ -> failwith $"transformAsClassDef: Unknown kind: {kind}" + | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] printfn $"Body length: {body.Length}: ${body}" let name = Helpers.cleanNameAsPythonIdentifier (id.Value.Name) - [ yield! stmts; Statement.classDef(Python.Identifier(name), body = body, bases = bases) ] + [ yield! stmts; Statement.classDef (Python.Identifier(name), body = body, bases = bases) ] - let transformAsFunction (com: IPythonCompiler) (ctx: Context) (name: Babel.Identifier) (parms: Babel.Pattern array) (body: Babel.BlockStatement) = + let transformAsFunction + (com: IPythonCompiler) + (ctx: Context) + (name: Babel.Identifier) + (parms: Babel.Pattern array) + (body: Babel.BlockStatement) + = let args = parms |> List.ofArray |> List.map (fun pattern -> let name = Helpers.cleanNameAsPythonIdentifier (pattern.Name) - Arg.arg(Python.Identifier(name))) + Arg.arg (Python.Identifier(name))) + + let arguments = Arguments.arguments (args = args) + + let body = + com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Statement.BlockStatement) - let arguments = Arguments.arguments(args = args) - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Statement.BlockStatement) let name = Helpers.cleanNameAsPythonIdentifier (name.Name) FunctionDef.Create(Python.Identifier(name), arguments, body = body) /// Transform Babel expression as Python expression - let rec transformAsExpr - (com: IPythonCompiler) - (ctx: Context) - (expr: Babel.Expression) - : Python.Expression * list = + let rec transformAsExpr (com: IPythonCompiler) (ctx: Context) (expr: Babel.Expression): Python.Expression * list = printfn $"transformAsExpr: {expr}" match expr with - | Expression.AssignmentExpression(left=left; operator=operator; right=right) -> + | AssignmentExpression (left = left; operator = operator; right = right) -> let left, leftStmts = com.TransformAsExpr(ctx, left) let right, rightStmts = com.TransformAsExpr(ctx, right) + match operator with - | "=" -> Expression.namedExpr(left, right), leftStmts @ rightStmts + | "=" -> + Expression.namedExpr (left, right), leftStmts @ rightStmts | _ -> failwith $"Unsuppored assingment expression: {operator}" - | Expression.BinaryExpression(left=left; operator=operator; right=right) -> + | BinaryExpression (left = left; operator = operator; right = right) -> let left, leftStmts = com.TransformAsExpr(ctx, left) let right, rightStmts = com.TransformAsExpr(ctx, right) - let toBinOp op = Expression.binOp(left, op, right), leftStmts @ rightStmts - let toCompare op = Expression.compare(left, [ op ], [ right ]), leftStmts @ rightStmts + let toBinOp op = Expression.binOp (left, op, right), leftStmts @ rightStmts + let toCompare op = Expression.compare (left, [ op ], [ right ]), leftStmts @ rightStmts + let toCall name = - let func = Expression.name(Python.Identifier(name)) - let args = [left; right] - Expression.call(func, args),leftStmts @ rightStmts + let func = Expression.name (Python.Identifier(name)) + let args = [ left; right ] + Expression.call (func, args), leftStmts @ rightStmts match operator with | "+" -> Add |> toBinOp @@ -332,7 +358,7 @@ module Util = | "isinstance" -> toCall "isinstance" | _ -> failwith $"Unknown operator: {operator}" - | Expression.UnaryExpression(operator=operator; argument=arg) -> + | UnaryExpression (operator = operator; argument = arg) -> let op = match operator with | "-" -> USub |> Some @@ -345,29 +371,31 @@ module Util = let operand, stmts = com.TransformAsExpr(ctx, arg) match op with - | Some op -> Expression.unaryOp(op, operand), stmts + | Some op -> Expression.unaryOp (op, operand), stmts | _ -> // TODO: Should be Contant(value=None) but we cannot create that in F# - Expression.name(id = Python.Identifier("None")), stmts + Expression.name (id = Python.Identifier("None")), stmts - | Expression.ArrowFunctionExpression(``params``=parms; body=body) -> + | ArrowFunctionExpression (``params`` = parms; body = body) -> let args = parms |> List.ofArray - |> List.map (fun pattern -> Arg.arg(Python.Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.arg (Python.Identifier pattern.Name)) let arguments = let args = match args with - | [] -> [ Arg.arg(Python.Identifier("_"), Expression.name(Python.Identifier("None"))) ] // Need to receive unit + | [] -> [ Arg.arg (Python.Identifier("_"), Expression.name (Python.Identifier("None"))) ] // Need to receive unit | _ -> args - Arguments.arguments(args = args) + + Arguments.arguments (args = args) let stmts = body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. + match stmts with - | [| Statement.ReturnStatement(argument=argument) |] -> + | [| Statement.ReturnStatement (argument = argument) |] -> let body, stmts = com.TransformAsExpr(ctx, argument) - Expression.lambda(arguments, body), stmts + Expression.lambda (arguments, body), stmts | _ -> let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) let name = Helpers.getIdentifier "lifted" @@ -375,8 +403,8 @@ module Util = let func = FunctionDef.Create(name = name, args = arguments, body = body) - Expression.name(name), [ func ] - | Expression.CallExpression(callee=callee; arguments=args) -> // FIXME: use transformAsCall + Expression.name (name), [ func ] + | CallExpression (callee = callee; arguments = args) -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, callee) let args, stmtArgs = @@ -385,21 +413,22 @@ module Util = |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) |> Helpers.unzipArgs - Expression.call(func, args), stmts @ stmtArgs - | Expression.ArrayExpression(elements=elements) -> + Expression.call (func, args), stmts @ stmtArgs + | ArrayExpression (elements = elements) -> let elems, stmts = elements |> List.ofArray |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) |> Helpers.unzipArgs - Expression.tuple(elems), stmts - | Expression.Literal(Literal.NumericLiteral(value=value)) -> Expression.constant(value = value), [] - | Expression.Literal(Literal.StringLiteral(StringLiteral.StringLiteral(value=value))) -> Expression.constant(value = value), [] - | Expression.Identifier(Identifier(name=name)) -> + Expression.tuple (elems), stmts + | Expression.Literal (Literal.NumericLiteral (value = value)) -> Expression.constant (value = value), [] + | Expression.Literal (Literal.StringLiteral (StringLiteral.StringLiteral (value = value))) -> + Expression.constant (value = value), [] + | Expression.Identifier (Identifier (name = name)) -> let name = Helpers.cleanNameAsPythonIdentifier name - Expression.name(id = Python.Identifier name), [] - | Expression.NewExpression(callee=callee; arguments=args) -> // FIXME: use transformAsCall + Expression.name (id = Python.Identifier name), [] + | NewExpression (callee = callee; arguments = args) -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, callee) let args, stmtArgs = @@ -408,37 +437,36 @@ module Util = |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) |> Helpers.unzipArgs - Expression.call(func, args), stmts @ stmtArgs - | Expression.Super(se) -> Expression.name(Python.Identifier("super().__init__")), [] - | Expression.ObjectExpression(properties=properties) -> + Expression.call (func, args), stmts @ stmtArgs + | Expression.Super (se) -> Expression.name (Python.Identifier("super().__init__")), [] + | ObjectExpression (properties = properties) -> let keys, values, stmts = - [ - for prop in properties do - match prop with - | ObjectProperty(key=key; value=value) -> - let key, stmts1 = com.TransformAsExpr(ctx, key) - let value, stmts2 = com.TransformAsExpr(ctx, value) - key, value, stmts1 @ stmts2 - | Babel.ObjectMethod(key=key; ``params``=parms; body=body) -> - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) - let key, stmts = com.TransformAsExpr(ctx, key) - let args = - parms - |> List.ofArray - |> List.map (fun pattern -> Arg.arg(Python.Identifier pattern.Name)) - - let arguments = Arguments.arguments(args = args) - let name = Helpers.getIdentifier "lifted" - - let func = - FunctionDef.Create(name = name, args = arguments, body = body) - key, Expression.name(name), stmts @ [func] - - ] + [ for prop in properties do + match prop with + | ObjectProperty (key = key; value = value) -> + let key, stmts1 = com.TransformAsExpr(ctx, key) + let value, stmts2 = com.TransformAsExpr(ctx, value) + key, value, stmts1 @ stmts2 + | Babel.ObjectMethod (key = key; ``params`` = parms; body = body) -> + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) + let key, stmts = com.TransformAsExpr(ctx, key) + + let args = + parms + |> List.ofArray + |> List.map (fun pattern -> Arg.arg (Python.Identifier pattern.Name)) + + let arguments = Arguments.arguments (args = args) + let name = Helpers.getIdentifier "lifted" + + let func = + FunctionDef.Create(name = name, args = arguments, body = body) + + key, Expression.name (name), stmts @ [ func ] ] |> List.unzip3 - Expression.dict(keys = keys, values = values), stmts |> List.collect id - | Expression.EmitExpression(value=value; args=args) -> + Expression.dict (keys = keys, values = values), stmts |> List.collect id + | EmitExpression (value = value; args = args) -> let args, stmts = args |> List.ofArray @@ -448,39 +476,57 @@ module Util = match value with | "void $0" -> args.[0], stmts //| "raise %0" -> Raise.Create() - | _ -> Expression.emit(value, args), stmts - | Expression.MemberExpression(computed=true; object=object; property=Expression.Literal(literal)) -> + | _ -> Expression.emit (value, args), stmts + | MemberExpression (computed = true; object = object; property = Expression.Literal (literal)) -> let value, stmts = com.TransformAsExpr(ctx, object) - - let attr = - match literal with - | Literal.NumericLiteral(value=value) -> Expression.constant(value) - | Literal.StringLiteral(StringLiteral.StringLiteral(value=value)) -> Expression.constant(value) - | _ -> failwith $"transformExpressionAsStatements: unknown literal {literal}" - - Expression.subscript(value = value, slice = attr, ctx = Load), stmts - | Expression.MemberExpression(computed=false; object=object; property=Expression.Identifier(Identifier(name="indexOf"))) -> + match literal with + | NumericLiteral (value = numeric) -> + let attr = Expression.constant(numeric) + Expression.subscript(value = value, slice = attr, ctx = Load), stmts + | Literal.StringLiteral (StringLiteral (value = str)) -> + let attr = Expression.constant (str) + let func = Expression.name("getattr") + Expression.call(func, args=[value; attr]), stmts + | _ -> failwith $"transformExpr: unknown literal {literal}" + | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "indexOf"))) -> let value, stmts = com.TransformAsExpr(ctx, object) let attr = Python.Identifier "index" - Expression.attribute(value = value, attr = attr, ctx = Load), stmts - | Expression.MemberExpression(computed=false; object=object; property=Expression.Identifier(Identifier(name="length"))) -> + Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "length"))) -> let value, stmts = com.TransformAsExpr(ctx, object) - let func = Expression.name(Python.Identifier "len") - Expression.call(func, [value]), stmts - | Expression.MemberExpression(computed=false; object=object; property=Expression.Identifier(Identifier(name="message"))) -> + let func = Expression.name (Python.Identifier "len") + Expression.call (func, [ value ]), stmts + | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "message"))) -> let value, stmts = com.TransformAsExpr(ctx, object) - let func = Expression.name(Python.Identifier "str") - Expression.call(func, [value]), stmts - | Expression.MemberExpression(computed=false; object=object; property=property) -> + let func = Expression.name (Python.Identifier "str") + Expression.call (func, [ value ]), stmts + | MemberExpression (computed=true; object = object; property = property) -> let value, stmts = com.TransformAsExpr(ctx, object) + let attr = match property with - | Expression.Identifier(Identifier(name=name)) -> Python.Identifier(name) - | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" + | Expression.Identifier (Identifier (name = name)) -> Expression.constant(name) + | _ -> failwith $"transformAsExpr: unknown property {property}" let value = match value with | Name { Id = Python.Identifier (id); Context = ctx } -> + Expression.name (id = Python.Identifier(id), ctx = ctx) + | _ -> value + let func = Expression.name("getattr") + Expression.call(func=func, args=[value; attr]), stmts + | Expression.MemberExpression (computed=false; object = object; property = property) -> + let value, stmts = com.TransformAsExpr(ctx, object) + + let attr = + match property with + | Expression.Identifier (Identifier (name = name)) -> Python.Identifier(name) + | _ -> failwith $"transformAsExpr: unknown property {property}" + + let value = + match value with + | Name { Id = Python.Identifier (id) + Context = ctx } -> // TODO: Need to make this more generic and robust let id = if id = "Math" then @@ -489,64 +535,64 @@ module Util = else id - Expression.name(id = Python.Identifier(id), ctx = ctx) + Expression.name (id = Python.Identifier(id), ctx = ctx) | _ -> value - Expression.attribute(value = value, attr = attr, ctx = Load), stmts - | Expression.Literal(Literal.BooleanLiteral(value=value)) -> Expression.constant(value = value), [] - | Expression.FunctionExpression(``params``=parms; body=body) -> + Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | Expression.Literal (Literal.BooleanLiteral (value = value)) -> Expression.constant (value = value), [] + | Expression.FunctionExpression (``params`` = parms; body = body) -> let args = parms |> List.ofArray - |> List.map (fun pattern -> Arg.arg(Python.Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.arg (Python.Identifier pattern.Name)) - let arguments = Arguments.arguments(args = args) + let arguments = Arguments.arguments (args = args) match body.Body with - | [| Statement.ExpressionStatement(expr) |] -> + | [| Statement.ExpressionStatement (expr) |] -> let body, stmts = com.TransformAsExpr(ctx, expr) - Expression.lambda(arguments, body), stmts + Expression.lambda (arguments, body), stmts | _ -> - let body = - com.TransformAsStatements(ctx, ReturnStrategy.Return, body) + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) let name = Helpers.getIdentifier "lifted" let func = FunctionDef.Create(name = name, args = arguments, body = body) - Expression.name(name), [ func ] - | Expression.ConditionalExpression(test=test; consequent=consequent; alternate=alternate) -> + Expression.name (name), [ func ] + | Expression.ConditionalExpression (test = test; consequent = consequent; alternate = alternate) -> let test, stmts1 = com.TransformAsExpr(ctx, test) let body, stmts2 = com.TransformAsExpr(ctx, consequent) let orElse, stmts3 = com.TransformAsExpr(ctx, alternate) - Expression.ifExp(test, body, orElse), stmts1 @ stmts2 @ stmts3 - | Expression.Literal(Literal.NullLiteral(nl)) -> Expression.name(Python.Identifier("None")), [] - | Expression.SequenceExpression(expressions=exprs) -> + Expression.ifExp (test, body, orElse), stmts1 @ stmts2 @ stmts3 + | Expression.Literal (Literal.NullLiteral (nl)) -> Expression.name (Python.Identifier("None")), [] + | Expression.SequenceExpression (expressions = exprs) -> // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments - let exprs, stmts = + let stmts = exprs |> List.ofArray - |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) - |> Helpers.unzipArgs - - let body = - exprs - |> List.mapi - (fun i n -> - if i = exprs.Length - 1 then - Statement.return'(n) // Return the last statement - else - Statement.expr(n)) + |> List.map (fun ex -> com.TransformAsStatements(ctx, ReturnStrategy.Return, ex)) + |> List.collect id + + // let body = + // exprs + // |> List.mapi + // (fun i n -> + // if i = exprs.Length - 1 then + // Statement.return' (n) // Return the last statement + // else + // Statement.expr (n)) let name = Helpers.getIdentifier ("lifted") let func = - FunctionDef.Create(name = name, args = Arguments.arguments [], body = body) + FunctionDef.Create(name = name, args = Arguments.arguments [], body = stmts) - let name = Expression.name(name) - Expression.call(name), stmts @ [ func ] + let name = Expression.name (name) + Expression.call (name), [ func ] + | ThisExpression (_) -> Expression.name ("self"), [] | _ -> failwith $"transformAsExpr: Unhandled value: {expr}" /// Transform Babel expressions as Python statements. @@ -560,31 +606,43 @@ module Util = printfn $"transformExpressionAsStatements: {expr}" match expr with - | Expression.AssignmentExpression(left=left; right=right) -> + // Transform e.g `this["x@22"] = x;` into `setattr(self, "x@22", x)` + | AssignmentExpression (left = MemberExpression (object = object + property = Literal (Literal.StringLiteral (StringLiteral (value = attr)))) + right = right) -> + // object, attr, value + let object, stmts1 = com.TransformAsExpr(ctx, object) + let value, stmts2 = com.TransformAsExpr(ctx, right) + let attr = Expression.constant(attr) + + [ yield! stmts1 + yield! stmts2 + Statement.expr (value = Expression.call (func = Expression.name ("setattr"), args = [ object; attr; value ])) ] + + // Transform e.g `this.x = x;` into `self.x = x` + | AssignmentExpression (left = left; right = right) -> let value, stmts = com.TransformAsExpr(ctx, right) - let targets: Python.Expression list = + let targets, stmts2: Python.Expression list * Python.Statement list = match left with - | Expression.Identifier(Identifier(name=name)) -> + | Expression.Identifier (Identifier (name = name)) -> let target = Python.Identifier(Helpers.cleanNameAsPythonIdentifier (name)) - [ Expression.name(id = target, ctx = Store) ] - | Expression.MemberExpression(property=property) -> - match property with - | Expression.Identifier(id) -> - let attr = Python.Identifier(Helpers.cleanNameAsPythonIdentifier (id.Name)) - [ - Expression.attribute( - value = Expression.name(id = Python.Identifier("self")), - attr = attr, - ctx = Store - ) - ] - | _ -> failwith $"transformExpressionAsStatements: unknown property {property}" + [ Expression.name (id = target, ctx = Store) ], [] + | MemberExpression (property = Expression.Identifier (id); object = object) -> + let attr = + Python.Identifier(Helpers.cleanNameAsPythonIdentifier (id.Name)) + + let value, stmts = com.TransformAsExpr(ctx, object) + [ Expression.attribute (value = value, attr = attr, ctx = Store) ], stmts | _ -> failwith $"AssignmentExpression, unknown expression: {left}" - [ yield! stmts; Statement.assign(targets = targets, value = value) ] - | _ -> failwith $"transformExpressionAsStatements: unknown expr: {expr}" + + [ yield! stmts; yield! stmts2; Statement.assign (targets = targets, value = value) ] + | _ -> + // Wrap the rest in statement expression + let expr, stmts = com.TransformAsExpr(ctx, expr) + [ yield! stmts; Statement.expr expr ] /// Transform Babel statement as Python statements. let rec transformStatementAsStatements @@ -596,41 +654,37 @@ module Util = printfn $"transformStatementAsStatements: {stmt}, returnStrategy: {returnStrategy}" match stmt with - | Statement.BlockStatement(bs) -> + | Statement.BlockStatement (bs) -> [ yield! com.TransformAsStatements(ctx, returnStrategy, bs) ] |> transformBody returnStrategy - | Statement.ReturnStatement(argument=arg) -> + | Statement.ReturnStatement (argument = arg) -> let expr, stmts = transformAsExpr com ctx arg match returnStrategy with - | ReturnStrategy.NoReturn -> stmts @ [ Statement.expr(expr) ] - | _ -> stmts @ [ Statement.return'(expr) ] - | Statement.Declaration(Declaration.VariableDeclaration(VariableDeclaration(declarations=declarations))) -> - [ - for (VariableDeclarator(id=id; init=init)) in declarations do - let targets: Python.Expression list = - let name = Helpers.cleanNameAsPythonIdentifier (id.Name) - [ Expression.name(id = Python.Identifier(name), ctx = Store) ] - - match init with - | Some value -> - let expr, stmts = com.TransformAsExpr(ctx, value) - yield! stmts - Statement.assign(targets, expr) - | None -> () - ] - | Statement.ExpressionStatement(expr=expression) -> + | ReturnStrategy.NoReturn -> stmts @ [ Statement.expr (expr) ] + | _ -> stmts @ [ Statement.return' (expr) ] + | Statement.Declaration (Declaration.VariableDeclaration (VariableDeclaration (declarations = declarations))) -> + [ for (VariableDeclarator (id = id; init = init)) in declarations do + let targets: Python.Expression list = + let name = Helpers.cleanNameAsPythonIdentifier (id.Name) + [ Expression.name (id = Python.Identifier(name), ctx = Store) ] + + match init with + | Some value -> + let expr, stmts = com.TransformAsExpr(ctx, value) + yield! stmts + Statement.assign (targets, expr) + | None -> () ] + | Statement.ExpressionStatement (expr = expression) -> // Handle Babel expressions that we need to transforme here as Python statements match expression with - | Expression.AssignmentExpression(_) -> com.TransformAsStatements(ctx, returnStrategy, expression) + | Expression.AssignmentExpression (_) -> com.TransformAsStatements(ctx, returnStrategy, expression) | _ -> - [ - let expr, stmts = com.TransformAsExpr(ctx, expression) - yield! stmts - Statement.expr(expr) - ] - | Statement.IfStatement(test=test; consequent=consequent; alternate=alternate) -> + [ let expr, stmts = com.TransformAsExpr(ctx, expression) + yield! stmts + Statement.expr (expr) ] + | Statement.IfStatement (test = test; consequent = consequent; alternate = alternate) -> let test, stmts = com.TransformAsExpr(ctx, test) let body = @@ -645,16 +699,16 @@ module Util = | _ -> [] - [ yield! stmts; Statement.if'(test = test, body = body, orelse = orElse) ] - | Statement.WhileStatement(test=test; body=body) -> + [ yield! stmts; Statement.if' (test = test, body = body, orelse = orElse) ] + | Statement.WhileStatement (test = test; body = body) -> let expr, stmts = com.TransformAsExpr(ctx, test) let body = com.TransformAsStatements(ctx, returnStrategy, body) |> transformBody ReturnStrategy.NoReturn - [ yield! stmts; Statement.while'(test = expr, body = body, orelse = []) ] - | Statement.TryStatement(block=block; handler=handler; finalizer=finalizer) -> + [ yield! stmts; Statement.while' (test = expr, body = body, orelse = []) ] + | Statement.TryStatement (block = block; handler = handler; finalizer = finalizer) -> let body = com.TransformAsStatements(ctx, returnStrategy, block) let finalBody = @@ -663,11 +717,11 @@ module Util = let handlers = match handler with - | Some (CatchClause(``param``=parm; body=body)) -> + | Some (CatchClause (param = parm; body = body)) -> let body = com.TransformAsStatements(ctx, returnStrategy, body) let exn = - Expression.name(Python.Identifier("Exception")) + Expression.name (Python.Identifier("Exception")) |> Some // Insert a ex.message = str(ex) for all aliased exceptions. @@ -679,19 +733,19 @@ module Util = // let msg = Assign.Create([trg], value) // let body = msg :: body let handlers = - [ ExceptHandler.exceptHandler(``type`` = exn, name = identifier, body = body) ] + [ ExceptHandler.exceptHandler (``type`` = exn, name = identifier, body = body) ] handlers | _ -> [] - [ Statement.try'(body = body, handlers = handlers, ?finalBody = finalBody) ] - | Statement.SwitchStatement(discriminant=discriminant; cases=cases) -> + [ Statement.try' (body = body, handlers = handlers, ?finalBody = finalBody) ] + | Statement.SwitchStatement (discriminant = discriminant; cases = cases) -> let value, stmts = com.TransformAsExpr(ctx, discriminant) let rec ifThenElse (fallThrough: Python.Expression option) (cases: Babel.SwitchCase list): Python.Statement list option = match cases with | [] -> None - | SwitchCase(test=test; consequent=consequent) :: cases -> + | SwitchCase (test = test; consequent = consequent) :: cases -> let body = consequent |> List.ofArray @@ -703,41 +757,48 @@ module Util = let test, st = com.TransformAsExpr(ctx, test) let expr = - Expression.compare(left = value, ops = [ Eq ], comparators = [ test ]) + Expression.compare (left = value, ops = [ Eq ], comparators = [ test ]) let test = match fallThrough with - | Some ft -> Expression.boolOp(op = Or, values = [ ft; expr ]) + | Some ft -> Expression.boolOp (op = Or, values = [ ft; expr ]) | _ -> expr // Check for fallthrough if body.IsEmpty then ifThenElse (Some test) cases else - [ Statement.if'(test = test, body = body, ?orelse = ifThenElse None cases) ] + [ Statement.if' (test = test, body = body, ?orelse = ifThenElse None cases) ] |> Some let result = cases |> List.ofArray |> ifThenElse None + match result with | Some ifStmt -> stmts @ ifStmt | None -> [] - | Statement.BreakStatement(_) -> [ Break ] - | Statement.Declaration(Declaration.FunctionDeclaration(``params``=parms; id=id; body=body)) -> + | Statement.BreakStatement (_) -> [ Break ] + | Statement.Declaration (Declaration.FunctionDeclaration (``params`` = parms; id = id; body = body)) -> [ com.TransformFunction(ctx, id, parms, body) ] - | Statement.Declaration(Declaration.ClassDeclaration(body, id, superClass, implements, superTypeParameters, typeParameters, loc)) -> + | Statement.Declaration (Declaration.ClassDeclaration (body, id, superClass, implements, superTypeParameters, typeParameters, loc)) -> transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc - | Statement.ForStatement - (init=Some(VariableDeclaration(declarations= [| VariableDeclarator(id=id; init=Some(init)) |] )) - test=Some(Expression.BinaryExpression(left=left; right=right; operator="<=")) - body=body) -> - let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) - let target = Expression.name(Python.Identifier id.Name) + | Statement.ForStatement (init = Some (VariableDeclaration(declarations = [| VariableDeclarator (id = id; init = Some (init)) |])) + test = Some (Expression.BinaryExpression (left = left; right = right; operator = "<=")) + body = body) -> + let body = + com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) + + let target = Expression.name (Python.Identifier id.Name) let start, stmts1 = com.TransformAsExpr(ctx, init) let stop, stmts2 = com.TransformAsExpr(ctx, right) - let stop = Expression.binOp(stop, Add, Expression.constant(1)) // Python `range` has exclusive end. - let iter = Expression.call(Expression.name(Python.Identifier "range"), args=[start; stop]) - stmts1 @ stmts2 @ [ Statement.for'(target=target, iter=iter, body=body) ] - | Statement.LabeledStatement(body=body) -> com.TransformAsStatements(ctx, returnStrategy, body) - | Statement.ContinueStatement(_) -> [ Continue ] + let stop = Expression.binOp (stop, Add, Expression.constant (1)) // Python `range` has exclusive end. + + let iter = + Expression.call (Expression.name (Python.Identifier "range"), args = [ start; stop ]) + + stmts1 + @ stmts2 + @ [ Statement.for' (target = target, iter = iter, body = body) ] + | Statement.LabeledStatement (body = body) -> com.TransformAsStatements(ctx, returnStrategy, body) + | Statement.ContinueStatement (_) -> [ Continue ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" let transformBlockStatementAsStatements @@ -748,45 +809,43 @@ module Util = : Python.Statement list = [ for stmt in block.Body do - yield! transformStatementAsStatements com ctx returnStrategy stmt ] + yield! transformStatementAsStatements com ctx returnStrategy stmt ] /// Transform Babel program to Python module. let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = let returnStrategy = ReturnStrategy.Return let stmt: Python.Statement list = - [ - for md in body do - match md with - | Babel.ExportNamedDeclaration(decl) -> - match decl with - | Babel.VariableDeclaration(VariableDeclaration(declarations=declarations)) -> - for (VariableDeclarator(id, init)) in declarations do - let value, stmts = com.TransformAsExpr(ctx, init.Value) - - let targets: Python.Expression list = - let name = Helpers.cleanNameAsPythonIdentifier (id.Name) - [ Expression.name(id = Python.Identifier(name), ctx = Store) ] - - yield! stmts - yield Statement.assign(targets = targets, value = value) - | Babel.FunctionDeclaration(``params``=``params``; body=body; id=id) -> - yield com.TransformFunction(ctx, id, ``params``, body) - - | Babel.ClassDeclaration(body, id, superClass, implements, superTypeParameters, typeParameters, loc) -> - yield! transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc - | _ -> failwith $"Unhandled Declaration: {decl}" - - | Babel.ImportDeclaration(specifiers, source) -> yield! com.TransformAsImports(ctx, specifiers, source) - | Babel.PrivateModuleDeclaration(statement=statement) -> - yield! - com.TransformAsStatements(ctx, returnStrategy, statement) - |> transformBody returnStrategy - | _ -> failwith $"Unknown module declaration: {md}" - ] + [ for md in body do + match md with + | Babel.ExportNamedDeclaration (decl) -> + match decl with + | Babel.VariableDeclaration (VariableDeclaration (declarations = declarations)) -> + for (VariableDeclarator (id, init)) in declarations do + let value, stmts = com.TransformAsExpr(ctx, init.Value) + + let targets: Python.Expression list = + let name = Helpers.cleanNameAsPythonIdentifier (id.Name) + [ Expression.name (id = Python.Identifier(name), ctx = Store) ] + + yield! stmts + yield Statement.assign (targets = targets, value = value) + | Babel.FunctionDeclaration (``params`` = ``params``; body = body; id = id) -> + yield com.TransformFunction(ctx, id, ``params``, body) + + | Babel.ClassDeclaration (body, id, superClass, implements, superTypeParameters, typeParameters, loc) -> + yield! transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc + | _ -> failwith $"Unhandled Declaration: {decl}" + + | Babel.ImportDeclaration (specifiers, source) -> yield! com.TransformAsImports(ctx, specifiers, source) + | Babel.PrivateModuleDeclaration (statement = statement) -> + yield! + com.TransformAsStatements(ctx, returnStrategy, statement) + |> transformBody returnStrategy + | _ -> failwith $"Unknown module declaration: {md}" ] let imports = com.GetAllImports() - Module.module'(imports @ stmt) + Module.module' (imports @ stmt) let getIdentForImport (ctx: Context) (moduleName: string) (name: string) = if String.IsNullOrEmpty name then @@ -797,7 +856,8 @@ module Util = | "default" -> Path.GetFileNameWithoutExtension(moduleName) | _ -> name //|> getUniqueNameInRootScope ctx - |> Python.Identifier |> Some + |> Python.Identifier + |> Some module Compiler = open Util @@ -813,34 +873,46 @@ module Compiler = member _.GetImportExpr(ctx, name, moduleName, r) = let cachedName = moduleName + "::" + name + match imports.TryGetValue(cachedName) with - | true, { Names = [ { AsName=localIdent}] } -> + | true, { Names = [ { AsName = localIdent } ] } -> match localIdent with | Some localIdent -> localIdent |> Some | None -> None | _ -> let localId = getIdentForImport ctx moduleName name + let nameId = if name = Naming.placeholder then - "`importMember` must be assigned to a variable" - |> addError com [] r; (name |> Python.Identifier) - else name |> Python.Identifier + "`importMember` must be assigned to a variable" + |> addError com [] r + + (name |> Python.Identifier) + else + name |> Python.Identifier + let i = - ImportFrom.importFrom( - Python.Identifier moduleName |> Some, - [ Alias.alias(nameId, localId) ]) + ImportFrom.importFrom (Python.Identifier moduleName |> Some, [ Alias.alias (nameId, localId) ]) + imports.Add(cachedName, i) + match localId with | Some localId -> localId |> Some | None -> None - member _.GetAllImports() = imports.Values |> List.ofSeq |> List.map ImportFrom + member _.GetAllImports() = + imports.Values + |> List.ofSeq + |> List.map ImportFrom member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e member bcom.TransformAsStatements(ctx, ret, e) = transformExpressionAsStatements bcom ctx ret e member bcom.TransformAsStatements(ctx, ret, e) = transformStatementAsStatements bcom ctx ret e member bcom.TransformAsStatements(ctx, ret, e) = transformBlockStatementAsStatements bcom ctx ret e - member bcom.TransformAsClassDef(ctx, body, id, superClass, implements, superTypeParameters, typeParameters, loc) = transformAsClassDef bcom ctx body id superClass implements superTypeParameters typeParameters loc + + member bcom.TransformAsClassDef(ctx, body, id, superClass, implements, superTypeParameters, typeParameters, loc) = + transformAsClassDef bcom ctx body id superClass implements superTypeParameters typeParameters loc + member bcom.TransformFunction(ctx, name, args, body) = transformAsFunction bcom ctx name args body member bcom.TransformAsImports(ctx, specifiers, source) = transformAsImports bcom ctx specifiers source @@ -854,6 +926,7 @@ module Compiler = member _.GetRootModule(fileName) = com.GetRootModule(fileName) member _.GetOrAddInlineExpr(fullName, generate) = com.GetOrAddInlineExpr(fullName, generate) member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) + member _.AddLog(msg, severity, ?range, ?fileName: string, ?tag: string) = com.AddLog(msg, severity, ?range = range, ?fileName = fileName, ?tag = tag) @@ -863,13 +936,11 @@ module Compiler = let com = makeCompiler com :> IPythonCompiler let ctx = - { - DecisionTargets = [] - HoistVars = fun _ -> false - TailCallOpportunity = None - OptimizeTailCall = fun () -> () - ScopedTypeParams = Set.empty - } + { DecisionTargets = [] + HoistVars = fun _ -> false + TailCallOpportunity = None + OptimizeTailCall = fun () -> () + ScopedTypeParams = Set.empty } let (Program body) = program transformProgram com ctx body diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 2f9703dace..2737055df8 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -42,10 +42,10 @@ type Expression = | Dict of Dict | Tuple of Tuple - // member val Lineno: int = 0 with get, set - // member val ColOffset: int = 0 with get, set - // member val EndLineno: int option = None with get, set - // member val EndColOffset: int option = None with get, set +// member val Lineno: int = 0 with get, set +// member val ColOffset: int = 0 with get, set +// member val EndLineno: int option = None with get, set +// member val EndColOffset: int option = None with get, set type Operator = | Add | Sub @@ -88,8 +88,7 @@ type ExpressionContext = | Del | Store -type Identifier = - | Identifier of string +type Identifier = Identifier of string type Statement = | Pass @@ -112,15 +111,12 @@ type Statement = | FunctionDef of FunctionDef | AsyncFunctionDef of AsyncFunctionDef - // member val Lineno: int = 0 with get, set - // member val ColOffset: int = 0 with get, set - // member val EndLineno: int option = None with get, set - // member val EndColOffset: int option = None with get, set +// member val Lineno: int = 0 with get, set +// member val ColOffset: int = 0 with get, set +// member val EndLineno: int option = None with get, set +// member val EndColOffset: int option = None with get, set -type Module = - { - Body: Statement list - } +type Module = { Body: Statement list } /// Both parameters are raw strings of the names. asname can be None if the regular name is to be used. /// @@ -137,59 +133,49 @@ type Module = /// type_ignores=[]) /// ``` type Alias = - { - Name: Identifier - AsName: Identifier option - } + { Name: Identifier + AsName: Identifier option } /// A single except clause. type is the exception type it will match, typically a Name node (or None for a catch-all /// except: clause). name is a raw string for the name to hold the exception, or None if the clause doesn’t have as foo. /// body is a list of nodes. type ExceptHandler = - { - Type: Expression option - Name: Identifier option - Body: Statement list - Loc: SourceLocation option - } + { Type: Expression option + Name: Identifier option + Body: Statement list + Loc: SourceLocation option } /// try blocks. All attributes are list of nodes to execute, except for handlers, which is a list of ExceptHandler /// nodes. type Try = - { - Body: Statement list - Handlers: ExceptHandler list - OrElse: Statement list - FinalBody: Statement list - Loc: SourceLocation option - } + { Body: Statement list + Handlers: ExceptHandler list + OrElse: Statement list + FinalBody: Statement list + Loc: SourceLocation option } /// A single argument in a list. arg is a raw string of the argument name, annotation is its annotation, such as a Str /// or Name node. /// /// - type_comment is an optional string with the type annotation as a comment type Arg = - { - Lineno: int - ColOffset: int - EndLineno: int option - EndColOffset: int option + { Lineno: int + ColOffset: int + EndLineno: int option + EndColOffset: int option - Arg: Identifier - Annotation: Expression option - TypeComment: string option - } + Arg: Identifier + Annotation: Expression option + TypeComment: string option } type Keyword = - { - Lineno: int - ColOffset: int - EndLineno: int option - EndColOffset: int option + { Lineno: int + ColOffset: int + EndLineno: int option + EndColOffset: int option - Arg: Identifier - Value: Expression - } + Arg: Identifier + Value: Expression } /// The arguments for a function. /// @@ -200,15 +186,13 @@ type Keyword = /// - defaults is a list of default values for arguments that can be passed positionally. If there are fewer defaults, /// they correspond to the last n arguments. type Arguments = - { - PosOnlyArgs: Arg list - Args: Arg list - VarArg: Arg option - KwOnlyArgs: Arg list - KwDefaults: Expression list - KwArg: Arg option - Defaults: Expression list - } + { PosOnlyArgs: Arg list + Args: Arg list + VarArg: Arg option + KwOnlyArgs: Arg list + KwDefaults: Expression list + KwArg: Arg option + Defaults: Expression list } //#region Statements @@ -244,11 +228,9 @@ type Arguments = /// type_ignores=[]) /// ``` type Assign = - { - Targets: Expression list - Value: Expression - TypeComment: string option - } + { Targets: Expression list + Value: Expression + TypeComment: string option } /// When an expression, such as a function call, appears as a statement by itself with its return value not used or /// stored, it is wrapped in this container. value holds one of the other nodes in this section, a Constant, a Name, a @@ -264,10 +246,7 @@ type Assign = /// operand=Name(id='a', ctx=Load())))], /// type_ignores=[]) ///``` -type Expr = - { - Value: Expression - } +type Expr = { Value: Expression } /// A for loop. target holds the variable(s) the loop assigns to, as a single Name, Tuple or List node. iter holds the /// item to be looped over, again as a single node. body and orelse contain lists of nodes to execute. Those in orelse @@ -296,22 +275,18 @@ type Expr = /// type_ignores=[]) ///``` type For = - { - Target: Expression - Iterator: Expression - Body: Statement list - Else: Statement list - TypeComment: string option - } + { Target: Expression + Iterator: Expression + Body: Statement list + Else: Statement list + TypeComment: string option } type AsyncFor = - { - Target: Expression - Iterator: Expression - Body: Statement list - Else: Statement list - TypeComment: string option - } + { Target: Expression + Iterator: Expression + Body: Statement list + Else: Statement list + TypeComment: string option } /// A while loop. test holds the condition, such as a Compare node. /// @@ -335,11 +310,9 @@ type AsyncFor = /// type_ignores=[]) /// ``` type While = - { - Test: Expression - Body: Statement list - Else: Statement list - } + { Test: Expression + Body: Statement list + Else: Statement list } /// A class definition. /// @@ -378,14 +351,12 @@ type While = /// type_ignores=[]) ///``` type ClassDef = - { - Name: Identifier - Bases: Expression list - Keyword: Keyword list - Body: Statement list - DecoratorList: Expression list - Loc: SourceLocation option - } + { Name: Identifier + Bases: Expression list + Keyword: Keyword list + Body: Statement list + DecoratorList: Expression list + Loc: SourceLocation option } /// An if statement. test holds a single node, such as a Compare node. body and orelse each hold a list of nodes. /// @@ -420,11 +391,9 @@ type ClassDef = /// type_ignores=[]) /// ``` type If = - { - Test: Expression - Body: Statement list - Else: Statement list - } + { Test: Expression + Body: Statement list + Else: Statement list } /// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone /// raise. cause is the optional part for y in raise x from y. @@ -439,10 +408,8 @@ type If = /// type_ignores=[]) /// ``` type Raise = - { - Exception: Expression - Cause: Expression option - } + { Exception: Expression + Cause: Expression option } static member Create(exc, ?cause): Statement = { Exception = exc; Cause = cause } |> Raise @@ -456,24 +423,20 @@ type Raise = /// - returns is the return annotation. /// - type_comment is an optional string with the type annotation as a comment. type FunctionDef = - { - Name: Identifier - Args: Arguments - Body: Statement list - DecoratorList: Expression list - Returns: Expression option - TypeComment: string option - } + { Name: Identifier + Args: Arguments + Body: Statement list + DecoratorList: Expression list + Returns: Expression option + TypeComment: string option } static member Create(name, args, body, ?decoratorList, ?returns, ?typeComment): Statement = - { - Name = name - Args = args - Body = body - DecoratorList = defaultArg decoratorList [] - Returns = returns - TypeComment = typeComment - } + { Name = name + Args = args + Body = body + DecoratorList = defaultArg decoratorList [] + Returns = returns + TypeComment = typeComment } |> FunctionDef /// global and nonlocal statements. names is a list of raw strings. @@ -491,9 +454,7 @@ type FunctionDef = /// /// ``` type Global = - { - Names: Identifier list - } + { Names: Identifier list } static member Create(names) = { Names = names } @@ -511,9 +472,7 @@ type Global = /// type_ignores=[]) /// ````` type NonLocal = - { - Names: Identifier list - } + { Names: Identifier list } static member Create(names) = { Names = names } @@ -527,24 +486,20 @@ type NonLocal = /// - returns is the return annotation. /// - type_comment is an optional string with the type annotation as a comment. type AsyncFunctionDef = - { - Name: Identifier - Args: Arguments - Body: Statement list - DecoratorList: Expression list - Returns: Expression option - TypeComment: string option - } + { Name: Identifier + Args: Arguments + Body: Statement list + DecoratorList: Expression list + Returns: Expression option + TypeComment: string option } static member Create(name, args, body, decoratorList, ?returns, ?typeComment) = - { - Name = name - Args = args - Body = body - DecoratorList = decoratorList - Returns = returns - TypeComment = typeComment - } + { Name = name + Args = args + Body = body + DecoratorList = decoratorList + Returns = returns + TypeComment = typeComment } /// An import statement. names is a list of alias nodes. /// @@ -559,10 +514,7 @@ type AsyncFunctionDef = /// alias(name='z')])], /// type_ignores=[]) /// ````` -type Import = - { - Names: Alias list - } +type Import = { Names: Alias list } /// Represents from x import y. module is a raw string of the ‘from’ name, without any leading dots, or None for @@ -583,12 +535,9 @@ type Import = /// type_ignores=[]) /// ``` type ImportFrom = - { - Module: Identifier option - Names: Alias list - Level: int option - } - + { Module: Identifier option + Names: Alias list + Level: int option } /// A return statement. /// @@ -600,10 +549,7 @@ type ImportFrom = /// value=Constant(value=4))], /// type_ignores=[]) /// ``` -type Return = - { - Value: Expression option - } +type Return = { Value: Expression option } //#endregion @@ -621,17 +567,13 @@ type Return = /// ctx=Load())) /// ``` type Attribute = - { - Value: Expression - Attr: Identifier - Ctx: ExpressionContext - } + { Value: Expression + Attr: Identifier + Ctx: ExpressionContext } type NamedExpr = - { - Target: Expression - Value: Expression - } + { Target: Expression + Value: Expression } /// A subscript, such as l[1]. value is the subscripted object (usually sequence or mapping). slice is an index, slice /// or key. It can be a Tuple and contain a Slice. ctx is Load, Store or Del according to the action performed with the @@ -652,24 +594,18 @@ type NamedExpr = /// ctx=Load())) /// ``` type Subscript = - { - Value: Expression - Slice: Expression - Ctx: ExpressionContext - } + { Value: Expression + Slice: Expression + Ctx: ExpressionContext } type BinOp = - { - Left: Expression - Right: Expression - Operator: Operator - } + { Left: Expression + Right: Expression + Operator: Operator } type BoolOp = - { - Values: Expression list - Operator: BoolOperator - } + { Values: Expression list + Operator: BoolOperator } /// A comparison of two or more values. left is the first value in the comparison, ops the list of operators, and /// comparators the list of values after the first element in the comparison. @@ -687,19 +623,15 @@ type BoolOp = /// Constant(value=10)])) /// ````` type Compare = - { - Left: Expression - Comparators: Expression list - Ops: ComparisonOperator list - } + { Left: Expression + Comparators: Expression list + Ops: ComparisonOperator list } /// A unary operation. op is the operator, and operand any expression node. type UnaryOp = - { - Op: UnaryOperator - Operand: Expression - Loc: SourceLocation option - } + { Op: UnaryOperator + Operand: Expression + Loc: SourceLocation option } /// A constant value. The value attribute of the Constant literal contains the Python object it represents. The values /// represented can be simple types such as a number, string or None, but also immutable container types (tuples and @@ -710,10 +642,7 @@ type UnaryOp = /// Expression( /// body=Constant(value=123)) /// ````` -type Constant = - { - Value: obj - } +type Constant = { Value: obj } /// Node representing a single formatting field in an f-string. If the string contains a single formatting field and /// nothing else the node can be isolated otherwise it appears in JoinedStr. @@ -727,11 +656,9 @@ type Constant = /// - format_spec is a JoinedStr node representing the formatting of the value, or None if no format was specified. Both /// conversion and format_spec can be set at the same time. type FormattedValue = - { - Value: Expression - Conversion: int option - FormatSpec: Expression option - } + { Value: Expression + Conversion: int option + FormatSpec: Expression option } /// A function call. func is the function, which will often be a Name or Attribute object. Of the arguments: /// @@ -759,17 +686,13 @@ type FormattedValue = /// value=Name(id='e', ctx=Load()))])) /// ``` type Call = - { - Func: Expression - Args: Expression list - Keywords: Keyword list - } + { Func: Expression + Args: Expression list + Keywords: Keyword list } type Emit = - { - Value: string - Args: Expression list - } + { Value: string + Args: Expression list } /// An expression such as a if b else c. Each field holds a single node, so in the following example, all three are Name nodes. /// @@ -782,11 +705,9 @@ type Emit = /// orelse=Name(id='c', ctx=Load()))) /// ``` type IfExp = - { - Test: Expression - Body: Expression - OrElse: Expression - } + { Test: Expression + Body: Expression + OrElse: Expression } /// lambda is a minimal function definition that can be used inside an expression. Unlike FunctionDef, body holds a /// single node. @@ -808,11 +729,7 @@ type IfExp = /// body=Constant(value=Ellipsis)))], /// type_ignores=[]) /// ``` -type Lambda = - { - Args: Arguments - Body: Expression - } +type Lambda = { Args: Arguments; Body: Expression } /// A tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an assignment target /// (i.e. (x,y)=something), and Load otherwise. @@ -828,10 +745,8 @@ type Lambda = /// ctx=Load())) ///``` type Tuple = - { - Elements: Expression list - Loc: SourceLocation option - } + { Elements: Expression list + Loc: SourceLocation option } /// A list or tuple. elts holds a list of nodes representing the elements. ctx is Store if the container is an /// assignment target (i.e. (x,y)=something), and Load otherwise. @@ -846,10 +761,7 @@ type Tuple = /// Constant(value=3)], /// ctx=Load())) ///``` -type List = - { - Elements: Expression list - } +type List = { Elements: Expression list } /// A set. elts holds a list of nodes representing the set’s elements. /// @@ -862,10 +774,7 @@ type List = /// Constant(value=2), /// Constant(value=3)])) /// ``` -type Set = - { - Elements: Expression list - } +type Set = { Elements: Expression list } /// A dictionary. keys and values hold lists of nodes representing the keys and the values respectively, in matching /// order (what would be returned when calling dictionary.keys() and dictionary.values()). @@ -885,229 +794,222 @@ type Set = /// Name(id='d', ctx=Load())])) /// ``` type Dict = - { - Keys: Expression list - Values: Expression list - } + { Keys: Expression list + Values: Expression list } /// A variable name. id holds the name as a string, and ctx is one of the following types. type Name = - { - Id: Identifier - Context: ExpressionContext - } + { Id: Identifier + Context: ExpressionContext } [] module PythonExtensions = type Statement with + static member import(names): Statement = Import { Names = names } static member expr(value): Statement = { Expr.Value = value } |> Expr + static member try'(body, ?handlers, ?orElse, ?finalBody, ?loc): Statement = - Try.try'(body, ?handlers=handlers, ?orElse=orElse, ?finalBody=finalBody, ?loc=loc) |> Try + Try.try' (body, ?handlers = handlers, ?orElse = orElse, ?finalBody = finalBody, ?loc = loc) + |> Try + static member classDef(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc): Statement = - { - Name = name - Bases = defaultArg bases [] - Keyword = defaultArg keywords [] - Body = defaultArg body [] - DecoratorList = defaultArg decoratorList [] - Loc = loc - } + { Name = name + Bases = defaultArg bases [] + Keyword = defaultArg keywords [] + Body = defaultArg body [] + DecoratorList = defaultArg decoratorList [] + Loc = loc } |> ClassDef + static member assign(targets, value, ?typeComment): Statement = - { - Targets = targets - Value = value - TypeComment = typeComment - } + { Targets = targets + Value = value + TypeComment = typeComment } |> Assign + static member return'(?value): Statement = Return { Value = value } - static member for'(target, iter, ?body, ?orelse, ?typeComment) : Statement = - For.for'(target, iter, ?body=body, ?orelse=orelse, ?typeComment=typeComment) + + static member for'(target, iter, ?body, ?orelse, ?typeComment): Statement = + For.for' (target, iter, ?body = body, ?orelse = orelse, ?typeComment = typeComment) |> For + static member while'(test, body, ?orelse): Statement = - { - While.Test = test - Body = body - Else = defaultArg orelse [] - } + { While.Test = test + Body = body + Else = defaultArg orelse [] } |> While + static member if'(test, body, ?orelse): Statement = - { - Test = test - Body = body - Else = defaultArg orelse [] - } + { Test = test + Body = body + Else = defaultArg orelse [] } |> If + static member importFrom(``module``, names, ?level) = - ImportFrom.importFrom(``module``, names, ?level=level) |> ImportFrom + ImportFrom.importFrom (``module``, names, ?level = level) + |> ImportFrom type Expression with - static member name(id, ?ctx): Expression = { Id = id; Context = defaultArg ctx Load } |> Name + + static member name(id, ?ctx): Expression = + { Id = id + Context = defaultArg ctx Load } + |> Name + static member name(name, ?ctx): Expression = + Expression.name(Identifier(name), ?ctx=ctx) + static member dict(keys, values): Expression = { Keys = keys; Values = values } |> Dict static member tuple(elts, ?loc): Expression = { Elements = elts; Loc = loc } |> Tuple + static member ifExp(test, body, orElse): Expression = - { - Test = test - Body = body - OrElse = orElse - } + { Test = test + Body = body + OrElse = orElse } |> IfExp + static member lambda(args, body): Expression = { Args = args; Body = body } |> Lambda + static member emit(value, ?args): Expression = - { - Value = value - Args = defaultArg args [] - } + { Value = value + Args = defaultArg args [] } |> Emit + static member call(func, ?args, ?kw): Expression = - { - Func = func - Args = defaultArg args [] - Keywords = defaultArg kw [] - } + { Func = func + Args = defaultArg args [] + Keywords = defaultArg kw [] } |> Call + static member compare(left, ops, comparators): Expression = - { - Left = left - Comparators = comparators - Ops = ops - } + { Left = left + Comparators = comparators + Ops = ops } |> Compare + static member attribute(value, attr, ctx): Expression = - { - Value = value - Attr = attr - Ctx = ctx - } + { Value = value + Attr = attr + Ctx = ctx } |> Attribute + static member unaryOp(op, operand, ?loc): Expression = - { - Op = op - Operand = operand - Loc = loc - } + { Op = op + Operand = operand + Loc = loc } |> UnaryOp - static member namedExpr(target, value) = - { - Target = target - Value = value - } - |> NamedExpr + + static member namedExpr(target, value) = { Target = target; Value = value } |> NamedExpr + static member subscript(value, slice, ctx): Expression = - { - Value = value - Slice = slice - Ctx = ctx - } + { Value = value + Slice = slice + Ctx = ctx } |> Subscript + static member binOp(left, op, right): Expression = - { - Left = left - Right = right - Operator = op - } + { Left = left + Right = right + Operator = op } |> BinOp + static member boolOp(op, values): Expression = { Values = values; Operator = op } |> BoolOp static member constant(value: obj): Expression = { Value = value } |> Constant - type List with + static member list(elts) = { Elements = elts } type ExceptHandler with + static member exceptHandler(``type``, ?name, ?body, ?loc) = - { - Type = ``type`` - Name = name - Body = defaultArg body [] - Loc = loc - } + { Type = ``type`` + Name = name + Body = defaultArg body [] + Loc = loc } + type Alias with + static member alias(name, asname) = { Name = name; AsName = asname } type Try with + static member try'(body, ?handlers, ?orElse, ?finalBody, ?loc) = - { - Body = body - Handlers = defaultArg handlers [] - OrElse = defaultArg orElse [] - FinalBody = defaultArg finalBody [] - Loc = loc - } + { Body = body + Handlers = defaultArg handlers [] + OrElse = defaultArg orElse [] + FinalBody = defaultArg finalBody [] + Loc = loc } type FormattedValue with + static member formattedValue(value, ?conversion, ?formatSpec) = - { - Value = value - Conversion = conversion - FormatSpec = formatSpec - } + { Value = value + Conversion = conversion + FormatSpec = formatSpec } type Module with + static member module'(body) = { Body = body } type Arg with + static member arg(arg, ?annotation, ?typeComment) = - { - Lineno = 0 - ColOffset = 0 - EndLineno = None - EndColOffset = None + { Lineno = 0 + ColOffset = 0 + EndLineno = None + EndColOffset = None - Arg = arg - Annotation = annotation - TypeComment = typeComment - } + Arg = arg + Annotation = annotation + TypeComment = typeComment } type Keyword with + static member keyword(arg, value) = - { - Lineno = 0 - ColOffset = 0 - EndLineno = None - EndColOffset = None + { Lineno = 0 + ColOffset = 0 + EndLineno = None + EndColOffset = None - Arg = arg - Value = value - } + Arg = arg + Value = value } type Arguments with + static member arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ?defaults) = - { - PosOnlyArgs = defaultArg posonlyargs [] - Args = defaultArg args [] - VarArg = vararg - KwOnlyArgs = defaultArg kwonlyargs [] - KwDefaults = defaultArg kwDefaults [] - KwArg = kwarg - Defaults = defaultArg defaults [] - } + { PosOnlyArgs = defaultArg posonlyargs [] + Args = defaultArg args [] + VarArg = vararg + KwOnlyArgs = defaultArg kwonlyargs [] + KwDefaults = defaultArg kwDefaults [] + KwArg = kwarg + Defaults = defaultArg defaults [] } type For with + static member for'(target, iter, ?body, ?orelse, ?typeComment) = - { - Target = target - Iterator = iter - Body = defaultArg body [] - Else = defaultArg orelse [] - TypeComment = typeComment - } + { Target = target + Iterator = iter + Body = defaultArg body [] + Else = defaultArg orelse [] + TypeComment = typeComment } type AsyncFor with + static member asyncFor(target, iter, body, ?orelse, ?typeComment) = - { - Target = target - Iterator = iter - Body = body - Else = defaultArg orelse [] - TypeComment = typeComment - } + { Target = target + Iterator = iter + Body = body + Else = defaultArg orelse [] + TypeComment = typeComment } type ImportFrom with + static member importFrom(``module``, names, ?level) = - { - Module = ``module`` - Names = names - Level = level - } + { Module = ``module`` + Names = names + Level = level } + type Expr with + static member expr(value) : Expr = + { Value=value } \ No newline at end of file From cbb90dd9e929577003d7ce877e06661d2b2763e8 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 27 Mar 2021 08:11:45 +0100 Subject: [PATCH 056/145] Import fixes --- build.fsx | 2 +- src/Fable.Cli/Main.fs | 1 + src/Fable.Transforms/Global/Babel.fs | 7 +- src/Fable.Transforms/Python/Babel2Python.fs | 138 +++++++++----------- 4 files changed, 65 insertions(+), 83 deletions(-) diff --git a/build.fsx b/build.fsx index 8bd0db493f..aa32b54aa4 100644 --- a/build.fsx +++ b/build.fsx @@ -526,7 +526,7 @@ match argsLower with | "test-integration"::_ -> testIntegration() | "quicktest"::_ -> buildLibraryIfNotExists() - run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --noCache --runScript --python" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --python --exclude Fable.Core --noCache --runScript" | "jupyter" :: _ -> buildLibraryIfNotExists () run "dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src --exclude Fable.Core --noCache --runScript --python" diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index 4738161c11..81efade0f9 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -139,6 +139,7 @@ module private Util = if fileExt.EndsWith(".ts") then Path.replaceExtension ".js" fileExt else fileExt let targetDir = Path.GetDirectoryName(targetPath) let stream = new IO.StreamWriter(targetPath) + let mapGenerator = lazy (SourceMapSharp.SourceMapGenerator()) interface BabelPrinter.Writer with member _.Write(str) = stream.WriteAsync(str) |> Async.AwaitTask diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index 600cae419f..33200480a4 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -1,6 +1,9 @@ // fsharplint:disable MemberNames InterfaceNames namespace rec Fable.AST.Babel +/// Babel AST. Note that this AST is a bit fragile in the sense that you cannot refer to all the types and union +/// constructors fully qualified. Thus you must fully open the namespace in referring files. + open Fable.AST /// The type field is a string representing the AST variant type. @@ -113,8 +116,6 @@ type Statement = | TryStatement of block: BlockStatement * handler: CatchClause option * finalizer: BlockStatement option * loc: SourceLocation option | ForStatement of body: BlockStatement * init: VariableDeclaration option * test: Expression option * update: Expression option * loc: SourceLocation option - - /// Note that declarations are considered statements; this is because declarations can appear in any statement context. type Declaration = | ClassDeclaration of @@ -400,7 +401,6 @@ type ImportSpecifier = /// A namespace import specifier, e.g., * as foo in import * as foo from "mod". | ImportNamespaceSpecifier of local: Identifier - /// An exported variable binding, e.g., {foo} in export {foo} or {bar as foo} in export {bar as foo}. /// The exported field refers to the name exported in the module. /// The local field refers to the binding into the local module scope. @@ -411,7 +411,6 @@ type ExportSpecifier = | ExportSpecifier of local: Identifier * exported: Identifier // Type Annotations - type TypeAnnotationInfo = | AnyTypeAnnotation | VoidTypeAnnotation diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 9c932e2e69..b2c9a01e33 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -37,8 +37,8 @@ type Context = type IPythonCompiler = inherit Compiler abstract GetAllImports: unit -> Python.Statement list - - abstract GetImportExpr: Context * name: string * moduleName: string * SourceLocation option -> Python.Identifier option + abstract GetIdentifier: ctx: Context * name: string -> Python.Identifier + abstract GetImportExpr: ctx: Context * moduleName: string * ?name: string * ?loc: SourceLocation -> Python.Identifier option abstract TransformAsExpr: Context * Babel.Expression -> Python.Expression * Python.Statement list abstract TransformAsStatements: Context * ReturnStrategy * Babel.Expression -> Python.Statement list @@ -65,18 +65,20 @@ type IPythonCompiler = module Helpers = let index = (Seq.initInfinite id).GetEnumerator() - let getIdentifier (name: string): Python.Identifier = + let getUniqueIdentifier (name: string): Python.Identifier = do index.MoveNext() |> ignore let idx = index.Current.ToString() Python.Identifier($"{name}_{idx}") - /// Replaces all '$' with '_' - let cleanNameAsPythonIdentifier (name: string) = + /// Replaces all '$' and `.`with '_' + let clean (name: string) = match name with - | "this" -> "self" // TODO: Babel should use ThisExpression to avoid this hack. + | "this" -> "self" | "async" -> "asyncio" - | _ -> name.Replace('$', '_').Replace('.', '_') + | "Math" -> "math" + | _ -> + name.Replace('$', '_').Replace('.', '_') let rewriteFableImport moduleName = let _reFableLib = @@ -87,7 +89,7 @@ module Helpers = if m.Groups.Count > 1 then let pymodule = m.Groups.["module"].Value.ToLower() - |> cleanNameAsPythonIdentifier + |> clean let moduleName = String.concat "." [ "fable"; pymodule ] @@ -95,7 +97,6 @@ module Helpers = else // TODO: Can we expect all modules to be lower case? let moduleName = moduleName.Replace("/", "").ToLower() - printfn "moduleName: %s" moduleName moduleName let unzipArgs (args: (Python.Expression * Python.Statement list) list): Python.Expression list * Python.Statement list = @@ -124,13 +125,15 @@ module Helpers = | _ -> Some stmt module Util = - let makeImportTypeId (com: IPythonCompiler) ctx moduleName typeName = - let expr = - com.GetImportExpr(ctx, typeName, getLibPath com moduleName, None) + let getIdentifier (com: IPythonCompiler) (ctx: Context) (name: string) = + let name = Helpers.clean name - match expr with - | Some (id) -> id - | _ -> Python.Identifier typeName + match name with + | "math" -> + com.GetImportExpr(ctx, "math", name="math") |> ignore + | _ -> () + + Python.Identifier name let rec transformBody (returnStrategy: ReturnStrategy) (body: Python.Statement list) = let body = body |> List.choose Helpers.isProductiveStatement @@ -168,7 +171,7 @@ module Util = Alias.alias ( Python.Identifier(imported.Name), if imported.Name <> local.Name then - Python.Identifier(local.Name) |> Some + com.GetIdentifier(ctx, local.Name) |> Some else None ) @@ -265,8 +268,7 @@ module Util = match key with | Expression.Identifier (id) -> Python.Identifier(id.Name) | Expression.Literal(Literal.StringLiteral(StringLiteral(value=name))) -> - let name = Helpers.cleanNameAsPythonIdentifier(name) - Python.Identifier(name) + com.GetIdentifier(ctx, name) | _ -> failwith $"transformAsClassDef: Unknown key: {key}" FunctionDef.Create(name, arguments, body = body) @@ -281,9 +283,9 @@ module Util = | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] printfn $"Body length: {body.Length}: ${body}" - let name = Helpers.cleanNameAsPythonIdentifier (id.Value.Name) + let name = com.GetIdentifier(ctx, id.Value.Name) - [ yield! stmts; Statement.classDef (Python.Identifier(name), body = body, bases = bases) ] + [ yield! stmts; Statement.classDef(name, body = body, bases = bases) ] let transformAsFunction (com: IPythonCompiler) @@ -297,17 +299,16 @@ module Util = |> List.ofArray |> List.map (fun pattern -> - let name = Helpers.cleanNameAsPythonIdentifier (pattern.Name) - Arg.arg (Python.Identifier(name))) + let ident = com.GetIdentifier(ctx, pattern.Name) + Arg.arg (ident)) let arguments = Arguments.arguments (args = args) let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Statement.BlockStatement) - let name = Helpers.cleanNameAsPythonIdentifier (name.Name) - - FunctionDef.Create(Python.Identifier(name), arguments, body = body) + let name = com.GetIdentifier(ctx, name.Name) + FunctionDef.Create(name, arguments, body = body) /// Transform Babel expression as Python expression let rec transformAsExpr (com: IPythonCompiler) (ctx: Context) (expr: Babel.Expression): Python.Expression * list = @@ -398,7 +399,7 @@ module Util = Expression.lambda (arguments, body), stmts | _ -> let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) - let name = Helpers.getIdentifier "lifted" + let name = Helpers.getUniqueIdentifier "lifted" let func = FunctionDef.Create(name = name, args = arguments, body = body) @@ -426,8 +427,8 @@ module Util = | Expression.Literal (Literal.StringLiteral (StringLiteral.StringLiteral (value = value))) -> Expression.constant (value = value), [] | Expression.Identifier (Identifier (name = name)) -> - let name = Helpers.cleanNameAsPythonIdentifier name - Expression.name (id = Python.Identifier name), [] + let name = com.GetIdentifier(ctx, name) + Expression.name (id = name), [] | NewExpression (callee = callee; arguments = args) -> // FIXME: use transformAsCall let func, stmts = com.TransformAsExpr(ctx, callee) @@ -457,7 +458,7 @@ module Util = |> List.map (fun pattern -> Arg.arg (Python.Identifier pattern.Name)) let arguments = Arguments.arguments (args = args) - let name = Helpers.getIdentifier "lifted" + let name = Helpers.getUniqueIdentifier "lifted" let func = FunctionDef.Create(name = name, args = arguments, body = body) @@ -515,32 +516,16 @@ module Util = | _ -> value let func = Expression.name("getattr") Expression.call(func=func, args=[value; attr]), stmts - | Expression.MemberExpression (computed=false; object = object; property = property) -> + | MemberExpression (computed=false; object = object; property = property) -> let value, stmts = com.TransformAsExpr(ctx, object) let attr = match property with - | Expression.Identifier (Identifier (name = name)) -> Python.Identifier(name) + | Expression.Identifier (Identifier (name = name)) -> com.GetIdentifier(ctx, name) | _ -> failwith $"transformAsExpr: unknown property {property}" - - let value = - match value with - | Name { Id = Python.Identifier (id) - Context = ctx } -> - // TODO: Need to make this more generic and robust - let id = - if id = "Math" then - //com.imports.Add("math", ) - "math" - else - id - - Expression.name (id = Python.Identifier(id), ctx = ctx) - | _ -> value - Expression.attribute (value = value, attr = attr, ctx = Load), stmts | Expression.Literal (Literal.BooleanLiteral (value = value)) -> Expression.constant (value = value), [] - | Expression.FunctionExpression (``params`` = parms; body = body) -> + | FunctionExpression (``params`` = parms; body = body) -> let args = parms |> List.ofArray @@ -555,20 +540,20 @@ module Util = | _ -> let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) - let name = Helpers.getIdentifier "lifted" + let name = Helpers.getUniqueIdentifier "lifted" let func = FunctionDef.Create(name = name, args = arguments, body = body) Expression.name (name), [ func ] - | Expression.ConditionalExpression (test = test; consequent = consequent; alternate = alternate) -> + | ConditionalExpression (test = test; consequent = consequent; alternate = alternate) -> let test, stmts1 = com.TransformAsExpr(ctx, test) let body, stmts2 = com.TransformAsExpr(ctx, consequent) let orElse, stmts3 = com.TransformAsExpr(ctx, alternate) Expression.ifExp (test, body, orElse), stmts1 @ stmts2 @ stmts3 | Expression.Literal (Literal.NullLiteral (nl)) -> Expression.name (Python.Identifier("None")), [] - | Expression.SequenceExpression (expressions = exprs) -> + | SequenceExpression (expressions = exprs) -> // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments let stmts = exprs @@ -585,7 +570,7 @@ module Util = // else // Statement.expr (n)) - let name = Helpers.getIdentifier ("lifted") + let name = Helpers.getUniqueIdentifier ("lifted") let func = FunctionDef.Create(name = name, args = Arguments.arguments [], body = stmts) @@ -626,13 +611,11 @@ module Util = let targets, stmts2: Python.Expression list * Python.Statement list = match left with | Expression.Identifier (Identifier (name = name)) -> - let target = - Python.Identifier(Helpers.cleanNameAsPythonIdentifier (name)) + let target = com.GetIdentifier(ctx, name) [ Expression.name (id = target, ctx = Store) ], [] | MemberExpression (property = Expression.Identifier (id); object = object) -> - let attr = - Python.Identifier(Helpers.cleanNameAsPythonIdentifier (id.Name)) + let attr = com.GetIdentifier(ctx, id.Name) let value, stmts = com.TransformAsExpr(ctx, object) [ Expression.attribute (value = value, attr = attr, ctx = Store) ], stmts @@ -658,7 +641,7 @@ module Util = [ yield! com.TransformAsStatements(ctx, returnStrategy, bs) ] |> transformBody returnStrategy - | Statement.ReturnStatement (argument = arg) -> + | ReturnStatement (argument = arg) -> let expr, stmts = transformAsExpr com ctx arg match returnStrategy with @@ -667,8 +650,8 @@ module Util = | Statement.Declaration (Declaration.VariableDeclaration (VariableDeclaration (declarations = declarations))) -> [ for (VariableDeclarator (id = id; init = init)) in declarations do let targets: Python.Expression list = - let name = Helpers.cleanNameAsPythonIdentifier (id.Name) - [ Expression.name (id = Python.Identifier(name), ctx = Store) ] + let name = com.GetIdentifier(ctx, id.Name) + [ Expression.name (id = name, ctx = Store) ] match init with | Some value -> @@ -676,7 +659,7 @@ module Util = yield! stmts Statement.assign (targets, expr) | None -> () ] - | Statement.ExpressionStatement (expr = expression) -> + | ExpressionStatement (expr = expression) -> // Handle Babel expressions that we need to transforme here as Python statements match expression with | Expression.AssignmentExpression (_) -> com.TransformAsStatements(ctx, returnStrategy, expression) @@ -684,7 +667,7 @@ module Util = [ let expr, stmts = com.TransformAsExpr(ctx, expression) yield! stmts Statement.expr (expr) ] - | Statement.IfStatement (test = test; consequent = consequent; alternate = alternate) -> + | IfStatement (test = test; consequent = consequent; alternate = alternate) -> let test, stmts = com.TransformAsExpr(ctx, test) let body = @@ -700,7 +683,7 @@ module Util = | _ -> [] [ yield! stmts; Statement.if' (test = test, body = body, orelse = orElse) ] - | Statement.WhileStatement (test = test; body = body) -> + | WhileStatement (test = test; body = body) -> let expr, stmts = com.TransformAsExpr(ctx, test) let body = @@ -708,7 +691,7 @@ module Util = |> transformBody ReturnStrategy.NoReturn [ yield! stmts; Statement.while' (test = expr, body = body, orelse = []) ] - | Statement.TryStatement (block = block; handler = handler; finalizer = finalizer) -> + | TryStatement (block = block; handler = handler; finalizer = finalizer) -> let body = com.TransformAsStatements(ctx, returnStrategy, block) let finalBody = @@ -739,7 +722,7 @@ module Util = | _ -> [] [ Statement.try' (body = body, handlers = handlers, ?finalBody = finalBody) ] - | Statement.SwitchStatement (discriminant = discriminant; cases = cases) -> + | SwitchStatement (discriminant = discriminant; cases = cases) -> let value, stmts = com.TransformAsExpr(ctx, discriminant) let rec ifThenElse (fallThrough: Python.Expression option) (cases: Babel.SwitchCase list): Python.Statement list option = @@ -756,8 +739,7 @@ module Util = | Some test -> let test, st = com.TransformAsExpr(ctx, test) - let expr = - Expression.compare (left = value, ops = [ Eq ], comparators = [ test ]) + let expr = Expression.compare (left = value, ops = [ Eq ], comparators = [ test ]) let test = match fallThrough with @@ -797,8 +779,8 @@ module Util = stmts1 @ stmts2 @ [ Statement.for' (target = target, iter = iter, body = body) ] - | Statement.LabeledStatement (body = body) -> com.TransformAsStatements(ctx, returnStrategy, body) - | Statement.ContinueStatement (_) -> [ Continue ] + | LabeledStatement (body = body) -> com.TransformAsStatements(ctx, returnStrategy, body) + | ContinueStatement (_) -> [ Continue ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" let transformBlockStatementAsStatements @@ -825,8 +807,8 @@ module Util = let value, stmts = com.TransformAsExpr(ctx, init.Value) let targets: Python.Expression list = - let name = Helpers.cleanNameAsPythonIdentifier (id.Name) - [ Expression.name (id = Python.Identifier(name), ctx = Store) ] + let name = com.GetIdentifier(ctx, id.Name) + [ Expression.name (id = name, ctx = Store) ] yield! stmts yield Statement.assign (targets = targets, value = value) @@ -871,15 +853,16 @@ module Compiler = if onlyOnceWarnings.Add(msg) then addWarning com [] range msg - member _.GetImportExpr(ctx, name, moduleName, r) = - let cachedName = moduleName + "::" + name + member bcom.GetIdentifier(ctx, name) = getIdentifier bcom ctx name + member _.GetImportExpr(ctx, moduleName, ?name, ?r) = + let cachedName = moduleName + "::" + defaultArg name "module" - match imports.TryGetValue(cachedName) with - | true, { Names = [ { AsName = localIdent } ] } -> + match imports.TryGetValue(cachedName), name with + | (true, { Names = [ { AsName = localIdent } ] }), _ -> match localIdent with | Some localIdent -> localIdent |> Some | None -> None - | _ -> + | _, Some name -> let localId = getIdentForImport ctx moduleName name let nameId = @@ -891,14 +874,13 @@ module Compiler = else name |> Python.Identifier - let i = - ImportFrom.importFrom (Python.Identifier moduleName |> Some, [ Alias.alias (nameId, localId) ]) - + let i = ImportFrom.importFrom(Python.Identifier moduleName |> Some, [ Alias.alias (nameId, localId) ]) imports.Add(cachedName, i) match localId with | Some localId -> localId |> Some | None -> None + | _ -> None member _.GetAllImports() = imports.Values From fded5c6090f819a7c9e8fc8e4bba701fcf5ee39f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 27 Mar 2021 08:54:28 +0100 Subject: [PATCH 057/145] Fix import statement --- src/Fable.Transforms/Python/Babel2Python.fs | 94 +++++++++----------- src/Fable.Transforms/Python/Python.fs | 3 +- src/Fable.Transforms/Python/PythonPrinter.fs | 12 ++- 3 files changed, 56 insertions(+), 53 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index b2c9a01e33..d3e7581a3e 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -130,7 +130,7 @@ module Util = match name with | "math" -> - com.GetImportExpr(ctx, "math", name="math") |> ignore + com.GetImportExpr(ctx, "math") |> ignore | _ -> () Python.Identifier name @@ -167,41 +167,32 @@ module Util = | Babel.ImportMemberSpecifier (local, imported) -> printfn "ImportMemberSpecifier" - let alias = - Alias.alias ( - Python.Identifier(imported.Name), - if imported.Name <> local.Name then - com.GetIdentifier(ctx, local.Name) |> Some - else - None - ) - + let asname = + if imported.Name <> local.Name then + com.GetIdentifier(ctx, local.Name) |> Some + else + None + let alias = Alias.alias(Python.Identifier(imported.Name),?asname=asname) importFroms.Add(alias) | Babel.ImportDefaultSpecifier (local) -> printfn "ImportDefaultSpecifier" - let alias = - Alias.alias ( - Python.Identifier(pymodule), - if local.Name <> pymodule then - Python.Identifier(local.Name) |> Some - else - None - ) - + let asname = + if local.Name <> pymodule then + Python.Identifier(local.Name) |> Some + else + None + let alias = Alias.alias (Python.Identifier(pymodule), ?asname=asname) imports.Add(alias) | Babel.ImportNamespaceSpecifier (Identifier (name = name)) -> printfn "ImportNamespaceSpecifier: %A" (name, name) - let alias = - Alias.alias ( - Python.Identifier(pymodule), - if pymodule <> name then - Python.Identifier(name) |> Some - else - None - ) - + let asname = + if pymodule <> name then + Python.Identifier(name) |> Some + else + None + let alias = Alias.alias(Python.Identifier(pymodule), ?asname=asname) importFroms.Add(alias) [ if imports.Count > 0 then @@ -829,15 +820,18 @@ module Util = let imports = com.GetAllImports() Module.module' (imports @ stmt) - let getIdentForImport (ctx: Context) (moduleName: string) (name: string) = - if String.IsNullOrEmpty name then - None - else + let getIdentForImport (ctx: Context) (moduleName: string) (name: string option) = + // import math + // from seq import a + match name with + | None -> + Path.GetFileNameWithoutExtension(moduleName) + |> Python.Identifier + |> Some + | Some name -> match name with | "*" - | "default" -> Path.GetFileNameWithoutExtension(moduleName) | _ -> name - //|> getUniqueNameInRootScope ctx |> Python.Identifier |> Some @@ -846,7 +840,7 @@ module Compiler = type PythonCompiler (com: Compiler) = let onlyOnceWarnings = HashSet() - let imports = Dictionary() + let imports = Dictionary() interface IPythonCompiler with member _.WarnOnlyOnce(msg, ?range) = @@ -854,38 +848,38 @@ module Compiler = addWarning com [] range msg member bcom.GetIdentifier(ctx, name) = getIdentifier bcom ctx name - member _.GetImportExpr(ctx, moduleName, ?name, ?r) = + member _.GetImportExpr(ctx, moduleName, ?name, ?loc) = let cachedName = moduleName + "::" + defaultArg name "module" - match imports.TryGetValue(cachedName), name with - | (true, { Names = [ { AsName = localIdent } ] }), _ -> + match imports.TryGetValue(cachedName) with + | (true, ImportFrom { Names = [ { AsName = localIdent } ] }) -> match localIdent with | Some localIdent -> localIdent |> Some | None -> None - | _, Some name -> + | _ -> let localId = getIdentForImport ctx moduleName name - let nameId = - if name = Naming.placeholder then - "`importMember` must be assigned to a variable" - |> addError com [] r - - (name |> Python.Identifier) - else + match name with + | Some name -> + let nameId = + if name = Naming.placeholder then + "`importMember` must be assigned to a variable" + |> addError com [] loc name |> Python.Identifier - let i = ImportFrom.importFrom(Python.Identifier moduleName |> Some, [ Alias.alias (nameId, localId) ]) - imports.Add(cachedName, i) + let i = Statement.importFrom(Python.Identifier moduleName |> Some, [ Alias.alias (nameId, ?asname=localId) ]) + imports.Add(cachedName, i) + | None -> + let i = Statement.import([ Alias.alias (Python.Identifier moduleName)]) + imports.Add(cachedName, i) match localId with | Some localId -> localId |> Some | None -> None - | _ -> None member _.GetAllImports() = imports.Values |> List.ofSeq - |> List.map ImportFrom member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e member bcom.TransformAsStatements(ctx, ret, e) = transformExpressionAsStatements bcom ctx ret e diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 2737055df8..3b67064905 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -929,8 +929,7 @@ module PythonExtensions = Loc = loc } type Alias with - - static member alias(name, asname) = { Name = name; AsName = asname } + static member alias(name, ?asname) = { Name = name; AsName = asname } type Try with diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index 6a5b9dc938..4a7fb3664b 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -247,7 +247,17 @@ module PrinterExtensions = member printer.Print(af: AsyncFunctionDef) = printer.Print("(AsyncFunctionDef)") - member printer.Print(im: Import) = printer.Print("(Import)") + member printer.Print(im: Import) = + if not (List.isEmpty im.Names) then + printer.Print("import ") + + if List.length im.Names > 1 then + printer.Print("(") + + printer.PrintCommaSeparatedList(im.Names) + + if List.length im.Names > 1 then + printer.Print(")") member printer.Print(im: ImportFrom) = let (Identifier path) = im.Module |> Option.defaultValue (Identifier ".") From 3af2ade10a5a943a132333e6319e0d3abbb2e628 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 27 Mar 2021 09:28:07 +0100 Subject: [PATCH 058/145] Rename --- .../{Fable.Core.Pyinterop.fs => Fable.Core.PyInterop.fs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Fable.Core/{Fable.Core.Pyinterop.fs => Fable.Core.PyInterop.fs} (100%) diff --git a/src/Fable.Core/Fable.Core.Pyinterop.fs b/src/Fable.Core/Fable.Core.PyInterop.fs similarity index 100% rename from src/Fable.Core/Fable.Core.Pyinterop.fs rename to src/Fable.Core/Fable.Core.PyInterop.fs From 06edc58801e7ff64b1fd2c03c4689e866691abdc Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 28 Mar 2021 11:08:57 +0200 Subject: [PATCH 059/145] First test running --- build.fsx | 19 +++++++++++++++++++ src/Fable.Cli/Main.fs | 3 ++- src/Fable.Transforms/Replacements.fs | 2 +- tests/Python/Fable.Tests.fsproj | 26 ++++++++++++++++++++++++++ tests/Python/Main.fs | 6 ++++++ tests/Python/TestSeq.fs | 16 ++++++++++++++++ tests/Python/Util.fs | 25 +++++++++++++++++++++++++ 7 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 tests/Python/Fable.Tests.fsproj create mode 100644 tests/Python/Main.fs create mode 100644 tests/Python/TestSeq.fs create mode 100644 tests/Python/Util.fs diff --git a/build.fsx b/build.fsx index aa32b54aa4..14db30fa3d 100644 --- a/build.fsx +++ b/build.fsx @@ -359,6 +359,24 @@ let test() = if envVarOrNone "APPVEYOR" |> Option.isSome then testJsFast() +let testPython() = + buildLibraryIfNotExists() + + let projectDir = "tests/Python" + let buildDir = "build/tests/Python" + + cleanDirs [buildDir] + runInDir projectDir "dotnet test" + runFableWithArgs projectDir [ + "--outDir " + buildDir + "--exclude Fable.Core" + "--python" + ] + + runInDir buildDir "touch __init__.py" // So relative imports works. + runInDir buildDir "pytest" + + let buildLocalPackageWith pkgDir pkgCommand fsproj action = let version = "3.0.0-local-build-" + DateTime.Now.ToString("yyyyMMdd-HHmm") action version @@ -524,6 +542,7 @@ match argsLower with | "test-js-fast"::_ -> testJsFast() | "test-react"::_ -> testReact() | "test-integration"::_ -> testIntegration() +| "test-py"::_ -> testPython() | "quicktest"::_ -> buildLibraryIfNotExists() run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --python --exclude Fable.Core --noCache --runScript" diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index 81efade0f9..d10eb6def3 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -166,7 +166,8 @@ module private Util = do printfn "TargetPath: %s" targetPath let targetDir = Path.GetDirectoryName(targetPath) // PEP8: Modules should have short, all-lowercase names - let fileName = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(targetPath)).ToLower() + let fileName = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(targetPath)) + let fileName = Naming.applyCaseRule Core.CaseRules.SnakeCase fileName // Note that Python modules cannot contain dots or it will be impossible to import them let targetPath = Path.Combine(targetDir, fileName + fileExt) do printfn "TargetPath: %s" targetPath diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index fcc2ef5e4e..9097b23308 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -1083,7 +1083,7 @@ let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp | _, "pyNative" | _, "jsNative" -> // TODO: Fail at compile time? - addWarning com ctx.InlinePath r "jsNative is being compiled without replacement, this will fail at runtime." + addWarning com ctx.InlinePath r $"{i.CompiledName} is being compiled without replacement, this will fail at runtime." let runtimeMsg = "A function supposed to be replaced by JS native code has been called, please check." |> StringConstant |> makeValue None diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj new file mode 100644 index 0000000000..bee470cdc1 --- /dev/null +++ b/tests/Python/Fable.Tests.fsproj @@ -0,0 +1,26 @@ + + + net5.0 + false + false + true + preview + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + diff --git a/tests/Python/Main.fs b/tests/Python/Main.fs new file mode 100644 index 0000000000..ae5f6f6959 --- /dev/null +++ b/tests/Python/Main.fs @@ -0,0 +1,6 @@ +#if FABLE_COMPILER +module Program +() +#else +module Program = let [] main _ = 0 +#endif \ No newline at end of file diff --git a/tests/Python/TestSeq.fs b/tests/Python/TestSeq.fs new file mode 100644 index 0000000000..e2738dbf27 --- /dev/null +++ b/tests/Python/TestSeq.fs @@ -0,0 +1,16 @@ +module Fable.Tests.Seqs + +open Util.Testing +open Fable.Tests.Util + +#if FABLE_COMPILER +#else +open Xunit +#endif + + +[] +let ``test Seq.length works`` () = + let xs = [1.; 2.; 3.; 4.] + Seq.length xs + |> equal 4 diff --git a/tests/Python/Util.fs b/tests/Python/Util.fs new file mode 100644 index 0000000000..594a70c4e6 --- /dev/null +++ b/tests/Python/Util.fs @@ -0,0 +1,25 @@ +module Fable.Tests.Util + +open System + +module Testing = +#if FABLE_COMPILER + open Fable.Core + open Fable.Core.PyInterop + + type Assert = + [] + static member AreEqual(actual: 'T, expected: 'T, ?msg: string): unit = pyNative + [] + static member NotEqual(actual: 'T, expected: 'T, ?msg: string): unit = pyNative + + let equal expected actual: unit = Assert.AreEqual(actual, expected) + let notEqual expected actual: unit = Assert.NotEqual(actual, expected) + + type Fact() = inherit System.Attribute() +#else + open Xunit + + let equal<'T> (expected: 'T) (actual: 'T): unit = Assert.Equal(expected, actual) + let notEqual<'T> (expected: 'T) (actual: 'T) : unit = Assert.NotEqual(expected, actual) +#endif From 29f612991c5766bb23413148d95e2d7b72ee7280 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 28 Mar 2021 11:18:11 +0200 Subject: [PATCH 060/145] Avoid importing Xunit for every test file --- tests/Python/TestSeq.fs | 6 ------ tests/Python/Util.fs | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/Python/TestSeq.fs b/tests/Python/TestSeq.fs index e2738dbf27..cdbfcc5034 100644 --- a/tests/Python/TestSeq.fs +++ b/tests/Python/TestSeq.fs @@ -3,12 +3,6 @@ module Fable.Tests.Seqs open Util.Testing open Fable.Tests.Util -#if FABLE_COMPILER -#else -open Xunit -#endif - - [] let ``test Seq.length works`` () = let xs = [1.; 2.; 3.; 4.] diff --git a/tests/Python/Util.fs b/tests/Python/Util.fs index 594a70c4e6..96a4ed3268 100644 --- a/tests/Python/Util.fs +++ b/tests/Python/Util.fs @@ -19,6 +19,7 @@ module Testing = type Fact() = inherit System.Attribute() #else open Xunit + type FactAttribute = Xunit.FactAttribute let equal<'T> (expected: 'T) (actual: 'T): unit = Assert.Equal(expected, actual) let notEqual<'T> (expected: 'T) (actual: 'T) : unit = Assert.NotEqual(expected, actual) From 405a938e3d36c09f791a09a40b489883436a05b5 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 28 Mar 2021 13:37:10 +0200 Subject: [PATCH 061/145] Add more tests --- tests/Python/TestSeq.fs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/Python/TestSeq.fs b/tests/Python/TestSeq.fs index cdbfcc5034..aae6e232a0 100644 --- a/tests/Python/TestSeq.fs +++ b/tests/Python/TestSeq.fs @@ -1,10 +1,24 @@ module Fable.Tests.Seqs open Util.Testing -open Fable.Tests.Util + + +[] +let ``test Seq.empty works`` () = + let xs = Seq.empty + Seq.length xs + |> equal 0 [] let ``test Seq.length works`` () = let xs = [1.; 2.; 3.; 4.] Seq.length xs |> equal 4 + +[] +let ``test Seq.map works`` () = + let xs = [1; 2; 3; 4] + xs + |> Seq.map string + |> List.ofSeq + |> equal ["1"; "2"; "3"; "4"] From f909256a548400af7da857ef8c90738475df8a39 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 28 Mar 2021 16:16:22 +0200 Subject: [PATCH 062/145] Add more tests --- tests/Python/Fable.Tests.fsproj | 2 ++ tests/Python/TestFn.fs | 29 +++++++++++++++++++++++++++++ tests/Python/TestMath.fs | 10 ++++++++++ 3 files changed, 41 insertions(+) create mode 100644 tests/Python/TestFn.fs create mode 100644 tests/Python/TestMath.fs diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj index bee470cdc1..ebb077700e 100644 --- a/tests/Python/Fable.Tests.fsproj +++ b/tests/Python/Fable.Tests.fsproj @@ -21,6 +21,8 @@ + + diff --git a/tests/Python/TestFn.fs b/tests/Python/TestFn.fs new file mode 100644 index 0000000000..2200de7c75 --- /dev/null +++ b/tests/Python/TestFn.fs @@ -0,0 +1,29 @@ +module Fable.Tests.Fn + +open Util.Testing + +let add(a, b, cont) = + cont(a + b) + +let square(x, cont) = + cont(x * x) + +let sqrt(x, cont) = + cont(sqrt(x)) + +let pythagoras(a, b, cont) = + square(a, (fun aa -> + square(b, (fun bb -> + add(aa, bb, (fun aabb -> + sqrt(aabb, (fun result -> + cont(result) + )) + )) + )) + )) + +[] +let ``test pythagoras works`` () = + let result = pythagoras(10.0, 2.1, id) + result + |> equal 10.218121158021175 diff --git a/tests/Python/TestMath.fs b/tests/Python/TestMath.fs new file mode 100644 index 0000000000..a5224ab35c --- /dev/null +++ b/tests/Python/TestMath.fs @@ -0,0 +1,10 @@ +module Fable.Tests.Math + +open Util.Testing + + +[] +let ``test power works`` () = + let x = 10.0 ** 2. + x + |> equal 100.0 From cf1744676cd6b7f83cf5d9863b7320a5ca9e5feb Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 28 Mar 2021 17:16:11 +0200 Subject: [PATCH 063/145] Test list --- tests/Python/Fable.Tests.fsproj | 1 + tests/Python/TestFn.fs | 11 +++++++++++ tests/Python/TestList.fs | 30 ++++++++++++++++++++++++++++++ tests/Python/TestSeq.fs | 8 ++++++++ 4 files changed, 50 insertions(+) create mode 100644 tests/Python/TestList.fs diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj index ebb077700e..58b7b2489f 100644 --- a/tests/Python/Fable.Tests.fsproj +++ b/tests/Python/Fable.Tests.fsproj @@ -21,6 +21,7 @@ + diff --git a/tests/Python/TestFn.fs b/tests/Python/TestFn.fs index 2200de7c75..d889257dd3 100644 --- a/tests/Python/TestFn.fs +++ b/tests/Python/TestFn.fs @@ -27,3 +27,14 @@ let ``test pythagoras works`` () = let result = pythagoras(10.0, 2.1, id) result |> equal 10.218121158021175 + +[] +let ``test nonlocal works`` () = + let mutable value = 0 + + let fn () = + value <- 42 + + fn () + + value |> equal 42 diff --git a/tests/Python/TestList.fs b/tests/Python/TestList.fs new file mode 100644 index 0000000000..c049842edb --- /dev/null +++ b/tests/Python/TestList.fs @@ -0,0 +1,30 @@ +module Fable.Tests.List + +open Util.Testing + + +[] +let ``test List.empty works`` () = + let xs = List.empty + List.length xs + |> equal 0 + +[] +let ``test List.length works`` () = + let xs = [1.; 2.; 3.; 4.] + List.length xs + |> equal 4 + +[] +let ``test List.map works`` () = + let xs = [1; 2; 3; 4] + xs + |> List.map string + |> equal ["1"; "2"; "3"; "4"] + + +[] +let ``test List.singleton works`` () = + let xs = List.singleton 42 + xs + |> equal [42] diff --git a/tests/Python/TestSeq.fs b/tests/Python/TestSeq.fs index aae6e232a0..6d48bc62fd 100644 --- a/tests/Python/TestSeq.fs +++ b/tests/Python/TestSeq.fs @@ -22,3 +22,11 @@ let ``test Seq.map works`` () = |> Seq.map string |> List.ofSeq |> equal ["1"; "2"; "3"; "4"] + + +[] +let ``test Seq.singleton works`` () = + let xs = Seq.singleton 42 + xs + |> List.ofSeq + |> equal [42] From 634f181a374535b71528663335bf45e01082db7f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 28 Mar 2021 20:12:52 +0200 Subject: [PATCH 064/145] Fix for member expressions --- src/Fable.Core/Fable.Core.PyInterop.fs | 8 ++++---- src/Fable.Transforms/Python/Babel2Python.fs | 17 +++++++---------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Fable.Core/Fable.Core.PyInterop.fs b/src/Fable.Core/Fable.Core.PyInterop.fs index fa654c2ee4..78d1d5329f 100644 --- a/src/Fable.Core/Fable.Core.PyInterop.fs +++ b/src/Fable.Core/Fable.Core.PyInterop.fs @@ -50,16 +50,16 @@ let createObj (fields: #seq): obj = pyNative /// E.g. `keyValueList CaseRules.LowerFirst [ MyUnion 4 ]` in Python becomes `{ myUnion: 4 }` let keyValueList (caseRule: CaseRules) (li: 'T seq): obj = pyNative -/// Create a literal JS object from a mutator lambda. Normally used when +/// Create a literal Py object from a mutator lambda. Normally used when /// the options interface has too many fields to be represented with a Pojo record. /// E.g. `jsOptions (fun o -> o.foo <- 5)` in JS becomes `{ foo: 5 }` -let jsOptions<'T> (f: 'T->unit): 'T = pyNative +let pyOptions<'T> (f: 'T->unit): 'T = pyNative /// Create an empty JS object: {} ///let createEmpty<'T> : 'T = pyNative -/// Get the JS function constructor for class types -let jsConstructor<'T> : obj = pyNative +/// Get the Py function constructor for class types +let pyConstructor<'T> : obj = pyNative /// Makes an expression the default export for the JS module. /// Used to interact with JS tools that require a default export. diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index d3e7581a3e..a05ae491e8 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -492,28 +492,26 @@ module Util = let value, stmts = com.TransformAsExpr(ctx, object) let func = Expression.name (Python.Identifier "str") Expression.call (func, [ value ]), stmts + // If computed is true, the node corresponds to a computed (a[b]) member expression and property is an Expression | MemberExpression (computed=true; object = object; property = property) -> let value, stmts = com.TransformAsExpr(ctx, object) - let attr = - match property with - | Expression.Identifier (Identifier (name = name)) -> Expression.constant(name) - | _ -> failwith $"transformAsExpr: unknown property {property}" - + let attr, stmts' = com.TransformAsExpr(ctx, property) let value = match value with | Name { Id = Python.Identifier (id); Context = ctx } -> Expression.name (id = Python.Identifier(id), ctx = ctx) | _ -> value let func = Expression.name("getattr") - Expression.call(func=func, args=[value; attr]), stmts + Expression.call(func=func, args=[value; attr]), stmts @ stmts' + // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier | MemberExpression (computed=false; object = object; property = property) -> let value, stmts = com.TransformAsExpr(ctx, object) let attr = match property with | Expression.Identifier (Identifier (name = name)) -> com.GetIdentifier(ctx, name) - | _ -> failwith $"transformAsExpr: unknown property {property}" + | _ -> failwith $"transformAsExpr: MemberExpression (false): unknown property {property}" Expression.attribute (value = value, attr = attr, ctx = Load), stmts | Expression.Literal (Literal.BooleanLiteral (value = value)) -> Expression.constant (value = value), [] | FunctionExpression (``params`` = parms; body = body) -> @@ -530,9 +528,7 @@ module Util = Expression.lambda (arguments, body), stmts | _ -> let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) - let name = Helpers.getUniqueIdentifier "lifted" - let func = FunctionDef.Create(name = name, args = arguments, body = body) @@ -786,7 +782,7 @@ module Util = /// Transform Babel program to Python module. let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = - let returnStrategy = ReturnStrategy.Return + let returnStrategy = ReturnStrategy.NoReturn let stmt: Python.Statement list = [ for md in body do @@ -856,6 +852,7 @@ module Compiler = match localIdent with | Some localIdent -> localIdent |> Some | None -> None + | (true, Import _) -> None | _ -> let localId = getIdentForImport ctx moduleName name From 0515e5b0ea05703ce6254b3c63e71e074f354857 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 29 Mar 2021 08:13:50 +0200 Subject: [PATCH 065/145] String interpolation --- tests/Python/Fable.Tests.fsproj | 1 + tests/Python/TestList.fs | 8 ++++++++ tests/Python/TestSeq.fs | 9 +++++++++ tests/Python/TestString.fs | 11 +++++++++++ 4 files changed, 29 insertions(+) create mode 100644 tests/Python/TestString.fs diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj index 58b7b2489f..995037a69a 100644 --- a/tests/Python/Fable.Tests.fsproj +++ b/tests/Python/Fable.Tests.fsproj @@ -24,6 +24,7 @@ + diff --git a/tests/Python/TestList.fs b/tests/Python/TestList.fs index c049842edb..affc9b028f 100644 --- a/tests/Python/TestList.fs +++ b/tests/Python/TestList.fs @@ -28,3 +28,11 @@ let ``test List.singleton works`` () = let xs = List.singleton 42 xs |> equal [42] + + +[] +let ``test List.collect works`` () = + let xs = ["a"; "fable"; "bar" ] + xs + |> List.collect (fun a -> [a.Length]) + |> equal [1; 5; 3] diff --git a/tests/Python/TestSeq.fs b/tests/Python/TestSeq.fs index 6d48bc62fd..ce9b3974da 100644 --- a/tests/Python/TestSeq.fs +++ b/tests/Python/TestSeq.fs @@ -30,3 +30,12 @@ let ``test Seq.singleton works`` () = xs |> List.ofSeq |> equal [42] + +[] +let ``test Seq.collect works`` () = + let xs = ["a"; "fable"; "bar" ] + xs + |> Seq.ofList + |> Seq.collect (fun a -> [a.Length]) + |> List.ofSeq + |> equal [1; 5; 3] diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs new file mode 100644 index 0000000000..aa98d024eb --- /dev/null +++ b/tests/Python/TestString.fs @@ -0,0 +1,11 @@ +module Fable.Tests.String + +open Util.Testing + + +[] +let ``test interpolate works`` () = + let name = "Phillip" + let age = 29 + $"Name: {name}, Age: %i{age}" + |> equal "Name: Phillip, Age: 29" From 65181886a1e2057d671704ceae1069fb47655784 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 30 Mar 2021 07:49:33 +0200 Subject: [PATCH 066/145] Better string testing --- tests/Python/TestString.fs | 43 ++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index aa98d024eb..7988c10336 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -2,10 +2,45 @@ module Fable.Tests.String open Util.Testing +[] +let ``test sprintf works`` () = + // Immediately applied + sprintf "%.2f %g" 0.5468989 5. + |> equal "0.55 5" + // Curried + let printer = sprintf "Hi %s, good %s!" + let printer = printer "Alfonso" + printer "morning" |> equal "Hi Alfonso, good morning!" + printer "evening" |> equal "Hi Alfonso, good evening!" + +[] +let ``test sprintf works II`` () = + let printer2 = sprintf "Hi %s, good %s%s" "Maxime" + let printer2 = printer2 "afternoon" + printer2 "?" |> equal "Hi Maxime, good afternoon?" + +[] +let ``test sprintf with different decimal digits works`` () = + sprintf "Percent: %.0f%%" 5.0 |> equal "Percent: 5%" + sprintf "Percent: %.2f%%" 5. |> equal "Percent: 5.00%" + sprintf "Percent: %.1f%%" 5.24 |> equal "Percent: 5.2%" + sprintf "Percent: %.2f%%" 5.268 |> equal "Percent: 5.27%" + sprintf "Percent: %f%%" 5.67 |> equal "Percent: 5.670000%" + +[] +let ``sprintf displays sign correctly`` () = + sprintf "%i" 1 |> equal "1" + sprintf "%d" 1 |> equal "1" + sprintf "%d" 1L |> equal "1" + sprintf "%.2f" 1. |> equal "1.00" + sprintf "%i" -1 |> equal "-1" + sprintf "%d" -1 |> equal "-1" + sprintf "%d" -1L |> equal "-1" + sprintf "%.2f" -1. |> equal "-1.00" [] let ``test interpolate works`` () = - let name = "Phillip" - let age = 29 - $"Name: {name}, Age: %i{age}" - |> equal "Name: Phillip, Age: 29" + let name = "Phillip" + let age = 29 + $"Name: {name}, Age: %i{age}" + |> equal "Name: Phillip, Age: 29" From 45fcad18cd514f390ad639cf55f8ee3f4a8e5fa6 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 30 Mar 2021 08:14:53 +0200 Subject: [PATCH 067/145] String tests --- tests/Python/TestString.fs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index 7988c10336..aba8bb1eaf 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -38,9 +38,43 @@ let ``sprintf displays sign correctly`` () = sprintf "%d" -1L |> equal "-1" sprintf "%.2f" -1. |> equal "-1.00" +[] +let ``test Print.sprintf works`` () = + let res = Printf.sprintf "%s" "abc" + equal "res: abc" ("res: " + res) + +[] +let ``test sprintf without arguments works`` () = + sprintf "hello" |> equal "hello" + +[] +let ``test input of print format can be retrieved`` () = + let pathScan (pf:PrintfFormat<_,_,_,_,'t>) = + let formatStr = pf.Value + formatStr + + equal "/hello/%s" (pathScan "/hello/%s") + [] let ``test interpolate works`` () = let name = "Phillip" let age = 29 $"Name: {name}, Age: %i{age}" |> equal "Name: Phillip, Age: 29" + +#if FABLE_COMPILER +[] +let ``test string interpolation works with inline expressions`` () = + $"I think {3.0 + 0.14} is close to %.8f{3.14159265}!" + |> equal "I think 3.14 is close to 3.14159265!" +#endif + +// [] +// let ``test string interpolation works with anonymous records`` () = +// let person = +// {| Name = "John" +// Surname = "Doe" +// Age = 32 +// Country = "The United Kingdom" |} +// $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" +// |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" From b082f4f466a3d671c2600aecbdaa9700110dfd92 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 30 Mar 2021 19:24:24 +0200 Subject: [PATCH 068/145] Fix string test --- src/Fable.Transforms/Python/Babel2Python.fs | 13 ++++++++++++- src/Fable.Transforms/Python/Python.fs | 6 ++++-- src/Fable.Transforms/Python/PythonPrinter.fs | 7 +++++++ tests/Python/TestString.fs | 18 +++++++++--------- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index a05ae491e8..ee64bd1ac1 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -457,7 +457,14 @@ module Util = key, Expression.name (name), stmts @ [ func ] ] |> List.unzip3 - Expression.dict (keys = keys, values = values), stmts |> List.collect id + // Transform as namedtuple to allow attribute access of keys. + com.GetImportExpr(ctx, "collections", "namedtuple") |> ignore + let keys = + keys + |> List.map (function | Expression.Name { Id=Python.Identifier name} -> Expression.constant(name) | ex -> ex) + + Expression.call(Expression.call(Expression.name("namedtuple"), [ Expression.constant("object"); Expression.list(keys)]), values), stmts |> List.collect id + | EmitExpression (value = value; args = args) -> let args, stmts = args @@ -484,6 +491,10 @@ module Util = let value, stmts = com.TransformAsExpr(ctx, object) let attr = Python.Identifier "index" Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "toLocaleUpperCase"))) -> + let value, stmts = com.TransformAsExpr(ctx, object) + let attr = Python.Identifier "upper" + Expression.attribute (value = value, attr = attr, ctx = Load), stmts | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "length"))) -> let value, stmts = com.TransformAsExpr(ctx, object) let func = Expression.name (Python.Identifier "len") diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 3b67064905..20501329a9 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -41,6 +41,8 @@ type Expression = | Name of Name | Dict of Dict | Tuple of Tuple + | Starred of value: Expression * ctx: ExpressionContext + | List of elts: Expression list * ctx: ExpressionContext // member val Lineno: int = 0 with get, set // member val ColOffset: int = 0 with get, set @@ -915,9 +917,9 @@ module PythonExtensions = static member boolOp(op, values): Expression = { Values = values; Operator = op } |> BoolOp static member constant(value: obj): Expression = { Value = value } |> Constant - + static member starred(value: Expression, ?ctx: ExpressionContext) : Expression = Starred(value, ctx |> Option.defaultValue Load) + static member list(elts: Expression list, ?ctx: ExpressionContext) : Expression = List(elts, ctx |> Option.defaultValue Load) type List with - static member list(elts) = { Elements = elts } type ExceptHandler with diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index 4a7fb3664b..bed69b5591 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -585,6 +585,13 @@ module PrinterExtensions = | Compare (cp) -> printer.Print(cp) | Dict (di) -> printer.Print(di) | Tuple (tu) -> printer.Print(tu) + | Starred (ex, ctx) -> + printer.Print("*") + printer.Print(ex) + | List (elts, ctx) -> + printer.Print("[") + printer.PrintCommaSeparatedList(elts) + printer.Print("]") member printer.Print(node: AST) = diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index aba8bb1eaf..a1b8815ad5 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -69,12 +69,12 @@ let ``test string interpolation works with inline expressions`` () = |> equal "I think 3.14 is close to 3.14159265!" #endif -// [] -// let ``test string interpolation works with anonymous records`` () = -// let person = -// {| Name = "John" -// Surname = "Doe" -// Age = 32 -// Country = "The United Kingdom" |} -// $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" -// |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" +[] +let ``test string interpolation works with anonymous records`` () = + let person = + {| Name = "John" + Surname = "Doe" + Age = 32 + Country = "The United Kingdom" |} + $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" + |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" From 8dba1d23293ed6e8194849a0d6d342def10a4195 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 30 Mar 2021 21:25:00 +0200 Subject: [PATCH 069/145] Fix sequence expression to lifted function --- src/Fable.Transforms/Python/Babel2Python.fs | 26 ++++++++++----------- tests/Python/Fable.Tests.fsproj | 1 + tests/Python/TestRecordType.fs | 18 ++++++++++++++ 3 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 tests/Python/TestRecordType.fs diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index ee64bd1ac1..b339e22881 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -551,27 +551,27 @@ module Util = Expression.ifExp (test, body, orElse), stmts1 @ stmts2 @ stmts3 | Expression.Literal (Literal.NullLiteral (nl)) -> Expression.name (Python.Identifier("None")), [] - | SequenceExpression (expressions = exprs) -> + | SequenceExpression (expressions = exprs) -> //XXX // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments - let stmts = + let body = exprs |> List.ofArray - |> List.map (fun ex -> com.TransformAsStatements(ctx, ReturnStrategy.Return, ex)) + |> List.mapi + (fun i ex -> + // Return the last expression + if i = exprs.Length - 1 then + let expr, stmts = com.TransformAsExpr(ctx, ex) + [Statement.return' (expr)] + else + com.TransformAsStatements(ctx, ReturnStrategy.Return, ex)) |> List.collect id + |> transformBody ReturnStrategy.Return - // let body = - // exprs - // |> List.mapi - // (fun i n -> - // if i = exprs.Length - 1 then - // Statement.return' (n) // Return the last statement - // else - // Statement.expr (n)) let name = Helpers.getUniqueIdentifier ("lifted") let func = - FunctionDef.Create(name = name, args = Arguments.arguments [], body = stmts) + FunctionDef.Create(name = name, args = Arguments.arguments [], body = body) let name = Expression.name (name) Expression.call (name), [ func ] @@ -820,7 +820,7 @@ module Util = | Babel.ImportDeclaration (specifiers, source) -> yield! com.TransformAsImports(ctx, specifiers, source) | Babel.PrivateModuleDeclaration (statement = statement) -> yield! - com.TransformAsStatements(ctx, returnStrategy, statement) + com.TransformAsStatements(ctx, ReturnStrategy.Return, statement) |> transformBody returnStrategy | _ -> failwith $"Unknown module declaration: {md}" ] diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj index 995037a69a..e5df49abdf 100644 --- a/tests/Python/Fable.Tests.fsproj +++ b/tests/Python/Fable.Tests.fsproj @@ -25,6 +25,7 @@ + diff --git a/tests/Python/TestRecordType.fs b/tests/Python/TestRecordType.fs new file mode 100644 index 0000000000..3bc9e46b5a --- /dev/null +++ b/tests/Python/TestRecordType.fs @@ -0,0 +1,18 @@ +module Fable.Tests.Record + +open Util.Testing + +let makeAnonRec() = + {| X = 5; Y = "Foo"; F = fun x y -> x + y |} + + +[] +let ``test Anonymous records work`` () = + let r = makeAnonRec() + sprintf "Tell me %s %i times" r.Y (r.F r.X 3) + |> equal "Tell me Foo 8 times" + let x = {| Foo = "baz"; Bar = 23 |} + let y = {| Foo = "baz" |} + x = {| y with Bar = 23 |} |> equal true + // x = {| y with Baz = 23 |} |> equal true // Doesn't compile + x = {| y with Bar = 14 |} |> equal false From d9f7c42706ec1d62b50fab164fce34aa3b670e51 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 2 Apr 2021 09:19:40 +0200 Subject: [PATCH 070/145] Fix nonlocals and print of iterables --- src/Fable.Transforms/Python/Babel2Python.fs | 132 +++++++++++-------- src/Fable.Transforms/Python/Python.fs | 38 +++--- src/Fable.Transforms/Python/PythonPrinter.fs | 48 +++---- tests/Python/TestRecordType.fs | 109 +++++++++++++++ tests/Python/TestString.fs | 10 ++ 5 files changed, 240 insertions(+), 97 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index b339e22881..51e2ae8ed8 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -21,18 +21,14 @@ type ITailCallOpportunity = abstract IsRecursiveRef: Fable.Expr -> bool type UsedNames = - { RootScope: HashSet - DeclarationScopes: HashSet - CurrentDeclarationScope: HashSet } + { GlobalScope: HashSet + EnclosingScope: HashSet + LocalScope: HashSet + NonLocals: HashSet } type Context = - { - //UsedNames: UsedNames - DecisionTargets: (Fable.Ident list * Fable.Expr) list - HoistVars: Fable.Ident list -> bool - TailCallOpportunity: ITailCallOpportunity option - OptimizeTailCall: unit -> unit - ScopedTypeParams: Set } + { ReturnStrategy: ReturnStrategy + UsedNames: UsedNames } type IPythonCompiler = inherit Compiler @@ -57,8 +53,7 @@ type IPythonCompiler = Python.Statement list abstract TransformAsImports: Context * Babel.ImportSpecifier array * Babel.StringLiteral -> Python.Statement list - - abstract TransformFunction: Context * Babel.Identifier * Babel.Pattern array * Babel.BlockStatement -> Python.Statement + abstract TransformAsFunction: Context * string * Babel.Pattern array * Babel.BlockStatement -> Python.Statement abstract WarnOnlyOnce: string * ?range: SourceLocation -> unit @@ -76,6 +71,9 @@ module Helpers = match name with | "this" -> "self" | "async" -> "asyncio" + | "from" -> "from_" + | "class" -> "class_" + | "for" -> "for_" | "Math" -> "math" | _ -> name.Replace('$', '_').Replace('.', '_') @@ -237,14 +235,15 @@ module Util = parms |> List.choose (function - | Pattern.Identifier (id) -> Arg.arg (Python.Identifier(id.Name)) |> Some + | Pattern.Identifier (id) -> + Arg.arg (com.GetIdentifier(ctx, id.Name)) |> Some | _ -> None) let varargs = parms |> List.choose (function - | Pattern.RestElement (argument = argument) -> Arg.arg (Python.Identifier(argument.Name)) |> Some + | Pattern.RestElement (argument = argument) -> Arg.arg (com.GetIdentifier(ctx, argument.Name)) |> Some | _ -> None) |> List.tryHead @@ -257,10 +256,11 @@ module Util = let name = match key with - | Expression.Identifier (id) -> Python.Identifier(id.Name) + | Expression.Identifier (id) -> com.GetIdentifier(ctx, id.Name) | Expression.Literal(Literal.StringLiteral(StringLiteral(value=name))) -> - com.GetIdentifier(ctx, name) - | _ -> failwith $"transformAsClassDef: Unknown key: {key}" + com.GetIdentifier(ctx, name) + | _ -> + failwith $"transformAsClassDef: Unknown key: {key}" FunctionDef.Create(name, arguments, body = body) | "constructor" -> @@ -273,15 +273,14 @@ module Util = | _ -> failwith $"transformAsClassDef: Unknown kind: {kind}" | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] - printfn $"Body length: {body.Length}: ${body}" let name = com.GetIdentifier(ctx, id.Value.Name) - [ yield! stmts; Statement.classDef(name, body = body, bases = bases) ] + /// Transform Bable parameters as Python function let transformAsFunction (com: IPythonCompiler) (ctx: Context) - (name: Babel.Identifier) + (name: string) (parms: Babel.Pattern array) (body: Babel.BlockStatement) = @@ -294,11 +293,10 @@ module Util = Arg.arg (ident)) let arguments = Arguments.arguments (args = args) + let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope; NonLocals = HashSet () }} + let body = com.TransformAsStatements(ctx', ReturnStrategy.Return, body) - let body = - com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Statement.BlockStatement) - - let name = com.GetIdentifier(ctx, name.Name) + let name = com.GetIdentifier(ctx, name) FunctionDef.Create(name, arguments, body = body) /// Transform Babel expression as Python expression @@ -350,6 +348,11 @@ module Util = | "isinstance" -> toCall "isinstance" | _ -> failwith $"Unknown operator: {operator}" + // Transform `~(~(a/b))` to `a // b` + | UnaryExpression (operator = "~"; argument = UnaryExpression(operator="~"; argument=BinaryExpression(left, right, operator, loc))) when operator = "/" -> + let left, leftStmts = com.TransformAsExpr(ctx, left) + let right, rightStmts = com.TransformAsExpr(ctx, right) + Expression.binOp(left, FloorDiv,right), leftStmts @ rightStmts | UnaryExpression (operator = operator; argument = arg) -> let op = match operator with @@ -372,7 +375,7 @@ module Util = let args = parms |> List.ofArray - |> List.map (fun pattern -> Arg.arg (Python.Identifier pattern.Name)) + |> List.map (fun pattern -> Arg.arg (com.GetIdentifier(ctx, pattern.Name))) let arguments = let args = @@ -389,11 +392,11 @@ module Util = let body, stmts = com.TransformAsExpr(ctx, argument) Expression.lambda (arguments, body), stmts | _ -> - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) + let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope; NonLocals = HashSet () }} + let body = com.TransformAsStatements(ctx', ReturnStrategy.Return, body) let name = Helpers.getUniqueIdentifier "lifted" - let func = - FunctionDef.Create(name = name, args = arguments, body = body) + let func = FunctionDef.Create(name = name, args = arguments, body = body) Expression.name (name), [ func ] | CallExpression (callee = callee; arguments = args) -> // FIXME: use transformAsCall @@ -413,7 +416,7 @@ module Util = |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) |> Helpers.unzipArgs - Expression.tuple (elems), stmts + Expression.list (elems), stmts | Expression.Literal (Literal.NumericLiteral (value = value)) -> Expression.constant (value = value), [] | Expression.Literal (Literal.StringLiteral (StringLiteral.StringLiteral (value = value))) -> Expression.constant (value = value), [] @@ -440,20 +443,12 @@ module Util = let value, stmts2 = com.TransformAsExpr(ctx, value) key, value, stmts1 @ stmts2 | Babel.ObjectMethod (key = key; ``params`` = parms; body = body) -> - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) + //let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) let key, stmts = com.TransformAsExpr(ctx, key) - let args = - parms - |> List.ofArray - |> List.map (fun pattern -> Arg.arg (Python.Identifier pattern.Name)) - - let arguments = Arguments.arguments (args = args) let name = Helpers.getUniqueIdentifier "lifted" - let func = - FunctionDef.Create(name = name, args = arguments, body = body) - + let func = com.TransformAsFunction(ctx, name.Name, parms, body) key, Expression.name (name), stmts @ [ func ] ] |> List.unzip3 @@ -463,7 +458,11 @@ module Util = keys |> List.map (function | Expression.Name { Id=Python.Identifier name} -> Expression.constant(name) | ex -> ex) - Expression.call(Expression.call(Expression.name("namedtuple"), [ Expression.constant("object"); Expression.list(keys)]), values), stmts |> List.collect id + Expression.call( + Expression.call( + Expression.name("namedtuple"), [ Expression.constant("object"); Expression.list(keys)] + ), values + ), stmts |> List.collect id | EmitExpression (value = value; args = args) -> let args, stmts = @@ -476,6 +475,7 @@ module Util = | "void $0" -> args.[0], stmts //| "raise %0" -> Raise.Create() | _ -> Expression.emit (value, args), stmts + // If computed is true, the node corresponds to a computed (a[b]) member expression and property is an Expression | MemberExpression (computed = true; object = object; property = Expression.Literal (literal)) -> let value, stmts = com.TransformAsExpr(ctx, object) match literal with @@ -487,18 +487,22 @@ module Util = let func = Expression.name("getattr") Expression.call(func, args=[value; attr]), stmts | _ -> failwith $"transformExpr: unknown literal {literal}" + // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "indexOf"))) -> let value, stmts = com.TransformAsExpr(ctx, object) let attr = Python.Identifier "index" Expression.attribute (value = value, attr = attr, ctx = Load), stmts + // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "toLocaleUpperCase"))) -> let value, stmts = com.TransformAsExpr(ctx, object) let attr = Python.Identifier "upper" Expression.attribute (value = value, attr = attr, ctx = Load), stmts + // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "length"))) -> let value, stmts = com.TransformAsExpr(ctx, object) let func = Expression.name (Python.Identifier "len") Expression.call (func, [ value ]), stmts + // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "message"))) -> let value, stmts = com.TransformAsExpr(ctx, object) let func = Expression.name (Python.Identifier "str") @@ -510,8 +514,8 @@ module Util = let attr, stmts' = com.TransformAsExpr(ctx, property) let value = match value with - | Name { Id = Python.Identifier (id); Context = ctx } -> - Expression.name (id = Python.Identifier(id), ctx = ctx) + | Name { Id = Python.Identifier (id); Context = exprCtx } -> + Expression.name (id = com.GetIdentifier(ctx, id), ctx = exprCtx) | _ -> value let func = Expression.name("getattr") Expression.call(func=func, args=[value; attr]), stmts @ stmts' @@ -538,7 +542,9 @@ module Util = let body, stmts = com.TransformAsExpr(ctx, expr) Expression.lambda (arguments, body), stmts | _ -> - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) + let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope; NonLocals = HashSet () }} + + let body = com.TransformAsStatements(ctx', ReturnStrategy.Return, body) let name = Helpers.getUniqueIdentifier "lifted" let func = FunctionDef.Create(name = name, args = arguments, body = body) @@ -553,6 +559,8 @@ module Util = | Expression.Literal (Literal.NullLiteral (nl)) -> Expression.name (Python.Identifier("None")), [] | SequenceExpression (expressions = exprs) -> //XXX // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments + let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope; NonLocals = HashSet () }} + let body = exprs |> List.ofArray @@ -560,16 +568,15 @@ module Util = (fun i ex -> // Return the last expression if i = exprs.Length - 1 then - let expr, stmts = com.TransformAsExpr(ctx, ex) + let expr, stmts = com.TransformAsExpr(ctx', ex) [Statement.return' (expr)] else - com.TransformAsStatements(ctx, ReturnStrategy.Return, ex)) + com.TransformAsStatements(ctx', ReturnStrategy.Return, ex)) |> List.collect id |> transformBody ReturnStrategy.Return let name = Helpers.getUniqueIdentifier ("lifted") - let func = FunctionDef.Create(name = name, args = Arguments.arguments [], body = body) @@ -605,13 +612,19 @@ module Util = // Transform e.g `this.x = x;` into `self.x = x` | AssignmentExpression (left = left; right = right) -> let value, stmts = com.TransformAsExpr(ctx, right) - let targets, stmts2: Python.Expression list * Python.Statement list = match left with | Expression.Identifier (Identifier (name = name)) -> let target = com.GetIdentifier(ctx, name) + let stmts = + if not (ctx.UsedNames.LocalScope.Contains name) && ctx.UsedNames.EnclosingScope.Contains name then + ctx.UsedNames.NonLocals.Add name |> ignore + printfn "**** Adding non-local: %A" target + [ Statement.nonLocal [target] ] + else + [] - [ Expression.name (id = target, ctx = Store) ], [] + [ Expression.name (id = target, ctx = Store) ], stmts | MemberExpression (property = Expression.Identifier (id); object = object) -> let attr = com.GetIdentifier(ctx, id.Name) @@ -653,6 +666,7 @@ module Util = match init with | Some value -> + ctx.UsedNames.LocalScope.Add id.Name |> ignore let expr, stmts = com.TransformAsExpr(ctx, value) yield! stmts Statement.assign (targets, expr) @@ -757,7 +771,7 @@ module Util = | None -> [] | Statement.BreakStatement (_) -> [ Break ] | Statement.Declaration (Declaration.FunctionDeclaration (``params`` = parms; id = id; body = body)) -> - [ com.TransformFunction(ctx, id, parms, body) ] + [ com.TransformAsFunction(ctx, id.Name, parms, body) ] | Statement.Declaration (Declaration.ClassDeclaration (body, id, superClass, implements, superTypeParameters, typeParameters, loc)) -> transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc | Statement.ForStatement (init = Some (VariableDeclaration(declarations = [| VariableDeclarator (id = id; init = Some (init)) |])) @@ -806,12 +820,13 @@ module Util = let targets: Python.Expression list = let name = com.GetIdentifier(ctx, id.Name) + ctx.UsedNames.GlobalScope.Add id.Name |> ignore [ Expression.name (id = name, ctx = Store) ] yield! stmts yield Statement.assign (targets = targets, value = value) | Babel.FunctionDeclaration (``params`` = ``params``; body = body; id = id) -> - yield com.TransformFunction(ctx, id, ``params``, body) + yield com.TransformAsFunction(ctx, id.Name, ``params``, body) | Babel.ClassDeclaration (body, id, superClass, implements, superTypeParameters, typeParameters, loc) -> yield! transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc @@ -893,11 +908,9 @@ module Compiler = member bcom.TransformAsStatements(ctx, ret, e) = transformExpressionAsStatements bcom ctx ret e member bcom.TransformAsStatements(ctx, ret, e) = transformStatementAsStatements bcom ctx ret e member bcom.TransformAsStatements(ctx, ret, e) = transformBlockStatementAsStatements bcom ctx ret e - member bcom.TransformAsClassDef(ctx, body, id, superClass, implements, superTypeParameters, typeParameters, loc) = transformAsClassDef bcom ctx body id superClass implements superTypeParameters typeParameters loc - - member bcom.TransformFunction(ctx, name, args, body) = transformAsFunction bcom ctx name args body + member bcom.TransformAsFunction(ctx, name, args, body) = transformAsFunction bcom ctx name args body member bcom.TransformAsImports(ctx, specifiers, source) = transformAsImports bcom ctx specifiers source interface Compiler with @@ -920,11 +933,14 @@ module Compiler = let com = makeCompiler com :> IPythonCompiler let ctx = - { DecisionTargets = [] - HoistVars = fun _ -> false - TailCallOpportunity = None - OptimizeTailCall = fun () -> () - ScopedTypeParams = Set.empty } + { ReturnStrategy = ReturnStrategy.NoReturn // Don't return in global scope. + UsedNames = { + GlobalScope = HashSet () + EnclosingScope = HashSet () + LocalScope = HashSet () + NonLocals = HashSet () + } + } let (Program body) = program transformProgram com ctx body diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 20501329a9..c8fd6821bb 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -3,20 +3,6 @@ namespace rec Fable.AST.Python open Fable.AST -type AST = - | Expression of Expression - | Statement of Statement - | Operator of Operator - | BoolOperator of BoolOperator - | ComparisonOperator of ComparisonOperator - | UnaryOperator of UnaryOperator - | ExpressionContext of ExpressionContext - | Alias of Alias - | Module of Module - | Arguments of Arguments - | Keyword of Keyword - | Arg of Arg - type Expression = | Attribute of Attribute | Subscript of Subscript @@ -90,7 +76,12 @@ type ExpressionContext = | Del | Store -type Identifier = Identifier of string +type Identifier = + Identifier of string + with + member this.Name = + let (Identifier name) = this + name type Statement = | Pass @@ -804,6 +795,22 @@ type Name = { Id: Identifier Context: ExpressionContext } +[] +type AST = + | Expression of Expression + | Statement of Statement + | Operator of Operator + | BoolOperator of BoolOperator + | ComparisonOperator of ComparisonOperator + | UnaryOperator of UnaryOperator + | ExpressionContext of ExpressionContext + | Alias of Alias + | Module of Module + | Arguments of Arguments + | Keyword of Keyword + | Arg of Arg + | Identifier of Identifier + [] module PythonExtensions = type Statement with @@ -851,6 +858,7 @@ module PythonExtensions = static member importFrom(``module``, names, ?level) = ImportFrom.importFrom (``module``, names, ?level = level) |> ImportFrom + static member nonLocal(ids) = NonLocal.Create ids |> Statement.NonLocal type Expression with diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index bed69b5591..c3b779ab39 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -162,7 +162,6 @@ module PrinterExtensions = printer.PrintCommaSeparatedList(args) member printer.Print(assign: Assign) = - printfn "Assign: %A" (assign.Targets, assign.Value) //printer.PrintOperation(targets.[0], "=", value, None) for target in assign.Targets do @@ -243,7 +242,9 @@ module PrinterExtensions = member printer.Print(gl: Global) = printer.Print("(Global)") - member printer.Print(nl: NonLocal) = printer.Print("(NonLocal)") + member printer.Print(nl: NonLocal) = + printer.Print("nonlocal ") + printer.PrintCommaSeparatedList nl.Names member printer.Print(af: AsyncFunctionDef) = printer.Print("(AsyncFunctionDef)") @@ -327,7 +328,7 @@ module PrinterExtensions = match box node.Value with | :? string as str -> printer.Print("\"") - printer.Print(string node.Value) + printer.Print(Web.HttpUtility.JavaScriptStringEncode(string node.Value)) printer.Print("\"") | _ -> printer.Print(string node.Value) @@ -593,21 +594,21 @@ module PrinterExtensions = printer.PrintCommaSeparatedList(elts) printer.Print("]") - member printer.Print(node: AST) = match node with - | Expression (ex) -> printer.Print(ex) - | Operator (op) -> printer.Print(op) - | BoolOperator (op) -> printer.Print(op) - | ComparisonOperator (op) -> printer.Print(op) - | UnaryOperator (op) -> printer.Print(op) - | ExpressionContext (_) -> () - | Alias (al) -> printer.Print(al) - | Module ``mod`` -> printer.Print(``mod``) - | Arguments (arg) -> printer.Print(arg) - | Keyword (kw) -> printer.Print(kw) - | Arg (arg) -> printer.Print(arg) - | Statement (st) -> printer.Print(st) + | AST.Expression (ex) -> printer.Print(ex) + | AST.Operator (op) -> printer.Print(op) + | AST.BoolOperator (op) -> printer.Print(op) + | AST.ComparisonOperator (op) -> printer.Print(op) + | AST.UnaryOperator (op) -> printer.Print(op) + | AST.ExpressionContext (_) -> () + | AST.Alias (al) -> printer.Print(al) + | AST.Module ``mod`` -> printer.Print(``mod``) + | AST.Arguments (arg) -> printer.Print(arg) + | AST.Keyword (kw) -> printer.Print(kw) + | AST.Arg (arg) -> printer.Print(arg) + | AST.Statement (st) -> printer.Print(st) + | AST.Identifier (id) -> printer.Print(id) member printer.PrintBlock ( @@ -638,8 +639,6 @@ module PrinterExtensions = member _.IsProductiveStatement(stmt: Statement) = let rec hasNoSideEffects(e: Expression) = - printfn $"hasNoSideEffects: {e}" - match e with | Constant (_) -> true | Dict { Keys = keys } -> keys.IsEmpty @@ -689,7 +688,7 @@ module PrinterExtensions = | Some node -> printer.Print(node) member printer.PrintOptional(node: Expression option) = - printer.PrintOptional(node |> Option.map Expression) + printer.PrintOptional(node |> Option.map AST.Expression) member printer.PrintOptional(node: Identifier option) = match node with | None -> () @@ -708,11 +707,14 @@ module PrinterExtensions = member printer.PrintCommaSeparatedList(nodes: Expression list) = printer.PrintList(nodes, (fun p x -> p.SequenceExpressionWithParens(x)), (fun p -> p.Print(", "))) member printer.PrintCommaSeparatedList(nodes: Arg list) = - printer.PrintCommaSeparatedList(nodes |> List.map Arg) + printer.PrintCommaSeparatedList(nodes |> List.map AST.Arg) member printer.PrintCommaSeparatedList(nodes: Keyword list) = - printer.PrintCommaSeparatedList(nodes |> List.map Keyword) + printer.PrintCommaSeparatedList(nodes |> List.map AST.Keyword) member printer.PrintCommaSeparatedList(nodes: Alias list) = - printer.PrintCommaSeparatedList(nodes |> List.map Alias) + printer.PrintCommaSeparatedList(nodes |> List.map AST.Alias) + member printer.PrintCommaSeparatedList(nodes: Identifier list) = + printer.PrintCommaSeparatedList(nodes |> List.map AST.Identifier) + member printer.PrintFunction ( @@ -743,8 +745,6 @@ module PrinterExtensions = /// Surround with parens anything that can potentially conflict with operator precedence member printer.ComplexExpressionWithParens(expr: Expression) = - printfn "Expr: %A" expr - match expr with | Constant (_) -> printer.Print(expr) | Name (_) -> printer.Print(expr) diff --git a/tests/Python/TestRecordType.fs b/tests/Python/TestRecordType.fs index 3bc9e46b5a..94c0e5fd28 100644 --- a/tests/Python/TestRecordType.fs +++ b/tests/Python/TestRecordType.fs @@ -2,9 +2,42 @@ module Fable.Tests.Record open Util.Testing +type RecursiveRecord = + { things : RecursiveRecord list } + +type Person = + { name: string; mutable luckyNumber: int } + member x.LuckyDay = x.luckyNumber % 30 + member x.SignDoc str = str + " by " + x.name + +type JSKiller = + { ``for`` : float; ``class`` : float } + +type JSKiller2 = + { ``s p a c e`` : float; ``s*y*m*b*o*l`` : float } + +type Child = + { a: string; b: int } + member x.Sum() = (int x.a) + x.b + +type Parent = + { children: Child[] } + member x.Sum() = x.children |> Seq.sumBy (fun c -> c.Sum()) + +type MutatingRecord = + { uniqueA: int; uniqueB: int } + +type Id = Id of string + +let inline replaceById< ^t when ^t : (member Id : Id)> (newItem : ^t) (ar: ^t[]) = + Array.map (fun (x: ^t) -> if (^t : (member Id : Id) newItem) = (^t : (member Id : Id) x) then newItem else x) ar + let makeAnonRec() = {| X = 5; Y = "Foo"; F = fun x y -> x + y |} +type Time = + static member inline duration(value: {| from: int; until: int |}) = value.until - value.from + static member inline duration(value: {| from: int |}) = Time.duration {| value with until = 10 |} [] let ``test Anonymous records work`` () = @@ -16,3 +49,79 @@ let ``test Anonymous records work`` () = x = {| y with Bar = 23 |} |> equal true // x = {| y with Baz = 23 |} |> equal true // Doesn't compile x = {| y with Bar = 14 |} |> equal false + +[] +let ``test SRTP works with anonymous records`` () = + let ar = [| {|Id=Id"foo"; Name="Sarah"|}; {|Id=Id"bar"; Name="James"|} |] + replaceById {|Id=Id"ja"; Name="Voll"|} ar |> Seq.head |> fun x -> equal "Sarah" x.Name + replaceById {|Id=Id"foo"; Name="Anna"|} ar |> Seq.head |> fun x -> equal "Anna" x.Name + +[] +let ``test Overloads with anonymous record arguments don't have same mangled name`` () = + Time.duration {| from = 1 |} |> equal 9 + Time.duration {| from = 1; until = 5 |} |> equal 4 + +[] // TODO: Need to handle nonlocal variables in Python +let ``test Anonymous record execution order`` () = + let mutable x = 2 + let record = + {| + C = (x <- x * 3; x) + B = (x <- x + 5; x) + A = (x <- x / 2; x) + |} + record.A |> equal 5 + record.B |> equal 11 + record.C |> equal 6 + +[] +let ``test Recursive record does not cause issues`` () = + let r = { things = [ { things = [] } ] } + equal r.things.Length 1 + +[] +let ``test Record property access can be generated`` () = + let x = { name = "Alfonso"; luckyNumber = 7 } + equal "Alfonso" x.name + equal 7 x.luckyNumber + x.luckyNumber <- 14 + equal 14 x.luckyNumber + +[] +let ``test Record methods can be generated`` () = + let x = { name = "Alfonso"; luckyNumber = 54 } + equal 24 x.LuckyDay + x.SignDoc "Hello World!" + |> equal "Hello World! by Alfonso" + +[] +let ``test RRecord expression constructors can be generated`` () = + let x = { name = "Alfonso"; luckyNumber = 7 } + let y = { x with luckyNumber = 14 } + equal "Alfonso" y.name + equal 14 y.luckyNumber + +[] +let ``test Records with key/reserved words are mapped correctly`` () = + let x = { ``for`` = 1.0; ``class`` = 2.0 } + equal 2. x.``class`` + +[] +let ``test Records with special characters are mapped correctly`` () = + let x = { ``s p a c e`` = 1.0; ``s*y*m*b*o*l`` = 2.0 } + equal 1. x.``s p a c e`` + equal 2. x.``s*y*m*b*o*l`` + +[] +let ``test Mutating records work`` () = + let x = { uniqueA = 10; uniqueB = 20 } + equal 10 x.uniqueA + equal 20 x.uniqueB + let uniqueB' = -x.uniqueB + let x' = { x with uniqueB = uniqueB' } + equal 10 x.uniqueA + equal 10 x'.uniqueA + equal -20 x'.uniqueB + let x'' = { x' with uniqueA = -10 } + equal -10 x''.uniqueA + equal -20 x''.uniqueB diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index a1b8815ad5..ea6fb72883 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -78,3 +78,13 @@ let ``test string interpolation works with anonymous records`` () = Country = "The United Kingdom" |} $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" + +[] +let ``test sprintf \"%A\" with lists works`` () = + let xs = ["Hi"; "Hello"; "Hola"] + (sprintf "%A" xs).Replace("\"", "") |> equal "[Hi; Hello; Hola]" + +[] +let ``test sprintf \"%A\" with nested lists works`` () = + let xs = [["Hi"]; ["Hello"]; ["Hola"]] + (sprintf "%A" xs).Replace("\"", "") |> equal "[[Hi]; [Hello]; [Hola]]" From 34bef83e8ad2c293d0e222b9193e4f8f5291c33f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 2 Apr 2021 10:34:03 +0200 Subject: [PATCH 071/145] Fix nonlocal ordering issue --- src/Fable.Transforms/Python/Babel2Python.fs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 51e2ae8ed8..722d6ef7e8 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -333,7 +333,8 @@ module Util = | "%" -> Mod |> toBinOp | "**" -> Pow |> toBinOp | "<<" -> LShift |> toBinOp - | ">>" -> RShift |> toBinOp + | ">>" + | ">>>" -> RShift |> toBinOp | "|" -> BitOr |> toBinOp | "^" -> BitXor |> toBinOp | "&" -> BitAnd |> toBinOp @@ -803,7 +804,8 @@ module Util = : Python.Statement list = [ for stmt in block.Body do - yield! transformStatementAsStatements com ctx returnStrategy stmt ] + yield! transformStatementAsStatements com ctx returnStrategy stmt ] + |> List.sortBy (function | Statement.NonLocal _ -> 0 | _ -> 1) /// Transform Babel program to Python module. let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = From 9317fbf67ecd351f90d3f89b8c9c97ff1fafbca2 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 2 Apr 2021 13:29:10 +0200 Subject: [PATCH 072/145] Fix tests --- tests/Python/TestSeq.fs | 36 +++++++++++++++++++++++++++++++++ tests/Python/TestString.fs | 41 ++++++++++++++++++++++++++++++++++++++ tests/Python/Util.fs | 6 ++++++ 3 files changed, 83 insertions(+) diff --git a/tests/Python/TestSeq.fs b/tests/Python/TestSeq.fs index ce9b3974da..2eea20aaca 100644 --- a/tests/Python/TestSeq.fs +++ b/tests/Python/TestSeq.fs @@ -2,6 +2,12 @@ module Fable.Tests.Seqs open Util.Testing +let sumFirstTwo (zs: seq) = + let second = Seq.skip 1 zs |> Seq.head + let first = Seq.head zs + printfn "sumFirstTwo: %A" (first, second) + first + second + [] let ``test Seq.empty works`` () = @@ -39,3 +45,33 @@ let ``test Seq.collect works`` () = |> Seq.collect (fun a -> [a.Length]) |> List.ofSeq |> equal [1; 5; 3] + +[] +let ``test Seq.collect works II"`` () = + let xs = [[1.]; [2.]; [3.]; [4.]] + let ys = xs |> Seq.collect id + sumFirstTwo ys + |> equal 3. + + let xs1 = [[1.; 2.]; [3.]; [4.; 5.; 6.;]; [7.]] + let ys1 = xs1 |> Seq.collect id + sumFirstSeq ys1 5 + |> equal 15. + +[] +let ``test Seq.collect works with Options`` () = + let xss = [[Some 1; Some 2]; [None; Some 3]] + Seq.collect id xss + |> Seq.sumBy (function + | Some n -> n + | None -> 0 + ) + |> equal 6 + + seq { + for xs in xss do + for x in xs do + x + } + |> Seq.length + |> equal 4 diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index ea6fb72883..a7766f16e6 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -2,6 +2,14 @@ module Fable.Tests.String open Util.Testing +let containsInOrder (substrings: string list) (str: string) = + let mutable lastIndex = -1 + substrings |> List.forall (fun s -> + let i = str.IndexOf(s) + let success = i >= 0 && i > lastIndex + lastIndex <- i + success) + [] let ``test sprintf works`` () = // Immediately applied @@ -88,3 +96,36 @@ let ``test sprintf \"%A\" with lists works`` () = let ``test sprintf \"%A\" with nested lists works`` () = let xs = [["Hi"]; ["Hello"]; ["Hola"]] (sprintf "%A" xs).Replace("\"", "") |> equal "[[Hi]; [Hello]; [Hola]]" + +[] +let ``test sprintf \"%A\" with sequences works`` () = + let xs = seq { "Hi"; "Hello"; "Hola" } + sprintf "%A" xs |> containsInOrder ["Hi"; "Hello"; "Hola"] |> equal true + +// [] +// let ``test Storing result of Seq.tail and printing the result several times works. Related to #1996`` () = +// let tweets = seq { "Hi"; "Hello"; "Hola" } +// let tweetsTailR: seq = tweets |> Seq.tail + +// let a = sprintf "%A" (tweetsTailR) +// let b = sprintf "%A" (tweetsTailR) + +// containsInOrder ["Hello"; "Hola"] a |> equal true +// containsInOrder ["Hello"; "Hola"] b |> equal true + +// [] +// let ``test sprintf \"%X\" works`` () = +// //These should all be the Native JS Versions (except int64 / uint64) +// //See #1530 for more information. + +// sprintf "255: %X" 255 |> equal "255: FF" +// sprintf "255: %x" 255 |> equal "255: ff" +// sprintf "-255: %X" -255 |> equal "-255: FFFFFF01" +// sprintf "4095L: %X" 4095L |> equal "4095L: FFF" +// sprintf "-4095L: %X" -4095L |> equal "-4095L: FFFFFFFFFFFFF001" +// sprintf "1 <<< 31: %x" (1 <<< 31) |> equal "1 <<< 31: 80000000" +// sprintf "1u <<< 31: %x" (1u <<< 31) |> equal "1u <<< 31: 80000000" +// sprintf "2147483649L: %x" 2147483649L |> equal "2147483649L: 80000001" +// sprintf "2147483650uL: %x" 2147483650uL |> equal "2147483650uL: 80000002" +// sprintf "1L <<< 63: %x" (1L <<< 63) |> equal "1L <<< 63: 8000000000000000" +// sprintf "1uL <<< 63: %x" (1uL <<< 63) |> equal "1uL <<< 63: 8000000000000000" diff --git a/tests/Python/Util.fs b/tests/Python/Util.fs index 96a4ed3268..2f182f5cd5 100644 --- a/tests/Python/Util.fs +++ b/tests/Python/Util.fs @@ -24,3 +24,9 @@ module Testing = let equal<'T> (expected: 'T) (actual: 'T): unit = Assert.Equal(expected, actual) let notEqual<'T> (expected: 'T) (actual: 'T) : unit = Assert.NotEqual(expected, actual) #endif + + let rec sumFirstSeq (zs: seq) (n: int): float = + match n with + | 0 -> 0. + | 1 -> Seq.head zs + | _ -> (Seq.head zs) + sumFirstSeq (Seq.skip 1 zs) (n-1) From 677b4bd27bf6ff67c6865c0772d9c9bd42242841 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 2 Apr 2021 14:39:52 +0200 Subject: [PATCH 073/145] Set tests (wip) --- tests/Python/Fable.Tests.fsproj | 1 + tests/Python/TestSet.fs | 29 +++++++++++++++++++++++++++++ tests/Python/TestString.fs | 4 ++++ 3 files changed, 34 insertions(+) create mode 100644 tests/Python/TestSet.fs diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj index e5df49abdf..4e9f1c7c79 100644 --- a/tests/Python/Fable.Tests.fsproj +++ b/tests/Python/Fable.Tests.fsproj @@ -26,6 +26,7 @@ + diff --git a/tests/Python/TestSet.fs b/tests/Python/TestSet.fs new file mode 100644 index 0000000000..99ee2d39c2 --- /dev/null +++ b/tests/Python/TestSet.fs @@ -0,0 +1,29 @@ +module Fable.Tests.Set + +open Util.Testing + +// [] +// let ``test set function works`` () = +// let xs = set [1] +// xs |> Set.isEmpty +// |> equal false + +// [] +// let ``test Set.isEmpty works`` () = +// let xs = set [] +// Set.isEmpty xs |> equal true +// let ys = set [1] +// Set.isEmpty ys |> equal false + +// [] +// let ``test Set.IsEmpty works`` () = +// let xs = Set.empty +// xs.IsEmpty |> equal true +// let ys = set [1; 1] +// ys.IsEmpty |> equal false + +// [] +// let ``test Set.Count works`` () = +// let xs = Set.empty |> Set.add 1 +// xs.Count +// |> equal 1 diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index a7766f16e6..aa077b35db 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -87,6 +87,10 @@ let ``test string interpolation works with anonymous records`` () = $"Hi! My name is %s{person.Name} %s{person.Surname.ToUpper()}. I'm %i{person.Age} years old and I'm from %s{person.Country}!" |> equal "Hi! My name is John DOE. I'm 32 years old and I'm from The United Kingdom!" +[] +let ``test interpolated string with double % should be unescaped`` () = + $"{100}%%" |> equal "100%" + [] let ``test sprintf \"%A\" with lists works`` () = let xs = ["Hi"; "Hello"; "Hola"] From 30f9ba6401d93bde5d66ea6a15baceb7c247d9df Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 3 Apr 2021 08:24:14 +0200 Subject: [PATCH 074/145] Add language selector arg `--lang` since ts, js and py are exclusive --- build.fsx | 8 ++--- src/Fable.Cli/Entry.fs | 40 +++++++++++++++---------- src/Fable.Cli/Main.fs | 5 +--- src/Fable.Transforms/Global/Compiler.fs | 11 ++----- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/build.fsx b/build.fsx index 14db30fa3d..4fbb6cd085 100644 --- a/build.fsx +++ b/build.fsx @@ -160,7 +160,7 @@ let buildLibraryTs() = runFableWithArgs projectDir [ "--outDir " + buildDirTs "--fableLib " + buildDirTs - "--typescript" + "--lang TypeScript" "--exclude Fable.Core" "--define FX_NO_BIGINT" "--define FABLE_LIBRARY" @@ -370,7 +370,7 @@ let testPython() = runFableWithArgs projectDir [ "--outDir " + buildDir "--exclude Fable.Core" - "--python" + "--lang Python" ] runInDir buildDir "touch __init__.py" // So relative imports works. @@ -545,10 +545,10 @@ match argsLower with | "test-py"::_ -> testPython() | "quicktest"::_ -> buildLibraryIfNotExists() - run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --python --exclude Fable.Core --noCache --runScript" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --lang Python --exclude Fable.Core --noCache --runScript" | "jupyter" :: _ -> buildLibraryIfNotExists () - run "dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src --exclude Fable.Core --noCache --runScript --python" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src --lang Python --exclude Fable.Core --noCache" | "run"::_ -> buildLibraryIfNotExists() diff --git a/src/Fable.Cli/Entry.fs b/src/Fable.Cli/Entry.fs index bf0ef48c7b..2c927b1c35 100644 --- a/src/Fable.Cli/Entry.fs +++ b/src/Fable.Cli/Entry.fs @@ -74,8 +74,8 @@ Arguments: (Intended for plugin development) --optimize Compile with optimized F# AST (experimental) - --typescript Compile to TypeScript (experimental) - --python Compile to Python (experimental) + --lang|--language Compile to JavaScript (default), "TypeScript" or "Python". + Support for TypeScript and Python is experimental. Environment variables: DOTNET_USE_POLLING_FILE_WATCHER @@ -84,12 +84,24 @@ Arguments: Docker mounted volumes, and other virtual file systems. """ -let defaultFileExt isTypescript args = +let defaultFileExt language args = let fileExt = match argValueMulti ["-o"; "--outDir"] args with | Some _ -> ".js" | None -> CompilerOptionsHelper.DefaultExtension - if isTypescript then Path.replaceExtension ".ts" fileExt else fileExt + match language with + | TypeScript -> Path.replaceExtension ".ts" fileExt + | Python -> Path.replaceExtension ".py" fileExt + | _ -> fileExt + +let argLanguage args = + argValue "--lang" args + |> Option.orElse (argValue "--language" args) + |> Option.defaultValue "JavaScript" + |> (function + | "TypeScript" -> TypeScript + | "Python" -> Python + | _ -> JavaScript) type Runner = static member Run(args: string list, rootDir: string, runProc: RunProcess option, ?fsprojPath: string, ?watch, ?testInfo) = @@ -123,16 +135,16 @@ type Runner = // TODO: Remove this check when typed arrays are compatible with typescript |> Result.bind (fun projFile -> - let typescript = flagEnabled "--typescript" args - let python = flagEnabled "--python" args + let language = argLanguage args + let typedArrays = tryFlag "--typedArrays" args |> Option.defaultValue true - if typescript && typedArrays then + if language = TypeScript && typedArrays then Error("Typescript output is currently not compatible with typed arrays, pass: --typedArrays false") else - Ok(projFile, typescript, python, typedArrays) + Ok(projFile, language, typedArrays) ) - |> Result.bind (fun (projFile, typescript, python, typedArrays) -> + |> Result.bind (fun (projFile, language, typedArrays) -> let verbosity = if flagEnabled "--verbose" args then Log.makeVerbose() @@ -158,11 +170,10 @@ type Runner = let fileExt = argValueMulti ["-e"; "--extension"] args - |> Option.defaultValue (defaultFileExt typescript args) + |> Option.defaultValue (defaultFileExt language args) let compilerOptions = - CompilerOptionsHelper.Make(python = python, - typescript = typescript, + CompilerOptionsHelper.Make(language=language, typedArrays = typedArrays, fileExtension = fileExt, define = define, @@ -201,13 +212,12 @@ type Runner = let clean args dir = - let typescript = flagEnabled "--typescript" args - let python = flagEnabled "--python" args + let language = argLanguage args let ignoreDirs = set ["bin"; "obj"; "node_modules"] let fileExt = argValueMulti ["-e"; "--extension"] args - |> Option.defaultValue (defaultFileExt typescript args) + |> Option.defaultValue (defaultFileExt language args) let dir = argValueMulti ["-o"; "--outDir"] args diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index d10eb6def3..80143178f5 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -170,12 +170,9 @@ module private Util = let fileName = Naming.applyCaseRule Core.CaseRules.SnakeCase fileName // Note that Python modules cannot contain dots or it will be impossible to import them let targetPath = Path.Combine(targetDir, fileName + fileExt) - do printfn "TargetPath: %s" targetPath let stream = new IO.StreamWriter(targetPath) - do printfn $"PythonFileWriter: {sourcePath}, {targetPath}" - interface PythonPrinter.Writer with member _.Write(str) = stream.WriteAsync(str) |> Async.AwaitTask @@ -213,7 +210,7 @@ module private Util = do! Text.Json.JsonSerializer.SerializeAsync(sw, writer.SourceMap) |> Async.AwaitTask if com.Options.Language = Python then - printfn "Generating Python" + logger("Generating Python") let python = babel |> Babel2Python.Compiler.transformFile com let map = { new PythonPrinter.SourceMapGenerator with diff --git a/src/Fable.Transforms/Global/Compiler.fs b/src/Fable.Transforms/Global/Compiler.fs index 2d969bfccc..e662514794 100644 --- a/src/Fable.Transforms/Global/Compiler.fs +++ b/src/Fable.Transforms/Global/Compiler.fs @@ -5,9 +5,8 @@ module Literals = type CompilerOptionsHelper = static member DefaultExtension = ".fs.js" - static member Make(?python, + static member Make(?language, ?typedArrays, - ?typescript, ?define, ?optimizeFSharpAst, ?verbosity, @@ -15,16 +14,12 @@ type CompilerOptionsHelper = ?clampByteArrays) = let define = defaultArg define [] let isDebug = List.contains "DEBUG" define - let language = - let typescript = typescript |> Option.bind(fun ts -> if ts then Some TypeScript else None) - let python = python |> Option.bind(fun py -> if py then Some Python else None) + let language = language |> Option.defaultValue JavaScript - typescript |> Option.orElse python - printfn "Language: %A" language { new CompilerOptions with member _.Define = define member _.DebugMode = isDebug - member _.Language = defaultArg language JavaScript + member _.Language = language member _.TypedArrays = defaultArg typedArrays true member _.OptimizeFSharpAst = defaultArg optimizeFSharpAst false member _.Verbosity = defaultArg verbosity Verbosity.Normal From 044c11af8afcfbb673982fc705d3c3d17c127e4b Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 3 Apr 2021 08:30:39 +0200 Subject: [PATCH 075/145] Be a little forgiving about language selection --- src/Fable.Cli/Entry.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Fable.Cli/Entry.fs b/src/Fable.Cli/Entry.fs index 2c927b1c35..dbcf68cf55 100644 --- a/src/Fable.Cli/Entry.fs +++ b/src/Fable.Cli/Entry.fs @@ -99,8 +99,8 @@ let argLanguage args = |> Option.orElse (argValue "--language" args) |> Option.defaultValue "JavaScript" |> (function - | "TypeScript" -> TypeScript - | "Python" -> Python + | "ts" | "typescript" | "TypeScript" -> TypeScript + | "py" | "python" | "Python" -> Python | _ -> JavaScript) type Runner = From dc399fc544f4da524644579630ee49907ff32c50 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 3 Apr 2021 10:51:20 +0200 Subject: [PATCH 076/145] Better assignment handling. Handle len, str and iter for classes. --- src/Fable.Transforms/Python/Babel2Python.fs | 52 +++++++++++++-------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 722d6ef7e8..0bab957d17 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -228,7 +228,6 @@ module Util = match mber with | Babel.ClassMember.ClassMethod (kind, key, ``params``, body, computed, ``static``, ``abstract``, returnType, typeParameters, loc) -> let self = Arg.arg (Python.Identifier("self")) - let parms = ``params`` |> List.ofArray let args = @@ -249,26 +248,31 @@ module Util = let arguments = Arguments.arguments (args = self :: args, ?vararg = varargs) - match kind with - | "method" -> - let body = - com.TransformAsStatements(ctx, ReturnStrategy.Return, body |> Statement.BlockStatement) - + match kind, key with + | "method", _ -> + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) let name = match key with | Expression.Identifier (id) -> com.GetIdentifier(ctx, id.Name) | Expression.Literal(Literal.StringLiteral(StringLiteral(value=name))) -> com.GetIdentifier(ctx, name) + | MemberExpression(object=Expression.Identifier(Identifier(name="Symbol")); property=Expression.Identifier(Identifier(name="iterator"))) -> + com.GetIdentifier(ctx, "__iter__") | _ -> failwith $"transformAsClassDef: Unknown key: {key}" FunctionDef.Create(name, arguments, body = body) - | "constructor" -> + | "constructor", _ -> let name = Python.Identifier("__init__") - - let body = - com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body |> Statement.BlockStatement) - + let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) + FunctionDef.Create(name, arguments, body = body) + | "get", MemberExpression(object=Expression.Identifier(Identifier(name="Symbol")); property=Expression.Identifier(Identifier(name="toStringTag"))) -> + let name = Python.Identifier("__str__") + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) + FunctionDef.Create(name, arguments, body = body) + | "get", Expression.Identifier(Identifier(name="size")) -> + let name = Python.Identifier("__len__") + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) FunctionDef.Create(name, arguments, body = body) | _ -> failwith $"transformAsClassDef: Unknown kind: {kind}" | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] @@ -332,21 +336,19 @@ module Util = | "/" -> Div |> toBinOp | "%" -> Mod |> toBinOp | "**" -> Pow |> toBinOp - | "<<" -> LShift |> toBinOp - | ">>" - | ">>>" -> RShift |> toBinOp + | "<<<" | "<<" -> LShift |> toBinOp + | ">>" | ">>>" -> RShift |> toBinOp | "|" -> BitOr |> toBinOp | "^" -> BitXor |> toBinOp | "&" -> BitAnd |> toBinOp - | "===" - | "==" -> Eq |> toCompare - | "!==" - | "!=" -> NotEq |> toCompare + | "===" | "==" -> Eq |> toCompare + | "!==" | "!=" -> NotEq |> toCompare | ">" -> Gt |> toCompare | ">=" -> GtE |> toCompare | "<" -> Lt |> toCompare | "<=" -> LtE |> toCompare - | "isinstance" -> toCall "isinstance" + //| "isinstance" -> toCall "isinstance" + | "instanceof" -> toCall "isinstance" | _ -> failwith $"Unknown operator: {operator}" // Transform `~(~(a/b))` to `a // b` @@ -626,11 +628,21 @@ module Util = [] [ Expression.name (id = target, ctx = Store) ], stmts + // a.b = c | MemberExpression (property = Expression.Identifier (id); object = object) -> let attr = com.GetIdentifier(ctx, id.Name) - let value, stmts = com.TransformAsExpr(ctx, object) [ Expression.attribute (value = value, attr = attr, ctx = Store) ], stmts + // a.b[c] = d + | MemberExpression (property = Expression.Literal(NumericLiteral(value=value)); object = object) -> + let slice = Expression.constant(value) + let expr, stmts = com.TransformAsExpr(ctx, object) + [ Expression.subscript (value = expr, slice = slice, ctx = Store) ], stmts + // a[b] = + | MemberExpression (property = property; object = Expression.Identifier (id)) -> + let attr = com.GetIdentifier(ctx, id.Name) + let slice, stmts = com.TransformAsExpr(ctx, property) + [ Expression.subscript (value = Expression.name attr, slice = slice, ctx = Store) ], stmts | _ -> failwith $"AssignmentExpression, unknown expression: {left}" [ yield! stmts; yield! stmts2; Statement.assign (targets = targets, value = value) ] From 2d2fc0324e3543ec934e0ed2ab88022bd2086998 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 3 Apr 2021 11:26:11 +0200 Subject: [PATCH 077/145] Fix empty methods --- src/Fable.Transforms/Python/Babel2Python.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 0bab957d17..827da288be 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -818,6 +818,7 @@ module Util = [ for stmt in block.Body do yield! transformStatementAsStatements com ctx returnStrategy stmt ] |> List.sortBy (function | Statement.NonLocal _ -> 0 | _ -> 1) + |> transformBody returnStrategy /// Transform Babel program to Python module. let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = From 6e27f39b84c3c89e020d33cd81353be70d14b361 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 3 Apr 2021 11:34:21 +0200 Subject: [PATCH 078/145] Fix language handling --- src/fable-standalone/src/Main.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fable-standalone/src/Main.fs b/src/fable-standalone/src/Main.fs index 6a8aa050e4..a0f4440b39 100644 --- a/src/fable-standalone/src/Main.fs +++ b/src/fable-standalone/src/Main.fs @@ -1,6 +1,7 @@ module Fable.Standalone.Main open System +open Fable open Fable.AST open Fable.Transforms open Fable.Transforms.State @@ -265,7 +266,7 @@ let init () = if x.StartsWith("--define:") || x.StartsWith("-d:") then x.[(x.IndexOf(':') + 1)..] |> Some else None) |> Array.toList - let options = Fable.CompilerOptionsHelper.Make(define=define, ?typedArrays=typedArrays, ?typescript=typescript) + let options = Fable.CompilerOptionsHelper.Make(define=define, ?typedArrays=typedArrays, language=TypeScript) let com = CompilerImpl(fileName, project, options, fableLibrary) let ast = FSharp2Fable.Compiler.transformFile com From 8c82c80bb74fdc06b8644116ab499e4c1888b709 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 4 Apr 2021 08:31:39 +0200 Subject: [PATCH 079/145] Fixes - Sequence expressions - Various emits - For-loop downto - Identifier escaping - If-expr parenthesis --- src/Fable.Transforms/Python/Babel2Python.fs | 63 ++++++++++++-------- src/Fable.Transforms/Python/PythonPrinter.fs | 4 +- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 827da288be..7fcb9d6194 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -76,7 +76,7 @@ module Helpers = | "for" -> "for_" | "Math" -> "math" | _ -> - name.Replace('$', '_').Replace('.', '_') + name.Replace('$', '_').Replace('.', '_').Replace('`', '_') let rewriteFableImport moduleName = let _reFableLib = @@ -317,6 +317,14 @@ module Util = Expression.namedExpr (left, right), leftStmts @ rightStmts | _ -> failwith $"Unsuppored assingment expression: {operator}" + | BinaryExpression (left = left; operator = "=="; right = Literal(NullLiteral(_))) -> + let left, leftStmts = com.TransformAsExpr(ctx, left) + let right = Expression.name("None") + Expression.compare (left, [ Python.Is ], [ right ]), leftStmts + | BinaryExpression (left = left; operator = "!="; right = Literal(NullLiteral(_))) -> + let left, leftStmts = com.TransformAsExpr(ctx, left) + let right = Expression.name("None") + Expression.compare (left, [ Python.IsNot ], [ right ]), leftStmts | BinaryExpression (left = left; operator = operator; right = right) -> let left, leftStmts = com.TransformAsExpr(ctx, left) let right, rightStmts = com.TransformAsExpr(ctx, right) @@ -450,7 +458,6 @@ module Util = let key, stmts = com.TransformAsExpr(ctx, key) let name = Helpers.getUniqueIdentifier "lifted" - let func = com.TransformAsFunction(ctx, name.Name, parms, body) key, Expression.name (name), stmts @ [ func ] ] |> List.unzip3 @@ -476,7 +483,11 @@ module Util = match value with | "void $0" -> args.[0], stmts - //| "raise %0" -> Raise.Create() + | "throw $0" -> + let value = "raise $0" + Expression.emit (value, args), stmts + | Naming.StartsWith("new ") value -> + Expression.emit (value, args), stmts | _ -> Expression.emit (value, args), stmts // If computed is true, the node corresponds to a computed (a[b]) member expression and property is an Expression | MemberExpression (computed = true; object = object; property = Expression.Literal (literal)) -> @@ -572,7 +583,7 @@ module Util = // Return the last expression if i = exprs.Length - 1 then let expr, stmts = com.TransformAsExpr(ctx', ex) - [Statement.return' (expr)] + stmts @ [Statement.return' (expr)] else com.TransformAsStatements(ctx', ReturnStrategy.Return, ex)) |> List.collect id @@ -580,8 +591,7 @@ module Util = let name = Helpers.getUniqueIdentifier ("lifted") - let func = - FunctionDef.Create(name = name, args = Arguments.arguments [], body = body) + let func = FunctionDef.Create(name = name, args = Arguments.arguments [], body = body) let name = Expression.name (name) Expression.call (name), [ func ] @@ -629,20 +639,20 @@ module Util = [ Expression.name (id = target, ctx = Store) ], stmts // a.b = c - | MemberExpression (property = Expression.Identifier (id); object = object) -> + | MemberExpression (property = Expression.Identifier (id); object = object; computed=false) -> let attr = com.GetIdentifier(ctx, id.Name) let value, stmts = com.TransformAsExpr(ctx, object) [ Expression.attribute (value = value, attr = attr, ctx = Store) ], stmts // a.b[c] = d - | MemberExpression (property = Expression.Literal(NumericLiteral(value=value)); object = object) -> + | MemberExpression (property = Expression.Literal(NumericLiteral(value=value)); object = object; computed=false) -> let slice = Expression.constant(value) let expr, stmts = com.TransformAsExpr(ctx, object) [ Expression.subscript (value = expr, slice = slice, ctx = Store) ], stmts - // a[b] = - | MemberExpression (property = property; object = Expression.Identifier (id)) -> - let attr = com.GetIdentifier(ctx, id.Name) - let slice, stmts = com.TransformAsExpr(ctx, property) - [ Expression.subscript (value = Expression.name attr, slice = slice, ctx = Store) ], stmts + // object[property] = + | MemberExpression (property = property; object = object; computed=true) -> + let value, stmts = com.TransformAsExpr(ctx, object) + let index, stmts' = com.TransformAsExpr(ctx, property) + [ Expression.subscript (value = value, slice = index, ctx = Store) ], stmts @ stmts' | _ -> failwith $"AssignmentExpression, unknown expression: {left}" [ yield! stmts; yield! stmts2; Statement.assign (targets = targets, value = value) ] @@ -788,22 +798,27 @@ module Util = | Statement.Declaration (Declaration.ClassDeclaration (body, id, superClass, implements, superTypeParameters, typeParameters, loc)) -> transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc | Statement.ForStatement (init = Some (VariableDeclaration(declarations = [| VariableDeclarator (id = id; init = Some (init)) |])) - test = Some (Expression.BinaryExpression (left = left; right = right; operator = "<=")) + test = Some (Expression.BinaryExpression (left = left; right = right; operator = operator)) + update = Some (Expression.UpdateExpression (operator=update)) body = body) -> - let body = - com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) - - let target = Expression.name (Python.Identifier id.Name) + let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) let start, stmts1 = com.TransformAsExpr(ctx, init) let stop, stmts2 = com.TransformAsExpr(ctx, right) - let stop = Expression.binOp (stop, Add, Expression.constant (1)) // Python `range` has exclusive end. - let iter = - Expression.call (Expression.name (Python.Identifier "range"), args = [ start; stop ]) + let stop, step = + match operator, update with + | "<=", "++" -> + let stop = Expression.binOp (stop, Add, Expression.constant (1)) // Python `range` has exclusive end. + stop, 1 + | ">=", "--" -> stop, -1 + | _ -> failwith $"Unhandled for loop with operator {operator} and update {update}" + + let target = Expression.name (Python.Identifier id.Name) + let step = Expression.constant(step) + + let iter = Expression.call (Expression.name (Python.Identifier "range"), args = [ start; stop; step ]) - stmts1 - @ stmts2 - @ [ Statement.for' (target = target, iter = iter, body = body) ] + stmts1 @ stmts2 @ [ Statement.for' (target = target, iter = iter, body = body) ] | LabeledStatement (body = body) -> com.TransformAsStatements(ctx, returnStrategy, body) | ContinueStatement (_) -> [ Continue ] | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index c3b779ab39..bd127977ea 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -431,9 +431,9 @@ module PrinterExtensions = member printer.Print(node: IfExp) = printer.Print(node.Body) printer.Print(" if ") - printer.Print(node.Test) + printer.WithParens (node.Test) printer.Print(" else ") - printer.Print(node.OrElse) + printer.WithParens(node.OrElse) member printer.Print(node: Lambda) = printer.Print("lambda") From f0c66b3fd24582123f482baef4305a2a7079a40d Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 4 Apr 2021 10:55:06 +0200 Subject: [PATCH 080/145] Move Python fable-library to Fable So we can mixin the library .fs files --- build.fsx | 22 +- src/Fable.Cli/Main.fs | 25 +- src/fable-library-py/.flake8 | 3 + src/fable-library-py/README.md | 4 + .../fable/Fable.Library.fsproj | 38 ++ src/fable-library-py/fable/__init__.py | 3 + src/fable-library-py/fable/array.py | 3 + src/fable-library-py/fable/asyncbuilder.py | 66 +++ src/fable-library-py/fable/asyncio.py | 25 + src/fable-library-py/fable/int32.py | 29 + src/fable-library-py/fable/list.py | 39 ++ src/fable-library-py/fable/long.py | 6 + src/fable-library-py/fable/numeric.py | 28 + src/fable-library-py/fable/reflection.py | 101 ++++ src/fable-library-py/fable/seq.py | 60 ++ src/fable-library-py/fable/string.py | 518 ++++++++++++++++++ src/fable-library-py/fable/types.py | 161 ++++++ src/fable-library-py/fable/util.py | 79 +++ src/fable-library-py/requirements.txt | 4 + src/fable-library-py/setup.cfg | 6 + src/fable-library-py/setup.py | 43 ++ tests/Python/Fable.Tests.fsproj | 2 + tests/Python/TestLoops.fs | 35 ++ tests/Python/TestSudoku.fs | 99 ++++ tests/Python/TestUnionType.fs | 89 +++ 25 files changed, 1475 insertions(+), 13 deletions(-) create mode 100644 src/fable-library-py/.flake8 create mode 100644 src/fable-library-py/README.md create mode 100644 src/fable-library-py/fable/Fable.Library.fsproj create mode 100644 src/fable-library-py/fable/__init__.py create mode 100644 src/fable-library-py/fable/array.py create mode 100644 src/fable-library-py/fable/asyncbuilder.py create mode 100644 src/fable-library-py/fable/asyncio.py create mode 100644 src/fable-library-py/fable/int32.py create mode 100644 src/fable-library-py/fable/list.py create mode 100644 src/fable-library-py/fable/long.py create mode 100644 src/fable-library-py/fable/numeric.py create mode 100644 src/fable-library-py/fable/reflection.py create mode 100644 src/fable-library-py/fable/seq.py create mode 100644 src/fable-library-py/fable/string.py create mode 100644 src/fable-library-py/fable/types.py create mode 100644 src/fable-library-py/fable/util.py create mode 100644 src/fable-library-py/requirements.txt create mode 100644 src/fable-library-py/setup.cfg create mode 100644 src/fable-library-py/setup.py create mode 100644 tests/Python/TestLoops.fs create mode 100644 tests/Python/TestSudoku.fs create mode 100644 tests/Python/TestUnionType.fs diff --git a/build.fsx b/build.fsx index 4fbb6cd085..dfd148eb15 100644 --- a/build.fsx +++ b/build.fsx @@ -170,6 +170,25 @@ let buildLibraryTs() = runInDir buildDirTs "npm run tsc -- --init --target es2020 --module es2020 --allowJs" runInDir buildDirTs ("npm run tsc -- --outDir ../../" + buildDirJs) +let buildLibraryPy() = + let libraryDir = "src/fable-library-py" + let projectDir = libraryDir + "/fable" + let buildDirPy = "build/fable-library-py" + + cleanDirs [buildDirPy] + + runFableWithArgs projectDir [ + "--outDir " + buildDirPy + "--fableLib " + buildDirPy + "/fable" + "--lang Python" + "--exclude Fable.Core" + ] + // Copy *.py from projectDir to buildDir + runInDir libraryDir ("cp -R * ../../" + buildDirPy) + runInDir buildDirPy ("cp fable-library/*.py fable/") + runInDir buildDirPy ("python3 --version") + runInDir buildDirPy ("python3 ./setup.py develop") + // Like testJs() but doesn't create bundles/packages for fable-standalone & friends // Mainly intended for CI let testJsFast() = @@ -360,7 +379,7 @@ let test() = testJsFast() let testPython() = - buildLibraryIfNotExists() + buildLibraryIfNotExists() // NOTE: fable-library-py needs to be built seperatly. let projectDir = "tests/Python" let buildDir = "build/tests/Python" @@ -567,6 +586,7 @@ match argsLower with | ("watch-library")::_ -> watchLibrary() | ("fable-library"|"library")::_ -> buildLibrary() | ("fable-library-ts"|"library-ts")::_ -> buildLibraryTs() +| ("fable-library-py"|"library-py")::_ -> buildLibraryPy() | ("fable-compiler-js"|"compiler-js")::_ -> buildCompilerJs(minify) | ("fable-standalone"|"standalone")::_ -> buildStandalone {|minify=minify; watch=false|} | "watch-standalone"::_ -> buildStandalone {|minify=false; watch=true|} diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index 80143178f5..243b4b05da 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -198,18 +198,19 @@ module private Util = let dir = IO.Path.GetDirectoryName outPath if not (IO.Directory.Exists dir) then IO.Directory.CreateDirectory dir |> ignore - // write output to file - let writer = new FileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) - do! BabelPrinter.run writer babel - - // write source map to file - if cliArgs.SourceMaps then - let mapPath = outPath + ".map" - do! IO.File.AppendAllLinesAsync(outPath, [$"//# sourceMappingURL={IO.Path.GetFileName(mapPath)}"]) |> Async.AwaitTask - use sw = IO.File.Open(mapPath, IO.FileMode.Create) - do! Text.Json.JsonSerializer.SerializeAsync(sw, writer.SourceMap) |> Async.AwaitTask - - if com.Options.Language = Python then + match com.Options.Language with + | JavaScript | TypeScript -> + // write output to file + let writer = new FileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) + do! BabelPrinter.run writer babel + + // write source map to file + if cliArgs.SourceMaps then + let mapPath = outPath + ".map" + do! IO.File.AppendAllLinesAsync(outPath, [$"//# sourceMappingURL={IO.Path.GetFileName(mapPath)}"]) |> Async.AwaitTask + use sw = IO.File.Open(mapPath, IO.FileMode.Create) + do! Text.Json.JsonSerializer.SerializeAsync(sw, writer.SourceMap) |> Async.AwaitTask + | Python -> logger("Generating Python") let python = babel |> Babel2Python.Compiler.transformFile com diff --git a/src/fable-library-py/.flake8 b/src/fable-library-py/.flake8 new file mode 100644 index 0000000000..28a7341c5c --- /dev/null +++ b/src/fable-library-py/.flake8 @@ -0,0 +1,3 @@ +[flake8] +ignore = E731, T484, T400 # Do not assign a lambda expression, use a def +max-line-length = 121 \ No newline at end of file diff --git a/src/fable-library-py/README.md b/src/fable-library-py/README.md new file mode 100644 index 0000000000..e5a1cf1443 --- /dev/null +++ b/src/fable-library-py/README.md @@ -0,0 +1,4 @@ +# Fable Library for Python + +This module is used as the [Fable](https://fable.io/) library for +Python. \ No newline at end of file diff --git a/src/fable-library-py/fable/Fable.Library.fsproj b/src/fable-library-py/fable/Fable.Library.fsproj new file mode 100644 index 0000000000..a4f814ed92 --- /dev/null +++ b/src/fable-library-py/fable/Fable.Library.fsproj @@ -0,0 +1,38 @@ + + + + netstandard2.0 + $(DefineConstants);FABLE_COMPILER + $(DefineConstants);FX_NO_BIGINT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/fable-library-py/fable/__init__.py b/src/fable-library-py/fable/__init__.py new file mode 100644 index 0000000000..e9c63feb70 --- /dev/null +++ b/src/fable-library-py/fable/__init__.py @@ -0,0 +1,3 @@ +from . import string, types, list + +__all__ = ["list", "string", "types"] diff --git a/src/fable-library-py/fable/array.py b/src/fable-library-py/fable/array.py new file mode 100644 index 0000000000..e9902cf94e --- /dev/null +++ b/src/fable-library-py/fable/array.py @@ -0,0 +1,3 @@ +import builtins + +map = builtins.map diff --git a/src/fable-library-py/fable/asyncbuilder.py b/src/fable-library-py/fable/asyncbuilder.py new file mode 100644 index 0000000000..38841e0b73 --- /dev/null +++ b/src/fable-library-py/fable/asyncbuilder.py @@ -0,0 +1,66 @@ +from typing import Any, Callable, Optional, TypeVar, Awaitable +from expression.core.aiotools import from_result + +T = TypeVar("T") +U = TypeVar("U") + + +class AsyncBuilder: + def Bind(self, computation: Awaitable[T], binder: Callable[[T], Awaitable[U]]) -> Awaitable[U]: + async def bind() -> U: + t = await computation + return await binder(t) + + return bind() + + def Combine(self, computation1: Awaitable[None], computation2: Awaitable[T]) -> Awaitable[T]: + return self.Bind(computation1, lambda _: computation2) + + def Delay(self, generator: Callable[[], Awaitable[T]]) -> Awaitable[T]: + async def deferred() -> T: + return await generator() + + return deferred() + + def Return(self, value: Optional[T] = None) -> Awaitable[Optional[T]]: + return from_result(value) + + def ReturnFrom(self, computation: Awaitable[T]) -> Awaitable[T]: + return computation + + def TryFinally(self, computation: Awaitable[T], compensation: Callable[[], None]) -> Awaitable[T]: + async def try_finally() -> T: + try: + t = await computation + finally: + compensation() + return t + + return try_finally() + + def TryWith(self, computation: Awaitable[T], catchHandler: Callable[[Any], Awaitable[T]]) -> Awaitable[T]: + async def try_with() -> T: + try: + t = await computation + except Exception as exn: + t = await catchHandler(exn) + return t + + return try_with() + + def Using(self, resource: T, binder: Callable[[T], Awaitable[U]]) -> Awaitable[U]: + return self.TryFinally(binder(resource), lambda: resource.Dispose()) + + def While(self, guard: Callable[[], bool], computation: Awaitable[None]) -> Awaitable[None]: + if guard(): + return self.Bind(computation, lambda _: self.While(guard, computation)) + else: + return self.Return() + + def Zero(self) -> Awaitable[None]: + return from_result(None) + + +singleton = AsyncBuilder() + +__all__ = ["singleton"] diff --git a/src/fable-library-py/fable/asyncio.py b/src/fable-library-py/fable/asyncio.py new file mode 100644 index 0000000000..2cdd147fd8 --- /dev/null +++ b/src/fable-library-py/fable/asyncio.py @@ -0,0 +1,25 @@ +from expression.core import aiotools +from expression.system import OperationCanceledError, CancellationToken + +# class Trampoline: +# maxTrampolineCallCount = 2000 + +# def __init__(self) -> None: +# self.callCount = 0 + +# def incrementAndCheck(self): +# self.callCount += 1 +# return self.callCount > Trampoline.maxTrampolineCallCount + +# def hijack(self, f: Callable[[], None]): +# self.callCount = 0 +# setTimeout(f, 0) +# asyncio.e + +Continuation = aiotools.Continuation + +sleep = aiotools.sleep +start = aiotools.start +runSynchronously = aiotools.run_synchronously + +__all__ = ["sleep", "start", "runSynchronously"] diff --git a/src/fable-library-py/fable/int32.py b/src/fable-library-py/fable/int32.py new file mode 100644 index 0000000000..3747305946 --- /dev/null +++ b/src/fable-library-py/fable/int32.py @@ -0,0 +1,29 @@ +def parse(string: str, style, unsigned, bitsize, radix) -> int: + return int(string) + # const res = isValid(str, style, radix); + # if (res != null) { + # let v = Number.parseInt(res.sign + res.digits, res.radix); + # if (!Number.isNaN(v)) { + # const [umin, umax] = getRange(true, bitsize); + # if (!unsigned && res.radix !== 10 && v >= umin && v <= umax) { + # v = v << (32 - bitsize) >> (32 - bitsize); + # } + # const [min, max] = getRange(unsigned, bitsize); + # if (v >= min && v <= max) { + # return v; + # } + # } + # } + # throw new Error("Input string was not in a correct format."); + + +def op_UnaryNegation_Int8(x): + return x if x == -128 else -x + + +def op_UnaryNegation_Int16(x): + return x if x == -32768 else -x + + +def op_UnaryNegation_Int32(x): + return x if x == -2147483648 else -x diff --git a/src/fable-library-py/fable/list.py b/src/fable-library-py/fable/list.py new file mode 100644 index 0000000000..8cdf4dbb64 --- /dev/null +++ b/src/fable-library-py/fable/list.py @@ -0,0 +1,39 @@ +# flake8: noqa + +from typing import Any, Callable, TypeVar + +from expression.collections import FrozenList + +A = TypeVar("A") +B = TypeVar("B") + + +def collect(mapper: Callable[[A], FrozenList[B]], lst: FrozenList[A]) -> FrozenList[B]: + return lst.collect(mapper) + + +def empty() -> FrozenList[Any]: + return FrozenList.empty() + + +def filter(predicate: Callable[[A], bool], lst: FrozenList[A]) -> FrozenList[A]: + return lst.filter(predicate) + + +def forAll(predicate, source): + return source.forall(predicate) + + +def length(xs): + return len(xs) + + +def map(mapper: Callable[[A], B], lst: FrozenList[A]) -> FrozenList[B]: + return lst.map(mapper) + + +ofArray = FrozenList.of_seq +ofSeq = FrozenList.of_seq +singleton = FrozenList.singleton + +__all__ = ["collect", "empty", "forAll", "length", "map", "ofArray", "ofSeq", "singleton"] diff --git a/src/fable-library-py/fable/long.py b/src/fable-library-py/fable/long.py new file mode 100644 index 0000000000..0c880440b5 --- /dev/null +++ b/src/fable-library-py/fable/long.py @@ -0,0 +1,6 @@ +def fromBits(lowBits: int, highBits: int, unsigned: bool): + return lowBits + (highBits << 32) + + +def op_LeftShift(self, numBits): + return self << numBits diff --git a/src/fable-library-py/fable/numeric.py b/src/fable-library-py/fable/numeric.py new file mode 100644 index 0000000000..2bb1db73f9 --- /dev/null +++ b/src/fable-library-py/fable/numeric.py @@ -0,0 +1,28 @@ +from abc import ABC, abstractmethod +from typing import Any, Optional, Union + +from .util import IComparable + + +def to_fixed(x: float, dp: Optional[int] = None) -> str: + if dp is not None: + fmt = "{:.%sf}" % dp + return fmt.format(x) + + return "{}".format(x) + + +def to_precision(x: float, sd: Optional[int] = None): + if sd is not None: + fmt = "{:.%se}" % sd + return fmt.format(x) + + return "{}".format(x) + + +def to_exponential(x: float, dp: Optional[int] = None): + if dp is not None: + fmt = "{:.%se}" % dp + return fmt.format(x) + + return "{}".format(x) diff --git a/src/fable-library-py/fable/reflection.py b/src/fable-library-py/fable/reflection.py new file mode 100644 index 0000000000..4b74f2fc16 --- /dev/null +++ b/src/fable-library-py/fable/reflection.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Callable, List, Optional, Type, Union + +from .types import Union as FsUnion + +Constructor = Callable[..., Any] + +EnumCase = Union[str, int] +FieldInfo = Union[str, "TypeInfo"] + + +@dataclass +class CaseInfo: + declaringType: TypeInfo + tag: int + name: str + fields: List[FieldInfo] + + +@dataclass +class TypeInfo: + fullname: str + generics: Optional[List[TypeInfo]] = None + construct: Optional[Constructor] = None + parent: Optional[TypeInfo] = None + fields: Optional[Callable[[], List[FieldInfo]]] = None + cases: Optional[Callable[[], List[CaseInfo]]] = None + enum_cases: Optional[List[EnumCase]] = None + + def __str__(self) -> str: + return full_name(self) + + +def class_type( + fullname: str, + generics: Optional[List[TypeInfo]] = None, + construct: Optional[Constructor] = None, + parent: Optional[TypeInfo] = None, +) -> TypeInfo: + return TypeInfo(fullname, generics, construct, parent) + + +def union_type( + fullname: str, + generics: List[TypeInfo], + construct: Type[FsUnion], + cases: Callable[[], List[List[FieldInfo]]], +) -> TypeInfo: + def fn() -> List[CaseInfo]: + nonlocal construct + + caseNames: List[str] = construct.cases() + mapper: Callable[[int, List[FieldInfo]], CaseInfo] = lambda i, fields: CaseInfo(t, i, caseNames[i], fields) + return [mapper(i, x) for i, x in enumerate(cases())] + + t: TypeInfo = TypeInfo(fullname, generics, construct, None, None, fn, None) + return t + + +def record_type( + fullname: str, generics: List[TypeInfo], construct: Constructor, fields: Callable[[], List[FieldInfo]] +) -> TypeInfo: + return TypeInfo(fullname, generics, construct, fields=fields) + + +def full_name(t: TypeInfo) -> str: + gen = t.generics if t.generics is not None else [] + if len(gen): + return t.fullname + "[" + ",".join(map(full_name, gen)) + "]" + + return t.fullname + + +def list_type(generic: TypeInfo) -> TypeInfo: + return TypeInfo("Microsoft.FSharp.Collections.FSharpList`1", [generic]) + + +def array_type(generic: TypeInfo) -> TypeInfo: + return TypeInfo(generic.fullname + "[]", [generic]) + + +def tuple_type(*generics: TypeInfo) -> TypeInfo: + return TypeInfo(fullname=f"System.Tuple`{len(generics)}", generics=list(generics)) + + +obj_type: TypeInfo = TypeInfo(fullname="System.Object") +unit_type: TypeInfo = TypeInfo("Microsoft.FSharp.Core.Unit") +char_type: TypeInfo = TypeInfo("System.Char") +string_type: TypeInfo = TypeInfo("System.String") +bool_type: TypeInfo = TypeInfo("System.Boolean") +int8_type: TypeInfo = TypeInfo("System.SByte") +uint8_type: TypeInfo = TypeInfo("System.Byte") +int16_type: TypeInfo = TypeInfo("System.Int16") +uint16_type: TypeInfo = TypeInfo("System.UInt16") +int32_type: TypeInfo = TypeInfo("System.Int32") +uint32_type: TypeInfo = TypeInfo("System.UInt32") +float32_type: TypeInfo = TypeInfo("System.Single") +float64_type: TypeInfo = TypeInfo("System.Double") +decimal_type: TypeInfo = TypeInfo("System.Decimal") diff --git a/src/fable-library-py/fable/seq.py b/src/fable-library-py/fable/seq.py new file mode 100644 index 0000000000..3064d2b8ac --- /dev/null +++ b/src/fable-library-py/fable/seq.py @@ -0,0 +1,60 @@ +from typing import Callable, Iterable, TypeVar +from expression.collections import Seq, seq, frozenlist + +A = TypeVar("A") +B = TypeVar("B") + + +def map(mapper: Callable[[A], B], xs: Seq[A]) -> Seq[B]: + return Seq(xs).map(mapper) + + +def filter(predicate: Callable[[A], bool], xs: Seq[A]) -> Seq[A]: + return Seq(xs).filter(predicate) + + +def length(xs): + return Seq(xs).length() + + +def empty(): + return seq.empty + + +def collect(mapper: Callable[[A], Seq[B]], source: Seq[A]) -> Seq[B]: + return Seq(source).collect(mapper) + + +def skip(count: int, xs: Seq[A]) -> Seq[A]: + return Seq(xs).skip(count) + + +def sum(source: Iterable[A]) -> A: + return Seq(source).sum() + + +def sumBy(projection: Callable[[A], B], source: Iterable[A], _) -> B: + return Seq(source).sum_by(projection) + + +delay = seq.delay +head = seq.head +rangeNumber = seq.range +singleton = seq.singleton +append = seq.concat +ofList = seq.of_list +toList = frozenlist.of_seq +concat = seq.concat +tail = seq.tail + +__all__ = [ + "delay", + "empty", + "head", + "map", + "length", + "rangeNumber", + "singleton", + "skip", + "tail", +] diff --git a/src/fable-library-py/fable/string.py b/src/fable-library-py/fable/string.py new file mode 100644 index 0000000000..4697ffddb9 --- /dev/null +++ b/src/fable-library-py/fable/string.py @@ -0,0 +1,518 @@ +import re +from abc import ABC +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Callable, Match, Optional, Pattern, Union, cast + +# import multiply +# import Numeric +# import toExponential +# import toFixed +# import toHex +# import toPrecision +# import "./Date.js" +# import "./Numeric.js" +# import "./RegExp.js" +# import "./Types.js" +from .numeric import to_fixed, to_precision, to_exponential +from .types import toString + +# import { escape } +# import { toString as dateToString } +# import { toString } + +fsFormatRegExp: Pattern[str] = re.compile(r"(^|[^%])%([0+\- ]*)(\d+)?(?:\.(\d+))?(\w)") +interpolateRegExp: Pattern[str] = re.compile(r"(?:(^|[^%])%([0+\- ]*)(\d+)?(?:\.(\d+))?(\w))?%P\(\)") +formatRegExp: Pattern[str] = re.compile(r"\{(\d+)(,-?\d+)?(?:\:([a-zA-Z])(\d{0,2})|\:(.+?))?\}") + + +# const enum StringComparison { +# CurrentCulture = 0, +# CurrentCultureIgnoreCase = 1, +# InvariantCulture = 2, +# InvariantCultureIgnoreCase = 3, +# Ordinal = 4, +# OrdinalIgnoreCase = 5, +# } + +# function cmp(x: string, y: string, ic: boolean | StringComparison) { +# function isIgnoreCase(i: boolean | StringComparison) { +# return i === true || +# i === StringComparison.CurrentCultureIgnoreCase || +# i === StringComparison.InvariantCultureIgnoreCase || +# i === StringComparison.OrdinalIgnoreCase; +# } +# function isOrdinal(i: boolean | StringComparison) { +# return i === StringComparison.Ordinal || +# i === StringComparison.OrdinalIgnoreCase; +# } +# if (x == null) { return y == null ? 0 : -1; } +# if (y == null) { return 1; } // everything is bigger than null + +# if (isOrdinal(ic)) { +# if (isIgnoreCase(ic)) { x = x.toLowerCase(); y = y.toLowerCase(); } +# return (x === y) ? 0 : (x < y ? -1 : 1); +# } else { +# if (isIgnoreCase(ic)) { x = x.toLocaleLowerCase(); y = y.toLocaleLowerCase(); } +# return x.localeCompare(y); +# } +# } + +# export function compare(...args: any[]): number { +# switch (args.length) { +# case 2: return cmp(args[0], args[1], false); +# case 3: return cmp(args[0], args[1], args[2]); +# case 4: return cmp(args[0], args[1], args[2] === true); +# case 5: return cmp(args[0].substr(args[1], args[4]), args[2].substr(args[3], args[4]), false); +# case 6: return cmp(args[0].substr(args[1], args[4]), args[2].substr(args[3], args[4]), args[5]); +# case 7: return cmp(args[0].substr(args[1], args[4]), args[2].substr(args[3], args[4]), args[5] === true); +# default: throw new Error("String.compare: Unsupported number of parameters"); +# } +# } + +# export function compareOrdinal(x: string, y: string) { +# return cmp(x, y, StringComparison.Ordinal); +# } + +# export function compareTo(x: string, y: string) { +# return cmp(x, y, StringComparison.CurrentCulture); +# } + +# export function startsWith(str: string, pattern: string, ic: number) { +# if (str.length >= pattern.length) { +# return cmp(str.substr(0, pattern.length), pattern, ic) === 0; +# } +# return false; +# } + +# export function indexOfAny(str: string, anyOf: string[], ...args: number[]) { +# if (str == null || str === "") { +# return -1; +# } +# const startIndex = (args.length > 0) ? args[0] : 0; +# if (startIndex < 0) { +# throw new Error("Start index cannot be negative"); +# } +# const length = (args.length > 1) ? args[1] : str.length - startIndex; +# if (length < 0) { +# throw new Error("Length cannot be negative"); +# } +# if (length > str.length - startIndex) { +# throw new Error("Invalid startIndex and length"); +# } +# str = str.substr(startIndex, length); +# for (const c of anyOf) { +# const index = str.indexOf(c); +# if (index > -1) { +# return index + startIndex; +# } +# } +# return -1; +# } + +IPrintfFormatContinuation = Callable[[Callable[[str], Any]], Callable[[str], Any]] + + +@dataclass +class IPrintfFormat(ABC): + input: str + cont: IPrintfFormatContinuation + + +def printf(input: str) -> IPrintfFormat: + # print("printf: ", input) + format: IPrintfFormatContinuation = fsFormat(input) + return IPrintfFormat(input=input, cont=format) + + +def continuePrint(cont: Callable[[str], Any], arg: Union[IPrintfFormat, str]) -> Union[Any, Callable[[str], Any]]: + # print("continuePrint", cont) + if isinstance(arg, str): + return cont(arg) + + return arg.cont(cont) + + +def toConsole(arg: Union[IPrintfFormat, str]) -> Union[Any, Callable[[str], Any]]: + return continuePrint(print, arg) + + +# export function toConsoleError(arg: IPrintfFormat | string) { +# return continuePrint((x: string) => console.error(x), arg); +# } + + +def toText(arg: Union[IPrintfFormat, str]) -> str: + cont: Callable[[str], Any] = lambda x: x + return cast(str, continuePrint(cont, arg)) + + +def toFail(arg: Union[IPrintfFormat, str]): + def fail(msg: str): + raise Exception(msg) + + return continuePrint(fail, arg) + + +def formatReplacement(rep: Any, flags: Any, padLength: Any, precision: Any, format: Any): + print("Got here", rep, format) + sign = "" + flags = flags or "" + format = format or "" + + if isinstance(rep, (int, float)): + if format.lower() != "x": + if rep < 0: + rep = rep * -1 + sign = "-" + else: + if flags.find(" ") >= 0: + sign = " " + elif flags.find("+") >= 0: + sign = "+" + + elif format == "x": + rep = "{0:x}".format(rep) + elif format == "X": + rep = "{0:x}".format(rep).upper() + + precision = None if precision is None else int(precision) + if format in ("f", "F"): + precision = precision if precision is not None else 6 + rep = to_fixed(rep, precision) + elif format in ("g", "G"): + rep = to_precision(rep, precision) if precision is not None else to_precision(rep) + elif format in ("e", "E"): + rep = to_exponential(rep, precision) if precision is not None else to_exponential(rep) + else: # AOid + rep = str(rep) + + elif isinstance(rep, datetime): + rep = dateToString(rep) + else: + rep = toString(rep) + + if padLength is not None: + padLength = int(padLength) + zeroFlag = flags.find("0") >= 0 # Use '0' for left padding + minusFlag = flags.find("-") >= 0 # Right padding + ch = minusFlag or " " if not zeroFlag else "0" + if ch == "0": + rep = padLeft(rep, padLength - len(sign), ch, minusFlag) + rep = sign + rep + else: + rep = padLeft(sign + rep, padLength, ch, minusFlag) + + else: + rep = sign + rep + + return rep + + +def interpolate(string: str, values: Any) -> str: + valIdx = 0 + strIdx = 0 + result = "" + matches = interpolateRegExp.finditer(string) + for match in matches: + # The first group corresponds to the no-escape char (^|[^%]), the actual pattern starts in the next char + # Note: we don't use negative lookbehind because some browsers don't support it yet + matchIndex = match.start() + len(match[1] or "") + result += string[strIdx:matchIndex].replace("%%", "%") + [_, flags, padLength, precision, format] = match.groups() + # print(match.groups()) + result += formatReplacement(values[valIdx], flags, padLength, precision, format) + valIdx += 1 + + strIdx = match.end() + + result += string[strIdx:].replace("%%", "%") + return result + + +def formatOnce(str2: str, rep: Any): + # print("formatOnce: ", str2, rep) + + def match(m: Match[str]): + prefix, flags, padLength, precision, format = m.groups() + # print("prefix: ", [prefix]) + once: str = formatReplacement(rep, flags, padLength, precision, format) + # print("once:", [once]) + return prefix + once.replace("%", "%%") + + ret = fsFormatRegExp.sub(match, str2, count=1) + # print(ret) + return ret + + +def createPrinter(string: str, cont: Callable[..., Any]): + # print("createPrinter", string) + + def _(*args: Any): + strCopy: str = string + for arg in args: + # print("Arg: ", [arg]) + strCopy = formatOnce(strCopy, arg) + # print("strCopy", strCopy) + + # print("strCopy", strCopy) + if fsFormatRegExp.search(strCopy): + return createPrinter(strCopy, cont) + return cont(strCopy.replace("%%", "%")) + + return _ + + +def fsFormat(str: str): + # print("fsFormat: ", [str]) + + def _(cont: Callable[..., Any]): + if fsFormatRegExp.search(str): + return createPrinter(str, cont) + return cont(str) + + return _ + + +# export function format(str: string, ...args: any[]) { +# if (typeof str === "object" && args.length > 0) { +# // Called with culture info +# str = args[0]; +# args.shift(); +# } + +# return str.replace(formatRegExp, (_, idx, padLength, format, precision, pattern) => { +# let rep = args[idx]; +# if (is_numeric(rep)) { +# precision = precision == null ? null : parseInt(precision, 10); +# switch (format) { +# case "f": case "F": +# precision = precision != null ? precision : 2; +# rep = toFixed(rep, precision); +# break; +# case "g": case "G": +# rep = precision != null ? toPrecision(rep, precision) : toPrecision(rep); +# break; +# case "e": case "E": +# rep = precision != null ? toExponential(rep, precision) : toExponential(rep); +# break; +# case "p": case "P": +# precision = precision != null ? precision : 2; +# rep = toFixed(multiply(rep, 100), precision) + " %"; +# break; +# case "d": case "D": +# rep = precision != null ? padLeft(String(rep), precision, "0") : String(rep); +# break; +# case "x": case "X": +# rep = precision != null ? padLeft(toHex(rep), precision, "0") : toHex(rep); +# if (format === "X") { rep = rep.toUpperCase(); } +# break; +# default: +# if (pattern) { +# let sign = ""; +# rep = (pattern as string).replace(/(0+)(\.0+)?/, (_, intPart, decimalPart) => { +# if (isLessThan(rep, 0)) { +# rep = multiply(rep, -1); +# sign = "-"; +# } +# rep = toFixed(rep, decimalPart != null ? decimalPart.length - 1 : 0); +# return padLeft(rep, (intPart || "").length - sign.length + (decimalPart != null ? decimalPart.length : 0), "0"); +# }); +# rep = sign + rep; +# } +# } +# } else if (rep instanceof Date) { +# rep = dateToString(rep, pattern || format); +# } else { +# rep = toString(rep) +# } +# padLength = parseInt((padLength || " ").substring(1), 10); +# if (!isNaN(padLength)) { +# rep = padLeft(String(rep), Math.abs(padLength), " ", padLength < 0); +# } +# return rep; +# }); +# } + +# export function endsWith(str: string, search: string) { +# const idx = str.lastIndexOf(search); +# return idx >= 0 && idx === str.length - search.length; +# } + + +def initialize(n: int, f: Callable[[int], str]) -> str: + if n < 0: + raise Exception("String length must be non-negative") + + xs = [""] * n + for i in range(n): + xs[i] = f(i) + + return "".join(xs) + + +# export function insert(str: string, startIndex: number, value: string) { +# if (startIndex < 0 || startIndex > str.length) { +# throw new Error("startIndex is negative or greater than the length of this instance."); +# } +# return str.substring(0, startIndex) + value + str.substring(startIndex); +# } + + +def isNullOrEmpty(string: Optional[str]): + not isinstance(string, str) or not len(string) + + +# export function isNullOrWhiteSpace(str: string | any) { +# return typeof str !== "string" || /^\s*$/.test(str); +# } + +# export function concat(...xs: any[]): string { +# return xs.map((x) => String(x)).join(""); +# } + +# export function join(delimiter: string, xs: Iterable): string { +# if (Array.isArray(xs)) { +# return xs.join(delimiter); +# } else { +# return Array.from(xs).join(delimiter); +# } +# } + +# export function joinWithIndices(delimiter: string, xs: string[], startIndex: number, count: number) { +# const endIndexPlusOne = startIndex + count; +# if (endIndexPlusOne > xs.length) { +# throw new Error("Index and count must refer to a location within the buffer."); +# } +# return xs.slice(startIndex, endIndexPlusOne).join(delimiter); +# } + +# function notSupported(name: string): never { +# throw new Error("The environment doesn't support '" + name + "', please use a polyfill."); +# } + +# export function toBase64String(inArray: number[]) { +# let str = ""; +# for (let i = 0; i < inArray.length; i++) { +# str += String.fromCharCode(inArray[i]); +# } +# return typeof btoa === "function" ? btoa(str) : notSupported("btoa"); +# } + +# export function fromBase64String(b64Encoded: string) { +# const binary = typeof atob === "function" ? atob(b64Encoded) : notSupported("atob"); +# const bytes = new Uint8Array(binary.length); +# for (let i = 0; i < binary.length; i++) { +# bytes[i] = binary.charCodeAt(i); +# } +# return bytes; +# } + +# export function padLeft(str: string, len: number, ch?: string, isRight?: boolean) { +# ch = ch || " "; +# len = len - str.length; +# for (let i = 0; i < len; i++) { +# str = isRight ? str + ch : ch + str; +# } +# return str; +# } + +# export function padRight(str: string, len: number, ch?: string) { +# return padLeft(str, len, ch, true); +# } + +# export function remove(str: string, startIndex: number, count?: number) { +# if (startIndex >= str.length) { +# throw new Error("startIndex must be less than length of string"); +# } +# if (typeof count === "number" && (startIndex + count) > str.length) { +# throw new Error("Index and count must refer to a location within the string."); +# } +# return str.slice(0, startIndex) + (typeof count === "number" ? str.substr(startIndex + count) : ""); +# } + + +def replace(string: str, search: str, replace: str): + return re.sub(search, replace, string) + + +def replicate(n: int, x: str): + return initialize(n, lambda: x) + + +# export function getCharAtIndex(input: string, index: number) { +# if (index < 0 || index >= input.length) { +# throw new Error("Index was outside the bounds of the array."); +# } +# return input[index]; +# } + +# export function split(str: string, splitters: string[], count?: number, removeEmpty?: number) { +# count = typeof count === "number" ? count : undefined; +# removeEmpty = typeof removeEmpty === "number" ? removeEmpty : undefined; +# if (count && count < 0) { +# throw new Error("Count cannot be less than zero"); +# } +# if (count === 0) { +# return []; +# } +# if (!Array.isArray(splitters)) { +# if (removeEmpty === 0) { +# return str.split(splitters, count); +# } +# const len = arguments.length; +# splitters = Array(len - 1); +# for (let key = 1; key < len; key++) { +# splitters[key - 1] = arguments[key]; +# } +# } +# splitters = splitters.map((x) => escape(x)); +# splitters = splitters.length > 0 ? splitters : [" "]; +# let i = 0; +# const splits: string[] = []; +# const reg = new RegExp(splitters.join("|"), "g"); +# while (count == null || count > 1) { +# const m = reg.exec(str); +# if (m === null) { break; } +# if (!removeEmpty || (m.index - i) > 0) { +# count = count != null ? count - 1 : count; +# splits.push(str.substring(i, m.index)); +# } +# i = reg.lastIndex; +# } +# if (!removeEmpty || (str.length - i) > 0) { +# splits.push(str.substring(i)); +# } +# return splits; +# } + +# export function trim(str: string, ...chars: string[]) { +# if (chars.length === 0) { +# return str.trim(); +# } +# const pattern = "[" + escape(chars.join("")) + "]+"; +# return str.replace(new RegExp("^" + pattern), "").replace(new RegExp(pattern + "$"), ""); +# } + +# export function trimStart(str: string, ...chars: string[]) { +# return chars.length === 0 +# ? (str as any).trimStart() +# : str.replace(new RegExp("^[" + escape(chars.join("")) + "]+"), ""); +# } + +# export function trimEnd(str: string, ...chars: string[]) { +# return chars.length === 0 +# ? (str as any).trimEnd() +# : str.replace(new RegExp("[" + escape(chars.join("")) + "]+$"), ""); +# } + +# export function filter(pred: (char: string) => boolean, x: string) { +# return x.split("").filter((c) => pred(c)).join(""); +# } + +# export function substring(str: string, startIndex: number, length?: number) { +# if ((startIndex + (length || 0) > str.length)) { +# throw new Error("Invalid startIndex and/or length"); +# } +# return length != null ? str.substr(startIndex, length) : str.substr(startIndex); +# } diff --git a/src/fable-library-py/fable/types.py b/src/fable-library-py/fable/types.py new file mode 100644 index 0000000000..f46c6f59f2 --- /dev/null +++ b/src/fable-library-py/fable/types.py @@ -0,0 +1,161 @@ +from __future__ import annotations + +from abc import abstractstaticmethod +from typing import Any, Iterable, List + +from .util import IComparable + + +class Union(IComparable["Union"]): + def __init__(self): + self.tag: int + self.fields: List[int] = [] + + @abstractstaticmethod + def cases() -> List[str]: + ... + + @property + def name(self) -> str: + return self.cases()[self.tag] + + def to_JSON(self) -> str: + raise NotImplementedError + # return str([self.name] + self.fields) if len(self.fields) else self.name + + def __str__(self) -> str: + if not len(self.fields): + return self.name + + fields = "" + with_parens = True + if len(self.fields) == 1: + field = str(self.fields[0]) + with_parens = field.find(" ") >= 0 + fields = field + else: + fields = ", ".join(map(str, self.fields)) + + return self.name + (" (" if with_parens else " ") + fields + (")" if with_parens else "") + + def __hash__(self) -> int: + hashes = map(hash, self.fields) + return hash([hash(self.tag), *hashes]) + + def __eq__(self, other: Any) -> bool: + if self is other: + return True + if not isinstance(other, Union): + return False + + if self.tag == other.tag: + return self.fields == other.fields + + return False + + def __lt__(self, other: Any) -> bool: + if self.tag == other.tag: + return self.fields < other.fields + + return self.tag < other.tag + + +def recordEquals(self, other): + if self is other: + return True + + else: + for name in self.keys(): + if self[name] != other.get(name): + return False + + return True + + +def recordCompareTo(self, other): + if self is other: + return 0 + + else: + for name in self.keys(): + if self[name] < other.get(name): + return -1 + elif self[name] > other.get(name): + return 1 + + return 0 + + +def recordGetHashCode(self): + return hash(*self.values()) + + +class Record(IComparable["Record"]): + def toJSON(self) -> str: + return recordToJSON(this) + + def toString(self) -> str: + return recordToString(self) + + def GetHashCode(self) -> int: + return recordGetHashCode(self) + + def Equals(self, other: Record) -> bool: + return recordEquals(self, other) + + def CompareTo(self, other: Record) -> int: + return recordCompareTo(self, other) + + def __lt__(self, other: Any) -> bool: + raise NotImplementedError + + def __eq__(self, other: Any) -> bool: + return recordEquals(self, other) + + def __hash__(self) -> int: + return recordGetHashCode(self) + + +class Attribute: + pass + + +def seqToString(self): + str = "[" + + for count, x in enumerate(self): + if count == 0: + str += toString(x) + + elif count == 100: + str += "; ..." + break + + else: + str += "; " + toString(x) + + return str + "]" + + +def toString(x, callStack=0): + if x is not None: + # if (typeof x.toString === "function") { + # return x.toString(); + + if isinstance(x, str): + return str(x) + + if isinstance(x, Iterable): + return seqToString(x) + + # else: // TODO: Date? + # const cons = Object.getPrototypeOf(x).constructor; + # return cons === Object && callStack < 10 + # // Same format as recordToString + # ? "{ " + Object.entries(x).map(([k, v]) => k + " = " + toString(v, callStack + 1)).join("\n ") + " }" + # : cons.name; + + return str(x) + + +__all__ = ["Attribute", "Union"] diff --git a/src/fable-library-py/fable/util.py b/src/fable-library-py/fable/util.py new file mode 100644 index 0000000000..afd1825e6e --- /dev/null +++ b/src/fable-library-py/fable/util.py @@ -0,0 +1,79 @@ +from abc import ABC, abstractmethod +from typing import Callable, Generic, Iterable, Tuple, TypeVar, Any, Optional + +T = TypeVar("T") + + +class IEquatable(Generic[T], ABC): + def GetHashCode(self) -> int: + return self.GetHashCode() + + def Equals(self, other: T) -> bool: + return self.Equals(other) + + @abstractmethod + def __eq__(self, other: Any) -> bool: + return NotImplemented + + @abstractmethod + def __hash__(self) -> int: + raise NotImplementedError + + +class IComparable(IEquatable[T]): + def CompareTo(self, other: T) -> int: + if self < other: + return -1 + elif self == other: + return 0 + return 1 + + @abstractmethod + def __lt__(self, other: Any) -> bool: + raise NotImplementedError + + +def equals(a, b): + return a == b + + +def assertEqual(actual: T, expected: T, msg: Optional[str] = None) -> None: + if actual != expected: + raise Exception(msg or f"Expected: ${expected} - Actual: ${actual}") + + +def assertNotEqual(actual: T, expected: T, msg: Optional[str] = None) -> None: + if actual == expected: + raise Exception(msg or f"Expected: ${expected} - Actual: ${actual}") + + +def createAtom(value: Optional[T] = None) -> Callable[[Optional[T], Optional[bool]], Optional[T]]: + atom = value + + def _(value: Optional[T] = None, isSetter: Optional[bool] = None) -> Optional[T]: + nonlocal atom + + if not isSetter: + return atom + else: + atom = value + return None + + return _ + + +def createObj(fields: Iterable[Tuple[str, Any]]): + obj: Any = {} + + for k, v in fields: + obj[k] = v + + return obj + + +def int32ToString(i: int, radix: int = 10): + convertString = "0123456789ABCDEF" + if i < radix: + return convertString[i] + else: + return int32ToString(i // radix, radix) + convertString[i % radix] diff --git a/src/fable-library-py/requirements.txt b/src/fable-library-py/requirements.txt new file mode 100644 index 0000000000..0a991ee23b --- /dev/null +++ b/src/fable-library-py/requirements.txt @@ -0,0 +1,4 @@ +expression +pytest +pytest-cov +pytest-asyncio diff --git a/src/fable-library-py/setup.cfg b/src/fable-library-py/setup.cfg new file mode 100644 index 0000000000..2f50783781 --- /dev/null +++ b/src/fable-library-py/setup.cfg @@ -0,0 +1,6 @@ + +[aliases] +test = pytest + +[tool:pytest] +testpaths = tests \ No newline at end of file diff --git a/src/fable-library-py/setup.py b/src/fable-library-py/setup.py new file mode 100644 index 0000000000..89353b603b --- /dev/null +++ b/src/fable-library-py/setup.py @@ -0,0 +1,43 @@ +# coding=utf-8 +# read the contents of your README file +from os import path + +import setuptools + +this_directory = path.abspath(path.dirname(__file__)) +with open(path.join(this_directory, "README.md"), encoding="utf-8") as f: + long_description = f.read() + +setuptools.setup( + name="fable-library", + version="0.1.0", + description="Fable library for Python", + long_description=long_description, + long_description_content_type="text/markdown", + author="Dag Brattli", + author_email="dag@brattli.net", + license="MIT License", + url="https://github.com/fable-compiler/Fable", + download_url="https://github.com/fable-compiler/Fable", + zip_safe=True, + # https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + "Development Status :: 3 - Alpha", + # 'Development Status :: 4 - Beta', + # 'Development Status :: 5 - Production/Stable', + "Environment :: Other Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.8", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + python_requires=">=3.8", + install_requires=["expression"], + setup_requires=["pytest-runner"], + tests_require=["pytest", "pytest-cov", "pytest-asyncio"], + package_data={"fable": ["py.typed"]}, + packages=setuptools.find_packages(), + package_dir={"fable": "fable"}, + include_package_data=True, +) diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj index 4e9f1c7c79..5f532aed36 100644 --- a/tests/Python/Fable.Tests.fsproj +++ b/tests/Python/Fable.Tests.fsproj @@ -27,6 +27,8 @@ + + diff --git a/tests/Python/TestLoops.fs b/tests/Python/TestLoops.fs new file mode 100644 index 0000000000..2e2d153e25 --- /dev/null +++ b/tests/Python/TestLoops.fs @@ -0,0 +1,35 @@ +module Fable.Tests.Loops + +open Util.Testing + + +[] +let ``test For-loop upto works`` () = + let mutable result = 0 + + for i = 0 to 10 do + result <- result + i + done + + result + |> equal 55 + +[] +let ``test For-loop upto minus one works`` () = + let mutable result = 0 + + for i = 0 to 10 - 1 do + result <- result + i + done + + result + |> equal 45 + +[] +let ``test For-loop downto works`` () = + let mutable result = 0 + for i = 10 downto 0 do + result <- result + i + + result + |> equal 55 diff --git a/tests/Python/TestSudoku.fs b/tests/Python/TestSudoku.fs new file mode 100644 index 0000000000..aeb4cccc1c --- /dev/null +++ b/tests/Python/TestSudoku.fs @@ -0,0 +1,99 @@ + +/// This module tests a couple of collection functions in a more complex setting. +module Fable.Tests.Sudoku + +open System.Collections.Generic +open Util.Testing + +type Box = int +type Sudoku = Box array array + +let rows = id +let cols (sudoku:Sudoku) = + sudoku + |> Array.mapi (fun a row -> row |> Array.mapi (fun b cell -> sudoku.[b].[a])) + +let getBoxIndex count row col = + let n = row/count + let m = col/count + n * count + m + +let boxes (sudoku:Sudoku) = + let d = sudoku |> Array.length |> float |> System.Math.Sqrt |> int + let list = new List<_>() + for a in 0..(d*d) - 1 do list.Add(new List<_>()) + + for a in 0..(Array.length sudoku - 1) do + for b in 0..(Array.length sudoku - 1) do + list.[getBoxIndex d a b].Add(sudoku.[a].[b]) + + list + |> Seq.map Seq.toArray + +let toSudoku x : Sudoku = + x + |> Seq.map Seq.toArray + |> Seq.toArray + +let allUnique numbers = + let set = new HashSet<_>() + numbers + |> Seq.filter ((<>) 0) + |> Seq.forall set.Add + +let solvable sudoku = + rows sudoku + |> Seq.append (cols sudoku) + |> Seq.append (boxes sudoku) + |> Seq.forall allUnique + +let replaceAtPos (x:Sudoku) row col newValue :Sudoku = + [| for a in 0..(Array.length x - 1) -> + [| for b in 0..(Array.length x - 1) -> + if a = row && b = col then newValue else x.[a].[b] |] |] + +let rec substitute row col (x:Sudoku) = + let a,b = if col >= Array.length x then row+1,0 else row,col + if a >= Array.length x then seq { yield x } else + if x.[a].[b] = 0 then + [1..Array.length x] + |> Seq.map (replaceAtPos x a b) + |> Seq.filter solvable + |> Seq.map (substitute a (b+1)) + |> Seq.concat + else substitute a (b+1) x + +let getFirstSolution = substitute 0 0 >> Seq.head + +//[] +let testsSudoku () = + let solution = + [[0; 0; 8; 3; 0; 0; 6; 0; 0] + [0; 0; 4; 0; 0; 0; 0; 1; 0] + [6; 7; 0; 0; 8; 0; 0; 0; 0] + + [0; 1; 6; 4; 3; 0; 0; 0; 0] + [0; 0; 0; 7; 9; 0; 0; 2; 0] + [0; 9; 0; 0; 0; 0; 4; 0; 1] + + [0; 0; 0; 9; 1; 0; 0; 0; 5] + [0; 0; 3; 0; 5; 0; 0; 0; 2] + [0; 5; 0; 0; 0; 0; 0; 7; 4]] + |> toSudoku + |> getFirstSolution + + let expectedSolution = + [[1; 2; 8; 3; 4; 5; 6; 9; 7] + [5; 3; 4; 6; 7; 9; 2; 1; 8] + [6; 7; 9; 1; 8; 2; 5; 4; 3] + + [2; 1; 6; 4; 3; 8; 7; 5; 9] + [4; 8; 5; 7; 9; 1; 3; 2; 6] + [3; 9; 7; 5; 2; 6; 4; 8; 1] + + [7; 6; 2; 9; 1; 4; 8; 3; 5] + [9; 4; 3; 8; 5; 7; 1; 6; 2] + [8; 5; 1; 2; 6; 3; 9; 7; 4]] + |> toSudoku + + solution = expectedSolution |> equal true diff --git a/tests/Python/TestUnionType.fs b/tests/Python/TestUnionType.fs new file mode 100644 index 0000000000..6d462a81de --- /dev/null +++ b/tests/Python/TestUnionType.fs @@ -0,0 +1,89 @@ +module Fable.Tests.UnionTypes + +open Util.Testing + +type Gender = Male | Female + +type Either<'TL,'TR> = + | Left of 'TL + | Right of 'TR + member x.AsString() = + match x with + | Left y -> y.ToString() + | Right y -> y.ToString() + +// type TestUnion = +// | Case0 +// | Case1 of string +// | Case2 of string * string +// | Case3 of string * string * string + + +// type TestUnion2 = +// | Tag of string +// | NewTag of string + +// let (|Functional|NonFunctional|) (s: string) = +// match s with +// | "fsharp" | "haskell" | "ocaml" -> Functional +// | _ -> NonFunctional + +// let (|Small|Medium|Large|) i = +// if i < 3 then Small 5 +// elif i >= 3 && i < 6 then Medium "foo" +// else Large + +// let (|FSharp|_|) (document : string) = +// if document = "fsharp" then Some FSharp else None + +// let (|A|) n = n + +// type JsonTypeInner = { +// Prop1: string +// Prop2: int +// } + +// type JsonTestUnion = +// | IntType of int +// | StringType of string +// | TupleType of string * int +// | ObjectType of JsonTypeInner + +// type Tree = +// | Leaf of int +// | Branch of Tree[] +// member this.Sum() = +// match this with +// | Leaf i -> i +// | Branch trees -> trees |> Seq.map (fun x -> x.Sum()) |> Seq.sum + +type MyExUnion = MyExUnionCase of exn + +// type Wrapper(s: string) = +// member x.Value = s |> Seq.rev |> Seq.map string |> String.concat "" + +// [] +// type MyUnion = +// | Case1 +// | Case2 +// | Case3 + +// type R = { +// Name: string +// Case: MyUnion +// } + +#if FABLE_COMPILER +open Fable.Core + +[] +#endif +type DU = Int of int | Str of string + +[] +let ``test Union cases matches with no arguments can be generated`` () = + let x = Male + match x with + | Female -> true + | Male -> false + |> equal false From 17e703b2fd689035e48df45b1ecade6567e0b94f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 4 Apr 2021 11:17:30 +0200 Subject: [PATCH 081/145] Add ts option compatibility --- src/Fable.Cli/Entry.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Fable.Cli/Entry.fs b/src/Fable.Cli/Entry.fs index dbcf68cf55..d24ed650c7 100644 --- a/src/Fable.Cli/Entry.fs +++ b/src/Fable.Cli/Entry.fs @@ -97,6 +97,7 @@ let defaultFileExt language args = let argLanguage args = argValue "--lang" args |> Option.orElse (argValue "--language" args) + |> Option.orElse (argValue "--typescript" args) // Compatibility |> Option.defaultValue "JavaScript" |> (function | "ts" | "typescript" | "TypeScript" -> TypeScript From db90eea9b9464f993b08a819d1e935cee3b1c915 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 4 Apr 2021 17:31:22 +0200 Subject: [PATCH 082/145] Add option tests --- src/Fable.Cli/Entry.fs | 2 +- src/fable-library-py/fable/option.py | 14 ++++++++++ tests/Python/Fable.Tests.fsproj | 1 + tests/Python/TestOption.fs | 41 ++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/fable-library-py/fable/option.py create mode 100644 tests/Python/TestOption.fs diff --git a/src/Fable.Cli/Entry.fs b/src/Fable.Cli/Entry.fs index d24ed650c7..c2389dacca 100644 --- a/src/Fable.Cli/Entry.fs +++ b/src/Fable.Cli/Entry.fs @@ -97,7 +97,7 @@ let defaultFileExt language args = let argLanguage args = argValue "--lang" args |> Option.orElse (argValue "--language" args) - |> Option.orElse (argValue "--typescript" args) // Compatibility + |> Option.orElse (tryFlag "--typescript" args |> Option.map (fun _ -> "typescript"))// Compatibility with "--typescript". |> Option.defaultValue "JavaScript" |> (function | "ts" | "typescript" | "TypeScript" -> TypeScript diff --git a/src/fable-library-py/fable/option.py b/src/fable-library-py/fable/option.py new file mode 100644 index 0000000000..3e810e500a --- /dev/null +++ b/src/fable-library-py/fable/option.py @@ -0,0 +1,14 @@ +from expression.core import option + + +def defaultArg(value, default_value): + return option.default_arg(option.of_optional(value), default_value) + + +def defaultArgWith(value, default_value): + return option.default_arg(option.of_optional(value), default_value()) + + +__all__ = [ + "defaultArg", +] diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj index 5f532aed36..9b5b56f5c3 100644 --- a/tests/Python/Fable.Tests.fsproj +++ b/tests/Python/Fable.Tests.fsproj @@ -27,6 +27,7 @@ + diff --git a/tests/Python/TestOption.fs b/tests/Python/TestOption.fs new file mode 100644 index 0000000000..8b25dae2e9 --- /dev/null +++ b/tests/Python/TestOption.fs @@ -0,0 +1,41 @@ +module Fable.Tests.Option + +open Util.Testing + + +[] +let ``test defaultArg works`` () = + let f o = defaultArg o 5 + f (Some 2) |> equal 2 + f None |> equal 5 + +let ``test Option.defaultValue works`` () = + let a = Some "MyValue" + let b = None + + a |> Option.defaultValue "" |> equal "MyValue" + b |> Option.defaultValue "default" |> equal "default" + +let ``test Option.defaultValue works II`` () = + Some 5 |> Option.defaultValue 4 |> equal 5 + None |> Option.defaultValue "foo" |> equal "foo" + +let ``test Option.orElse works`` () = + Some 5 |> Option.orElse (Some 4) |> equal (Some 5) + None |> Option.orElse (Some "foo") |> equal (Some "foo") + +let ``test Option.defaultWith works`` () = + Some 5 |> Option.defaultWith (fun () -> 4) |> equal 5 + None |> Option.defaultWith (fun () -> "foo") |> equal "foo" + +let ``test Option.orElseWith works`` () = + Some 5 |> Option.orElseWith (fun () -> Some 4) |> equal (Some 5) + None |> Option.orElseWith (fun () -> Some "foo") |> equal (Some "foo") + +let ``test Option.isSome/isNone works`` () = + let o1 = None + let o2 = Some 5 + Option.isNone o1 |> equal true + Option.isSome o1 |> equal false + Option.isNone o2 |> equal false + Option.isSome o2 |> equal true From a156a62431412faa237031b15ab9e42a2ce31476 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 4 Apr 2021 19:27:26 +0200 Subject: [PATCH 083/145] Fix option test --- src/Fable.Transforms/Python/Babel2Python.fs | 7 +++-- src/fable-library-py/fable/option.py | 11 +++++++ tests/Python/TestOption.fs | 32 +++++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 7fcb9d6194..dbb43838b1 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -709,15 +709,16 @@ module Util = com.TransformAsStatements(ctx, returnStrategy, consequent) |> transformBody ReturnStrategy.NoReturn - let orElse = + let orElse, nonLocals = match alternate with | Some alt -> com.TransformAsStatements(ctx, returnStrategy, alt) |> transformBody ReturnStrategy.NoReturn + |> List.splitWhile (function | Statement.NonLocal (_) -> false | _ -> true ) - | _ -> [] + | _ -> [], [] - [ yield! stmts; Statement.if' (test = test, body = body, orelse = orElse) ] + [ yield! nonLocals @ stmts; Statement.if' (test = test, body = body, orelse = orElse) ] | WhileStatement (test = test; body = body) -> let expr, stmts = com.TransformAsExpr(ctx, test) diff --git a/src/fable-library-py/fable/option.py b/src/fable-library-py/fable/option.py index 3e810e500a..5216db7eb3 100644 --- a/src/fable-library-py/fable/option.py +++ b/src/fable-library-py/fable/option.py @@ -9,6 +9,17 @@ def defaultArgWith(value, default_value): return option.default_arg(option.of_optional(value), default_value()) +def map(mapping, value): + return option.of_optional(value).map(mapping).default_value(None) + + +def toArray(value): + return option.of_optional(value).to_list() + + __all__ = [ "defaultArg", + "defaultArgWith", + "map", + "toArray" ] diff --git a/tests/Python/TestOption.fs b/tests/Python/TestOption.fs index 8b25dae2e9..7cb19c91c9 100644 --- a/tests/Python/TestOption.fs +++ b/tests/Python/TestOption.fs @@ -9,6 +9,7 @@ let ``test defaultArg works`` () = f (Some 2) |> equal 2 f None |> equal 5 +[] let ``test Option.defaultValue works`` () = let a = Some "MyValue" let b = None @@ -16,22 +17,27 @@ let ``test Option.defaultValue works`` () = a |> Option.defaultValue "" |> equal "MyValue" b |> Option.defaultValue "default" |> equal "default" +[] let ``test Option.defaultValue works II`` () = Some 5 |> Option.defaultValue 4 |> equal 5 None |> Option.defaultValue "foo" |> equal "foo" +[] let ``test Option.orElse works`` () = Some 5 |> Option.orElse (Some 4) |> equal (Some 5) None |> Option.orElse (Some "foo") |> equal (Some "foo") +[] let ``test Option.defaultWith works`` () = Some 5 |> Option.defaultWith (fun () -> 4) |> equal 5 None |> Option.defaultWith (fun () -> "foo") |> equal "foo" +[] let ``test Option.orElseWith works`` () = Some 5 |> Option.orElseWith (fun () -> Some 4) |> equal (Some 5) None |> Option.orElseWith (fun () -> Some "foo") |> equal (Some "foo") +[] let ``test Option.isSome/isNone works`` () = let o1 = None let o2 = Some 5 @@ -39,3 +45,29 @@ let ``test Option.isSome/isNone works`` () = Option.isSome o1 |> equal false Option.isNone o2 |> equal false Option.isSome o2 |> equal true + +[] +let ``test Option.IsSome/IsNone works II`` () = + let o1 = None + let o2 = Some 5 + o1.IsNone |> equal true + o1.IsSome |> equal false + o2.IsNone |> equal false + o2.IsSome |> equal true + +// [] +// let ``test Option.iter works`` () = +// let mutable res = false +// let getOnlyOnce = +// let mutable value = Some "Hello" +// fun () -> match value with Some x -> value <- None; Some x | None -> None +// getOnlyOnce() |> Option.iter (fun s -> if s = "Hello" then res <- true) +// equal true res + +[] +let ``test Option.map works`` () = + let getOnlyOnce = + let mutable value = Some "Alfonso" + fun () -> match value with Some x -> value <- None; Some x | None -> None + getOnlyOnce() |> Option.map ((+) "Hello ") |> equal (Some "Hello Alfonso") + getOnlyOnce() |> Option.map ((+) "Hello ") |> equal None From 7cf882123b8a9047b102d94d12842e12d282496b Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 5 Apr 2021 15:30:35 +0200 Subject: [PATCH 084/145] Update fable-library for Python --- build.fsx | 2 +- src/Fable.Transforms/Python/Babel2Python.fs | 28 +++++++++++-------- .../fable/Fable.Library.fsproj | 2 +- src/fable-library-py/fable/reflection.py | 11 ++++++++ src/fable-library-py/fable/string.py | 2 +- src/fable-library-py/fable/types.py | 3 +- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/build.fsx b/build.fsx index dfd148eb15..fb955c4509 100644 --- a/build.fsx +++ b/build.fsx @@ -567,7 +567,7 @@ match argsLower with run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --lang Python --exclude Fable.Core --noCache --runScript" | "jupyter" :: _ -> buildLibraryIfNotExists () - run "dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src --lang Python --exclude Fable.Core --noCache" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src --lang Python --exclude Fable.Core --noCache 2>> /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src/fable.out" | "run"::_ -> buildLibraryIfNotExists() diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index dbb43838b1..ab74756775 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -75,12 +75,14 @@ module Helpers = | "class" -> "class_" | "for" -> "for_" | "Math" -> "math" + | "Error" -> "Exception" | _ -> name.Replace('$', '_').Replace('.', '_').Replace('`', '_') let rewriteFableImport moduleName = + //printfn "ModuleName: %s" moduleName let _reFableLib = - Regex(".*\/fable-library[\.0-9]*\/(?[^\/]*)\.js", RegexOptions.Compiled) + Regex(".*\/fable-library.*\/(?[^\/]*)\.js", RegexOptions.Compiled) let m = _reFableLib.Match(moduleName) @@ -106,7 +108,7 @@ module Helpers = /// left in the AST and we need to skip them since they are not valid for Python (either). let isProductiveStatement (stmt: Python.Statement) = let rec hasNoSideEffects (e: Python.Expression) = - printfn $"hasNoSideEffects: {e}" + //printfn $"hasNoSideEffects: {e}" match e with | Constant _ -> true @@ -210,7 +212,7 @@ module Util = (typeParameters: Babel.TypeParameterDeclaration option) (loc: SourceLocation option) : Python.Statement list = - printfn $"transformAsClassDef" + //printfn $"transformAsClassDef" let bases, stmts = let entries = @@ -297,7 +299,10 @@ module Util = Arg.arg (ident)) let arguments = Arguments.arguments (args = args) - let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope; NonLocals = HashSet () }} + let enclosingScope = HashSet() + enclosingScope.UnionWith(ctx.UsedNames.EnclosingScope) + enclosingScope.UnionWith(ctx.UsedNames.LocalScope) + let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = enclosingScope; NonLocals = HashSet () }} let body = com.TransformAsStatements(ctx', ReturnStrategy.Return, body) let name = com.GetIdentifier(ctx, name) @@ -305,7 +310,7 @@ module Util = /// Transform Babel expression as Python expression let rec transformAsExpr (com: IPythonCompiler) (ctx: Context) (expr: Babel.Expression): Python.Expression * list = - printfn $"transformAsExpr: {expr}" + //printfn $"transformAsExpr: {expr}" match expr with | AssignmentExpression (left = left; operator = operator; right = right) -> @@ -355,7 +360,6 @@ module Util = | ">=" -> GtE |> toCompare | "<" -> Lt |> toCompare | "<=" -> LtE |> toCompare - //| "isinstance" -> toCall "isinstance" | "instanceof" -> toCall "isinstance" | _ -> failwith $"Unknown operator: {operator}" @@ -482,10 +486,11 @@ module Util = |> Helpers.unzipArgs match value with - | "void $0" -> args.[0], stmts + //| "void $0" -> args.[0], stmts | "throw $0" -> let value = "raise $0" Expression.emit (value, args), stmts + | Naming.StartsWith("void ") value | Naming.StartsWith("new ") value -> Expression.emit (value, args), stmts | _ -> Expression.emit (value, args), stmts @@ -606,7 +611,7 @@ module Util = (expr: Babel.Expression) : Python.Statement list = - printfn $"transformExpressionAsStatements: {expr}" + //printfn $"transformExpressionAsStatements: {expr}" match expr with // Transform e.g `this["x@22"] = x;` into `setattr(self, "x@22", x)` @@ -632,7 +637,7 @@ module Util = let stmts = if not (ctx.UsedNames.LocalScope.Contains name) && ctx.UsedNames.EnclosingScope.Contains name then ctx.UsedNames.NonLocals.Add name |> ignore - printfn "**** Adding non-local: %A" target + // printfn "**** Adding non-local: %A" target [ Statement.nonLocal [target] ] else [] @@ -668,7 +673,7 @@ module Util = (returnStrategy: ReturnStrategy) (stmt: Babel.Statement) : Python.Statement list = - printfn $"transformStatementAsStatements: {stmt}, returnStrategy: {returnStrategy}" + //printfn $"transformStatementAsStatements: {stmt}, returnStrategy: {returnStrategy}" match stmt with | Statement.BlockStatement (bs) -> @@ -751,8 +756,7 @@ module Util = // let value = Call.Create(Name.Create(Identifier("str"), Load), [idName]) // let msg = Assign.Create([trg], value) // let body = msg :: body - let handlers = - [ ExceptHandler.exceptHandler (``type`` = exn, name = identifier, body = body) ] + let handlers = [ ExceptHandler.exceptHandler (``type`` = exn, name = identifier, body = body) ] handlers | _ -> [] diff --git a/src/fable-library-py/fable/Fable.Library.fsproj b/src/fable-library-py/fable/Fable.Library.fsproj index a4f814ed92..4c76cd3e95 100644 --- a/src/fable-library-py/fable/Fable.Library.fsproj +++ b/src/fable-library-py/fable/Fable.Library.fsproj @@ -15,7 +15,7 @@ - + diff --git a/src/fable-library-py/fable/reflection.py b/src/fable-library-py/fable/reflection.py index 4b74f2fc16..49bd256ac9 100644 --- a/src/fable-library-py/fable/reflection.py +++ b/src/fable-library-py/fable/reflection.py @@ -99,3 +99,14 @@ def tuple_type(*generics: TypeInfo) -> TypeInfo: float32_type: TypeInfo = TypeInfo("System.Single") float64_type: TypeInfo = TypeInfo("System.Double") decimal_type: TypeInfo = TypeInfo("System.Decimal") + +def equals(t1: TypeInfo, t2: TypeInfo) -> bool: + return t1 == t2 +# if (t1.fullname === "") { // Anonymous records +# return t2.fullname === "" +# && equalArraysWith(getRecordElements(t1), +# getRecordElements(t2), +# ([k1, v1], [k2, v2]) => k1 === k2 && equals(v1, v2)); +# } else { +# return t1.fullname === t2.fullname +# && equalArraysWith(getGenerics(t1), getGenerics(t2), equals); diff --git a/src/fable-library-py/fable/string.py b/src/fable-library-py/fable/string.py index 4697ffddb9..a91172f950 100644 --- a/src/fable-library-py/fable/string.py +++ b/src/fable-library-py/fable/string.py @@ -155,7 +155,7 @@ def fail(msg: str): def formatReplacement(rep: Any, flags: Any, padLength: Any, precision: Any, format: Any): - print("Got here", rep, format) + # print("Got here", rep, format) sign = "" flags = flags or "" format = format or "" diff --git a/src/fable-library-py/fable/types.py b/src/fable-library-py/fable/types.py index f46c6f59f2..b081a1e306 100644 --- a/src/fable-library-py/fable/types.py +++ b/src/fable-library-py/fable/types.py @@ -157,5 +157,6 @@ def toString(x, callStack=0): return str(x) +Exception = Exception -__all__ = ["Attribute", "Union"] +__all__ = ["Attribute", "Exception", "Union"] From 0e1d2e1b3af00ce19e006f6fb164583f058046ee Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 6 Apr 2021 07:40:04 +0200 Subject: [PATCH 085/145] Fix tostring, __str__ and str --- src/Fable.Transforms/Python/Babel2Python.fs | 11 +++++++---- src/fable-library-py/fable/util.py | 7 ++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index ab74756775..eed3ccc5f8 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -76,6 +76,7 @@ module Helpers = | "for" -> "for_" | "Math" -> "math" | "Error" -> "Exception" + | "toString" -> "str" | _ -> name.Replace('$', '_').Replace('.', '_').Replace('`', '_') @@ -165,8 +166,6 @@ module Util = for expr in specifiers do match expr with | Babel.ImportMemberSpecifier (local, imported) -> - printfn "ImportMemberSpecifier" - let asname = if imported.Name <> local.Name then com.GetIdentifier(ctx, local.Name) |> Some @@ -175,8 +174,6 @@ module Util = let alias = Alias.alias(Python.Identifier(imported.Name),?asname=asname) importFroms.Add(alias) | Babel.ImportDefaultSpecifier (local) -> - printfn "ImportDefaultSpecifier" - let asname = if local.Name <> pymodule then Python.Identifier(local.Name) |> Some @@ -255,6 +252,8 @@ module Util = let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) let name = match key with + | Expression.Identifier(Identifier(name="toString")) -> + com.GetIdentifier(ctx, "__str__") | Expression.Identifier (id) -> com.GetIdentifier(ctx, id.Name) | Expression.Literal(Literal.StringLiteral(StringLiteral(value=name))) -> com.GetIdentifier(ctx, name) @@ -516,6 +515,10 @@ module Util = let value, stmts = com.TransformAsExpr(ctx, object) let attr = Python.Identifier "upper" Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "push"))) -> + let value, stmts = com.TransformAsExpr(ctx, object) + let attr = Python.Identifier "append" + Expression.attribute (value = value, attr = attr, ctx = Load), stmts // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "length"))) -> let value, stmts = com.TransformAsExpr(ctx, object) diff --git a/src/fable-library-py/fable/util.py b/src/fable-library-py/fable/util.py index afd1825e6e..50983cb88e 100644 --- a/src/fable-library-py/fable/util.py +++ b/src/fable-library-py/fable/util.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Callable, Generic, Iterable, Tuple, TypeVar, Any, Optional +from typing import Callable, Generic, Iterable, List, Tuple, TypeVar, Any, Optional T = TypeVar("T") @@ -77,3 +77,8 @@ def int32ToString(i: int, radix: int = 10): return convertString[i] else: return int32ToString(i // radix, radix) + convertString[i % radix] + + +def clear(col: Iterable[Any]): + if isinstance(col, List): + col.clear() From 6dd686a1a758dd442b4ccdd9fc259caa69a2d43f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 6 Apr 2021 08:02:23 +0200 Subject: [PATCH 086/145] Fix substring and join for string module --- src/fable-library-py/fable/string.py | 31 +++++++++++++--------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/fable-library-py/fable/string.py b/src/fable-library-py/fable/string.py index a91172f950..46659588a0 100644 --- a/src/fable-library-py/fable/string.py +++ b/src/fable-library-py/fable/string.py @@ -2,7 +2,7 @@ from abc import ABC from dataclasses import dataclass from datetime import datetime -from typing import Any, Callable, Match, Optional, Pattern, Union, cast +from typing import Any, Callable, Iterable, Match, Optional, Pattern, Union, cast, TypeVar # import multiply # import Numeric @@ -21,6 +21,9 @@ # import { toString as dateToString } # import { toString } +T = TypeVar("T") + + fsFormatRegExp: Pattern[str] = re.compile(r"(^|[^%])%([0+\- ]*)(\d+)?(?:\.(\d+))?(\w)") interpolateRegExp: Pattern[str] = re.compile(r"(?:(^|[^%])%([0+\- ]*)(\d+)?(?:\.(\d+))?(\w))?%P\(\)") formatRegExp: Pattern[str] = re.compile(r"\{(\d+)(,-?\d+)?(?:\:([a-zA-Z])(\d{0,2})|\:(.+?))?\}") @@ -367,17 +370,12 @@ def isNullOrEmpty(string: Optional[str]): # return typeof str !== "string" || /^\s*$/.test(str); # } -# export function concat(...xs: any[]): string { -# return xs.map((x) => String(x)).join(""); -# } +def concat(*xs: Iterable[Any]) -> str: + return "".join(map(str, xs)) -# export function join(delimiter: string, xs: Iterable): string { -# if (Array.isArray(xs)) { -# return xs.join(delimiter); -# } else { -# return Array.from(xs).join(delimiter); -# } -# } + +def join(delimiter: str, xs: Iterable[Any]) -> str: + return delimiter.join(xs) # export function joinWithIndices(delimiter: string, xs: string[], startIndex: number, count: number) { # const endIndexPlusOne = startIndex + count; @@ -510,9 +508,8 @@ def replicate(n: int, x: str): # return x.split("").filter((c) => pred(c)).join(""); # } -# export function substring(str: string, startIndex: number, length?: number) { -# if ((startIndex + (length || 0) > str.length)) { -# throw new Error("Invalid startIndex and/or length"); -# } -# return length != null ? str.substr(startIndex, length) : str.substr(startIndex); -# } +def substring(string: str, startIndex: int, length: Optional[int] = None) -> str: + if length is not None: + return string[startIndex:length] + + return string[startIndex] From df9e0bd8e0981837bc60f190481766b31ec89b65 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 6 Apr 2021 22:55:59 +0200 Subject: [PATCH 087/145] Add stringbuilder test --- build.fsx | 1 + src/Fable.Transforms/Python/Babel2Python.fs | 26 +-- src/Fable.Transforms/Python/PythonPrinter.fs | 11 -- .../fable/Fable.Library.fsproj | 2 +- src/fable-library-py/fable/numeric.py | 7 + src/fable-library-py/fable/reflection.py | 5 + src/fable-library-py/fable/string.py | 184 +++++++++--------- src/fable-library-py/fable/types.py | 3 +- tests/Python/TestString.fs | 12 ++ 9 files changed, 134 insertions(+), 117 deletions(-) diff --git a/build.fsx b/build.fsx index fb955c4509..4c7e1dad09 100644 --- a/build.fsx +++ b/build.fsx @@ -186,6 +186,7 @@ let buildLibraryPy() = // Copy *.py from projectDir to buildDir runInDir libraryDir ("cp -R * ../../" + buildDirPy) runInDir buildDirPy ("cp fable-library/*.py fable/") + runInDir buildDirPy ("mv fable/system.text.py fable/system_text.py") // Python modules cannot contain "." runInDir buildDirPy ("python3 --version") runInDir buildDirPy ("python3 ./setup.py develop") diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index eed3ccc5f8..17b1bf784e 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -15,11 +15,6 @@ type ReturnStrategy = | NoReturn | NoBreak // Used in switch statement blocks -type ITailCallOpportunity = - abstract Label: string - abstract Args: string list - abstract IsRecursiveRef: Fable.Expr -> bool - type UsedNames = { GlobalScope: HashSet EnclosingScope: HashSet @@ -65,7 +60,6 @@ module Helpers = let idx = index.Current.ToString() Python.Identifier($"{name}_{idx}") - /// Replaces all '$' and `.`with '_' let clean (name: string) = match name with @@ -130,8 +124,7 @@ module Util = let name = Helpers.clean name match name with - | "math" -> - com.GetImportExpr(ctx, "math") |> ignore + | "math" -> com.GetImportExpr(ctx, "math") |> ignore | _ -> () Python.Identifier name @@ -171,22 +164,22 @@ module Util = com.GetIdentifier(ctx, local.Name) |> Some else None - let alias = Alias.alias(Python.Identifier(imported.Name),?asname=asname) + let alias = Alias.alias(com.GetIdentifier(ctx, imported.Name),?asname=asname) importFroms.Add(alias) | Babel.ImportDefaultSpecifier (local) -> let asname = if local.Name <> pymodule then - Python.Identifier(local.Name) |> Some + com.GetIdentifier(ctx, local.Name) |> Some else None - let alias = Alias.alias (Python.Identifier(pymodule), ?asname=asname) + let alias = Alias.alias(com.GetIdentifier(ctx, pymodule), ?asname=asname) imports.Add(alias) | Babel.ImportNamespaceSpecifier (Identifier (name = name)) -> printfn "ImportNamespaceSpecifier: %A" (name, name) let asname = if pymodule <> name then - Python.Identifier(name) |> Some + com.GetIdentifier(ctx, name) |> Some else None let alias = Alias.alias(Python.Identifier(pymodule), ?asname=asname) @@ -379,11 +372,12 @@ module Util = let operand, stmts = com.TransformAsExpr(ctx, arg) - match op with - | Some op -> Expression.unaryOp (op, operand), stmts - | _ -> - // TODO: Should be Contant(value=None) but we cannot create that in F# + match op, arg with + | Some op, _ -> Expression.unaryOp (op, operand), stmts + | None, Literal(NumericLiteral(value=0.)) -> Expression.name (id = Python.Identifier("None")), stmts + | _ -> + operand, stmts | ArrowFunctionExpression (``params`` = parms; body = body) -> let args = diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index bd127977ea..840f1ef219 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -637,17 +637,6 @@ module PrinterExtensions = printer.Print("") printer.PrintNewLine() - member _.IsProductiveStatement(stmt: Statement) = - let rec hasNoSideEffects(e: Expression) = - match e with - | Constant (_) -> true - | Dict { Keys = keys } -> keys.IsEmpty - | _ -> false - - match stmt with - | Expr (expr) -> hasNoSideEffects expr.Value |> not - | _ -> true - member printer.PrintStatement(stmt: Statement, ?printSeparator) = printer.Print(stmt) diff --git a/src/fable-library-py/fable/Fable.Library.fsproj b/src/fable-library-py/fable/Fable.Library.fsproj index 4c76cd3e95..cc65abd954 100644 --- a/src/fable-library-py/fable/Fable.Library.fsproj +++ b/src/fable-library-py/fable/Fable.Library.fsproj @@ -27,7 +27,7 @@ - + diff --git a/src/fable-library-py/fable/numeric.py b/src/fable-library-py/fable/numeric.py index 2bb1db73f9..04614a03dc 100644 --- a/src/fable-library-py/fable/numeric.py +++ b/src/fable-library-py/fable/numeric.py @@ -26,3 +26,10 @@ def to_exponential(x: float, dp: Optional[int] = None): return fmt.format(x) return "{}".format(x) + + +def to_hex(x) -> str: + return "{0:x}".format(x) + +def multiply(x: int, y: int): + return x * y diff --git a/src/fable-library-py/fable/reflection.py b/src/fable-library-py/fable/reflection.py index 49bd256ac9..49f1e95869 100644 --- a/src/fable-library-py/fable/reflection.py +++ b/src/fable-library-py/fable/reflection.py @@ -73,6 +73,10 @@ def full_name(t: TypeInfo) -> str: return t.fullname +def option_type(generic: TypeInfo) -> TypeInfo: + return TypeInfo("Microsoft.FSharp.Core.FSharpOption`1", [generic]) + + def list_type(generic: TypeInfo) -> TypeInfo: return TypeInfo("Microsoft.FSharp.Collections.FSharpList`1", [generic]) @@ -100,6 +104,7 @@ def tuple_type(*generics: TypeInfo) -> TypeInfo: float64_type: TypeInfo = TypeInfo("System.Double") decimal_type: TypeInfo = TypeInfo("System.Decimal") + def equals(t1: TypeInfo, t2: TypeInfo) -> bool: return t1 == t2 # if (t1.fullname === "") { // Anonymous records diff --git a/src/fable-library-py/fable/string.py b/src/fable-library-py/fable/string.py index 46659588a0..b9bb5ca769 100644 --- a/src/fable-library-py/fable/string.py +++ b/src/fable-library-py/fable/string.py @@ -1,8 +1,12 @@ +from curses.ascii import isspace +from gettext import find import re from abc import ABC from dataclasses import dataclass from datetime import datetime -from typing import Any, Callable, Iterable, Match, Optional, Pattern, Union, cast, TypeVar +from typing import Any, Callable, Iterable, Match, NoReturn, Optional, Pattern, Union, cast, TypeVar + +from fable.list import length # import multiply # import Numeric @@ -14,7 +18,7 @@ # import "./Numeric.js" # import "./RegExp.js" # import "./Types.js" -from .numeric import to_fixed, to_precision, to_exponential +from .numeric import to_fixed, to_precision, to_exponential, to_hex, multiply from .types import toString # import { escape } @@ -140,9 +144,8 @@ def toConsole(arg: Union[IPrintfFormat, str]) -> Union[Any, Callable[[str], Any] return continuePrint(print, arg) -# export function toConsoleError(arg: IPrintfFormat | string) { -# return continuePrint((x: string) => console.error(x), arg); -# } +def toConsoleError(arg: Union[IPrintfFormat, str]): + return continuePrint(lambda x: print(x), arg) def toText(arg: Union[IPrintfFormat, str]) -> str: @@ -175,9 +178,9 @@ def formatReplacement(rep: Any, flags: Any, padLength: Any, precision: Any, form sign = "+" elif format == "x": - rep = "{0:x}".format(rep) + rep = to_hex(rep) elif format == "X": - rep = "{0:x}".format(rep).upper() + rep = to_hex(rep).upper() precision = None if precision is None else int(precision) if format in ("f", "F"): @@ -277,70 +280,74 @@ def _(cont: Callable[..., Any]): return _ -# export function format(str: string, ...args: any[]) { -# if (typeof str === "object" && args.length > 0) { -# // Called with culture info -# str = args[0]; -# args.shift(); -# } +def format(string: str, *args: Any) -> str: + print("format: ", string, args) + # if (typeof str === "object" and args.length > 0): + # # Called with culture info + # str = args[0] + # args.shift() -# return str.replace(formatRegExp, (_, idx, padLength, format, precision, pattern) => { -# let rep = args[idx]; -# if (is_numeric(rep)) { -# precision = precision == null ? null : parseInt(precision, 10); -# switch (format) { -# case "f": case "F": -# precision = precision != null ? precision : 2; -# rep = toFixed(rep, precision); -# break; -# case "g": case "G": -# rep = precision != null ? toPrecision(rep, precision) : toPrecision(rep); -# break; -# case "e": case "E": -# rep = precision != null ? toExponential(rep, precision) : toExponential(rep); -# break; -# case "p": case "P": -# precision = precision != null ? precision : 2; -# rep = toFixed(multiply(rep, 100), precision) + " %"; -# break; -# case "d": case "D": -# rep = precision != null ? padLeft(String(rep), precision, "0") : String(rep); -# break; -# case "x": case "X": -# rep = precision != null ? padLeft(toHex(rep), precision, "0") : toHex(rep); -# if (format === "X") { rep = rep.toUpperCase(); } -# break; -# default: -# if (pattern) { -# let sign = ""; -# rep = (pattern as string).replace(/(0+)(\.0+)?/, (_, intPart, decimalPart) => { -# if (isLessThan(rep, 0)) { -# rep = multiply(rep, -1); -# sign = "-"; -# } -# rep = toFixed(rep, decimalPart != null ? decimalPart.length - 1 : 0); -# return padLeft(rep, (intPart || "").length - sign.length + (decimalPart != null ? decimalPart.length : 0), "0"); -# }); -# rep = sign + rep; -# } -# } -# } else if (rep instanceof Date) { -# rep = dateToString(rep, pattern || format); -# } else { -# rep = toString(rep) -# } -# padLength = parseInt((padLength || " ").substring(1), 10); -# if (!isNaN(padLength)) { -# rep = padLeft(String(rep), Math.abs(padLength), " ", padLength < 0); -# } -# return rep; -# }); -# } + def match(m: Match[str]): + print("Groups: ", m.groups()) + idx, padLength, format, precision_, pattern = list(m.groups()) + rep = args[int(idx)] + print("rep: ", [rep]) + if isinstance(rep, (int, float)): + precision: Optional[int] = None if precision_ is None else int(precision_) + + if format in ["f", "F"]: + precision = precision if precision is not None else 2 + rep = to_fixed(rep, precision) + + elif format in ["g", "G"]: + rep = to_precision(rep, precision) if precision is not None else to_precision(rep) + + elif format in ["e", "E"]: + rep = to_exponential(rep, precision) if precision is not None else to_exponential(rep) + + elif format in ["p", "P"]: + precision = precision if precision is not None else 2 + rep = to_fixed(multiply(rep, 100), precision) + " %" + + elif format in ["d", "D"]: + rep = padLeft(str(rep), precision, "0") if precision is not None else str(rep) + + elif format in ["x", "X"]: + rep = padLeft(to_hex(rep), precision, "0") if precision is not None else to_hex(rep) + if format == "X": + rep = rep.upper() + elif pattern: + sign = "" + # rep = (pattern as string).replace(/(0+)(\.0+)?/, (_, intPart, decimalPart) => { + # if (isLessThan(rep, 0)) { + # rep = multiply(rep, -1); + # sign = "-"; + # } + # rep = toFixed(rep, decimalPart != null ? decimalPart.length - 1 : 0); + # return padLeft(rep, (intPart || "").length - sign.length + (decimalPart != null ? decimalPart.length : 0), "0"); + # }); + rep = sign + rep + + elif isinstance(rep, datetime): + rep = dateToString(rep, pattern or format) + else: + rep = toString(rep) -# export function endsWith(str: string, search: string) { -# const idx = str.lastIndexOf(search); -# return idx >= 0 && idx === str.length - search.length; -# } + try: + padLength = int((padLength or " ")[1:]) + rep = padLeft(str(rep), abs(padLength), " ", padLength < 0) + except ValueError: + pass + + print("return rep: ", [rep]) + return rep + + ret = formatRegExp.sub(match, string) + print("ret: ", ret) + return ret + +def endsWith(string: str, search: str) -> bool: + return string.endswith(search) def initialize(n: int, f: Callable[[int], str]) -> str: @@ -366,9 +373,9 @@ def isNullOrEmpty(string: Optional[str]): not isinstance(string, str) or not len(string) -# export function isNullOrWhiteSpace(str: string | any) { -# return typeof str !== "string" || /^\s*$/.test(str); -# } +def isNullOrWhiteSpace(string: Optional[Any]) -> bool: + return not isinstance(string, str) or string.isspace() + def concat(*xs: Iterable[Any]) -> str: return "".join(map(str, xs)) @@ -385,9 +392,10 @@ def join(delimiter: str, xs: Iterable[Any]) -> str: # return xs.slice(startIndex, endIndexPlusOne).join(delimiter); # } -# function notSupported(name: string): never { -# throw new Error("The environment doesn't support '" + name + "', please use a polyfill."); -# } + +def notSupported(name: str) -> NoReturn: + raise Exception("The environment doesn't support '" + name + "', please use a polyfill.") + # export function toBase64String(inArray: number[]) { # let str = ""; @@ -406,18 +414,18 @@ def join(delimiter: str, xs: Iterable[Any]) -> str: # return bytes; # } -# export function padLeft(str: string, len: number, ch?: string, isRight?: boolean) { -# ch = ch || " "; -# len = len - str.length; -# for (let i = 0; i < len; i++) { -# str = isRight ? str + ch : ch + str; -# } -# return str; -# } +def padLeft(string: str, length: int, ch: Optional[str]=None, isRight: Optional[bool]=False) -> str: + ch = ch or " " + length = length - len(string) + for i in range(length): + string = string + ch if isRight else ch + string + + return string + + +def padRight(string: str, len: int, ch: Optional[str]=None) -> str: + return padLeft(string, len, ch, True) -# export function padRight(str: string, len: number, ch?: string) { -# return padLeft(str, len, ch, true); -# } # export function remove(str: string, startIndex: number, count?: number) { # if (startIndex >= str.length) { @@ -434,8 +442,8 @@ def replace(string: str, search: str, replace: str): return re.sub(search, replace, string) -def replicate(n: int, x: str): - return initialize(n, lambda: x) +def replicate(n: int, x: str) -> str: + return initialize(n, lambda _=0: x) # export function getCharAtIndex(input: string, index: number) { diff --git a/src/fable-library-py/fable/types.py b/src/fable-library-py/fable/types.py index b081a1e306..4b98f4436d 100644 --- a/src/fable-library-py/fable/types.py +++ b/src/fable-library-py/fable/types.py @@ -157,6 +157,7 @@ def toString(x, callStack=0): return str(x) +str = str Exception = Exception -__all__ = ["Attribute", "Exception", "Union"] +__all__ = ["Attribute", "Exception", "str", "Union"] diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index aa077b35db..a02f150f81 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -1,5 +1,6 @@ module Fable.Tests.String +open System open Util.Testing let containsInOrder (substrings: string list) (str: string) = @@ -133,3 +134,14 @@ let ``test sprintf \"%A\" with sequences works`` () = // sprintf "2147483650uL: %x" 2147483650uL |> equal "2147483650uL: 80000002" // sprintf "1L <<< 63: %x" (1L <<< 63) |> equal "1L <<< 63: 8000000000000000" // sprintf "1uL <<< 63: %x" (1uL <<< 63) |> equal "1uL <<< 63: 8000000000000000" + +[] +let ``test StringBuilder works`` () = + let sb = System.Text.StringBuilder() + sb.Append "Hello" |> ignore + sb.AppendLine () |> ignore + sb.AppendLine "World!" |> ignore + let expected = System.Text.StringBuilder() + .AppendFormat("Hello{0}World!{0}", Environment.NewLine) + .ToString() + sb.ToString() |> equal expected From 5f9f9eb2fd681b28329edd78d79fd556e7b5d397 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 7 Apr 2021 07:41:59 +0200 Subject: [PATCH 088/145] Add stringbuilder tests and fix member expressions --- src/Fable.Cli/Main.fs | 1 - src/Fable.Transforms/Python/Babel2Python.fs | 33 ++++++++++----------- src/fable-library-py/fable/string.py | 4 +-- tests/Python/TestString.fs | 16 ++++++++++ 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index 243b4b05da..45777ac7a7 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -163,7 +163,6 @@ module private Util = type PythonFileWriter(sourcePath: string, targetPath: string, cliArgs: CliArgs, dedupTargetDir) = let fileExt = ".py" - do printfn "TargetPath: %s" targetPath let targetDir = Path.GetDirectoryName(targetPath) // PEP8: Modules should have short, all-lowercase names let fileName = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(targetPath)) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 17b1bf784e..4ba600b4d5 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -71,6 +71,7 @@ module Helpers = | "Math" -> "math" | "Error" -> "Exception" | "toString" -> "str" + | "len" -> "len_" | _ -> name.Replace('$', '_').Replace('.', '_').Replace('`', '_') @@ -151,7 +152,7 @@ module Util = let (StringLiteral (value = value)) = source let pymodule = value |> Helpers.rewriteFableImport - printfn "Module: %A" pymodule + //printfn "Module: %A" pymodule let imports: ResizeArray = ResizeArray() let importFroms = ResizeArray() @@ -488,17 +489,23 @@ module Util = Expression.emit (value, args), stmts | _ -> Expression.emit (value, args), stmts // If computed is true, the node corresponds to a computed (a[b]) member expression and property is an Expression - | MemberExpression (computed = true; object = object; property = Expression.Literal (literal)) -> + | MemberExpression (computed = true; object = object; property = property) -> let value, stmts = com.TransformAsExpr(ctx, object) - match literal with - | NumericLiteral (value = numeric) -> + match property with + | Expression.Literal (NumericLiteral (value = numeric)) -> let attr = Expression.constant(numeric) Expression.subscript(value = value, slice = attr, ctx = Load), stmts - | Literal.StringLiteral (StringLiteral (value = str)) -> + | Expression.Literal (Literal.StringLiteral (StringLiteral (value = str))) -> let attr = Expression.constant (str) let func = Expression.name("getattr") Expression.call(func, args=[value; attr]), stmts - | _ -> failwith $"transformExpr: unknown literal {literal}" + | Expression.Identifier (Identifier(name=name)) -> + let attr = Expression.name (com.GetIdentifier(ctx, name)) + Expression.subscript(value = value, slice = attr, ctx = Load), stmts + | _ -> + let attr, stmts' = com.TransformAsExpr(ctx, property) + let func = Expression.name("getattr") + Expression.call(func=func, args=[value; attr]), stmts @ stmts' // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "indexOf"))) -> let value, stmts = com.TransformAsExpr(ctx, object) @@ -523,18 +530,6 @@ module Util = let value, stmts = com.TransformAsExpr(ctx, object) let func = Expression.name (Python.Identifier "str") Expression.call (func, [ value ]), stmts - // If computed is true, the node corresponds to a computed (a[b]) member expression and property is an Expression - | MemberExpression (computed=true; object = object; property = property) -> - let value, stmts = com.TransformAsExpr(ctx, object) - - let attr, stmts' = com.TransformAsExpr(ctx, property) - let value = - match value with - | Name { Id = Python.Identifier (id); Context = exprCtx } -> - Expression.name (id = com.GetIdentifier(ctx, id), ctx = exprCtx) - | _ -> value - let func = Expression.name("getattr") - Expression.call(func=func, args=[value; attr]), stmts @ stmts' // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier | MemberExpression (computed=false; object = object; property = property) -> let value, stmts = com.TransformAsExpr(ctx, object) @@ -628,6 +623,7 @@ module Util = | AssignmentExpression (left = left; right = right) -> let value, stmts = com.TransformAsExpr(ctx, right) let targets, stmts2: Python.Expression list * Python.Statement list = + //printfn "AssignmentExpression: left: %A" left match left with | Expression.Identifier (Identifier (name = name)) -> let target = com.GetIdentifier(ctx, name) @@ -803,6 +799,7 @@ module Util = test = Some (Expression.BinaryExpression (left = left; right = right; operator = operator)) update = Some (Expression.UpdateExpression (operator=update)) body = body) -> + //printfn "For: %A" body let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) let start, stmts1 = com.TransformAsExpr(ctx, init) let stop, stmts2 = com.TransformAsExpr(ctx, right) diff --git a/src/fable-library-py/fable/string.py b/src/fable-library-py/fable/string.py index b9bb5ca769..6e0ecbe0ef 100644 --- a/src/fable-library-py/fable/string.py +++ b/src/fable-library-py/fable/string.py @@ -518,6 +518,6 @@ def replicate(n: int, x: str) -> str: def substring(string: str, startIndex: int, length: Optional[int] = None) -> str: if length is not None: - return string[startIndex:length] + return string[startIndex:startIndex+length] - return string[startIndex] + return string[startIndex:] diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index a02f150f81..d7aef737e7 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -145,3 +145,19 @@ let ``test StringBuilder works`` () = .AppendFormat("Hello{0}World!{0}", Environment.NewLine) .ToString() sb.ToString() |> equal expected + +[] +let ``test StringBuilder.Lengh works`` () = + let sb = System.Text.StringBuilder() + sb.Append("Hello") |> ignore + // We don't test the AppendLine for Length because depending on the OS + // the result is different. Unix \n VS Windows \r\n + // sb.AppendLine() |> ignore + equal 5 sb.Length + +[] +let ``test StringBuilder.ToString works with index and length`` () = + let sb = System.Text.StringBuilder() + sb.Append("Hello") |> ignore + sb.AppendLine() |> ignore + equal "ll" (sb.ToString(2, 2)) From 021ee83f855b7b9020a97c6ee46a27618e13fff0 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 7 Apr 2021 23:25:14 +0200 Subject: [PATCH 089/145] Add stringbuilder tests --- src/Fable.Transforms/Python/Babel2Python.fs | 26 +++++++++++- src/Fable.Transforms/Python/Python.fs | 8 ++-- src/fable-library-py/fable/long.py | 6 ++- src/fable-library-py/fable/string.py | 6 +-- tests/Python/TestString.fs | 44 ++++++++++++++++----- 5 files changed, 71 insertions(+), 19 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 4ba600b4d5..33acae1b67 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -408,7 +408,22 @@ module Util = let func = FunctionDef.Create(name = name, args = arguments, body = body) Expression.name (name), [ func ] - | CallExpression (callee = callee; arguments = args) -> // FIXME: use transformAsCall + // Transform xs.toString() to str(xs) + | CallExpression (callee = MemberExpression(object=object; property=Expression.Identifier(Identifier(name="toString")))) -> + let object, stmts = com.TransformAsExpr(ctx, object) + let func = Expression.name("str") + Expression.call (func, [ object ]), stmts + // Transform "text".split("") to list("text") + | CallExpression (callee = MemberExpression(object=object; property=Expression.Identifier(Identifier(name="split"))); arguments = [| Literal(Literal.StringLiteral(StringLiteral(value=""))) |]) -> + let object, stmts = com.TransformAsExpr(ctx, object) + let func = Expression.name("list") + Expression.call (func, [ object ]), stmts + // Transform xs.join("|") to "|".join(xs) + | CallExpression (callee = MemberExpression(object=object; property=Expression.Identifier(Identifier(name="join"))); arguments = [| Literal(Literal.StringLiteral(StringLiteral(value=value))) |]) -> + let object, stmts = com.TransformAsExpr(ctx, object) + let func = Expression.attribute(value=Expression.constant(value), attr=Python.Identifier("join")) + Expression.call (func, [ object ]), stmts + | CallExpression (callee = callee; arguments = args) -> let func, stmts = com.TransformAsExpr(ctx, callee) let args, stmtArgs = @@ -481,6 +496,9 @@ module Util = match value with //| "void $0" -> args.[0], stmts + | "$0.join('')" -> + let value = "''.join($0)" + Expression.emit (value, args), stmts | "throw $0" -> let value = "raise $0" Expression.emit (value, args), stmts @@ -516,6 +534,10 @@ module Util = let value, stmts = com.TransformAsExpr(ctx, object) let attr = Python.Identifier "upper" Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "toLocaleLowerCase"))) -> + let value, stmts = com.TransformAsExpr(ctx, object) + let attr = Python.Identifier "lower" + Expression.attribute (value = value, attr = attr, ctx = Load), stmts | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "push"))) -> let value, stmts = com.TransformAsExpr(ctx, object) let attr = Python.Identifier "append" @@ -799,7 +821,7 @@ module Util = test = Some (Expression.BinaryExpression (left = left; right = right; operator = operator)) update = Some (Expression.UpdateExpression (operator=update)) body = body) -> - //printfn "For: %A" body + // printfn "For: %A" body let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) let start, stmts1 = com.TransformAsExpr(ctx, init) let stop, stmts2 = com.TransformAsExpr(ctx, right) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index c8fd6821bb..203aae8f9e 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -897,10 +897,10 @@ module PythonExtensions = Ops = ops } |> Compare - static member attribute(value, attr, ctx): Expression = + static member attribute(value, attr, ?ctx): Expression = { Value = value Attr = attr - Ctx = ctx } + Ctx = defaultArg ctx Load } |> Attribute static member unaryOp(op, operand, ?loc): Expression = @@ -911,10 +911,10 @@ module PythonExtensions = static member namedExpr(target, value) = { Target = target; Value = value } |> NamedExpr - static member subscript(value, slice, ctx): Expression = + static member subscript(value, slice, ?ctx): Expression = { Value = value Slice = slice - Ctx = ctx } + Ctx = defaultArg ctx Load } |> Subscript static member binOp(left, op, right): Expression = diff --git a/src/fable-library-py/fable/long.py b/src/fable-library-py/fable/long.py index 0c880440b5..e09180fb57 100644 --- a/src/fable-library-py/fable/long.py +++ b/src/fable-library-py/fable/long.py @@ -1,5 +1,9 @@ def fromBits(lowBits: int, highBits: int, unsigned: bool): - return lowBits + (highBits << 32) + ret = lowBits + (highBits << 32) + if ret > 0x7FFFFFFFFFFFFFFF: + return ret - 0x10000000000000000 + + return ret def op_LeftShift(self, numBits): diff --git a/src/fable-library-py/fable/string.py b/src/fable-library-py/fable/string.py index 6e0ecbe0ef..50a398df6c 100644 --- a/src/fable-library-py/fable/string.py +++ b/src/fable-library-py/fable/string.py @@ -202,7 +202,7 @@ def formatReplacement(rep: Any, flags: Any, padLength: Any, precision: Any, form padLength = int(padLength) zeroFlag = flags.find("0") >= 0 # Use '0' for left padding minusFlag = flags.find("-") >= 0 # Right padding - ch = minusFlag or " " if not zeroFlag else "0" + ch = " " if minusFlag or not zeroFlag else "0" if ch == "0": rep = padLeft(rep, padLength - len(sign), ch, minusFlag) rep = sign + rep @@ -414,11 +414,11 @@ def notSupported(name: str) -> NoReturn: # return bytes; # } -def padLeft(string: str, length: int, ch: Optional[str]=None, isRight: Optional[bool]=False) -> str: +def padLeft(string: str, length: int, ch: Optional[str] = None, isRight: Optional[bool] = False) -> str: ch = ch or " " length = length - len(string) for i in range(length): - string = string + ch if isRight else ch + string + string = string + ch if isRight else ch + string return string diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index d7aef737e7..9a50aedb9f 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -107,18 +107,18 @@ let ``test sprintf \"%A\" with sequences works`` () = let xs = seq { "Hi"; "Hello"; "Hola" } sprintf "%A" xs |> containsInOrder ["Hi"; "Hello"; "Hola"] |> equal true -// [] -// let ``test Storing result of Seq.tail and printing the result several times works. Related to #1996`` () = -// let tweets = seq { "Hi"; "Hello"; "Hola" } -// let tweetsTailR: seq = tweets |> Seq.tail +[] +let ``test Storing result of Seq.tail and printing the result several times works. Related to #1996`` () = + let tweets = seq { "Hi"; "Hello"; "Hola" } + let tweetsTailR: seq = tweets |> Seq.tail -// let a = sprintf "%A" (tweetsTailR) -// let b = sprintf "%A" (tweetsTailR) + let a = sprintf "%A" (tweetsTailR) + let b = sprintf "%A" (tweetsTailR) -// containsInOrder ["Hello"; "Hola"] a |> equal true -// containsInOrder ["Hello"; "Hola"] b |> equal true + containsInOrder ["Hello"; "Hola"] a |> equal true + containsInOrder ["Hello"; "Hola"] b |> equal true -// [] +// [] FIXME: we should get this working as well. // let ``test sprintf \"%X\" works`` () = // //These should all be the Native JS Versions (except int64 / uint64) // //See #1530 for more information. @@ -135,6 +135,14 @@ let ``test sprintf \"%A\" with sequences works`` () = // sprintf "1L <<< 63: %x" (1L <<< 63) |> equal "1L <<< 63: 8000000000000000" // sprintf "1uL <<< 63: %x" (1uL <<< 63) |> equal "1uL <<< 63: 8000000000000000" +[] +let ``test sprintf integers with sign and padding works`` () = + sprintf "%+04i" 1 |> equal "+001" + sprintf "%+04i" -1 |> equal "-001" + sprintf "%5d" -5 |> equal " -5" + sprintf "%5d" -5L |> equal " -5" + sprintf "%- 4i" 5 |> equal " 5 " + [] let ``test StringBuilder works`` () = let sb = System.Text.StringBuilder() @@ -161,3 +169,21 @@ let ``test StringBuilder.ToString works with index and length`` () = sb.Append("Hello") |> ignore sb.AppendLine() |> ignore equal "ll" (sb.ToString(2, 2)) + +[] +let ``test StringBuilder.Clear works`` () = + let builder = new System.Text.StringBuilder() + builder.Append("1111") |> ignore + builder.Clear() |> ignore + equal "" (builder.ToString()) + +[] +let ``test StringBuilder.Append works with various overloads`` () = + let builder = Text.StringBuilder() + .Append(Text.StringBuilder "aaa") + .Append("bcd".ToCharArray()) + .Append('/') + .Append(true) + .Append(5.2) + .Append(34) + equal "aaabcd/true5.234" (builder.ToString().ToLower()) From a859e3303d901d2c82abaa8accfb1f9e96f2ee5f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 8 Apr 2021 22:58:45 +0200 Subject: [PATCH 090/145] Add more string tests --- src/Fable.Transforms/Python/Babel2Python.fs | 10 +++ src/fable-library-py/fable/int32.py | 2 +- src/fable-library-py/fable/long.py | 23 ++++++ src/fable-library-py/fable/string.py | 41 ++++++----- src/fable-library-py/fable/util.py | 24 ++++-- tests/Python/TestString.fs | 81 +++++++++++++++++++++ 6 files changed, 156 insertions(+), 25 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 33acae1b67..e30a90402f 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -413,6 +413,16 @@ module Util = let object, stmts = com.TransformAsExpr(ctx, object) let func = Expression.name("str") Expression.call (func, [ object ]), stmts + // Transform xs.charCodeAt(0) to ord(xs) + | CallExpression (callee = MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "charCodeAt")))) -> + let value, stmts = com.TransformAsExpr(ctx, object) + let func = Expression.name (Python.Identifier "ord") + Expression.call (func, [ value ]), stmts + // Transform String.fromCharCode(97) to chr(xs) + | CallExpression (callee = MemberExpression (computed = false; object = Expression.Identifier (Identifier(name = "String")); property = Expression.Identifier (Identifier(name = "fromCharCode"))); arguments=args) -> + let args, stmts = args |> Array.map (fun obj -> com.TransformAsExpr(ctx, obj)) |> List.ofArray |> Helpers.unzipArgs + let func = Expression.name (Python.Identifier "chr") + Expression.call (func, args), stmts // Transform "text".split("") to list("text") | CallExpression (callee = MemberExpression(object=object; property=Expression.Identifier(Identifier(name="split"))); arguments = [| Literal(Literal.StringLiteral(StringLiteral(value=""))) |]) -> let object, stmts = com.TransformAsExpr(ctx, object) diff --git a/src/fable-library-py/fable/int32.py b/src/fable-library-py/fable/int32.py index 3747305946..1bf7e400a3 100644 --- a/src/fable-library-py/fable/int32.py +++ b/src/fable-library-py/fable/int32.py @@ -1,4 +1,4 @@ -def parse(string: str, style, unsigned, bitsize, radix) -> int: +def parse(string: str, style, unsigned, bitsize, radix: int = 10) -> int: return int(string) # const res = isValid(str, style, radix); # if (res != null) { diff --git a/src/fable-library-py/fable/long.py b/src/fable-library-py/fable/long.py index e09180fb57..ce0533ab53 100644 --- a/src/fable-library-py/fable/long.py +++ b/src/fable-library-py/fable/long.py @@ -1,3 +1,6 @@ +from typing import Optional + + def fromBits(lowBits: int, highBits: int, unsigned: bool): ret = lowBits + (highBits << 32) if ret > 0x7FFFFFFFFFFFFFFF: @@ -8,3 +11,23 @@ def fromBits(lowBits: int, highBits: int, unsigned: bool): def op_LeftShift(self, numBits): return self << numBits + + +def parse(string: str, style: int, unsigned: bool, _bitsize: int, radix: Optional[int] = None): + return int(string) + # res = isValid(str, style, radix) + # if res: + # def lessOrEqual(x: str, y: str): + # length = max(len(x), len(y)) + # return x.padStart(len, "0") <= y.padStart(len, "0"); + + # isNegative = res.sign == "-" + # maxValue = getMaxValue(unsigned or res.radix != 10, res.radix, isNegative); + # if (lessOrEqual(res.digits.upper(), maxValue)): + # string = res.sign + res.digits if isNegative else res.digits + # return LongLib.fromString(str, unsigned, res.radix); + + # raise Exception("Input string was not in a correct format."); + + +str = str # FIXME: remove str imports in the compiler. diff --git a/src/fable-library-py/fable/string.py b/src/fable-library-py/fable/string.py index 50a398df6c..cdabedb9dc 100644 --- a/src/fable-library-py/fable/string.py +++ b/src/fable-library-py/fable/string.py @@ -1,12 +1,8 @@ -from curses.ascii import isspace -from gettext import find import re from abc import ABC from dataclasses import dataclass from datetime import datetime -from typing import Any, Callable, Iterable, Match, NoReturn, Optional, Pattern, Union, cast, TypeVar - -from fable.list import length +from typing import Any, Callable, Iterable, Match, NoReturn, Optional, Pattern, Union, TypeVar # import multiply # import Numeric @@ -148,9 +144,9 @@ def toConsoleError(arg: Union[IPrintfFormat, str]): return continuePrint(lambda x: print(x), arg) -def toText(arg: Union[IPrintfFormat, str]) -> str: +def toText(arg: Union[IPrintfFormat, str]) -> Union[str, Callable]: cont: Callable[[str], Any] = lambda x: x - return cast(str, continuePrint(cont, arg)) + return continuePrint(cont, arg) def toFail(arg: Union[IPrintfFormat, str]): @@ -247,7 +243,6 @@ def match(m: Match[str]): return prefix + once.replace("%", "%%") ret = fsFormatRegExp.sub(match, str2, count=1) - # print(ret) return ret @@ -318,14 +313,20 @@ def match(m: Match[str]): rep = rep.upper() elif pattern: sign = "" - # rep = (pattern as string).replace(/(0+)(\.0+)?/, (_, intPart, decimalPart) => { - # if (isLessThan(rep, 0)) { - # rep = multiply(rep, -1); - # sign = "-"; - # } - # rep = toFixed(rep, decimalPart != null ? decimalPart.length - 1 : 0); - # return padLeft(rep, (intPart || "").length - sign.length + (decimalPart != null ? decimalPart.length : 0), "0"); - # }); + + def match(m: Match[str]): + nonlocal sign, rep + + intPart, decimalPart = list(m.groups()) + # print("**************************: ", rep) + if rep < 0: + rep = multiply(rep, -1) + sign = "-" + + rep = to_fixed(rep, len(decimalPart) - 1 if decimalPart else 0) + return padLeft(rep, len(intPart or "") - len(sign) + (len(decimalPart) if decimalPart else 0), "0") + + rep = re.sub(r"(0+)(\.0+)?", match, pattern) rep = sign + rep elif isinstance(rep, datetime): @@ -346,6 +347,7 @@ def match(m: Match[str]): print("ret: ", ret) return ret + def endsWith(string: str, search: str) -> bool: return string.endswith(search) @@ -384,6 +386,7 @@ def concat(*xs: Iterable[Any]) -> str: def join(delimiter: str, xs: Iterable[Any]) -> str: return delimiter.join(xs) + # export function joinWithIndices(delimiter: string, xs: string[], startIndex: number, count: number) { # const endIndexPlusOne = startIndex + count; # if (endIndexPlusOne > xs.length) { @@ -414,6 +417,7 @@ def notSupported(name: str) -> NoReturn: # return bytes; # } + def padLeft(string: str, length: int, ch: Optional[str] = None, isRight: Optional[bool] = False) -> str: ch = ch or " " length = length - len(string) @@ -423,7 +427,7 @@ def padLeft(string: str, length: int, ch: Optional[str] = None, isRight: Optiona return string -def padRight(string: str, len: int, ch: Optional[str]=None) -> str: +def padRight(string: str, len: int, ch: Optional[str] = None) -> str: return padLeft(string, len, ch, True) @@ -516,8 +520,9 @@ def replicate(n: int, x: str) -> str: # return x.split("").filter((c) => pred(c)).join(""); # } + def substring(string: str, startIndex: int, length: Optional[int] = None) -> str: if length is not None: - return string[startIndex:startIndex+length] + return string[startIndex:startIndex + length] return string[startIndex:] diff --git a/src/fable-library-py/fable/util.py b/src/fable-library-py/fable/util.py index 50983cb88e..c00f9ad36e 100644 --- a/src/fable-library-py/fable/util.py +++ b/src/fable-library-py/fable/util.py @@ -71,12 +71,24 @@ def createObj(fields: Iterable[Tuple[str, Any]]): return obj -def int32ToString(i: int, radix: int = 10): - convertString = "0123456789ABCDEF" - if i < radix: - return convertString[i] - else: - return int32ToString(i // radix, radix) + convertString[i % radix] +def int16ToString(i: int, radix: int = 10) -> str: + if radix == 10: + return '{:d}'.format(i) + if radix == 16: + return '{:x}'.format(i) + if radix == 2: + return '{:b}'.format(i) + return str(i) + + +def int32ToString(i: int, radix: int = 10) -> str: + if radix == 10: + return '{:d}'.format(i) + if radix == 16: + return '{:x}'.format(i) + if radix == 2: + return '{:b}'.format(i) + return str(i) def clear(col: Iterable[Any]): diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index 9a50aedb9f..16c61b48f7 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -143,6 +143,17 @@ let ``test sprintf integers with sign and padding works`` () = sprintf "%5d" -5L |> equal " -5" sprintf "%- 4i" 5 |> equal " 5 " +// [] +// let ``test parameterized padding works`` () = +// sprintf "[%*s][%*s]" 6 "Hello" 5 "Foo" +// |> equal "[ Hello][ Foo]" + +[] +let ``test String.Format combining padding and zeroes pattern works`` () = + String.Format("{0:++0.00++}", -5000.5657) |> equal "-++5000.57++" + String.Format("{0:000.00}foo", 5) |> equal "005.00foo" + String.Format("{0,-8:000.00}foo", 12.456) |> equal "012.46 foo" + [] let ``test StringBuilder works`` () = let sb = System.Text.StringBuilder() @@ -187,3 +198,73 @@ let ``test StringBuilder.Append works with various overloads`` () = .Append(5.2) .Append(34) equal "aaabcd/true5.234" (builder.ToString().ToLower()) + +[] +let ``test Conversion char to int works`` () = + equal 97 (int 'a') + equal 'a' (char 97) + +[] +let ``test Conversion string to char works`` () = + equal 'a' (char "a") + equal "a" (string 'a') + +[] +let ``test Conversion string to negative int8 works`` () = + equal -5y (int8 "-5") + equal "-5" (string -5y) + +[] +let ``test Conversion string to negative int16 works`` () = + equal -5s (int16 "-5") + equal "-5" (string -5s) + +[] +let ``test Conversion string to negative int32 works`` () = + equal -5 (int32 "-5") + equal "-5" (string -5) + +[] +let ``test Conversion string to negative int64 works`` () = + equal -5L (int64 "-5") + equal "-5" (string -5L) + +[] +let ``test Conversion string to int8 works`` () = + equal 5y (int8 "5") + equal "5" (string 5y) + +[] +let ``test Conversion string to int16 works`` () = + equal 5s (int16 "5") + equal "5" (string 5s) + +[] +let ``test Conversion string to int32 works`` () = + equal 5 (int32 "5") + equal "5" (string 5) + +[] +let ``test Conversion string to int64 works`` () = + equal 5L (int64 "5") + equal "5" (string 5L) + +[] +let ``test Conversion string to uint8 works`` () = + equal 5uy (uint8 "5") + equal "5" (string 5uy) + +[] +let ``test Conversion string to uint16 works`` () = + equal 5us (uint16 "5") + equal "5" (string 5us) + +[] +let ``test Conversion string to uint32 works`` () = + equal 5u (uint32 "5") + equal "5" (string 5u) + +[] +let ``test Conversion string to uint64 works`` () = + equal 5uL (uint64 "5") + equal "5" (string 5uL) From 4f0956ae60e56dc52a8a0e0ddb9c03f6b5ceeba9 Mon Sep 17 00:00:00 2001 From: Tengel Skar Date: Thu, 15 Apr 2021 11:14:24 +0200 Subject: [PATCH 091/145] fix install on windows --- build.fsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/build.fsx b/build.fsx index 4c7e1dad09..4de8b51988 100644 --- a/build.fsx +++ b/build.fsx @@ -184,11 +184,14 @@ let buildLibraryPy() = "--exclude Fable.Core" ] // Copy *.py from projectDir to buildDir - runInDir libraryDir ("cp -R * ../../" + buildDirPy) - runInDir buildDirPy ("cp fable-library/*.py fable/") - runInDir buildDirPy ("mv fable/system.text.py fable/system_text.py") // Python modules cannot contain "." - runInDir buildDirPy ("python3 --version") - runInDir buildDirPy ("python3 ./setup.py develop") + copyDirRecursive libraryDir buildDirPy + copyDirRecursive (buildDirPy "fable-library/") (buildDirPy "fable/") + + copyFile (buildDirPy "fable/system.text.py") (buildDirPy "fable/system_text.py") + removeFile (buildDirPy "fable/system.text.py") + + runInDir buildDirPy ("python --version") + runInDir buildDirPy ("python ./setup.py develop") // Like testJs() but doesn't create bundles/packages for fable-standalone & friends // Mainly intended for CI From cb2ba24c993ec911d4e3607a8bd4f72547396545 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 13 May 2021 21:03:53 +0200 Subject: [PATCH 092/145] Fix pyinterop --- build.fsx | 2 +- src/Fable.Core/Fable.Core.PyInterop.fs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.fsx b/build.fsx index 4de8b51988..06a7f1ba4c 100644 --- a/build.fsx +++ b/build.fsx @@ -568,7 +568,7 @@ match argsLower with | "test-py"::_ -> testPython() | "quicktest"::_ -> buildLibraryIfNotExists() - run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --lang Python --exclude Fable.Core --noCache --runScript" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --lang Python --exclude Fable.Core --noCache" | "jupyter" :: _ -> buildLibraryIfNotExists () run "dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src --lang Python --exclude Fable.Core --noCache 2>> /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src/fable.out" diff --git a/src/Fable.Core/Fable.Core.PyInterop.fs b/src/Fable.Core/Fable.Core.PyInterop.fs index 78d1d5329f..45e3073315 100644 --- a/src/Fable.Core/Fable.Core.PyInterop.fs +++ b/src/Fable.Core/Fable.Core.PyInterop.fs @@ -35,12 +35,12 @@ let (==>) (key: string) (v: obj): string*obj = pyNative /// Destructure a tuple of arguments and applies to literal JS code as with EmitAttribute. /// E.g. `emitJsExpr (arg1, arg2) "$0 + $1"` in Python becomes `arg1 + arg2` -let emitJsExpr<'T> (args: obj) (jsCode: string): 'T = pyNative +let emitPyExpr<'T> (args: obj) (jsCode: string): 'T = pyNative /// Same as emitJsExpr but intended for JS code that must appear in a statement position /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements /// E.g. `emitJsExpr aValue "while($0 < 5) doSomething()"` -let emitJsStatement<'T> (args: obj) (jsCode: string): 'T = pyNative +let emitPyStatement<'T> (args: obj) (pyCode: string): 'T = pyNative /// Create a literal Python object from a collection of key-value tuples. /// E.g. `createObj [ "a" ==> 5 ]` in Python becomes `{ a: 5 }` From fa7871638213bfb7c4a6eea8d765893ab4a44048 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 15 May 2021 10:16:07 +0200 Subject: [PATCH 093/145] Adding initial async support (wip) --- build.fsx | 17 +- src/Fable.Transforms/Python/Babel2Python.fs | 54 +++- src/Fable.Transforms/Python/Python.fs | 4 + src/Fable.Transforms/Python/PythonPrinter.fs | 24 +- .../fable/Fable.Library.fsproj | 3 + src/fable-library-py/fable/Timer.fs | 26 ++ src/fable-library-py/fable/async_.py | 36 +++ src/fable-library-py/fable/async_builder.py | 272 ++++++++++++++++++ src/fable-library-py/fable/map_util.py | 11 + src/fable-library-py/fable/numeric.py | 13 +- src/fable-library-py/fable/types.py | 36 ++- src/fable-library-py/fable/util.py | 192 +++++++++++-- tests/Python/Fable.Tests.fsproj | 1 + tests/Python/TestAsync.fs | 75 +++++ 14 files changed, 707 insertions(+), 57 deletions(-) create mode 100644 src/fable-library-py/fable/Timer.fs create mode 100644 src/fable-library-py/fable/async_.py create mode 100644 src/fable-library-py/fable/async_builder.py create mode 100644 src/fable-library-py/fable/map_util.py create mode 100644 tests/Python/TestAsync.fs diff --git a/build.fsx b/build.fsx index 73cb855492..7341d3de30 100644 --- a/build.fsx +++ b/build.fsx @@ -178,20 +178,20 @@ let buildLibraryPy() = cleanDirs [buildDirPy] runFableWithArgs projectDir [ - "--outDir " + buildDirPy - "--fableLib " + buildDirPy + "/fable" + "--outDir " + buildDirPy "fable" + "--fableLib " + buildDirPy "fable" "--lang Python" "--exclude Fable.Core" ] // Copy *.py from projectDir to buildDir copyDirRecursive libraryDir buildDirPy - copyDirRecursive (buildDirPy "fable-library/") (buildDirPy "fable/") - copyFile (buildDirPy "fable/system.text.py") (buildDirPy "fable/system_text.py") - removeFile (buildDirPy "fable/system.text.py") + copyFile (buildDirPy "fable/fable-library/system.text.py") (buildDirPy "fable/system_text.py") + //copyFile (buildDirPy "fable/async.py") (buildDirPy "fable/async_.py") + //removeFile (buildDirPy "fable/async.py") - runInDir buildDirPy ("python --version") - runInDir buildDirPy ("python ./setup.py develop") + runInDir buildDirPy ("python3 --version") + runInDir buildDirPy ("python3 ./setup.py develop") // Like testJs() but doesn't create bundles/packages for fable-standalone & friends // Mainly intended for CI @@ -566,6 +566,9 @@ match argsLower with | "test-integration"::_ -> testIntegration() | "test-py"::_ -> testPython() | "quicktest"::_ -> + buildLibraryIfNotExists() + run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --noCache" +| "quicktest-py"::_ -> buildLibraryIfNotExists() run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --lang Python --exclude Fable.Core --noCache" | "jupyter" :: _ -> diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 40ea032085..695a1eaa22 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -8,6 +8,8 @@ open Fable open Fable.AST open Fable.AST.Python open Fable.AST.Babel +open Fable.Naming +open Fable.Core [] type ReturnStrategy = @@ -62,9 +64,10 @@ module Helpers = /// Replaces all '$' and `.`with '_' let clean (name: string) = + //printfn $"clean: {name}" match name with | "this" -> "self" - | "async" -> "asyncio" + | "async" -> "async_" | "from" -> "from_" | "class" -> "class_" | "for" -> "for_" @@ -72,6 +75,7 @@ module Helpers = | "Error" -> "Exception" | "toString" -> "str" | "len" -> "len_" + | "Map" -> "dict" | _ -> name.Replace('$', '_').Replace('.', '_').Replace('`', '_') @@ -81,18 +85,25 @@ module Helpers = Regex(".*\/fable-library.*\/(?[^\/]*)\.js", RegexOptions.Compiled) let m = _reFableLib.Match(moduleName) + let dashify = applyCaseRule CaseRules.SnakeCase if m.Groups.Count > 1 then let pymodule = - m.Groups.["module"].Value.ToLower() + m.Groups.["module"].Value + |> dashify |> clean let moduleName = String.concat "." [ "fable"; pymodule ] + printfn "-> Module: %A" moduleName moduleName else - // TODO: Can we expect all modules to be lower case? - let moduleName = moduleName.Replace("/", "").ToLower() + // Modules should have short, all-lowercase names. + let moduleName = + moduleName.Replace("/", "") + |> dashify + + printfn "-> Module: %A" moduleName moduleName let unzipArgs (args: (Python.Expression * Python.Statement list) list): Python.Expression list * Python.Statement list = @@ -238,8 +249,12 @@ module Util = | Pattern.RestElement (argument = argument) -> Arg.arg (com.GetIdentifier(ctx, argument.Name)) |> Some | _ -> None) |> List.tryHead - - let arguments = Arguments.arguments (args = self :: args, ?vararg = varargs) + let defaults = + if List.length args = 1 then + [ Expression.name "None"] + else + [] + let arguments = Arguments.arguments (args = self :: args, ?vararg = varargs, defaults=defaults) match kind, key with | "method", _ -> @@ -269,7 +284,12 @@ module Util = let name = Python.Identifier("__len__") let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) FunctionDef.Create(name, arguments, body = body) - | _ -> failwith $"transformAsClassDef: Unknown kind: {kind}" + | "get", Expression.Identifier(Identifier(name=name)) -> + let name = Python.Identifier(name) + let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) + let decorators = [ Python.Expression.name "property"] + FunctionDef.Create(name, arguments, body = body, decoratorList=decorators) + | _ -> failwith $"transformAsClassDef: Unknown kind: {kind}, key: {key}" | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] let name = com.GetIdentifier(ctx, id.Value.Name) @@ -457,7 +477,13 @@ module Util = | Expression.Identifier (Identifier (name = name)) -> let name = com.GetIdentifier(ctx, name) Expression.name (id = name), [] - | NewExpression (callee = callee; arguments = args) -> // FIXME: use transformAsCall + | NewExpression (callee = Expression.Identifier(Identifier(name="Int32Array")); arguments = args) -> + match args with + | [| arg |] -> + let expr, stmts = com.TransformAsExpr(ctx, arg) + expr, stmts + | _ -> failwith "Int32Array with multiple arguments not supported." + | NewExpression (callee = callee; arguments = args) -> let func, stmts = com.TransformAsExpr(ctx, callee) let args, stmtArgs = @@ -624,6 +650,15 @@ module Util = let name = Expression.name (name) Expression.call (name), [ func ] + | ClassExpression(body, id, superClass, implements, superTypeParameters, typeParameters, loc) -> + let name = + match id with + | Some id -> Python.Identifier(id.Name) + | None -> Helpers.getUniqueIdentifier "Lifted" + + let babelId = Identifier.identifier(name=name.Name) |> Some + let stmts = com.TransformAsClassDef(ctx, body, babelId, superClass, implements, superTypeParameters, typeParameters, loc) + Expression.name name, stmts | ThisExpression (_) -> Expression.name ("self"), [] | _ -> failwith $"transformAsExpr: Unhandled value: {expr}" @@ -870,6 +905,7 @@ module Util = let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = let returnStrategy = ReturnStrategy.NoReturn + //printfn "Transform Program: %A" body let stmt: Python.Statement list = [ for md in body do match md with @@ -904,8 +940,6 @@ module Util = Module.module' (imports @ stmt) let getIdentForImport (ctx: Context) (moduleName: string) (name: string option) = - // import math - // from seq import a match name with | None -> Path.GetFileNameWithoutExtension(moduleName) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 203aae8f9e..c6558ccbbd 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -1021,4 +1021,8 @@ module PythonExtensions = Level = level } type Expr with static member expr(value) : Expr = + { Value=value } + + type Constant with + static member contant(value) : Expr = { Value=value } \ No newline at end of file diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index 840f1ef219..b11c8470a1 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -150,16 +150,26 @@ module PrinterExtensions = printer.Print(kw.Value) member printer.Print(arguments: Arguments) = + let args = arguments.Args |> List.map AST.Arg + let defaults = arguments.Defaults + if defaults.Length > 0 then + printfn "Got defaults. %A" defaults + for i = 0 to args.Length - 1 do + printer.Print(args.[i]) + if i >= args.Length - defaults.Length then + printer.Print("=") + printer.Print(defaults.[i-defaults.Length]) + if i < args.Length - 1 then + printer.Print(", ") + match arguments.Args, arguments.VarArg with | [], Some vararg -> printer.Print("*") printer.Print(vararg) | args, Some vararg -> - printer.PrintCommaSeparatedList(args) printer.Print(", *") printer.Print(vararg) - | args, None -> - printer.PrintCommaSeparatedList(args) + | _ -> () member printer.Print(assign: Assign) = //printer.PrintOperation(targets.[0], "=", value, None) @@ -237,7 +247,7 @@ module PrinterExtensions = member printer.Print(ri: Raise) = printer.Print("(Raise)") member printer.Print(func: FunctionDef) = - printer.PrintFunction(Some func.Name, func.Args, func.Body, func.Returns, isDeclaration = true) + printer.PrintFunction(Some func.Name, func.Args, func.Body, func.Returns, func.DecoratorList, isDeclaration = true) printer.PrintNewLine() member printer.Print(gl: Global) = printer.Print("(Global)") @@ -711,8 +721,14 @@ module PrinterExtensions = args: Arguments, body: Statement list, returnType: Expression option, + decoratorList: Expression list, ?isDeclaration ) = + for deco in decoratorList do + printer.Print("@") + printer.Print(deco) + printer.PrintNewLine() + printer.Print("def ") printer.PrintOptional(id) printer.Print("(") diff --git a/src/fable-library-py/fable/Fable.Library.fsproj b/src/fable-library-py/fable/Fable.Library.fsproj index cc65abd954..2ef2d81128 100644 --- a/src/fable-library-py/fable/Fable.Library.fsproj +++ b/src/fable-library-py/fable/Fable.Library.fsproj @@ -7,6 +7,7 @@ + @@ -29,6 +30,8 @@ + + diff --git a/src/fable-library-py/fable/Timer.fs b/src/fable-library-py/fable/Timer.fs new file mode 100644 index 0000000000..0f951aa772 --- /dev/null +++ b/src/fable-library-py/fable/Timer.fs @@ -0,0 +1,26 @@ +module Timer + +open Fable.Core + +/// This class represents an action that should be run only after a +/// certain amount of time has passed — a timer. Timer is a subclass of +/// Thread and as such also functions as an example of creating custom +/// threads. +type ITimer = + abstract daemon : bool with get, set + + /// Start the thread’s activity. + abstract start : unit -> unit + /// Stop the timer, and cancel the execution of the timer’s action. + /// This will only work if the timer is still in its waiting stage. + abstract cancel : unit -> unit + + /// Create a timer that will run function with arguments args and + /// keyword arguments kwargs, after interval seconds have passed. If + /// args is None (the default) then an empty list will be used. If + /// kwargs is None (the default) then an empty dict will be used. + [] + abstract Create : float * (unit -> unit) -> ITimer + +[] +let Timer : ITimer = pyNative diff --git a/src/fable-library-py/fable/async_.py b/src/fable-library-py/fable/async_.py new file mode 100644 index 0000000000..ff3c4d13a1 --- /dev/null +++ b/src/fable-library-py/fable/async_.py @@ -0,0 +1,36 @@ +from .async_builder import CancellationToken, IAsyncContext, Trampoline + + +class Async: + pass + + +def empty_continuation(x=None): + pass + + +default_cancellation_token = CancellationToken() + + +def startWithContinuations( + computation, continuation=None, exception_continuation=None, cancellation_continuation=None, cancellation_token=None +): + trampoline = Trampoline() + + ctx = IAsyncContext.create( + continuation or empty_continuation, + exception_continuation or empty_continuation, + cancellation_continuation or empty_continuation, + trampoline, + cancellation_token or default_cancellation_token, + ) + + return computation(ctx) + + +def start(computation, cancellation_token=None): + return startWithContinuations(computation, cancellation_token=cancellation_token) + + +def startImmediate(computation): + return start(computation) diff --git a/src/fable-library-py/fable/async_builder.py b/src/fable-library-py/fable/async_builder.py new file mode 100644 index 0000000000..24b0626396 --- /dev/null +++ b/src/fable-library-py/fable/async_builder.py @@ -0,0 +1,272 @@ +from abc import abstractmethod +from threading import Timer +from fable.util import IDisposable + + +class OperationCanceledError(Exception): + def __init__(self): + super().__init__("The operation was canceled") + + +class CancellationToken: + def __init__(self, cancelled: bool = False): + self.cancelled = cancelled + self.listeners = {} + self.idx = 0 + + @property + def is_cancelled(self): + return self.cancelled + + def cancel(self): + if not self.cancelled: + self.cancelled = True + + for listener in self.listeners.values(): + listener() + + def add_listener(self, f): + id = self.idx + self.idx = self.idx + 1 + self.listeners[self.idx] = f + return id + + def remove_listener(self, id: int): + del self.listeners[id] + + def register(self, f, state=None): + if state: + id = self.add_listener(lambda _=None: f(state)) + else: + id = self.add_listener(f) + + def dispose(): + self.remove_listener(id) + + IDisposable.create(dispose) + + +class IAsyncContext: + @abstractmethod + def on_success(self, value): + ... + + @abstractmethod + def on_error(self, error): + ... + + @abstractmethod + def on_cancel(self, ct): + ... + + @property + @abstractmethod + def trapoline(self): + ... + + @property + @abstractmethod + def cancellation_token(self): + ... + + @staticmethod + def create(on_success, on_error, on_cancel, trampoline, cancel_token): + return AnonymousAsyncContext(on_success, on_error, on_cancel, trampoline, cancel_token) + + +class AnonymousAsyncContext: + def __init__(self, on_success=None, on_error=None, on_cancel=None, trampoline=None, cancel_token=None): + self._on_success = on_success + self._on_error = on_error + self._on_cancel = on_cancel + + self.cancel_token = cancel_token + self.trampoline = trampoline + + def on_success(self, value=None): + return self._on_success(value) + + def on_error(self, error): + return self._on_error(error) + + def on_cancel(self, ct): + return self.on_cancel(ct) + + +# type IAsync<'T> = IAsyncContext<'T> -> unit + + +class Trampoline: + MaxTrampolineCallCount = 2000 + + def __init__(self): + self.call_count = 0 + + def IncrementAndCheck(self): + self.call_count = self.call_count + 1 + return self.call_count > Trampoline.MaxTrampolineCallCount + + def Hijack(self, f): + self.call_count = 0 + timer = Timer(0.0, f) + timer.daemon = True + timer.start() + + +def protected_cont(f): + print("protected_cont") + + def _protected_cont(ctx): + print("_protected_cont") + + if ctx.cancel_token.is_cancelled: + ctx.on_cancel(OperationCanceledError()) + elif ctx.trampoline.IncrementAndCheck(): + print("Hijacking...") + + def fn(): + try: + return f(ctx) + except Exception as err: + print("Exception: ", err) + ctx.on_error(err) + + ctx.trampoline.Hijack(fn) + else: + try: + print("Calling cont", f, ctx) + return f(ctx) + except Exception as err: + print("Exception: ", err) + ctx.on_error(err) + + return _protected_cont + + +def protected_bind(computation, binder): + print("protected_bind") + + def cont(ctx): + print("protected_bind: inner") + + def on_success(x): + print("protected_bind: x", x, binder) + try: + binder(x)(ctx) + except Exception as err: + print("Exception: ", err) + ctx.on_error(err) + + ctx_ = IAsyncContext.create(on_success, ctx.on_error, ctx.on_cancel, ctx.trampoline, ctx.cancel_token) + return computation(ctx_) + + return protected_cont(cont) + + +def protected_return(value=None): + print("protected_return:", value) + return protected_cont(lambda ctx: ctx.on_success(value)) + + +class AsyncBuilder: + def Bind(self, computation, binder): + return protected_bind(computation, binder) + + def Combine(self, computation1, computation2): + return self.Bind(computation1, lambda _=None: computation2) + + def Delay(self, generator): + print("Delay", generator) + return protected_cont(lambda ctx: generator()(ctx)) + + def For(self, sequence, body): + print("For", sequence) + + done = False + it = iter(sequence) + try: + cur = next(it) + except StopIteration: + done = True + + def delay(): + print("For:delay") + nonlocal cur, done + print("cur", cur) + res = body(cur) + print("For:delay:res", res) + try: + cur = next(it) + print("For:delay:next", cur) + except StopIteration: + print("For:delay:stopIteration") + done = True + return res + + return self.While(lambda: not done, self.Delay(delay)) + + def Return(self, value=None): + print("Return: ", value) + return protected_return(value) + + def ReturnFrom(self, computation): + return computation + + def TryFinally(self, computation, compensation): + def cont(ctx): + def on_success(x): + compensation() + ctx.on_success(x) + + def on_error(x): + compensation() + ctx.on_error(x) + + def on_cancel(x): + compensation() + ctx.on_cancel(x) + + ctx_ = IAsyncContext.create(on_success, on_error, on_cancel, ctx.trampoline, ctx.cancel_token) + computation(ctx_) + + return protected_cont(cont) + + def TryWith(self, computation, catchHandler): + print("TryWith") + + def fn(ctx): + def on_error(err): + try: + catchHandler(err)(ctx) + except Exception as ex2: + ctx.on_error(ex2) + + ctx = IAsyncContext.create( + on_success=ctx.on_success, + on_cancel=ctx.on_cancel, + cancel_token=ctx.cancel_token, + trampoline=ctx.trampoline, + on_error=on_error, + ) + + return computation(ctx) + + return protected_cont(fn) + + def Using(self, resource, binder): + return self.TryFinally(binder(resource), lambda _=None: resource.Dispose()) + + def While(self, guard, computation): + print("while") + if guard(): + print("gard()") + return self.Bind(computation, lambda _=None: self.While(guard, computation)) + else: + print("While:return") + return self.Return() + + def Zero(self): + return protected_cont(lambda ctx: ctx.on_success()) + + +singleton = AsyncBuilder() diff --git a/src/fable-library-py/fable/map_util.py b/src/fable-library-py/fable/map_util.py new file mode 100644 index 0000000000..7687e79663 --- /dev/null +++ b/src/fable-library-py/fable/map_util.py @@ -0,0 +1,11 @@ +from typing import Dict, TypeVar + +K = TypeVar("K") +V = TypeVar("V") + + +def addToDict(dict: Dict[K, V], k: K, v: V): + if k in dict: + raise Exception("An item with the same key has already been added. Key: " + str(k)) + + dict[k] = v diff --git a/src/fable-library-py/fable/numeric.py b/src/fable-library-py/fable/numeric.py index 04614a03dc..8815c87765 100644 --- a/src/fable-library-py/fable/numeric.py +++ b/src/fable-library-py/fable/numeric.py @@ -1,10 +1,4 @@ -from abc import ABC, abstractmethod -from typing import Any, Optional, Union - -from .util import IComparable - - -def to_fixed(x: float, dp: Optional[int] = None) -> str: +def to_fixed(x: float, dp=None) -> str: if dp is not None: fmt = "{:.%sf}" % dp return fmt.format(x) @@ -12,7 +6,7 @@ def to_fixed(x: float, dp: Optional[int] = None) -> str: return "{}".format(x) -def to_precision(x: float, sd: Optional[int] = None): +def to_precision(x: float, sd=None): if sd is not None: fmt = "{:.%se}" % sd return fmt.format(x) @@ -20,7 +14,7 @@ def to_precision(x: float, sd: Optional[int] = None): return "{}".format(x) -def to_exponential(x: float, dp: Optional[int] = None): +def to_exponential(x: float, dp=None): if dp is not None: fmt = "{:.%se}" % dp return fmt.format(x) @@ -31,5 +25,6 @@ def to_exponential(x: float, dp: Optional[int] = None): def to_hex(x) -> str: return "{0:x}".format(x) + def multiply(x: int, y: int): return x * y diff --git a/src/fable-library-py/fable/types.py b/src/fable-library-py/fable/types.py index 4b98f4436d..3556dd7ced 100644 --- a/src/fable-library-py/fable/types.py +++ b/src/fable-library-py/fable/types.py @@ -1,12 +1,39 @@ from __future__ import annotations from abc import abstractstaticmethod -from typing import Any, Iterable, List +from typing import Any, Generic, Iterable, List, TypeVar, Union as Union_, Callable, Optional, cast from .util import IComparable +T = TypeVar("T") -class Union(IComparable["Union"]): + +class FSharpRef: + def __init__(self, contentsOrGetter, setter=None) -> None: + + contents = contentsOrGetter + + def set_contents(value): + nonlocal contents + contents = value + + if callable(setter): + self.getter = contentsOrGetter + self.setter = setter + else: + self.getter = lambda: contents + self.setter = set_contents + + @property + def contents(self): + return self.getter() + + @contents.setter + def contents(self, v): + self.setter(v) + + +class Union(IComparable): def __init__(self): self.tag: int self.fields: List[int] = [] @@ -90,7 +117,7 @@ def recordGetHashCode(self): return hash(*self.values()) -class Record(IComparable["Record"]): +class Record(IComparable): def toJSON(self) -> str: return recordToJSON(this) @@ -157,7 +184,8 @@ def toString(x, callStack=0): return str(x) + str = str Exception = Exception -__all__ = ["Attribute", "Exception", "str", "Union"] +__all__ = ["Attribute", "Exception", "FSharpRef", "str", "Union"] diff --git a/src/fable-library-py/fable/util.py b/src/fable-library-py/fable/util.py index c00f9ad36e..810fc150da 100644 --- a/src/fable-library-py/fable/util.py +++ b/src/fable-library-py/fable/util.py @@ -1,27 +1,79 @@ from abc import ABC, abstractmethod -from typing import Callable, Generic, Iterable, List, Tuple, TypeVar, Any, Optional +from threading import RLock +from typing import Callable, Iterable, List, TypeVar, Optional T = TypeVar("T") -class IEquatable(Generic[T], ABC): - def GetHashCode(self) -> int: - return self.GetHashCode() +class ObjectDisposedException(Exception): + def __init__(self): + super().__init__("Cannot access a disposed object") - def Equals(self, other: T) -> bool: + +class IDisposable: + @abstractmethod + def dispose(self) -> None: + ... + + def __enter__(self): + """Enter context management.""" + return self + + def __exit__(self, exctype, excinst, exctb): + """Exit context management.""" + + self.dispose() + return False + + @staticmethod + def create(action): + """Create disposable from action. Will call action when + disposed.""" + return AnonymousDisposable(action) + + +class AnonymousDisposable(IDisposable): + def __init__(self, action): + self._is_disposed = False + self._action = action + self._lock = RLock() + + def dispose(self) -> None: + """Performs the task of cleaning up resources.""" + + dispose = False + with self._lock: + if not self._is_disposed: + dispose = True + self._is_disposed = True + + if dispose: + self._action() + + def __enter__(self): + if self._is_disposed: + raise ObjectDisposedException() + return self + + +class IEquatable(ABC): + def GetHashCode(self): + return hash(self) + + def Equals(self, other): return self.Equals(other) @abstractmethod - def __eq__(self, other: Any) -> bool: + def __eq__(self, other): return NotImplemented @abstractmethod - def __hash__(self) -> int: + def __hash__(self): raise NotImplementedError -class IComparable(IEquatable[T]): - def CompareTo(self, other: T) -> int: +class IComparable(IEquatable): + def CompareTo(self, other): if self < other: return -1 elif self == other: @@ -29,7 +81,7 @@ def CompareTo(self, other: T) -> int: return 1 @abstractmethod - def __lt__(self, other: Any) -> bool: + def __lt__(self, other): raise NotImplementedError @@ -37,7 +89,7 @@ def equals(a, b): return a == b -def assertEqual(actual: T, expected: T, msg: Optional[str] = None) -> None: +def assertEqual(actual, expected, msg=None) -> None: if actual != expected: raise Exception(msg or f"Expected: ${expected} - Actual: ${actual}") @@ -47,10 +99,10 @@ def assertNotEqual(actual: T, expected: T, msg: Optional[str] = None) -> None: raise Exception(msg or f"Expected: ${expected} - Actual: ${actual}") -def createAtom(value: Optional[T] = None) -> Callable[[Optional[T], Optional[bool]], Optional[T]]: +def createAtom(value=None): atom = value - def _(value: Optional[T] = None, isSetter: Optional[bool] = None) -> Optional[T]: + def _(value=None, isSetter=None): nonlocal atom if not isSetter: @@ -62,8 +114,8 @@ def _(value: Optional[T] = None, isSetter: Optional[bool] = None) -> Optional[T] return _ -def createObj(fields: Iterable[Tuple[str, Any]]): - obj: Any = {} +def createObj(fields): + obj = {} for k, v in fields: obj[k] = v @@ -71,26 +123,120 @@ def createObj(fields: Iterable[Tuple[str, Any]]): return obj -def int16ToString(i: int, radix: int = 10) -> str: +def int16ToString(i, radix=10): if radix == 10: - return '{:d}'.format(i) + return "{:d}".format(i) if radix == 16: - return '{:x}'.format(i) + return "{:x}".format(i) if radix == 2: - return '{:b}'.format(i) + return "{:b}".format(i) return str(i) def int32ToString(i: int, radix: int = 10) -> str: if radix == 10: - return '{:d}'.format(i) + return "{:d}".format(i) if radix == 16: - return '{:x}'.format(i) + return "{:x}".format(i) if radix == 2: - return '{:b}'.format(i) + return "{:b}".format(i) return str(i) -def clear(col: Iterable[Any]): +def clear(col): if isinstance(col, List): col.clear() + + +class IEnumerator(IDisposable): + @property + @abstractmethod + def Current(self): + ... + + @abstractmethod + def MoveNext(self): + ... + + @abstractmethod + def Reset(self): + ... + + @abstractmethod + def Dispose(self): + ... + + def __getattr__(self, name): + return { + "System.Collections.Generic.IEnumerator`1.get_Current": self.Current, + "System.Collections.IEnumerator.get_Current": self.Current, + "System.Collections.IEnumerator.MoveNext": self.MoveNext, + "System.Collections.IEnumerator.Reset": self.Reset, + }[name] + + +class IEnumerable(Iterable): + @abstractmethod + def GetEnumerator(): + ... + + +class Enumerator(IEnumerator): + def __init__(self, iter) -> None: + self.iter = iter + self.current = None + + @property + def Current(self): + if self.current is not None: + return self.current + return None + + def MoveNext(self): + try: + cur = next(self.iter) + self.current = cur + return True + except StopIteration: + return False + + def Reset(self): + raise Exception("Python iterators cannot be reset") + + def Dispose(self): + return + + +def getEnumerator(o): + attr = getattr(o, "GetEnumerator", None) + if attr: + return attr + else: + return Enumerator(iter(o)) + + +CURRIED_KEY = "__CURRIED__" + + +def uncurry(arity: int, f: Callable): + # f may be a function option with None value + if f is None: + return f + + fns = { + 2: lambda a1, a2: f(a1)(a2), + 3: lambda a1, a2, a3: f(a1)(a2)(a3), + 4: lambda a1, a2, a3, a4: f(a1)(a2)(a3)(a4), + 5: lambda a1, a2, a3, a4, a5: f(a1)(a2)(a3)(a4)(a5), + 6: lambda a1, a2, a3, a4, a5, a6: f(a1)(a2)(a3)(a4)(a5)(a6), + 7: lambda a1, a2, a3, a4, a5, a6, a7: f(a1)(a2)(a3)(a4)(a5)(a6)(a7), + 8: lambda a1, a2, a3, a4, a5, a6, a7, a8: f(a1)(a2)(a3)(a4)(a5)(a6)(a7)(a8), + } + + try: + uncurriedFn = fns[arity] + except Exception: + raise Exception(f"Uncurrying to more than 8-arity is not supported: {arity}") + + setattr(f, CURRIED_KEY, f) + return uncurriedFn diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj index 9b5b56f5c3..4c9b7e7304 100644 --- a/tests/Python/Fable.Tests.fsproj +++ b/tests/Python/Fable.Tests.fsproj @@ -30,6 +30,7 @@ + diff --git a/tests/Python/TestAsync.fs b/tests/Python/TestAsync.fs new file mode 100644 index 0000000000..c317d18a2b --- /dev/null +++ b/tests/Python/TestAsync.fs @@ -0,0 +1,75 @@ +module Fable.Tests.Async + +open System +open Util.Testing + +type DisposableAction(f) = + interface IDisposable with + member __.Dispose() = f() + +[] +let ``test Simple async translates without exception`` () = + async { return () } + |> Async.StartImmediate + + +[] +let ``test Async while binding works correctly`` () = + let mutable result = 0 + async { + while result < 10 do + result <- result + 1 + } |> Async.StartImmediate + equal result 10 + +[] +let ``test Async for binding works correctly`` () = + let inputs = [|1; 2; 3|] + let result = ref 0 + async { + for inp in inputs do + result := !result + inp + } |> Async.StartImmediate + equal !result 6 + +[] +let ``test Async exceptions are handled correctly`` () = + let result = ref 0 + let f shouldThrow = + async { + try + if shouldThrow then failwith "boom!" + else result := 12 + with _ -> result := 10 + } |> Async.StartImmediate + !result + f true + f false |> equal 22 + +[] +let ``test Simple async is executed correctly`` () = + let result = ref false + let x = async { return 99 } + async { + let! x = x + let y = 99 + result := x = y + } + |> Async.StartImmediate + equal !result true + +[] +let ``test async use statements should dispose of resources when they go out of scope`` () = + let isDisposed = ref false + let step1ok = ref false + let step2ok = ref false + let resource = async { + return new DisposableAction(fun () -> isDisposed := true) + } + async { + use! r = resource + step1ok := not !isDisposed + } + //TODO: RunSynchronously would make more sense here but in JS I think this will be ok. + |> Async.StartImmediate + step2ok := !isDisposed + (!step1ok && !step2ok) |> equal true From eba41be2890b3f7c1f8241bd5348847db673aeb6 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 16 May 2021 09:08:24 +0200 Subject: [PATCH 094/145] Async fixes Refactor for Python threading model --- src/fable-library-py/fable/async_.py | 49 +++++++- src/fable-library-py/fable/async_builder.py | 127 ++++++++++++-------- tests/Python/TestAsync.fs | 81 +++++++++++++ 3 files changed, 205 insertions(+), 52 deletions(-) diff --git a/src/fable-library-py/fable/async_.py b/src/fable-library-py/fable/async_.py index ff3c4d13a1..4ca1e0290f 100644 --- a/src/fable-library-py/fable/async_.py +++ b/src/fable-library-py/fable/async_.py @@ -1,4 +1,5 @@ -from .async_builder import CancellationToken, IAsyncContext, Trampoline +from threading import Timer +from .async_builder import CancellationToken, IAsyncContext, OperationCanceledError, Trampoline, protected_cont class Async: @@ -12,6 +13,52 @@ def empty_continuation(x=None): default_cancellation_token = CancellationToken() +def createCancellationToken(arg): + print("createCancellationToken()", arg) + cancelled, number = (arg, False) if isinstance(arg, bool) else (False, True) + token = CancellationToken(cancelled) + if number: + timer = Timer(arg / 1000.0, token.cancel) # type: ignore + timer.start() + + return token + + +def cancel(token: CancellationToken): + print("cancel()") + token.cancel() + + +def cancelAfter(token: CancellationToken, ms: int): + print("cancelAfter()", ms / 1000.0) + timer = Timer(ms / 1000.0, token.cancel) + timer.start() + + +def sleep(millisecondsDueTime: int): + def cont(ctx: IAsyncContext): + def cancel(): + timer.cancel() + ctx.on_cancel(OperationCanceledError()) + + token_id = ctx.cancel_token.add_listener(cancel) + + def timeout(): + ctx.cancel_token.remove_listener(token_id) + ctx.on_success() + + timer = Timer(millisecondsDueTime / 1000.0, timeout) + + return protected_cont(cont) + + +def fromContinuations(f): + def cont(ctx: IAsyncContext): + f([ctx.on_success, ctx.on_error, ctx.on_cancel]) + + return protected_cont(cont) + + def startWithContinuations( computation, continuation=None, exception_continuation=None, cancellation_continuation=None, cancellation_token=None ): diff --git a/src/fable-library-py/fable/async_builder.py b/src/fable-library-py/fable/async_builder.py index 24b0626396..4dead199db 100644 --- a/src/fable-library-py/fable/async_builder.py +++ b/src/fable-library-py/fable/async_builder.py @@ -1,5 +1,6 @@ from abc import abstractmethod -from threading import Timer +from collections import deque +from threading import Timer, Lock, RLock from fable.util import IDisposable @@ -13,26 +14,36 @@ def __init__(self, cancelled: bool = False): self.cancelled = cancelled self.listeners = {} self.idx = 0 + self.lock = RLock() @property def is_cancelled(self): return self.cancelled def cancel(self): - if not self.cancelled: - self.cancelled = True - - for listener in self.listeners.values(): - listener() + # print("CancellationToken:cancel") + cancel = False + with self.lock: + if not self.cancelled: + cancel = True + self.cancelled = True + + # print("cancel", cancel) + if cancel: + for listener in self.listeners.values(): + listener() def add_listener(self, f): - id = self.idx - self.idx = self.idx + 1 - self.listeners[self.idx] = f + with self.lock: + id = self.idx + self.idx = self.idx + 1 + self.listeners[self.idx] = f + return id def remove_listener(self, id: int): - del self.listeners[id] + with self.lock: + del self.listeners[id] def register(self, f, state=None): if state: @@ -48,7 +59,7 @@ def dispose(): class IAsyncContext: @abstractmethod - def on_success(self, value): + def on_success(self, value=None): ... @abstractmethod @@ -61,12 +72,12 @@ def on_cancel(self, ct): @property @abstractmethod - def trapoline(self): + def trapoline(self) -> "Trampoline": ... @property @abstractmethod - def cancellation_token(self): + def cancel_token(self) -> CancellationToken: ... @staticmethod @@ -90,67 +101,81 @@ def on_error(self, error): return self._on_error(error) def on_cancel(self, ct): - return self.on_cancel(ct) + return self._on_cancel(ct) # type IAsync<'T> = IAsyncContext<'T> -> unit class Trampoline: - MaxTrampolineCallCount = 2000 + MaxTrampolineCallCount = 900 # Max recursion depth: 1000 def __init__(self): self.call_count = 0 + self.lock = Lock() + self.queue = deque() + self.running = False + + def increment_and_check(self): + with self.lock: + self.call_count = self.call_count + 1 + return self.call_count > Trampoline.MaxTrampolineCallCount + + def run(self, action): + + if self.increment_and_check(): + with self.lock: + # print("queueing...") + self.queue.append(action) + + if not self.running: + self.running = True + timer = Timer(0.0, self._run) + timer.start() + else: + action() - def IncrementAndCheck(self): - self.call_count = self.call_count + 1 - return self.call_count > Trampoline.MaxTrampolineCallCount + def _run(self): + while len(self.queue): + with self.lock: + self.call_count = 0 + action = self.queue.popleft() + # print("Running action: ", action) - def Hijack(self, f): - self.call_count = 0 - timer = Timer(0.0, f) - timer.daemon = True - timer.start() + action() + + self.running = False def protected_cont(f): - print("protected_cont") + # print("protected_cont") def _protected_cont(ctx): - print("_protected_cont") + # print("_protected_cont") if ctx.cancel_token.is_cancelled: ctx.on_cancel(OperationCanceledError()) - elif ctx.trampoline.IncrementAndCheck(): - print("Hijacking...") - - def fn(): - try: - return f(ctx) - except Exception as err: - print("Exception: ", err) - ctx.on_error(err) - ctx.trampoline.Hijack(fn) - else: + def fn(): try: - print("Calling cont", f, ctx) return f(ctx) except Exception as err: print("Exception: ", err) ctx.on_error(err) + ctx.trampoline.run(fn) + return _protected_cont def protected_bind(computation, binder): - print("protected_bind") + # print("protected_bind") def cont(ctx): - print("protected_bind: inner") + # print("protected_bind: inner") def on_success(x): - print("protected_bind: x", x, binder) + # print("protected_bind: x", x, binder) try: binder(x)(ctx) except Exception as err: @@ -164,7 +189,7 @@ def on_success(x): def protected_return(value=None): - print("protected_return:", value) + # print("protected_return:", value) return protected_cont(lambda ctx: ctx.on_success(value)) @@ -176,7 +201,7 @@ def Combine(self, computation1, computation2): return self.Bind(computation1, lambda _=None: computation2) def Delay(self, generator): - print("Delay", generator) + # print("Delay", generator) return protected_cont(lambda ctx: generator()(ctx)) def For(self, sequence, body): @@ -190,14 +215,14 @@ def For(self, sequence, body): done = True def delay(): - print("For:delay") + # print("For:delay") nonlocal cur, done - print("cur", cur) + # print("cur", cur) res = body(cur) - print("For:delay:res", res) + # print("For:delay:res", res) try: cur = next(it) - print("For:delay:next", cur) + # print("For:delay:next", cur) except StopIteration: print("For:delay:stopIteration") done = True @@ -206,7 +231,7 @@ def delay(): return self.While(lambda: not done, self.Delay(delay)) def Return(self, value=None): - print("Return: ", value) + # print("Return: ", value) return protected_return(value) def ReturnFrom(self, computation): @@ -232,7 +257,7 @@ def on_cancel(x): return protected_cont(cont) def TryWith(self, computation, catchHandler): - print("TryWith") + # print("TryWith") def fn(ctx): def on_error(err): @@ -257,12 +282,12 @@ def Using(self, resource, binder): return self.TryFinally(binder(resource), lambda _=None: resource.Dispose()) def While(self, guard, computation): - print("while") + # print("while") if guard(): - print("gard()") + # print("gard()") return self.Bind(computation, lambda _=None: self.While(guard, computation)) else: - print("While:return") + # print("While:return") return self.Return() def Zero(self): diff --git a/tests/Python/TestAsync.fs b/tests/Python/TestAsync.fs index c317d18a2b..54035978fd 100644 --- a/tests/Python/TestAsync.fs +++ b/tests/Python/TestAsync.fs @@ -7,6 +7,17 @@ type DisposableAction(f) = interface IDisposable with member __.Dispose() = f() +let sleepAndAssign token res = + Async.StartImmediate(async { + do! Async.Sleep 200 + res := true + }, token) + +let successWork: Async = Async.FromContinuations(fun (onSuccess,_,_) -> onSuccess "success") +let errorWork: Async = Async.FromContinuations(fun (_,onError,_) -> onError (exn "error")) +let cancelWork: Async = Async.FromContinuations(fun (_,_,onCancel) -> + System.OperationCanceledException("cancelled") |> onCancel) + [] let ``test Simple async translates without exception`` () = async { return () } @@ -73,3 +84,73 @@ let ``test async use statements should dispose of resources when they go out of |> Async.StartImmediate step2ok := !isDisposed (!step1ok && !step2ok) |> equal true + +[] +let ``test Try ... with ... expressions inside async expressions work the same`` () = + let result = ref "" + let throw() : unit = + raise(exn "Boo!") + let append(x) = + result := !result + x + let innerAsync() = + async { + append "b" + try append "c" + throw() + append "1" + with _ -> append "d" + append "e" + } + async { + append "a" + try do! innerAsync() + with _ -> append "2" + append "f" + } |> Async.StartImmediate + equal !result "abcdef" + +// Disable this test for dotnet as it's failing too many times in Appveyor +#if FABLE_COMPILER + +[] +let ``test async cancellation works`` () = + async { + let res1, res2, res3 = ref false, ref false, ref false + let tcs1 = new System.Threading.CancellationTokenSource(50) + let tcs2 = new System.Threading.CancellationTokenSource() + let tcs3 = new System.Threading.CancellationTokenSource() + sleepAndAssign tcs1.Token res1 + sleepAndAssign tcs2.Token res2 + sleepAndAssign tcs3.Token res3 + tcs2.Cancel() + tcs3.CancelAfter(1000) + do! Async.Sleep 500 + equal false !res1 + equal false !res2 + equal true !res3 + } |> Async.StartImmediate + +[] +let ``test CancellationTokenSourceRegister works`` () = + async { + let mutable x = 0 + let res1 = ref false + let tcs1 = new System.Threading.CancellationTokenSource(50) + let foo = tcs1.Token.Register(fun () -> + x <- x + 1) + sleepAndAssign tcs1.Token res1 + do! Async.Sleep 500 + equal false !res1 + equal 1 x + } |> Async.StartImmediate +#endif + +[] +let ``test Async StartWithContinuations works`` () = + let res1, res2, res3 = ref "", ref "", ref "" + Async.StartWithContinuations(successWork, (fun x -> res1 := x), ignore, ignore) + Async.StartWithContinuations(errorWork, ignore, (fun x -> res2 := x.Message), ignore) + Async.StartWithContinuations(cancelWork, ignore, ignore, (fun x -> res3 := x.Message)) + equal "success" !res1 + equal "error" !res2 + equal "cancelled" !res3 From 0abd586b229ebf03e457cd893f8922a4299c6b63 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 16 May 2021 12:16:25 +0200 Subject: [PATCH 095/145] Fix async catch and ignore --- build.fsx | 7 +++-- .../fable/Fable.Library.fsproj | 2 +- src/fable-library-py/fable/async_.py | 29 ++++++++++++++++++- src/fable-library-py/fable/async_builder.py | 2 +- src/fable-library-py/fable/option.py | 16 ++++++---- tests/Python/TestAsync.fs | 25 ++++++++++++++++ 6 files changed, 69 insertions(+), 12 deletions(-) diff --git a/build.fsx b/build.fsx index 7341d3de30..527fe2157a 100644 --- a/build.fsx +++ b/build.fsx @@ -185,10 +185,11 @@ let buildLibraryPy() = ] // Copy *.py from projectDir to buildDir copyDirRecursive libraryDir buildDirPy - - copyFile (buildDirPy "fable/fable-library/system.text.py") (buildDirPy "fable/system_text.py") + copyDirNonRecursive (buildDirPy "fable/fable-library") (buildDirPy "fable") + //copyFile (buildDirPy "fable/fable-library/*.py") (buildDirPy "fable") + copyFile (buildDirPy "fable/system.text.py") (buildDirPy "fable/system_text.py") //copyFile (buildDirPy "fable/async.py") (buildDirPy "fable/async_.py") - //removeFile (buildDirPy "fable/async.py") + removeFile (buildDirPy "fable/system.text.py") runInDir buildDirPy ("python3 --version") runInDir buildDirPy ("python3 ./setup.py develop") diff --git a/src/fable-library-py/fable/Fable.Library.fsproj b/src/fable-library-py/fable/Fable.Library.fsproj index 2ef2d81128..3235eb006d 100644 --- a/src/fable-library-py/fable/Fable.Library.fsproj +++ b/src/fable-library-py/fable/Fable.Library.fsproj @@ -22,7 +22,7 @@ - + diff --git a/src/fable-library-py/fable/async_.py b/src/fable-library-py/fable/async_.py index 4ca1e0290f..6bd0146315 100644 --- a/src/fable-library-py/fable/async_.py +++ b/src/fable-library-py/fable/async_.py @@ -1,5 +1,14 @@ from threading import Timer -from .async_builder import CancellationToken, IAsyncContext, OperationCanceledError, Trampoline, protected_cont +from .async_builder import ( + CancellationToken, + IAsyncContext, + OperationCanceledError, + Trampoline, + protected_bind, + protected_cont, + protected_return, +) +from .choice import Choice_makeChoice1Of2, Choice_makeChoice2Of2 class Async: @@ -52,6 +61,24 @@ def timeout(): return protected_cont(cont) +def ignore(computation): + return protected_bind(computation, lambda _x: protected_return()) + + +def catchAsync(work): + def cont(ctx: IAsyncContext): + def on_success(x): + ctx.on_success(Choice_makeChoice1Of2(x)) + + def on_error(err): + ctx.on_success(Choice_makeChoice2Of2(err)) + + ctx_ = IAsyncContext.create(on_success, on_error, ctx.on_cancel, ctx.trampoline, ctx.cancel_token) + work(ctx_) + + return protected_cont(cont) + + def fromContinuations(f): def cont(ctx: IAsyncContext): f([ctx.on_success, ctx.on_error, ctx.on_cancel]) diff --git a/src/fable-library-py/fable/async_builder.py b/src/fable-library-py/fable/async_builder.py index 4dead199db..6c2acbb7f8 100644 --- a/src/fable-library-py/fable/async_builder.py +++ b/src/fable-library-py/fable/async_builder.py @@ -72,7 +72,7 @@ def on_cancel(self, ct): @property @abstractmethod - def trapoline(self) -> "Trampoline": + def trampoline(self) -> "Trampoline": ... @property diff --git a/src/fable-library-py/fable/option.py b/src/fable-library-py/fable/option.py index 5216db7eb3..b688bffc64 100644 --- a/src/fable-library-py/fable/option.py +++ b/src/fable-library-py/fable/option.py @@ -1,6 +1,11 @@ from expression.core import option +class Some: + def __init__(self, value): + self.value = value + + def defaultArg(value, default_value): return option.default_arg(option.of_optional(value), default_value) @@ -17,9 +22,8 @@ def toArray(value): return option.of_optional(value).to_list() -__all__ = [ - "defaultArg", - "defaultArgWith", - "map", - "toArray" -] +def some(x): + return Some(x) if x is None or isinstance(x, Some) else x + + +__all__ = ["defaultArg", "defaultArgWith", "map", "toArray"] diff --git a/tests/Python/TestAsync.fs b/tests/Python/TestAsync.fs index 54035978fd..8c2ed34b54 100644 --- a/tests/Python/TestAsync.fs +++ b/tests/Python/TestAsync.fs @@ -154,3 +154,28 @@ let ``test Async StartWithContinuations works`` () = equal "success" !res1 equal "error" !res2 equal "cancelled" !res3 + +[] +let ``test Async.Catch works`` () = + let assign res = function + | Choice1Of2 msg -> res := msg + | Choice2Of2 (ex: Exception) -> res := "ERROR: " + ex.Message + let res1 = ref "" + let res2 = ref "" + async { + let! x1 = successWork |> Async.Catch + assign res1 x1 + let! x2 = errorWork |> Async.Catch + assign res2 x2 + } |> Async.StartImmediate + equal "success" !res1 + equal "ERROR: error" !res2 + +[] +let ``test Async.Ignore works`` () = + let res = ref false + async { + do! successWork |> Async.Ignore + res := true + } |> Async.StartImmediate + equal true !res From 6b1533146ec63cd1a2b5e4f037df5e0fac5e4983 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 17 May 2021 10:48:40 +0200 Subject: [PATCH 096/145] Nonlocal bugfix --- src/Fable.Transforms/Python/Babel2Python.fs | 15 +++++++++------ src/fable-library-py/fable/option.py | 9 ++++++++- src/fable-library-py/fable/util.py | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index 695a1eaa22..ef49660110 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -770,20 +770,21 @@ module Util = | IfStatement (test = test; consequent = consequent; alternate = alternate) -> let test, stmts = com.TransformAsExpr(ctx, test) - let body = + let body, nonLocals = com.TransformAsStatements(ctx, returnStrategy, consequent) |> transformBody ReturnStrategy.NoReturn + |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) - let orElse, nonLocals = + let orElse, nonLocals' = match alternate with | Some alt -> com.TransformAsStatements(ctx, returnStrategy, alt) |> transformBody ReturnStrategy.NoReturn - |> List.splitWhile (function | Statement.NonLocal (_) -> false | _ -> true ) + |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) | _ -> [], [] - [ yield! nonLocals @ stmts; Statement.if' (test = test, body = body, orelse = orElse) ] + [ yield! nonLocals @ nonLocals' @ stmts; Statement.if' (test = test, body = body, orelse = orElse) ] | WhileStatement (test = test; body = body) -> let expr, stmts = com.TransformAsExpr(ctx, test) @@ -795,9 +796,11 @@ module Util = | TryStatement (block = block; handler = handler; finalizer = finalizer) -> let body = com.TransformAsStatements(ctx, returnStrategy, block) - let finalBody = + let finalBody, nonLocals = finalizer |> Option.map (fun f -> com.TransformAsStatements(ctx, returnStrategy, f)) + |> Option.defaultValue [] + |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) let handlers = match handler with @@ -821,7 +824,7 @@ module Util = handlers | _ -> [] - [ Statement.try' (body = body, handlers = handlers, ?finalBody = finalBody) ] + [ yield! nonLocals; Statement.try' (body = body, handlers = handlers, finalBody = finalBody) ] | SwitchStatement (discriminant = discriminant; cases = cases) -> let value, stmts = com.TransformAsExpr(ctx, discriminant) diff --git a/src/fable-library-py/fable/option.py b/src/fable-library-py/fable/option.py index b688bffc64..443217ad32 100644 --- a/src/fable-library-py/fable/option.py +++ b/src/fable-library-py/fable/option.py @@ -26,4 +26,11 @@ def some(x): return Some(x) if x is None or isinstance(x, Some) else x -__all__ = ["defaultArg", "defaultArgWith", "map", "toArray"] +def value(x): + if x is None: + raise Exception("Option has no value") + else: + return x.value if isinstance(x, Some) else x + + +__all__ = ["defaultArg", "defaultArgWith", "map", "some", "Some", "toArray", "value"] diff --git a/src/fable-library-py/fable/util.py b/src/fable-library-py/fable/util.py index 810fc150da..480608ee0c 100644 --- a/src/fable-library-py/fable/util.py +++ b/src/fable-library-py/fable/util.py @@ -240,3 +240,19 @@ def uncurry(arity: int, f: Callable): setattr(f, CURRIED_KEY, f) return uncurriedFn + + +def isArrayLike(x): + return hasattr(x, "__len__") + + +def isDisposable(x): + return x is not None and isinstance(x, IDisposable) + + +def toIterator(x): + return iter(x) + + +def structuralHash(x): + return hash(x) From 48af9b81398dfd62baa0a1563207960397459823 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 25 May 2021 08:27:39 +0200 Subject: [PATCH 097/145] Fix merge conflict --- src/Fable.Cli/Entry.fs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Fable.Cli/Entry.fs b/src/Fable.Cli/Entry.fs index de2a6d4003..838053365b 100644 --- a/src/Fable.Cli/Entry.fs +++ b/src/Fable.Cli/Entry.fs @@ -138,10 +138,6 @@ type Runner = // TODO: Remove this check when typed arrays are compatible with typescript |> Result.bind (fun projFile -> let language = argLanguage args -<<<<<<< HEAD - -======= ->>>>>>> 18447c3f5c2ee0ff68d5e610124bf9242417124a let typedArrays = tryFlag "--typedArrays" args |> Option.defaultValue true if language = TypeScript && typedArrays then Error("Typescript output is currently not compatible with typed arrays, pass: --typedArrays false") From 6de0250d993b2456fa38aad7cc77a57a8d485f20 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 27 May 2021 21:12:27 +0200 Subject: [PATCH 098/145] Initial version of Fable2Python.fs (wip) --- src/Fable.Core/Fable.Core.PyInterop.fs | 16 +- src/Fable.Transforms/Fable.Transforms.fsproj | 1 + src/Fable.Transforms/Python/Babel2Python.fs | 69 +- src/Fable.Transforms/Python/Fable2Python.fs | 2117 ++++++++++++++++++ src/Fable.Transforms/Python/Python.fs | 195 +- src/Fable.Transforms/Python/PythonPrinter.fs | 21 +- 6 files changed, 2306 insertions(+), 113 deletions(-) create mode 100644 src/Fable.Transforms/Python/Fable2Python.fs diff --git a/src/Fable.Core/Fable.Core.PyInterop.fs b/src/Fable.Core/Fable.Core.PyInterop.fs index 45e3073315..6bf27ffd9c 100644 --- a/src/Fable.Core/Fable.Core.PyInterop.fs +++ b/src/Fable.Core/Fable.Core.PyInterop.fs @@ -56,11 +56,21 @@ let keyValueList (caseRule: CaseRules) (li: 'T seq): obj = pyNative let pyOptions<'T> (f: 'T->unit): 'T = pyNative /// Create an empty JS object: {} -///let createEmpty<'T> : 'T = pyNative +let createEmpty<'T> : 'T = pyNative /// Get the Py function constructor for class types let pyConstructor<'T> : obj = pyNative +[] +let pyTypeof (x: obj): string = pyNative + +[] +let pyInstanceof (x: obj) (cons: obj): bool = pyNative + +/// Check if object is callable, i.e a function. +[] +let callable(x: obj) = pyNative + /// Makes an expression the default export for the JS module. /// Used to interact with JS tools that require a default export. /// ATTENTION: This statement must appear on the root level of the file module. @@ -107,11 +117,11 @@ let [] jsThis<'T> : 'T = pyNative let [] isIn (key: string) (target: obj): bool = pyNative /// JS non-strict null checking -let [] isNullOrUndefined (target: obj): bool = pyNative +let [] isNullOrUndefined (target: obj): bool = pyNative /// Use it when importing a constructor from a JS library. type [] JsConstructor = - [] + [] abstract Create: []args: obj[] -> obj /// Use it when importing dynamic functions from JS. If you need a constructor, use `JsConstructor`. diff --git a/src/Fable.Transforms/Fable.Transforms.fsproj b/src/Fable.Transforms/Fable.Transforms.fsproj index bee4c6f2ae..3e25f17c84 100644 --- a/src/Fable.Transforms/Fable.Transforms.fsproj +++ b/src/Fable.Transforms/Fable.Transforms.fsproj @@ -22,6 +22,7 @@ + diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index ef49660110..a98e867456 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -82,7 +82,7 @@ module Helpers = let rewriteFableImport moduleName = //printfn "ModuleName: %s" moduleName let _reFableLib = - Regex(".*\/fable-library.*\/(?[^\/]*)\.js", RegexOptions.Compiled) + Regex(".*(\/fable-library.*)?\/(?[^\/]*)\.(js|fs)", RegexOptions.Compiled) let m = _reFableLib.Match(moduleName) let dashify = applyCaseRule CaseRules.SnakeCase @@ -95,15 +95,17 @@ module Helpers = let moduleName = String.concat "." [ "fable"; pymodule ] - printfn "-> Module: %A" moduleName + //printfn "-> Module: %A" moduleName moduleName else // Modules should have short, all-lowercase names. let moduleName = - moduleName.Replace("/", "") - |> dashify + let name = + moduleName.Replace("/", "") + |> dashify + string(name.[0]) + name.[1..].Replace(".", "_") - printfn "-> Module: %A" moduleName + //printfn "-> Module: %A" moduleName moduleName let unzipArgs (args: (Python.Expression * Python.Statement list) list): Python.Expression list * Python.Statement list = @@ -264,6 +266,7 @@ module Util = | Expression.Identifier(Identifier(name="toString")) -> com.GetIdentifier(ctx, "__str__") | Expression.Identifier (id) -> com.GetIdentifier(ctx, id.Name) + // E.g ["System.Collections.Generic.IEnumerator`1.get_Current"]() { ... } | Expression.Literal(Literal.StringLiteral(StringLiteral(value=name))) -> com.GetIdentifier(ctx, name) | MemberExpression(object=Expression.Identifier(Identifier(name="Symbol")); property=Expression.Identifier(Identifier(name="iterator"))) -> @@ -343,11 +346,11 @@ module Util = let left, leftStmts = com.TransformAsExpr(ctx, left) let right = Expression.name("None") Expression.compare (left, [ Python.IsNot ], [ right ]), leftStmts - | BinaryExpression (left = left; operator = operator; right = right) -> - let left, leftStmts = com.TransformAsExpr(ctx, left) - let right, rightStmts = com.TransformAsExpr(ctx, right) + | BinaryExpression (left = left'; operator = operator; right = right') -> + let left, leftStmts = com.TransformAsExpr(ctx, left') + let right, rightStmts = com.TransformAsExpr(ctx, right') - let toBinOp op = Expression.binOp (left, op, right), leftStmts @ rightStmts + let toBinOp (op: Operator) = Expression.binOp (left, op, right), leftStmts @ rightStmts let toCompare op = Expression.compare (left, [ op ], [ right ]), leftStmts @ rightStmts let toCall name = @@ -367,8 +370,19 @@ module Util = | "|" -> BitOr |> toBinOp | "^" -> BitXor |> toBinOp | "&" -> BitAnd |> toBinOp - | "===" | "==" -> Eq |> toCompare - | "!==" | "!=" -> NotEq |> toCompare + | "===" -> + match right' with + | Expression.Identifier(_) + | Literal(_) -> Eq |> toCompare + | _ -> Is |> toCompare + | "==" -> Eq |> toCompare + | "!==" -> + match right' with + | Expression.Identifier(_) + | Literal(_) -> NotEq |> toCompare + | _ -> IsNot |> toCompare + + | "!=" -> NotEq |> toCompare | ">" -> Gt |> toCompare | ">=" -> GtE |> toCompare | "<" -> Lt |> toCompare @@ -396,7 +410,7 @@ module Util = match op, arg with | Some op, _ -> Expression.unaryOp (op, operand), stmts | None, Literal(NumericLiteral(value=0.)) -> - Expression.name (id = Python.Identifier("None")), stmts + Expression.name(identifier = Python.Identifier("None")), stmts | _ -> operand, stmts @@ -476,7 +490,7 @@ module Util = Expression.constant (value = value), [] | Expression.Identifier (Identifier (name = name)) -> let name = com.GetIdentifier(ctx, name) - Expression.name (id = name), [] + Expression.name(identifier = name), [] | NewExpression (callee = Expression.Identifier(Identifier(name="Int32Array")); arguments = args) -> match args with | [| arg |] -> @@ -515,8 +529,13 @@ module Util = com.GetImportExpr(ctx, "collections", "namedtuple") |> ignore let keys = keys - |> List.map (function | Expression.Name { Id=Python.Identifier name} -> Expression.constant(name) | ex -> ex) - + |> List.map (function + | Expression.Name { Id=Python.Identifier name } -> Expression.constant(Helpers.clean name) + | Expression.Constant(value=value) -> + match value with + | :? string as name -> Expression.constant(Helpers.clean name) + | _ -> Expression.constant(value) + | ex -> ex) Expression.call( Expression.call( Expression.name("namedtuple"), [ Expression.constant("object"); Expression.list(keys)] @@ -702,7 +721,7 @@ module Util = else [] - [ Expression.name (id = target, ctx = Store) ], stmts + [ Expression.name(identifier = target, ctx = Store) ], stmts // a.b = c | MemberExpression (property = Expression.Identifier (id); object = object; computed=false) -> let attr = com.GetIdentifier(ctx, id.Name) @@ -750,7 +769,7 @@ module Util = [ for (VariableDeclarator (id = id; init = init)) in declarations do let targets: Python.Expression list = let name = com.GetIdentifier(ctx, id.Name) - [ Expression.name (id = name, ctx = Store) ] + [ Expression.name(identifier = name, ctx = Store) ] match init with | Some value -> @@ -813,15 +832,8 @@ module Util = // Insert a ex.message = str(ex) for all aliased exceptions. let identifier = Python.Identifier(parm.Name) - // let idName = Name.Create(identifier, Load) - // let message = Identifier("message") - // let trg = Attribute.Create(idName, message, Store) - // let value = Call.Create(Name.Create(Identifier("str"), Load), [idName]) - // let msg = Assign.Create([trg], value) - // let body = msg :: body - let handlers = [ ExceptHandler.exceptHandler (``type`` = exn, name = identifier, body = body) ] - - handlers + [ ExceptHandler.exceptHandler (``type`` = exn, name = identifier, body = body) ] + | _ -> [] [ yield! nonLocals; Statement.try' (body = body, handlers = handlers, finalBody = finalBody) ] @@ -921,7 +933,7 @@ module Util = let targets: Python.Expression list = let name = com.GetIdentifier(ctx, id.Name) ctx.UsedNames.GlobalScope.Add id.Name |> ignore - [ Expression.name (id = name, ctx = Store) ] + [ Expression.name (identifier = name, ctx = Store) ] yield! stmts yield Statement.assign (targets = targets, value = value) @@ -932,7 +944,8 @@ module Util = yield! transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc | _ -> failwith $"Unhandled Declaration: {decl}" - | Babel.ImportDeclaration (specifiers, source) -> yield! com.TransformAsImports(ctx, specifiers, source) + | Babel.ImportDeclaration (specifiers, source) -> + yield! com.TransformAsImports(ctx, specifiers, source) | Babel.PrivateModuleDeclaration (statement = statement) -> yield! com.TransformAsStatements(ctx, ReturnStrategy.Return, statement) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs new file mode 100644 index 0000000000..13058f7df2 --- /dev/null +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -0,0 +1,2117 @@ +module rec Fable.Transforms.Fable2Python + +open Fable +open Fable.AST +open Fable.AST.Python +open System.Collections.Generic +open System.Text.RegularExpressions +open Fable.Naming +open Fable.Core + +type ReturnStrategy = + | Return + | ReturnUnit + | Assign of Expression + | Target of Identifier + +type Import = + { Selector: string + LocalIdent: string option + Path: string } + +type ITailCallOpportunity = + abstract Label: string + abstract Args: string list + abstract IsRecursiveRef: Fable.Expr -> bool + +type UsedNames = + { RootScope: HashSet + DeclarationScopes: HashSet + CurrentDeclarationScope: HashSet } + +type Context = + { File: Fable.File + UsedNames: UsedNames + DecisionTargets: (Fable.Ident list * Fable.Expr) list + HoistVars: Fable.Ident list -> bool + TailCallOpportunity: ITailCallOpportunity option + OptimizeTailCall: unit -> unit + ScopedTypeParams: Set } + +type IPythonCompiler = + inherit Compiler + abstract GetIdentifier: ctx: Context * name: string -> Python.Identifier + abstract GetAllImports: unit -> seq + abstract GetImportExpr: Context * selector: string * path: string * SourceLocation option -> Expression + abstract TransformAsExpr: Context * Fable.Expr -> Expression * Statement list + abstract TransformAsStatements: Context * ReturnStrategy option * Fable.Expr -> Statement list + abstract TransformImport: Context * selector:string * path:string -> Expression + abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> Arg list * Statement list + + abstract WarnOnlyOnce: string * ?range: SourceLocation -> unit + +// TODO: All things that depend on the library should be moved to Replacements +// to become independent of the specific implementation +module Lib = + let libCall (com: IPythonCompiler) ctx r moduleName memberName args = + Expression.call(com.TransformImport(ctx, memberName, getLibPath com moduleName), args, ?loc=r) + + let libConsCall (com: IPythonCompiler) ctx r moduleName memberName args = + Expression.call(com.TransformImport(ctx, memberName, getLibPath com moduleName), args, ?loc=r) + + let libValue (com: IPythonCompiler) ctx moduleName memberName = + com.TransformImport(ctx, memberName, getLibPath com moduleName) + + let tryJsConstructor (com: IPythonCompiler) ctx ent = + match Replacements.tryJsConstructor com ent with + | Some e -> com.TransformAsExpr(ctx, e) |> Some + | None -> None + + let jsConstructor (com: IPythonCompiler) ctx ent = + let entRef = Replacements.jsConstructor com ent + com.TransformAsExpr(ctx, entRef) + +// TODO: This is too implementation-dependent, ideally move it to Replacements +module Reflection = + open Lib + + let private libReflectionCall (com: IPythonCompiler) ctx r memberName args = + libCall com ctx r "Reflection" (memberName + "_type") args + + let private transformRecordReflectionInfo com ctx r (ent: Fable.Entity) generics = + // TODO: Refactor these three bindings to reuse in transformUnionReflectionInfo + let fullname = ent.FullName + let fullnameExpr = Expression.constant(fullname) + let genMap = + let genParamNames = ent.GenericParameters |> List.mapToArray (fun x -> x.Name) |> Seq.toList + List.zip genParamNames generics |> Map + let fields, stmts = + ent.FSharpFields |> Seq.map (fun fi -> + let typeInfo, stmts = transformTypeInfo com ctx r genMap fi.FieldType + (Expression.list([ Expression.constant(fi.Name); typeInfo ])), stmts) + |> Seq.toList + |> Helpers.unzipArgs + let fields = Expression.lambda(Arguments.arguments [], Expression.list(fields)) + let js, stmts' = jsConstructor com ctx ent + [ fullnameExpr; Expression.list(generics); js; fields ] + |> libReflectionCall com ctx None "record", stmts @ stmts' + + let private transformUnionReflectionInfo com ctx r (ent: Fable.Entity) generics = + let fullname = ent.FullName + let fullnameExpr = Expression.constant(fullname) + let genMap = + let genParamNames = ent.GenericParameters |> List.map (fun x -> x.Name) |> Seq.toList + List.zip genParamNames generics |> Map + let cases = + ent.UnionCases |> Seq.map (fun uci -> + uci.UnionCaseFields |> List.map (fun fi -> + Expression.list([ + fi.Name |> Expression.constant + let expr, stmts = transformTypeInfo com ctx r genMap fi.FieldType + expr + ])) + |> Expression.list + ) |> Seq.toList + let cases = Expression.lambda(Arguments.arguments [], Expression.list(cases)) + let js, stmts = jsConstructor com ctx ent + [ fullnameExpr; Expression.list(generics); js; cases ] + |> libReflectionCall com ctx None "union", stmts + + let transformTypeInfo (com: IPythonCompiler) ctx r (genMap: Map) t: Expression * Statement list = + let primitiveTypeInfo name = + libValue com ctx "Reflection" (name + "_type") + let numberInfo kind = + getNumberKindName kind + |> primitiveTypeInfo + let nonGenericTypeInfo fullname = + [ Expression.constant(fullname) ] + |> libReflectionCall com ctx None "class" + let resolveGenerics generics: Expression list * Statement list = + generics |> Array.map (transformTypeInfo com ctx r genMap) |> List.ofArray |> Helpers.unzipArgs + let genericTypeInfo name genArgs = + let resolved, stmts = resolveGenerics genArgs + libReflectionCall com ctx None name resolved, stmts + let genericEntity (fullname: string) (generics: Expression list) = + libReflectionCall com ctx None "class" [ + Expression.constant(fullname) + if not(List.isEmpty generics) then + Expression.list(generics) + ] + match t with + | Fable.Any -> primitiveTypeInfo "obj", [] + | Fable.GenericParam name -> + match Map.tryFind name genMap with + | Some t -> t, [] + | None -> + Replacements.genericTypeInfoError name |> addError com [] r + Expression.none(), [] + | Fable.Unit -> primitiveTypeInfo "unit", [] + | Fable.Boolean -> primitiveTypeInfo "bool", [] + | Fable.Char -> primitiveTypeInfo "char", [] + | Fable.String -> primitiveTypeInfo "string", [] + | Fable.Enum entRef -> + let ent = com.GetEntity(entRef) + let mutable numberKind = Int32 + let cases = + ent.FSharpFields |> Seq.choose (fun fi -> + // F# seems to include a field with this name in the underlying type + match fi.Name with + | "value__" -> + match fi.FieldType with + | Fable.Number kind -> numberKind <- kind + | _ -> () + None + | name -> + let value = match fi.LiteralValue with Some v -> System.Convert.ToDouble v | None -> 0. + Expression.list([ Expression.constant(name); Expression.constant(value) ]) |> Some) + |> Seq.toList + |> Expression.list + [ Expression.constant(entRef.FullName); numberInfo numberKind; cases ] + |> libReflectionCall com ctx None "enum", [] + | Fable.Number kind -> + numberInfo kind, [] + | Fable.LambdaType(argType, returnType) -> + genericTypeInfo "lambda" [|argType; returnType|] + | Fable.DelegateType(argTypes, returnType) -> + genericTypeInfo "delegate" ([|yield! argTypes; yield returnType|]) + | Fable.Tuple genArgs -> genericTypeInfo "tuple" (List.toArray genArgs) + | Fable.Option genArg -> genericTypeInfo "option" [|genArg|] + | Fable.Array genArg -> genericTypeInfo "array" [|genArg|] + | Fable.List genArg -> genericTypeInfo "list" [|genArg|] + | Fable.Regex -> nonGenericTypeInfo Types.regex, [] + | Fable.MetaType -> nonGenericTypeInfo Types.type_, [] + | Fable.AnonymousRecordType(fieldNames, genArgs) -> + let genArgs, stmts = resolveGenerics (List.toArray genArgs) + List.zip (List.ofArray fieldNames) genArgs + |> List.map (fun (k, t) -> Expression.list[ Expression.constant(k); t ]) + |> libReflectionCall com ctx None "anonRecord", stmts + | Fable.DeclaredType(entRef, generics) -> + let fullName = entRef.FullName + match fullName, generics with + | Replacements.BuiltinEntity kind -> + match kind with + | Replacements.BclGuid + | Replacements.BclTimeSpan + | Replacements.BclDateTime + | Replacements.BclDateTimeOffset + | Replacements.BclTimer + | Replacements.BclInt64 + | Replacements.BclUInt64 + | Replacements.BclDecimal + | Replacements.BclBigInt -> genericEntity fullName [], [] + | Replacements.BclHashSet gen + | Replacements.FSharpSet gen -> + let gens, stmts = transformTypeInfo com ctx r genMap gen + genericEntity fullName [ gens ], stmts + | Replacements.BclDictionary(key, value) + | Replacements.BclKeyValuePair(key, value) + | Replacements.FSharpMap(key, value) -> + let keys, stmts = transformTypeInfo com ctx r genMap key + let values, stmts' = transformTypeInfo com ctx r genMap value + genericEntity fullName [ + keys + values + ], stmts @ stmts' + | Replacements.FSharpResult(ok, err) -> + let ent = com.GetEntity(entRef) + let ok', stmts = transformTypeInfo com ctx r genMap ok + let err', stmts' = transformTypeInfo com ctx r genMap err + let expr, stmts'' =transformUnionReflectionInfo com ctx r ent [ ok'; err' ] + expr, stmts @ stmts' @ stmts'' + | Replacements.FSharpChoice gen -> + let ent = com.GetEntity(entRef) + let gen, stmts = List.map (transformTypeInfo com ctx r genMap) gen |> Helpers.unzipArgs + let expr, stmts' = gen |> transformUnionReflectionInfo com ctx r ent + expr, stmts @ stmts' + | Replacements.FSharpReference gen -> + let ent = com.GetEntity(entRef) + let gen, stmts = transformTypeInfo com ctx r genMap gen + let expr, stmts' = [ gen ] |> transformRecordReflectionInfo com ctx r ent + expr, stmts @ stmts' + | _ -> + let ent = com.GetEntity(entRef) + let generics, stmts = generics |> List.map (transformTypeInfo com ctx r genMap) |> Helpers.unzipArgs + /// Check if the entity is actually declared in JS code + if ent.IsInterface + || FSharp2Fable.Util.isErasedOrStringEnumEntity ent + || FSharp2Fable.Util.isGlobalOrImportedEntity ent + || FSharp2Fable.Util.isReplacementCandidate ent then + genericEntity ent.FullName generics, stmts + else + let reflectionMethodExpr = FSharp2Fable.Util.entityRefWithSuffix com ent Naming.reflectionSuffix + let callee, stmts' = com.TransformAsExpr(ctx, reflectionMethodExpr) + Expression.call(callee, generics), stmts @ stmts' + + let transformReflectionInfo com ctx r (ent: Fable.Entity) generics = + if ent.IsFSharpRecord then + transformRecordReflectionInfo com ctx r ent generics + elif ent.IsFSharpUnion then + transformUnionReflectionInfo com ctx r ent generics + else + let fullname = ent.FullName + let exprs, stmts = + [ + yield Expression.constant(fullname), [] + match generics with + | [] -> yield Util.undefined None, [] + | generics -> yield Expression.list(generics), [] + match tryJsConstructor com ctx ent with + | Some (cons, stmts) -> yield cons, stmts + | None -> () + match ent.BaseType with + | Some d -> + let genMap = + Seq.zip ent.GenericParameters generics + |> Seq.map (fun (p, e) -> p.Name, e) + |> Map + yield Fable.DeclaredType(d.Entity, d.GenericArgs) + |> transformTypeInfo com ctx r genMap + | None -> () + ] + |> Helpers.unzipArgs + exprs + |> libReflectionCall com ctx r "class", stmts + + let private ofString s = Expression.constant(s) + let private ofArray babelExprs = Expression.list(babelExprs) + + let transformTypeTest (com: IPythonCompiler) ctx range expr (typ: Fable.Type): Expression * Statement list = + let warnAndEvalToFalse msg = + "Cannot type test (evals to false): " + msg + |> addWarning com [] range + Expression.constant(false) + + let jsTypeof (primitiveType: string) (Util.TransformExpr com ctx (expr, stmts)): Expression * Statement list = + let typeof = Expression.unaryOp(UnaryTypeof, expr) + Expression.binOp(typeof, BinaryEqualStrict, Expression.constant(primitiveType), ?loc=range), stmts + + let jsInstanceof consExpr (Util.TransformExpr com ctx (expr, stmts)): Expression * Statement list= + Expression.binOp(expr, BinaryInstanceOf, consExpr, ?loc=range), stmts + + match typ with + | Fable.Any -> Expression.constant(true), [] + | Fable.Unit -> + let expr, stmts = com.TransformAsExpr(ctx, expr) + Expression.compare(expr, [ Is ], [ Util.undefined None ], ?loc=range), stmts + | Fable.Boolean -> jsTypeof "boolean" expr + | Fable.Char | Fable.String _ -> jsTypeof "string" expr + | Fable.Number _ | Fable.Enum _ -> jsTypeof "number" expr + | Fable.Regex -> jsInstanceof (Expression.identifier("RegExp")) expr + | Fable.LambdaType _ | Fable.DelegateType _ -> jsTypeof "function" expr + | Fable.Array _ | Fable.Tuple _ -> + let expr, stmts = com.TransformAsExpr(ctx, expr) + libCall com ctx None "Util" "isArrayLike" [ expr ], stmts + | Fable.List _ -> + jsInstanceof (libValue com ctx "List" "FSharpList") expr + | Fable.AnonymousRecordType _ -> + warnAndEvalToFalse "anonymous records", [] + | Fable.MetaType -> + jsInstanceof (libValue com ctx "Reflection" "TypeInfo") expr + | Fable.Option _ -> warnAndEvalToFalse "options", [] // TODO + | Fable.GenericParam _ -> warnAndEvalToFalse "generic parameters", [] + | Fable.DeclaredType (ent, genArgs) -> + match ent.FullName with + | Types.idisposable -> + match expr with + | MaybeCasted(ExprType(Fable.DeclaredType (ent2, _))) + when com.GetEntity(ent2) |> FSharp2Fable.Util.hasInterface Types.idisposable -> + Expression.constant(true), [] + | _ -> + let expr, stmts = com.TransformAsExpr(ctx, expr) + libCall com ctx None "Util" "isDisposable" [ expr ], stmts + | Types.ienumerable -> + let expr, stmts = com.TransformAsExpr(ctx, expr) + [ expr ] + |> libCall com ctx None "Util" "isIterable", stmts + | Types.array -> + let expr, stmts = com.TransformAsExpr(ctx, expr) + [ expr ] + |> libCall com ctx None "Util" "isArrayLike", stmts + | Types.exception_ -> + let expr, stmts = com.TransformAsExpr(ctx, expr) + [ expr ] + |> libCall com ctx None "Types" "isException", stmts + | _ -> + let ent = com.GetEntity(ent) + if ent.IsInterface then + warnAndEvalToFalse "interfaces", [] + else + match tryJsConstructor com ctx ent with + | Some (cons, stmts) -> + if not(List.isEmpty genArgs) then + com.WarnOnlyOnce("Generic args are ignored in type testing", ?range=range) + let expr, stmts' = jsInstanceof cons expr + expr, stmts @ stmts' + | None -> + warnAndEvalToFalse ent.FullName, [] + +module Helpers = + let index = (Seq.initInfinite id).GetEnumerator() + + let getUniqueIdentifier (name: string): Python.Identifier = + do index.MoveNext() |> ignore + let idx = index.Current.ToString() + Python.Identifier($"{name}_{idx}") + + /// Replaces all '$' and `.`with '_' + let clean (name: string) = + //printfn $"clean: {name}" + match name with + | "this" -> "self" + | "async" -> "async_" + | "from" -> "from_" + | "class" -> "class_" + | "for" -> "for_" + | "Math" -> "math" + | "Error" -> "Exception" + | "toString" -> "str" + | "len" -> "len_" + | "Map" -> "dict" + | _ -> + name.Replace('$', '_').Replace('.', '_').Replace('`', '_') + + let rewriteFableImport moduleName = + //printfn "ModuleName: %s" moduleName + let _reFableLib = + Regex(".*(\/fable-library.*)?\/(?[^\/]*)\.(js|fs)", RegexOptions.Compiled) + + let m = _reFableLib.Match(moduleName) + let dashify = applyCaseRule CaseRules.SnakeCase + + if m.Groups.Count > 1 then + let pymodule = + m.Groups.["module"].Value + |> dashify + |> clean + + let moduleName = String.concat "." [ "fable"; pymodule ] + + //printfn "-> Module: %A" moduleName + moduleName + else + // Modules should have short, all-lowercase names. + let moduleName = + let name = + moduleName.Replace("/", "") + |> dashify + string(name.[0]) + name.[1..].Replace(".", "_") + + //printfn "-> Module: %A" moduleName + moduleName + + let unzipArgs (args: (Python.Expression * Python.Statement list) list): Python.Expression list * Python.Statement list = + let stmts = args |> List.map snd |> List.collect id + let args = args |> List.map fst + args, stmts + + /// A few statements in the generated Babel AST do not produce any effect, and will not be printet. But they are + /// left in the AST and we need to skip them since they are not valid for Python (either). + let isProductiveStatement (stmt: Python.Statement) = + let rec hasNoSideEffects (e: Python.Expression) = + //printfn $"hasNoSideEffects: {e}" + + match e with + | Constant _ -> true + | Dict { Keys = keys } -> keys.IsEmpty // Empty object + | Name _ -> true // E.g `void 0` is translated to Name(None) + | _ -> false + + match stmt with + | Expr expr -> + if hasNoSideEffects expr.Value then + None + else + Some stmt + | _ -> Some stmt + + +module Util = + open Lib + open Reflection + + let getIdentifier (com: IPythonCompiler) (ctx: Context) (name: string) = + let name = Helpers.clean name + + // FIXME: + //match name with + //| "math" -> com.GetImportExpr(ctx, "math") |> ignore + //| _ -> () + + Python.Identifier name + + let (|TransformExpr|) (com: IPythonCompiler) ctx e : Expression * Statement list = + com.TransformAsExpr(ctx, e) + + let (|Function|_|) = function + | Fable.Lambda(arg, body, _) -> Some([arg], body) + | Fable.Delegate(args, body, _) -> Some(args, body) + | _ -> None + + let (|Lets|_|) = function + | Fable.Let(ident, value, body) -> Some([ident, value], body) + | Fable.LetRec(bindings, body) -> Some(bindings, body) + | _ -> None + + let discardUnitArg (args: Fable.Ident list) = + match args with + | [] -> [] + | [unitArg] when unitArg.Type = Fable.Unit -> [] + | [thisArg; unitArg] when thisArg.IsThisArgument && unitArg.Type = Fable.Unit -> [thisArg] + | args -> args + + let getUniqueNameInRootScope (ctx: Context) name = + let name = (name, Naming.NoMemberPart) ||> Naming.sanitizeIdent (fun name -> + ctx.UsedNames.RootScope.Contains(name) + || ctx.UsedNames.DeclarationScopes.Contains(name)) + ctx.UsedNames.RootScope.Add(name) |> ignore + name + + let getUniqueNameInDeclarationScope (ctx: Context) name = + let name = (name, Naming.NoMemberPart) ||> Naming.sanitizeIdent (fun name -> + ctx.UsedNames.RootScope.Contains(name) || ctx.UsedNames.CurrentDeclarationScope.Contains(name)) + ctx.UsedNames.CurrentDeclarationScope.Add(name) |> ignore + name + + type NamedTailCallOpportunity(_com: Compiler, ctx, name, args: Fable.Ident list) = + // Capture the current argument values to prevent delayed references from getting corrupted, + // for that we use block-scoped ES2015 variable declarations. See #681, #1859 + // TODO: Local unique ident names + let argIds = discardUnitArg args |> List.map (fun arg -> + getUniqueNameInDeclarationScope ctx (arg.Name + "_mut")) + interface ITailCallOpportunity with + member _.Label = name + member _.Args = argIds + member _.IsRecursiveRef(e) = + match e with Fable.IdentExpr id -> name = id.Name | _ -> false + + let getDecisionTarget (ctx: Context) targetIndex = + match List.tryItem targetIndex ctx.DecisionTargets with + | None -> failwithf "Cannot find DecisionTree target %i" targetIndex + | Some(idents, target) -> idents, target + + let rec isJsStatement ctx preferStatement (expr: Fable.Expr) = + match expr with + | Fable.Value _ | Fable.Import _ | Fable.IdentExpr _ + | Fable.Lambda _ | Fable.Delegate _ | Fable.ObjectExpr _ + | Fable.Call _ | Fable.CurriedApply _ | Fable.Curry _ | Fable.Operation _ + | Fable.Get _ | Fable.Test _ | Fable.TypeCast _ -> false + + | Fable.TryCatch _ + | Fable.Sequential _ | Fable.Let _ | Fable.LetRec _ | Fable.Set _ + | Fable.ForLoop _ | Fable.WhileLoop _ -> true + + // TODO: If IsJsSatement is false, still try to infer it? See #2414 + // /^\s*(break|continue|debugger|while|for|switch|if|try|let|const|var)\b/ + | Fable.Emit(i,_,_) -> i.IsJsStatement + + | Fable.DecisionTreeSuccess(targetIndex,_, _) -> + getDecisionTarget ctx targetIndex + |> snd |> isJsStatement ctx preferStatement + + // Make it also statement if we have more than, say, 3 targets? + // That would increase the chances to convert it into a switch + | Fable.DecisionTree(_,targets) -> + preferStatement + || List.exists (snd >> (isJsStatement ctx false)) targets + + | Fable.IfThenElse(_,thenExpr,elseExpr,_) -> + preferStatement || isJsStatement ctx false thenExpr || isJsStatement ctx false elseExpr + + + let addErrorAndReturnNull (com: Compiler) (range: SourceLocation option) (error: string) = + addError com [] range error + Expression.name (Python.Identifier("None")) + + let ident (id: Fable.Ident) = + Identifier(id.Name) + + let identAsExpr (id: Fable.Ident) = + Expression.identifier(id.Name, ?loc=id.Range) + + //let identAsPattern (id: Fable.Ident) = + // Pattern.identifier(id.Name, ?loc=id.Range) + + let thisExpr = + Expression.name ("self") + + let ofInt (i: int) = + Expression.constant(float i) + + let ofString (s: string) = + Expression.constant(s) + + let memberFromName (memberName: string): Expression * Statement list = + match memberName with + | "ToString" -> Expression.identifier("toString"), [] + | n when n.StartsWith("Symbol.iterator") -> + //Expression.memberExpression(Expression.identifier("Symbol"), Expression.identifier(n.[7..]), false), true + let name = Identifier "__iter__" + Expression.name(name), [] + | n when Naming.hasIdentForbiddenChars n -> Expression.constant(n), [] + | n -> Expression.identifier(n), [] + + + let get r left memberName = + let expr = Identifier memberName + Expression.attribute (value = left, attr = expr, ctx = Load) + + let getExpr r (object: Expression) (expr: Expression) = + let attr, stmts = + match expr with + | Expression.Constant(value=value) -> + match value with + | :? string as str -> memberFromName str + | _ -> failwith "Need to be string" + | e -> e, [] + let func = Expression.name("getattr") + Expression.call(func=func, args=[object; attr]), stmts + //Expression.attribute (value = object, attr = expr, ctx = Load), stmts + //Expression.memberExpression(object, expr, computed, ?loc=r) + + let rec getParts (parts: string list) (expr: Expression) = + match parts with + | [] -> expr + | m::ms -> get None expr m |> getParts ms + + let makeArray (com: IPythonCompiler) ctx exprs = + let expr, stmts = exprs |> List.map (fun e -> com.TransformAsExpr(ctx, e)) |> Helpers.unzipArgs + expr |> Expression.list, stmts + + let makeStringArray strings = + strings + |> List.map (fun x -> Expression.constant(x)) + |> Expression.list + + let makeJsObject (pairs: seq) = + pairs |> Seq.map (fun (name, value) -> + let prop, computed = memberFromName name + prop, value) + |> Seq.toList + |> List.unzip + |> Expression.dict + + let assign range left right = + Expression.namedExpr(left, right, ?loc=range) + + /// Immediately Invoked Function Expression + let iife (com: IPythonCompiler) ctx (expr: Fable.Expr) = + let _, body = com.TransformFunction(ctx, None, [], expr) + // Use an arrow function in case we need to capture `this` + let afe, stmts = makeArrowFunctionExpression [] body + Expression.call(afe, []), stmts + + let multiVarDeclaration (variables: (Identifier * Expression option) list) = + let varDeclarators = + // TODO: Log error if there're duplicated non-empty var declarations + variables + |> List.distinctBy (fun (Identifier(name=name), _value) -> name) + + [ + for id, init in varDeclarators do + let name = Expression.name(id, Store) + match init with + | Some value -> + Statement.assign ([name], value) + | None -> () ] + + let varDeclaration (var: Expression) (isMutable: bool) value = + Statement.assign([var], value) + + let restElement (var: Expression) = + Expression.starred(var) + + let callSuper (args: Expression list) = + let super = Expression.name (Python.Identifier("super().__init__")) + Expression.call(super, args) + + let callSuperAsStatement (args: Expression list) = + Statement.expr(callSuper args) + + let makeClassConstructor args body = + // ClassMember.classMethod(ClassImplicitConstructor, Expression.identifier("constructor"), args, body) + let name = Python.Identifier("__init__") + let args = Arguments.arguments (args = args) + FunctionDef.Create(name, args, body = body) + + let callFunction r funcExpr (args: Expression list) = + Expression.call(funcExpr, args, ?loc=r) + + let callFunctionWithThisContext r funcExpr (args: Expression list) = + let args = thisExpr::args + Expression.call(get None funcExpr "call", args, ?loc=r) + + let emitExpression range (txt: string) args = + Expression.emit (txt, args, ?loc=range) + + let undefined range: Expression = + Expression.unaryOp(UnaryVoid, Expression.constant(0.), ?loc=range) + + let getGenericTypeParams (types: Fable.Type list) = + let rec getGenParams = function + | Fable.GenericParam name -> [name] + | t -> t.Generics |> List.collect getGenParams + types + |> List.collect getGenParams + |> Set.ofList + + let uncurryLambdaType t = + let rec uncurryLambdaArgs acc = function + | Fable.LambdaType(paramType, returnType) -> + uncurryLambdaArgs (paramType::acc) returnType + | t -> List.rev acc, t + uncurryLambdaArgs [] t + + type MemberKind = + | ClassConstructor + | NonAttached of funcName: string + | Attached of isStatic: bool + + let getMemberArgsAndBody (com: IPythonCompiler) ctx kind hasSpread (args: Fable.Ident list) (body: Fable.Expr) = + let funcName, genTypeParams, args, body = + match kind, args with + | Attached(isStatic=false), (thisArg::args) -> + let genTypeParams = Set.difference (getGenericTypeParams [thisArg.Type]) ctx.ScopedTypeParams + let body = + // TODO: If ident is not captured maybe we can just replace it with "this" + if FableTransforms.isIdentUsed thisArg.Name body then + let thisKeyword = Fable.IdentExpr { thisArg with Name = "this" } + Fable.Let(thisArg, thisKeyword, body) + else body + None, genTypeParams, args, body + | Attached(isStatic=true), _ + | ClassConstructor, _ -> None, ctx.ScopedTypeParams, args, body + | NonAttached funcName, _ -> Some funcName, Set.empty, args, body + | _ -> None, Set.empty, args, body + + let ctx = { ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams genTypeParams } + let args, body = transformFunction com ctx funcName args body + + let args = + let len = List.length args + if not hasSpread || len = 0 then args + else [ + if len > 1 then + yield! args.[..len-2] + // FIXME: yield restElement args.[len-1] + ] + + args, body + + let getUnionCaseName (uci: Fable.UnionCase) = + match uci.CompiledName with Some cname -> cname | None -> uci.Name + + let getUnionExprTag (com: IPythonCompiler) ctx r (fableExpr: Fable.Expr) = + let expr, stmts = com.TransformAsExpr(ctx, fableExpr) + let expr, stmts' = getExpr r expr (Expression.constant("tag")) + expr, stmts @ stmts' + + /// Wrap int expressions with `| 0` to help optimization of JS VMs + let wrapIntExpression typ (e: Expression) = + match e, typ with + | Expression.Constant(_), _ -> e + // TODO: Unsigned ints seem to cause problems, should we check only Int32 here? + | _, Fable.Number(Int8 | Int16 | Int32) + | _, Fable.Enum _ -> + Expression.binOp(e, BinaryOrBitwise, Expression.constant(0.)) + | _ -> e + + let wrapExprInBlockWithReturn (e, stmts) = + stmts @ [ Statement.return'(e) ] + + let makeArrowFunctionExpression (args: Arg list) (body: Statement list) : Expression * Statement list = + let name = Helpers.getUniqueIdentifier "lifted" + let args = Arguments.arguments(args) + let func = FunctionDef.Create(name = name, args = args, body = body) + Expression.name (name), [ func ] + + let makeFunctionExpression name (args, (body: Expression), returnType, typeParamDecl): Expression * Statement list= + let id = name |> Option.map Identifier + let body = wrapExprInBlockWithReturn (body, []) + let name = Helpers.getUniqueIdentifier "lifted" + let func = + FunctionDef.Create(name = name, args = args, body = body) + //Expression.functionExpression(args, body, ?id=id, ?returnType=returnType, ?typeParameters=typeParamDecl) + Expression.name (name), [ func ] + + let optimizeTailCall (com: IPythonCompiler) (ctx: Context) range (tc: ITailCallOpportunity) args = + let rec checkCrossRefs tempVars allArgs = function + | [] -> tempVars + | (argId, _arg)::rest -> + let found = allArgs |> List.exists (FableTransforms.deepExists (function + | Fable.IdentExpr i -> argId = i.Name + | _ -> false)) + let tempVars = + if found then + let tempVarName = getUniqueNameInDeclarationScope ctx (argId + "_tmp") + Map.add argId tempVarName tempVars + else tempVars + checkCrossRefs tempVars allArgs rest + ctx.OptimizeTailCall() + let zippedArgs = List.zip tc.Args args + let tempVars = checkCrossRefs Map.empty args zippedArgs + let tempVarReplacements = tempVars |> Map.map (fun _ v -> makeIdentExpr v) + [ + // First declare temp variables + for (KeyValue(argId, tempVar)) in tempVars do + yield varDeclaration (Expression.identifier(tempVar)) false (Expression.identifier(argId)) + // Then assign argument expressions to the original argument identifiers + // See https://github.com/fable-compiler/Fable/issues/1368#issuecomment-434142713 + for (argId, arg) in zippedArgs do + let arg = FableTransforms.replaceValues tempVarReplacements arg + let arg, stmts = com.TransformAsExpr(ctx, arg) + yield! stmts + yield assign None (Expression.identifier(argId)) arg |> Statement.expr + yield Statement.continue'(?loc=range) + ] + + let transformImport (com: IPythonCompiler) ctx r (selector: string) (path: string) = + let selector, parts = + let parts = Array.toList(selector.Split('.')) + parts.Head, parts.Tail + com.GetImportExpr(ctx, selector, path, r) + |> getParts parts + + let transformCast (com: IPythonCompiler) (ctx: Context) t tag e: Expression * Statement list = + // HACK: Try to optimize some patterns after FableTransforms + let optimized = + match tag with + | Some (Naming.StartsWith "optimizable:" optimization) -> + match optimization, e with + | "array", Fable.Call(_,info,_,_) -> + match info.Args with + | [Replacements.ArrayOrListLiteral(vals,_)] -> Fable.Value(Fable.NewArray(vals, Fable.Any), e.Range) |> Some + | _ -> None + | "pojo", Fable.Call(_,info,_,_) -> + match info.Args with + | keyValueList::caseRule::_ -> Replacements.makePojo com (Some caseRule) keyValueList + | keyValueList::_ -> Replacements.makePojo com None keyValueList + | _ -> None + | _ -> None + | _ -> None + + match optimized, t with + | Some e, _ -> com.TransformAsExpr(ctx, e) + // Optimization for (numeric) array or list literals casted to seq + // Done at the very end of the compile pipeline to get more opportunities + // of matching cast and literal expressions after resolving pipes, inlining... + | None, Fable.DeclaredType(ent,[_]) -> + match ent.FullName, e with + | Types.ienumerableGeneric, Replacements.ArrayOrListLiteral(exprs, _) -> + makeArray com ctx exprs + | _ -> com.TransformAsExpr(ctx, e) + | _ -> com.TransformAsExpr(ctx, e) + + let transformCurry (com: IPythonCompiler) (ctx: Context) _r expr arity: Expression * Statement list = + com.TransformAsExpr(ctx, Replacements.curryExprAtRuntime com arity expr) + + let transformValue (com: IPythonCompiler) (ctx: Context) r value: Expression * Statement list = + match value with + | Fable.BaseValue(None,_) -> Expression.identifier("super().__init__"), [] + | Fable.BaseValue(Some boundIdent,_) -> identAsExpr boundIdent, [] + | Fable.ThisValue _ -> Expression.identifier("self"), [] + | Fable.TypeInfo t -> transformTypeInfo com ctx r Map.empty t + | Fable.Null _t -> + // if com.Options.typescript + // let ta = typeAnnotation com ctx t |> TypeAnnotation |> Some + // upcast Identifier("null", ?typeAnnotation=ta, ?loc=r) + // else + Expression.identifier("None", ?loc=r), [] + | Fable.UnitConstant -> undefined r, [] + | Fable.BoolConstant x -> Expression.constant(x, ?loc=r), [] + | Fable.CharConstant x -> Expression.constant(string x, ?loc=r), [] + | Fable.StringConstant x -> Expression.constant(x, ?loc=r), [] + | Fable.NumberConstant (x,_) -> Expression.constant(x, ?loc=r), [] + //| Fable.RegexConstant (source, flags) -> Expression.regExpLiteral(source, flags, ?loc=r) + | Fable.NewArray (values, typ) -> makeArray com ctx values + //| Fable.NewArrayFrom (size, typ) -> makeAllocatedFrom com ctx size, [] + | Fable.NewTuple vals -> makeArray com ctx vals + // | Fable.NewList (headAndTail, _) when List.contains "FABLE_LIBRARY" com.Options.Define -> + // makeList com ctx r headAndTail + // Optimization for bundle size: compile list literals as List.ofArray + | Fable.NewList (headAndTail, _) -> + let rec getItems acc = function + | None -> List.rev acc, None + | Some(head, Fable.Value(Fable.NewList(tail, _),_)) -> getItems (head::acc) tail + | Some(head, tail) -> List.rev (head::acc), Some tail + match getItems [] headAndTail with + | [], None -> + libCall com ctx r "List" "empty" [], [] + | [TransformExpr com ctx (expr, stmts)], None -> + libCall com ctx r "List" "singleton" [ expr ], stmts + | exprs, None -> + let expr, stmts = makeArray com ctx exprs + [ expr ] + |> libCall com ctx r "List" "ofArray", stmts + | [TransformExpr com ctx (head, stmts)], Some(TransformExpr com ctx (tail, stmts')) -> + libCall com ctx r "List" "cons" [ head; tail], stmts @ stmts' + | exprs, Some(TransformExpr com ctx (tail, stmts)) -> + let expr, stmts' = makeArray com ctx exprs + [ expr; tail ] + |> libCall com ctx r "List" "ofArrayWithTail", stmts @ stmts' + | Fable.NewOption (value, t) -> + match value with + | Some (TransformExpr com ctx (e, stmts)) -> + if mustWrapOption t + then libCall com ctx r "Option" "some" [ e ], stmts + else e, [] + | None -> undefined r, [] + | Fable.EnumConstant(x,_) -> + com.TransformAsExpr(ctx, x) + | Fable.NewRecord(values, ent, genArgs) -> + let ent = com.GetEntity(ent) + let values, stmts = List.map (fun x -> com.TransformAsExpr(ctx, x)) values |> Helpers.unzipArgs + let consRef, stmts' = ent |> jsConstructor com ctx + Expression.call(consRef, values, ?loc=r), stmts @ stmts' + | Fable.NewAnonymousRecord(values, fieldNames, _genArgs) -> + let values, stmts = values |> List.map (fun x -> com.TransformAsExpr(ctx, x)) |> Helpers.unzipArgs + List.zip (List.ofArray fieldNames) values |> makeJsObject, stmts + | Fable.NewUnion(values, tag, ent, genArgs) -> + let ent = com.GetEntity(ent) + let values, stmts = List.map (fun x -> com.TransformAsExpr(ctx, x)) values |> Helpers.unzipArgs + let consRef, stmts' = ent |> jsConstructor com ctx + // let caseName = ent.UnionCases |> List.item tag |> getUnionCaseName |> ofString + let values = (ofInt tag)::values + Expression.call(consRef, values, ?loc=r), stmts @ stmts' + | _ -> failwith $"transformValue: value {value} not supported!" + + let enumerator2iterator com ctx = + let enumerator = Expression.call(get None (Expression.identifier("self")) "GetEnumerator", []) + [ Statement.return'(libCall com ctx None "Util" "toIterator" [ enumerator ]) ] + + let extractBaseExprFromBaseCall (com: IPythonCompiler) (ctx: Context) (baseType: Fable.DeclaredType option) baseCall = + match baseCall, baseType with + | Some (Fable.Call(baseRef, info, _, _)), _ -> + let baseExpr, stmts = + match baseRef with + | Fable.IdentExpr id -> Expression.identifier id.Name, [] + | _ -> transformAsExpr com ctx baseRef + let args = transformCallArgs com ctx info.HasSpread info.Args + Some (baseExpr, args) + | Some (Fable.Value _), Some baseType -> + // let baseEnt = com.GetEntity(baseType.Entity) + // let entityName = FSharp2Fable.Helpers.getEntityDeclarationName com baseType.Entity + // let entityType = FSharp2Fable.Util.getEntityType baseEnt + // let baseRefId = makeTypedIdent entityType entityName + // let baseExpr = (baseRefId |> typedIdent com ctx) :> Expression + // Some (baseExpr, []) // default base constructor + let range = baseCall |> Option.bind (fun x -> x.Range) + sprintf "Ignoring base call for %s" baseType.Entity.FullName |> addWarning com [] range + None + | Some _, _ -> + let range = baseCall |> Option.bind (fun x -> x.Range) + "Unexpected base call expression, please report" |> addError com [] range + None + | None, _ -> + None + + // let transformObjectExpr (com: IPythonCompiler) ctx (members: Fable.MemberDecl list) baseCall: Expression * Statement list = + // let compileAsClass = + // Option.isSome baseCall || members |> List.exists (fun m -> + // // Optimization: Object literals with getters and setters are very slow in V8 + // // so use a class expression instead. See https://github.com/fable-compiler/Fable/pull/2165#issuecomment-695835444 + // m.Info.IsSetter || (m.Info.IsGetter && canHaveSideEffects m.Body)) + + // let makeMethod kind prop computed hasSpread args body = + // let args, body = + // getMemberArgsAndBody com ctx (Attached(isStatic=false)) hasSpread args body + // ObjectMember.objectMethod(kind, prop, args, body, computed_=computed, + // ?returnType=returnType, ?typeParameters=typeParamDecl) + + // let members = + // members |> List.collect (fun memb -> + // let info = memb.Info + // let prop, computed = memberFromName memb.Name + // // If compileAsClass is false, it means getters don't have side effects + // // and can be compiled as object fields (see condition above) + // if info.IsValue || (not compileAsClass && info.IsGetter) then + // [ObjectMember.objectProperty(prop, com.TransformAsExpr(ctx, memb.Body), computed_=computed)] + // elif info.IsGetter then + // [makeMethod ObjectGetter prop computed false memb.Args memb.Body] + // elif info.IsSetter then + // [makeMethod ObjectSetter prop computed false memb.Args memb.Body] + // elif info.IsEnumerator then + // let method = makeMethod ObjectMeth prop computed info.HasSpread memb.Args memb.Body + // let iterator = + // let prop, computed = memberFromName "Symbol.iterator" + // let body = enumerator2iterator com ctx + // ObjectMember.objectMethod(ObjectMeth, prop, [||], body, computed_=computed) + // [method; iterator] + // else + // [makeMethod ObjectMeth prop computed info.HasSpread memb.Args memb.Body] + // ) + + // let classMembers = + // members |> List.choose (function + // | ObjectProperty(key, value, computed) -> + // ClassMember.classProperty(key, value, computed_=computed) |> Some + // | ObjectMethod(kind, key, ``params``, body, computed, returnType, typeParameters, _) -> + // let kind = + // match kind with + // | "get" -> ClassGetter + // | "set" -> ClassSetter + // | _ -> ClassFunction + // ClassMember.classMethod(kind, key, ``params``, body, computed_=computed, + // ?returnType=returnType, ?typeParameters=typeParameters) |> Some) + + // let baseExpr, classMembers = + // baseCall + // |> extractBaseExprFromBaseCall com ctx None + // |> Option.map (fun (baseExpr, baseArgs) -> + // let consBody = [ callSuperAsStatement baseArgs ] + // let cons = makeClassConstructor [] consBody + // Some baseExpr, cons::classMembers + // ) + // |> Option.defaultValue (None, classMembers) + + // let classBody = ClassBody.classBody(List.toArray classMembers) + // let classExpr = Expression.classExpression(classBody, ?superClass=baseExpr) + // Expression.newExpression(classExpr, [||]) + + let transformCallArgs (com: IPythonCompiler) ctx hasSpread args : Expression list * Statement list = + match args with + | [] + | [MaybeCasted(Fable.Value(Fable.UnitConstant,_))] -> [], [] + | args when hasSpread -> + match List.rev args with + | [] -> [], [] + | (Replacements.ArrayOrListLiteral(spreadArgs,_))::rest -> + let rest = List.rev rest |> List.map (fun e -> com.TransformAsExpr(ctx, e)) + rest @ (List.map (fun e -> com.TransformAsExpr(ctx, e)) spreadArgs) |> Helpers.unzipArgs + | last::rest -> + let rest, stmts = List.rev rest |> List.map (fun e -> com.TransformAsExpr(ctx, e)) |> Helpers.unzipArgs + let expr, stmts' = com.TransformAsExpr(ctx, last) + rest @ [ Expression.starred(expr) ], stmts @ stmts' + | args -> List.map (fun e -> com.TransformAsExpr(ctx, e)) args |> Helpers.unzipArgs + + let resolveExpr t strategy babelExpr: Statement = + match strategy with + | None | Some ReturnUnit -> Statement.expr(babelExpr) + // TODO: Where to put these int wrappings? Add them also for function arguments? + | Some Return -> Statement.return'(wrapIntExpression t babelExpr) + | Some(Assign left) -> Statement.expr(assign None left babelExpr) + | Some(Target left) -> Statement.expr(assign None (left |> Expression.identifier) babelExpr) + + let transformOperation com ctx range opKind: Expression * Statement list = + match opKind with + | Fable.Unary(op, TransformExpr com ctx (expr, stmts)) -> + Expression.unaryOp(op, expr, ?loc=range), stmts + + | Fable.Binary(op, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> + Expression.binOp(left, op, right, ?loc=range), stmts @ stmts' + + | Fable.Logical(op, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> + Expression.boolOp(op, [left; right], ?loc=range), stmts @ stmts' + + let transformEmit (com: IPythonCompiler) ctx range (info: Fable.EmitInfo) = + let macro = info.Macro + let info = info.CallInfo + let thisArg, stmts = info.ThisArg |> Option.map (fun e -> com.TransformAsExpr(ctx, e)) |> Option.toList |> Helpers.unzipArgs + let exprs, stmts' = transformCallArgs com ctx info.HasSpread info.Args + + let args = + exprs + |> List.append thisArg + Expression.emit(macro, args, ?loc=range), stmts @ stmts' + + let transformCall (com: IPythonCompiler) ctx range callee (callInfo: Fable.CallInfo) : Expression * Statement list = + let callee, stmts = com.TransformAsExpr(ctx, callee) + let args, stmts' = transformCallArgs com ctx callInfo.HasSpread callInfo.Args + match callInfo.ThisArg with + | Some(TransformExpr com ctx (thisArg, stmts'')) -> callFunction range callee (thisArg::args), stmts @ stmts' @ stmts'' + | None when callInfo.IsJsConstructor -> Expression.call(callee, args, ?loc=range), stmts @ stmts' + | None -> callFunction range callee args, stmts @ stmts' + + let transformCurriedApply com ctx range (TransformExpr com ctx (applied, stmts)) args = + match transformCallArgs com ctx false args with + | [], stmts' -> callFunction range applied [], stmts @ stmts' + | args, stmts' -> (applied, args) ||> List.fold (fun e arg -> callFunction range e [arg]), stmts @ stmts' + + let transformCallAsStatements com ctx range t returnStrategy callee callInfo = + let argsLen (i: Fable.CallInfo) = + List.length i.Args + (if Option.isSome i.ThisArg then 1 else 0) + // Warn when there's a recursive call that couldn't be optimized? + match returnStrategy, ctx.TailCallOpportunity with + | Some(Return|ReturnUnit), Some tc when tc.IsRecursiveRef(callee) + && argsLen callInfo = List.length tc.Args -> + let args = + match callInfo.ThisArg with + | Some thisArg -> thisArg::callInfo.Args + | None -> callInfo.Args + optimizeTailCall com ctx range tc args + | _ -> + let expr, stmts = transformCall com ctx range callee callInfo + stmts @ [ expr |> resolveExpr t returnStrategy ] + + let transformCurriedApplyAsStatements com ctx range t returnStrategy callee args = + // Warn when there's a recursive call that couldn't be optimized? + match returnStrategy, ctx.TailCallOpportunity with + | Some(Return|ReturnUnit), Some tc when tc.IsRecursiveRef(callee) + && List.sameLength args tc.Args -> + optimizeTailCall com ctx range tc args + | _ -> + let expr, stmts = transformCurriedApply com ctx range callee args + stmts @ [ expr |> resolveExpr t returnStrategy ] + + // When expecting a block, it's usually not necessary to wrap it + // in a lambda to isolate its variable context + let transformBlock (com: IPythonCompiler) ctx ret expr: Statement list = + com.TransformAsStatements(ctx, ret, expr) + + let transformTryCatch com ctx r returnStrategy (body, (catch: option), finalizer) = + // try .. catch statements cannot be tail call optimized + let ctx = { ctx with TailCallOpportunity = None } + let handlers = + catch |> Option.map (fun (param, body) -> + let body = transformBlock com ctx returnStrategy body + let exn = Expression.identifier("Exception") |> Some + let identifier = ident param + [ ExceptHandler.exceptHandler (``type`` = exn, name = identifier, body = body) ]) + let finalizer = + finalizer |> Option.map (transformBlock com ctx None) + [ Statement.try'(transformBlock com ctx returnStrategy body, + ?handlers=handlers, ?finalBody=finalizer, ?loc=r) ] + + let rec transformIfStatement (com: IPythonCompiler) ctx r ret guardExpr thenStmnt elseStmnt = + let expr, stmts = com.TransformAsExpr(ctx, guardExpr) + match expr with + | Constant(value=value) when (value :? bool) -> + match value with + | :? bool as value when value -> stmts @ com.TransformAsStatements(ctx, ret, thenStmnt) + | _ -> stmts @ com.TransformAsStatements(ctx, ret, elseStmnt) + | guardExpr -> + let thenStmnt = transformBlock com ctx ret thenStmnt + let ifStatement = + match com.TransformAsStatements(ctx, ret, elseStmnt) with + | [ ] -> Statement.if'(guardExpr, thenStmnt, ?loc=r) + | [ elseStmnt ] -> Statement.if'(guardExpr, thenStmnt, [ elseStmnt ], ?loc=r) + | statements -> Statement.if'(guardExpr, thenStmnt, statements, ?loc=r) + |> List.singleton + stmts @ ifStatement + + let transformGet (com: IPythonCompiler) ctx range typ fableExpr kind = + match kind with + | Fable.ByKey key -> + let fableExpr = + match fableExpr with + // If we're accessing a virtual member with default implementation (see #701) + // from base class, we can use `super` in JS so we don't need the bound this arg + | Fable.Value(Fable.BaseValue(_,t), r) -> Fable.Value(Fable.BaseValue(None, t), r) + | _ -> fableExpr + let expr, stmts = com.TransformAsExpr(ctx, fableExpr) + match key with + | Fable.ExprKey(TransformExpr com ctx (prop, stmts')) -> + let expr, stmts'' = getExpr range expr prop + expr, stmts @ stmts' @ stmts'' + | Fable.FieldKey field -> get range expr field.Name, stmts + + | Fable.ListHead -> + // get range (com.TransformAsExpr(ctx, fableExpr)) "head" + let expr, stmts = com.TransformAsExpr(ctx, fableExpr) + libCall com ctx range "List" "head" [ expr ], stmts + + | Fable.ListTail -> + // get range (com.TransformAsExpr(ctx, fableExpr)) "tail" + let expr, stmts = com.TransformAsExpr(ctx, fableExpr) + libCall com ctx range "List" "tail" [ expr ], stmts + + | Fable.TupleIndex index -> + match fableExpr with + // TODO: Check the erased expressions don't have side effects? + | Fable.Value(Fable.NewTuple exprs, _) -> + com.TransformAsExpr(ctx, List.item index exprs) + | TransformExpr com ctx (expr, stmts) -> + let expr, stmts' = getExpr range expr (ofInt index) + expr, stmts @ stmts' + + | Fable.OptionValue -> + let expr, stmts = com.TransformAsExpr(ctx, fableExpr) + if mustWrapOption typ || com.Options.Language = TypeScript + then libCall com ctx None "Option" "value" [ expr ], stmts + else expr, stmts + + | Fable.UnionTag -> + let expr, stmts = getUnionExprTag com ctx range fableExpr + expr, stmts + + | Fable.UnionField(index, _) -> + let expr, stmts = com.TransformAsExpr(ctx, fableExpr) + let expr, stmts' = getExpr None expr (Expression.constant("fields")) + let expr, stmts'' = getExpr range expr (ofInt index) + expr, stmts @ stmts' @ stmts'' + + let transformSet (com: IPythonCompiler) ctx range fableExpr (value: Fable.Expr) kind = + let expr, stmts = com.TransformAsExpr(ctx, fableExpr) + let value', stmts' = com.TransformAsExpr(ctx, value) + let value = value' |> wrapIntExpression value.Type + let ret, stmts = + match kind with + | None -> expr, stmts @ stmts' + | Some(Fable.FieldKey fi) -> get None expr fi.Name, stmts @ stmts' + | Some(Fable.ExprKey(TransformExpr com ctx (e, stmts''))) -> + let expr, stmts''' = getExpr None expr e + expr, stmts @ stmts' @ stmts'' @ stmts''' + assign range ret value, stmts + + // let transformBindingExprBody (com: IPythonCompiler) (ctx: Context) (var: Fable.Ident) (value: Fable.Expr) = + // match value with + // | Function(args, body) -> + // let name = Some var.Name + // transformFunction com ctx name args body + // |> makeArrowFunctionExpression name + // | _ -> + // if var.IsMutable then + // com.TransformAsExpr(ctx, value) + // else + // let expr, stmts = com.TransformAsExpr(ctx, value) + // expr |> wrapIntExpression value.Type, stmts + + // let transformBindingAsExpr (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = + // let expr, stmts = transformBindingExprBody com ctx var value + // expr |> assign None (identAsExpr var), stmts + + // let transformBindingAsStatements (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = + // if isJsStatement ctx false value then + // let varName, varExpr = Expression.name(var.Name), identAsExpr var + // let decl = Statement.assign([varName], varExpr) + // let body = com.TransformAsStatements(ctx, Some(Assign varExpr), value) + // List.append [ decl ] body + // else + // let value, stmts = transformBindingExprBody com ctx var value + // let varName = Expression.name(var.Name) + // let decl = varDeclaration varName var.IsMutable value + // [ decl ] + + let transformTest (com: IPythonCompiler) ctx range kind expr: Expression * Statement list = + match kind with + | Fable.TypeTest t -> + transformTypeTest com ctx range expr t + | Fable.OptionTest nonEmpty -> + let op = if nonEmpty then BinaryUnequal else BinaryEqual + let expr, stmts = com.TransformAsExpr(ctx, expr) + Expression.binOp(expr, op, Expression.none(), ?loc=range), stmts + | Fable.ListTest nonEmpty -> + let expr, stmts = com.TransformAsExpr(ctx, expr) + // let op = if nonEmpty then BinaryUnequal else BinaryEqual + // Expression.binaryExpression(op, get None expr "tail", Expression.none(), ?loc=range) + let expr = + let expr = libCall com ctx range "List" "isEmpty" [ expr ] + if nonEmpty then Expression.unaryOp(UnaryNot, expr, ?loc=range) else expr + expr, stmts + | Fable.UnionCaseTest tag -> + let expected = ofInt tag + let actual, stmts = getUnionExprTag com ctx None expr + Expression.compare(actual, [Eq], [ expected ], ?loc=range), stmts + + // let transformSwitch (com: IPythonCompiler) ctx useBlocks returnStrategy evalExpr cases defaultCase: Statement = + // let cases = + // cases |> List.collect (fun (guards, expr) -> + // // Remove empty branches + // match returnStrategy, expr, guards with + // | None, Fable.Value(Fable.UnitConstant,_), _ + // | _, _, [] -> [] + // | _, _, guards -> + // let guards, lastGuard = List.splitLast guards + // let guards = guards |> List.map (fun e -> SwitchCase.switchCase([||], com.TransformAsExpr(ctx, e))) + // let caseBody = com.TransformAsStatements(ctx, returnStrategy, expr) + // let caseBody = + // match returnStrategy with + // | Some Return -> caseBody + // | _ -> Array.append caseBody [|Statement.break'()|] + // guards @ [SwitchCase.switchCase(caseBody, com.TransformAsExpr(ctx, lastGuard))] + // ) + // let cases = + // match defaultCase with + // | Some expr -> + // let defaultCaseBody = com.TransformAsStatements(ctx, returnStrategy, expr) + // cases @ [SwitchCase.switchCase(consequent defaultCaseBody)] + // | None -> cases + // Statement.switchStatement(com.TransformAsExpr(ctx, evalExpr), List.toArray cases) + + let matchTargetIdentAndValues idents values = + if List.isEmpty idents then [] + elif List.sameLength idents values then List.zip idents values + else failwith "Target idents/values lengths differ" + + let getDecisionTargetAndBindValues (com: IPythonCompiler) (ctx: Context) targetIndex boundValues = + let idents, target = getDecisionTarget ctx targetIndex + let identsAndValues = matchTargetIdentAndValues idents boundValues + if not com.Options.DebugMode then + let bindings, replacements = + (([], Map.empty), identsAndValues) + ||> List.fold (fun (bindings, replacements) (ident, expr) -> + if canHaveSideEffects expr then + (ident, expr)::bindings, replacements + else + bindings, Map.add ident.Name expr replacements) + let target = FableTransforms.replaceValues replacements target + List.rev bindings, target + else + identsAndValues, target + + let transformDecisionTreeSuccessAsExpr (com: IPythonCompiler) (ctx: Context) targetIndex boundValues = + let bindings, target = getDecisionTargetAndBindValues com ctx targetIndex boundValues + match bindings with + | [] -> com.TransformAsExpr(ctx, target) + | bindings -> + let target = List.rev bindings |> List.fold (fun e (i,v) -> Fable.Let(i,v,e)) target + com.TransformAsExpr(ctx, target) + + // let transformDecisionTreeSuccessAsStatements (com: IPythonCompiler) (ctx: Context) returnStrategy targetIndex boundValues: Statement list = + // match returnStrategy with + // | Some(Target targetId) as target -> + // let idents, _ = getDecisionTarget ctx targetIndex + // let assignments = + // matchTargetIdentAndValues idents boundValues + // |> List.collect (fun (id, TransformExpr com ctx (value, stmts)) -> + // let stmt = assign None (identAsExpr id) value |> Statement.expr + // stmts @ [ stmt ]) + // let targetAssignment = assign None (targetId |> Expression.name) (ofInt targetIndex) |> Statement.expr + // [ targetAssignment ] @ assignments + // | ret -> + // let bindings, target = getDecisionTargetAndBindValues com ctx targetIndex boundValues + // let bindings = bindings |> Seq.collect (fun (i, v) -> transformBindingAsStatements com ctx i v) |> Seq.toList + // bindings @ (com.TransformAsStatements(ctx, ret, target)) + + let transformDecisionTreeAsSwitch expr = + let (|Equals|_|) = function + | Fable.Operation(Fable.Binary(BinaryEqualStrict, expr, right), _, _) -> + Some(expr, right) + | Fable.Test(expr, Fable.UnionCaseTest tag, _) -> + let evalExpr = Fable.Get(expr, Fable.UnionTag, Fable.Number Int32, None) + let right = Fable.NumberConstant(float tag, Int32) |> makeValue None + Some(evalExpr, right) + | _ -> None + let sameEvalExprs evalExpr1 evalExpr2 = + match evalExpr1, evalExpr2 with + | Fable.IdentExpr i1, Fable.IdentExpr i2 + | Fable.Get(Fable.IdentExpr i1,Fable.UnionTag,_,_), Fable.Get(Fable.IdentExpr i2,Fable.UnionTag,_,_) -> + i1.Name = i2.Name + | _ -> false + let rec checkInner cases evalExpr = function + | Fable.IfThenElse(Equals(evalExpr2, caseExpr), + Fable.DecisionTreeSuccess(targetIndex, boundValues, _), treeExpr, _) + when sameEvalExprs evalExpr evalExpr2 -> + match treeExpr with + | Fable.DecisionTreeSuccess(defaultTargetIndex, defaultBoundValues, _) -> + let cases = (caseExpr, targetIndex, boundValues)::cases |> List.rev + Some(evalExpr, cases, (defaultTargetIndex, defaultBoundValues)) + | treeExpr -> checkInner ((caseExpr, targetIndex, boundValues)::cases) evalExpr treeExpr + | _ -> None + match expr with + | Fable.IfThenElse(Equals(evalExpr, caseExpr), + Fable.DecisionTreeSuccess(targetIndex, boundValues, _), treeExpr, _) -> + match checkInner [caseExpr, targetIndex, boundValues] evalExpr treeExpr with + | Some(evalExpr, cases, defaultCase) -> + Some(evalExpr, cases, defaultCase) + | None -> None + | _ -> None + + let transformDecisionTreeAsExpr (com: IPythonCompiler) (ctx: Context) targets expr: Expression * Statement list = + // TODO: Check if some targets are referenced multiple times + let ctx = { ctx with DecisionTargets = targets } + com.TransformAsExpr(ctx, expr) + + let groupSwitchCases t (cases: (Fable.Expr * int * Fable.Expr list) list) (defaultIndex, defaultBoundValues) = + cases + |> List.groupBy (fun (_,idx,boundValues) -> + // Try to group cases with some target index and empty bound values + // If bound values are non-empty use also a non-empty Guid to prevent grouping + if List.isEmpty boundValues + then idx, System.Guid.Empty + else idx, System.Guid.NewGuid()) + |> List.map (fun ((idx,_), cases) -> + let caseExprs = cases |> List.map Tuple3.item1 + // If there are multiple cases, it means boundValues are empty + // (see `groupBy` above), so it doesn't mind which one we take as reference + let boundValues = cases |> List.head |> Tuple3.item3 + caseExprs, Fable.DecisionTreeSuccess(idx, boundValues, t)) + |> function + | [] -> [] + // Check if the last case can also be grouped with the default branch, see #2357 + | cases when List.isEmpty defaultBoundValues -> + match List.splitLast cases with + | cases, (_, Fable.DecisionTreeSuccess(idx, [], _)) + when idx = defaultIndex -> cases + | _ -> cases + | cases -> cases + + let getTargetsWithMultipleReferences expr = + let rec findSuccess (targetRefs: Map) = function + | [] -> targetRefs + | expr::exprs -> + match expr with + // We shouldn't actually see this, but shortcircuit just in case + | Fable.DecisionTree _ -> + findSuccess targetRefs exprs + | Fable.DecisionTreeSuccess(idx,_,_) -> + let count = + Map.tryFind idx targetRefs + |> Option.defaultValue 0 + let targetRefs = Map.add idx (count + 1) targetRefs + findSuccess targetRefs exprs + | expr -> + let exprs2 = FableTransforms.getSubExpressions expr + findSuccess targetRefs (exprs @ exprs2) + findSuccess Map.empty [expr] |> Seq.choose (fun kv -> + if kv.Value > 1 then Some kv.Key else None) |> Seq.toList + + /// When several branches share target create first a switch to get the target index and bind value + /// and another to execute the actual target + // let transformDecisionTreeWithTwoSwitches (com: IPythonCompiler) ctx returnStrategy + // (targets: (Fable.Ident list * Fable.Expr) list) treeExpr = + // // Declare target and bound idents + // let targetId = getUniqueNameInDeclarationScope ctx "pattern_matching_result" |> makeIdent |> ident + // let multiVarDecl = + // let boundIdents = + // targets |> List.collect (fun (idents,_) -> + // idents) |> List.map (fun id -> ident id, None) + // multiVarDeclaration ((targetId,None)::boundIdents) + // // Transform targets as switch + // let switch2 = + // // TODO: Declare the last case as the default case? + // let cases = targets |> List.mapi (fun i (_,target) -> [makeIntConst i], target) + // transformSwitch com ctx true returnStrategy (targetId |> Fable.IdentExpr) cases None + // // Transform decision tree + // let targetAssign = Target(ident targetId) + // let ctx = { ctx with DecisionTargets = targets } + // match transformDecisionTreeAsSwitch treeExpr with + // | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> + // let cases = groupSwitchCases (Fable.Number Int32) cases (defaultIndex, defaultBoundValues) + // let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, Fable.Number Int32) + // let switch1 = transformSwitch com ctx false (Some targetAssign) evalExpr cases (Some defaultCase) + // [ multiVarDecl; switch1; switch2 ] + // | None -> + // let decisionTree = com.TransformAsStatements(ctx, Some targetAssign, treeExpr) + // [ yield multiVarDecl; yield! decisionTree; yield switch2 ] + + // let transformDecisionTreeAsStatements (com: IPythonCompiler) (ctx: Context) returnStrategy + // (targets: (Fable.Ident list * Fable.Expr) list) (treeExpr: Fable.Expr): Statement list = + // // If some targets are referenced multiple times, hoist bound idents, + // // resolve the decision index and compile the targets as a switch + // let targetsWithMultiRefs = + // if com.Options.Language = TypeScript then [] // no hoisting when compiled with types + // else getTargetsWithMultipleReferences treeExpr + // match targetsWithMultiRefs with + // | [] -> + // let ctx = { ctx with DecisionTargets = targets } + // match transformDecisionTreeAsSwitch treeExpr with + // | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> + // let t = treeExpr.Type + // let cases = cases |> List.map (fun (caseExpr, targetIndex, boundValues) -> + // [caseExpr], Fable.DecisionTreeSuccess(targetIndex, boundValues, t)) + // let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, t) + // [ transformSwitch com ctx true returnStrategy evalExpr cases (Some defaultCase) ] + // | None -> + // com.TransformAsStatements(ctx, returnStrategy, treeExpr) + // | targetsWithMultiRefs -> + // // If the bound idents are not referenced in the target, remove them + // let targets = + // targets |> List.map (fun (idents, expr) -> + // idents + // |> List.exists (fun i -> FableTransforms.isIdentUsed i.Name expr) + // |> function + // | true -> idents, expr + // | false -> [], expr) + // let hasAnyTargetWithMultiRefsBoundValues = + // targetsWithMultiRefs |> List.exists (fun idx -> + // targets.[idx] |> fst |> List.isEmpty |> not) + // if not hasAnyTargetWithMultiRefsBoundValues then + // match transformDecisionTreeAsSwitch treeExpr with + // | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> + // let t = treeExpr.Type + // let cases = groupSwitchCases t cases (defaultIndex, defaultBoundValues) + // let ctx = { ctx with DecisionTargets = targets } + // let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, t) + // [ transformSwitch com ctx true returnStrategy evalExpr cases (Some defaultCase) ] + // | None -> + // transformDecisionTreeWithTwoSwitches com ctx returnStrategy targets treeExpr + // else + // transformDecisionTreeWithTwoSwitches com ctx returnStrategy targets treeExpr + + let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Fable.Expr): Expression * Statement list= + match expr with + | Fable.TypeCast(e,t,tag) -> transformCast com ctx t tag e + + | Fable.Curry(e, arity, _, r) -> transformCurry com ctx r e arity + + | Fable.Value(kind, r) -> transformValue com ctx r kind + + | Fable.IdentExpr id -> identAsExpr id, [] + + | Fable.Import({ Selector = selector; Path = path }, _, r) -> + transformImport com ctx r selector path, [] + + | Fable.Test(expr, kind, range) -> + transformTest com ctx range kind expr + + | Fable.Lambda(arg, body, name) -> + transformFunction com ctx name [arg] body + ||> makeArrowFunctionExpression + + | Fable.Delegate(args, body, name) -> + transformFunction com ctx name args body + ||> makeArrowFunctionExpression + + // FIXME: + //| Fable.ObjectExpr (members, _, baseCall) -> + // transformObjectExpr com ctx members baseCall + + | Fable.Call(callee, info, _, range) -> + transformCall com ctx range callee info + + | Fable.CurriedApply(callee, args, _, range) -> + transformCurriedApply com ctx range callee args + + | Fable.Operation(kind, _, range) -> + transformOperation com ctx range kind + + | Fable.Get(expr, kind, typ, range) -> + transformGet com ctx range typ expr kind + + | Fable.IfThenElse(TransformExpr com ctx (guardExpr, stmts), + TransformExpr com ctx (thenExpr, stmts'), + TransformExpr com ctx (elseExpr, stmts''), r) -> + Expression.ifExp (guardExpr, thenExpr, elseExpr), stmts @ stmts' @ stmts'' + + | Fable.DecisionTree(expr, targets) -> + transformDecisionTreeAsExpr com ctx targets expr + + | Fable.DecisionTreeSuccess(idx, boundValues, _) -> + transformDecisionTreeSuccessAsExpr com ctx idx boundValues + + | Fable.Set(expr, kind, value, range) -> + transformSet com ctx range expr value kind + + // | Fable.Let(ident, value, body) -> + // if ctx.HoistVars [ident] then + // let assignment = transformBindingAsExpr com ctx ident value + // Expression.sequenceExpression([|assignment; com.TransformAsExpr(ctx, body)|]) + // else iife com ctx expr + + // | Fable.LetRec(bindings, body) -> + // if ctx.HoistVars(List.map fst bindings) then + // let values = bindings |> List.mapToArray (fun (id, value) -> + // transformBindingAsExpr com ctx id value) + // Expression.sequenceExpression(Array.append values [|com.TransformAsExpr(ctx, body)|]) + // else iife com ctx expr + + // | Fable.Sequential exprs -> + // List.mapToArray (fun e -> com.TransformAsExpr(ctx, e)) exprs + // |> Expression.sequenceExpression + + | Fable.Emit(info, _, range) -> + if info.IsJsStatement then iife com ctx expr + else transformEmit com ctx range info + + // These cannot appear in expression position in JS, must be wrapped in a lambda + | Fable.WhileLoop _ | Fable.ForLoop _ | Fable.TryCatch _ -> + iife com ctx expr + + | _ -> failwith $"Expression {expr} not supported." + + let rec transformAsStatements (com: IPythonCompiler) ctx returnStrategy + (expr: Fable.Expr): Statement list = + match expr with + | Fable.TypeCast(e, t, tag) -> + let expr, stmts = transformCast com ctx t tag e + stmts @ [ expr |> resolveExpr t returnStrategy ] + + | Fable.Curry(e, arity, t, r) -> + let expr, stmts = transformCurry com ctx r e arity + stmts @ [ expr |> resolveExpr t returnStrategy ] + + | Fable.Value(kind, r) -> + let expr, stmts = transformValue com ctx r kind + stmts @ [ expr |> resolveExpr kind.Type returnStrategy ] + + | Fable.IdentExpr id -> + [ identAsExpr id |> resolveExpr id.Type returnStrategy ] + + | Fable.Import({ Selector = selector; Path = path }, t, r) -> + [ transformImport com ctx r selector path |> resolveExpr t returnStrategy ] + + | Fable.Test(expr, kind, range) -> + let expr, stmts = transformTest com ctx range kind expr + stmts @ [ expr |> resolveExpr Fable.Boolean returnStrategy ] + + | Fable.Lambda(arg, body, name) -> + let args, body = transformFunction com ctx name [arg] body + let expr', stmts = makeArrowFunctionExpression args body + stmts @ [ expr' |> resolveExpr expr.Type returnStrategy ] + + | Fable.Delegate(args, body, name) -> + let args, body = transformFunction com ctx name args body + let expr', stmts = makeArrowFunctionExpression args body + stmts @ [ expr' |> resolveExpr expr.Type returnStrategy ] + + // | Fable.ObjectExpr (members, t, baseCall) -> + // let expr, stmts = transformObjectExpr com ctx members baseCall + // stmts @ [ expr |> resolveExpr t returnStrategy ] + + | Fable.Call(callee, info, typ, range) -> + transformCallAsStatements com ctx range typ returnStrategy callee info + + | Fable.CurriedApply(callee, args, typ, range) -> + transformCurriedApplyAsStatements com ctx range typ returnStrategy callee args + + | Fable.Emit(info, t, range) -> + let e, stmts = transformEmit com ctx range info + if info.IsJsStatement then + stmts @ [ Statement.expr(e) ] // Ignore the return strategy + else stmts @ [ resolveExpr t returnStrategy e ] + + | Fable.Operation(kind, t, range) -> + let expr, stmts = transformOperation com ctx range kind + stmts @ [ expr |> resolveExpr t returnStrategy ] + + | Fable.Get(expr, kind, t, range) -> + let expr, stmts = transformGet com ctx range t expr kind + stmts @ [ expr |> resolveExpr t returnStrategy ] + + // | Fable.Let(ident, value, body) -> + // let binding = transformBindingAsStatements com ctx ident value + // List.append binding (transformAsStatements com ctx returnStrategy body) + + // | Fable.LetRec(bindings, body) -> + // let bindings = bindings |> Seq.collect (fun (i, v) -> transformBindingAsStatements com ctx i v) |> Seq.toList + // List.append bindings (transformAsStatements com ctx returnStrategy body) + + | Fable.Set(expr, kind, value, range) -> + let expr', stmts = transformSet com ctx range expr value kind + stmts @ [ expr' |> resolveExpr expr.Type returnStrategy ] + + | Fable.IfThenElse(guardExpr, thenExpr, elseExpr, r) -> + let asStatement = + match returnStrategy with + | None | Some ReturnUnit -> true + | Some(Target _) -> true // Compile as statement so values can be bound + | Some(Assign _) -> (isJsStatement ctx false thenExpr) || (isJsStatement ctx false elseExpr) + | Some Return -> + Option.isSome ctx.TailCallOpportunity + || (isJsStatement ctx false thenExpr) || (isJsStatement ctx false elseExpr) + if asStatement then + transformIfStatement com ctx r returnStrategy guardExpr thenExpr elseExpr + else + let guardExpr', stmts = transformAsExpr com ctx guardExpr + let thenExpr', stmts' = transformAsExpr com ctx thenExpr + let elseExpr', stmts'' = transformAsExpr com ctx elseExpr + stmts + @ stmts' + @ stmts'' + @ [ Expression.ifExp(guardExpr', thenExpr', elseExpr', ?loc=r) |> resolveExpr thenExpr.Type returnStrategy ] + + | Fable.Sequential statements -> + let lasti = (List.length statements) - 1 + statements |> List.mapiToArray (fun i statement -> + let ret = if i < lasti then None else returnStrategy + com.TransformAsStatements(ctx, ret, statement)) + |> List.concat + + | Fable.TryCatch (body, catch, finalizer, r) -> + transformTryCatch com ctx r returnStrategy (body, catch, finalizer) + + // | Fable.DecisionTree(expr, targets) -> + // transformDecisionTreeAsStatements com ctx returnStrategy targets expr + + // | Fable.DecisionTreeSuccess(idx, boundValues, _) -> + // transformDecisionTreeSuccessAsStatements com ctx returnStrategy idx boundValues + + | Fable.WhileLoop(TransformExpr com ctx (guard, stmts), body, range) -> + stmts @ [ Statement.while'(guard, transformBlock com ctx None body, ?loc=range) ] + + // | Fable.ForLoop (var, TransformExpr com ctx (start, stmts), TransformExpr com ctx (limit, stmts'), body, isUp, range) -> + // let op1, op2 = + // if isUp + // then BinaryOperator.BinaryLessOrEqual, UpdateOperator.UpdatePlus + // else BinaryOperator.BinaryGreaterOrEqual, UpdateOperator.UpdateMinus + + // let a = start |> varDeclaration (typedIdent com ctx var |> Pattern.Identifier) true + + // [ Statement.for'( + // transformBlock com ctx None body, + // start |> varDeclaration (Expression.identifier(ident var)) true, + // Expression.binOp(identAsExpr var, op1, limit), + // Expression.updateExpression(op2, false, identAsExpr var), ?loc=range) ] + + | _ -> failwith $"Expression {expr} not supported." + + let transformFunction com ctx name (args: Fable.Ident list) (body: Fable.Expr): Arg list * Statement list = + let tailcallChance = + Option.map (fun name -> + NamedTailCallOpportunity(com, ctx, name, args) :> ITailCallOpportunity) name + let args = discardUnitArg args + let declaredVars = ResizeArray() + let mutable isTailCallOptimized = false + let ctx = + { ctx with TailCallOpportunity = tailcallChance + HoistVars = fun ids -> declaredVars.AddRange(ids); true + OptimizeTailCall = fun () -> isTailCallOptimized <- true } + let body = + if body.Type = Fable.Unit then + transformBlock com ctx (Some ReturnUnit) body + elif isJsStatement ctx (Option.isSome tailcallChance) body then + transformBlock com ctx (Some Return) body + else + transformAsExpr com ctx body |> wrapExprInBlockWithReturn + let args, body = + match isTailCallOptimized, tailcallChance with + | true, Some tc -> + // Replace args, see NamedTailCallOpportunity constructor + let args' = + List.zip args tc.Args + |> List.map (fun (id, tcArg) -> id) + let varDecls = + List.zip args tc.Args + |> List.map (fun (id, tcArg) -> ident id, Some (Expression.identifier(tcArg))) + |> multiVarDeclaration + + let body = varDecls @ body + // Make sure we don't get trapped in an infinite loop, see #1624 + let body = body @ [ Statement.break'() ] + args', Statement.while'(Expression.constant(true), body) + |> List.singleton + | _ -> args |> List.map id, body + let body = + if declaredVars.Count = 0 then body + else + let varDeclStatement = multiVarDeclaration [for v in declaredVars -> ident v, None] + List.append varDeclStatement body + args |> List.map (ident >> Arg.arg), body + + let declareEntryPoint _com _ctx (funcExpr: Expression) = + let argv = emitExpression None "typeof process === 'object' ? process.argv.slice(2) : []" [] + let main = Expression.call(funcExpr, [ argv ]) + // Don't exit the process after leaving main, as there may be a server running + // Statement.expr(emitExpression funcExpr.loc "process.exit($0)" [main], ?loc=funcExpr.loc) + Statement.expr(main) + + let declareModuleMember isPublic (membName: string) isMutable (expr: Expression) = + let membName = Expression.name(membName) + match expr with + // | ClassExpression(body, id, superClass, implements, superTypeParameters, typeParameters, loc) -> + // Declaration.classDeclaration( + // body, + // ?id = Some membName, + // ?superClass = superClass, + // ?superTypeParameters = superTypeParameters, + // ?typeParameters = typeParameters, + // ?implements = implements) + // | FunctionExpression(_, ``params``, body, returnType, typeParameters, _) -> + // Declaration.functionDeclaration( + // ``params``, body, membName, + // ?returnType = returnType, + // ?typeParameters = typeParameters) + | _ -> + printfn $"declareModuleMember: Got {expr}" + varDeclaration membName isMutable expr + // if not isPublic then PrivateModuleDeclaration(decl |> Declaration) + // else ExportNamedDeclaration(decl) + +// let makeEntityTypeParamDecl (com: IPythonCompiler) _ctx (ent: Fable.Entity) = +// if com.Options.Language = TypeScript then +// getEntityGenParams ent |> makeTypeParamDecl +// else +// None + +// let getClassImplements com ctx (ent: Fable.Entity) = +// let mkNative genArgs typeName = +// let id = Identifier.identifier(typeName) +// let typeParamInst = makeGenTypeParamInst com ctx genArgs +// ClassImplements.classImplements(id, ?typeParameters=typeParamInst) |> Some +// // let mkImport genArgs moduleName typeName = +// // let id = makeImportTypeId com ctx moduleName typeName +// // let typeParamInst = makeGenTypeParamInst com ctx genArgs +// // ClassImplements(id, ?typeParameters=typeParamInst) |> Some +// ent.AllInterfaces |> Seq.choose (fun ifc -> +// match ifc.Entity.FullName with +// | "Fable.Core.JS.Set`1" -> mkNative ifc.GenericArgs "Set" +// | "Fable.Core.JS.Map`2" -> mkNative ifc.GenericArgs "Map" +// | _ -> None +// ) + +// let getUnionFieldsAsIdents (_com: IPythonCompiler) _ctx (_ent: Fable.Entity) = +// let tagId = makeTypedIdent (Fable.Number Int32) "tag" +// let fieldsId = makeTypedIdent (Fable.Array Fable.Any) "fields" +// [| tagId; fieldsId |] + +// let getEntityFieldsAsIdents _com (ent: Fable.Entity) = +// ent.FSharpFields +// |> Seq.map (fun field -> +// let name = field.Name |> Naming.sanitizeIdentForbiddenChars |> Naming.checkJsKeywords +// let typ = field.FieldType +// let id: Fable.Ident = { makeTypedIdent typ name with IsMutable = field.IsMutable } +// id) +// |> Seq.toArray + +// let getEntityFieldsAsProps (com: IPythonCompiler) ctx (ent: Fable.Entity) = +// if ent.IsFSharpUnion then +// getUnionFieldsAsIdents com ctx ent +// |> Array.map (fun id -> +// let prop = identAsExpr id +// let ta = typeAnnotation com ctx id.Type +// ObjectTypeProperty.objectTypeProperty(prop, ta)) +// else +// ent.FSharpFields +// |> Seq.map (fun field -> +// let prop, computed = memberFromName field.Name +// let ta = typeAnnotation com ctx field.FieldType +// let isStatic = if field.IsStatic then Some true else None +// ObjectTypeProperty.objectTypeProperty(prop, ta, computed_=computed, ?``static``=isStatic)) +// |> Seq.toArray + +// let declareClassType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Pattern[]) (consBody: BlockStatement) (baseExpr: Expression option) classMembers = +// let typeParamDecl = makeEntityTypeParamDecl com ctx ent +// let implements = +// if com.Options.Language = TypeScript then +// let implements = Util.getClassImplements com ctx ent |> Seq.toArray +// if Array.isEmpty implements then None else Some implements +// else None +// let classCons = makeClassConstructor consArgs consBody +// let classFields = +// if com.Options.Language = TypeScript then +// getEntityFieldsAsProps com ctx ent +// |> Array.map (fun (ObjectTypeProperty(key, value, _, _, ``static``, _, _, _)) -> +// let ta = value |> TypeAnnotation |> Some +// ClassMember.classProperty(key, ``static``=``static``, ?typeAnnotation=ta)) +// else Array.empty +// let classMembers = Array.append [| classCons |] classMembers +// let classBody = ClassBody.classBody([| yield! classFields; yield! classMembers |]) +// let classExpr = Expression.classExpression(classBody, ?superClass=baseExpr, ?typeParameters=typeParamDecl, ?implements=implements) +// classExpr |> declareModuleMember ent.IsPublic entName false + + // let declareType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Arg list) (consBody: Statement list) baseExpr classMembers: Module list = + // let typeDeclaration = declareClassType com ctx ent entName consArgs consBody baseExpr classMembers + // let reflectionDeclaration = + // let ta = + // if com.Options.Language = TypeScript then + // makeImportTypeAnnotation com ctx [] "Reflection" "TypeInfo" + // |> TypeAnnotation |> Some + // else None + // let genArgs = Array.init (ent.GenericParameters.Length) (fun i -> "gen" + string i |> makeIdent) + // let generics = genArgs |> Array.map identAsExpr + // let body = transformReflectionInfo com ctx None ent generics + // let args = genArgs |> Array.map (fun x -> Pattern.identifier(x.Name, ?typeAnnotation=ta)) + // let returnType = ta + // makeFunctionExpression None (args, body, returnType, None) + // |> declareModuleMember ent.IsPublic (entName + Naming.reflectionSuffix) false + // [typeDeclaration; reflectionDeclaration] + + let transformModuleFunction (com: IPythonCompiler) ctx (info: Fable.MemberInfo) (membName: string) args body = + let args, body = + getMemberArgsAndBody com ctx (NonAttached membName) info.HasSpread args body + //let expr = Expression.functionExpression(args, body, ?returnType=returnType, ?typeParameters=typeParamDecl) + let name = Helpers.getUniqueIdentifier "lifted" + let stmt = FunctionDef.Create(name = name, args = Arguments.arguments args, body = body) + let expr = Expression.name (name) + info.Attributes + |> Seq.exists (fun att -> att.Entity.FullName = Atts.entryPoint) + |> function + | true -> [ stmt; declareEntryPoint com ctx expr ] + | false -> [ declareModuleMember info.IsPublic membName false expr ] + + let transformAction (com: IPythonCompiler) ctx expr = + let statements = transformAsStatements com ctx None expr + // let hasVarDeclarations = + // statements |> List.exists (function + // | Declaration(_) -> true + // | _ -> false) + // if hasVarDeclarations then + // [ Expression.call(Expression.functionExpression([||], BlockStatement(statements)), [||]) + // |> Statement.expr |> PrivateModuleDeclaration ] + //else + statements + + // let transformAttachedProperty (com: IPythonCompiler) ctx (memb: Fable.MemberDecl) = + // let isStatic = not memb.Info.IsInstance + // let kind = if memb.Info.IsGetter then ClassGetter else ClassSetter + // let args, body, _returnType, _typeParamDecl = + // getMemberArgsAndBody com ctx (Attached isStatic) false memb.Args memb.Body + // let key, computed = memberFromName memb.Name + // ClassMember.classMethod(kind, key, args, body, computed_=computed, ``static``=isStatic) + // |> Array.singleton + + // let transformAttachedMethod (com: IPythonCompiler) ctx (memb: Fable.MemberDecl) = + // let isStatic = not memb.Info.IsInstance + // let makeMethod name args body = + // let key, computed = memberFromName name + // ClassMember.classMethod(ClassFunction, key, args, body, computed_=computed, ``static``=isStatic) + // let args, body, _returnType, _typeParamDecl = + // getMemberArgsAndBody com ctx (Attached isStatic) memb.Info.HasSpread memb.Args memb.Body + // [| + // yield makeMethod memb.Name args body + // if memb.Info.IsEnumerator then + // yield makeMethod "Symbol.iterator" [||] (enumerator2iterator com ctx) + // |] + + // let transformUnion (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = + // let fieldIds = getUnionFieldsAsIdents com ctx ent + // let args = + // [ fieldIds.[0] + // fieldIds.[1] |> restElement ] + // let body = + // [ + // yield callSuperAsStatement [] + // yield! fieldIds |> Array.map (fun id -> + // let left = get None thisExpr id.Name + // let right = + // match id.Type with + // | Fable.Number _ -> + // Expression.binOp(identAsExpr id, BinaryOrBitwise, Expression.constant(0.)) + // | _ -> identAsExpr id + // assign None left right |> Statement.expr) + // ] + // let cases = + // let body = + // ent.UnionCases + // |> Seq.map (getUnionCaseName >> makeStrConst) + // |> Seq.toList + // |> makeArray com ctx + // |> Statement.return' + // |> List.singleton + // let name = Identifier("cases") + // FunctionDef.Create(name, Arguments.arguments [], body = body) + + // let baseExpr = libValue com ctx "Types" "Union" |> Some + // let classMembers = Array.append [|cases|] classMembers + // declareType com ctx ent entName args body baseExpr classMembers + + // let transformClassWithCompilerGeneratedConstructor (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = + // let fieldIds = getEntityFieldsAsIdents com ent + // let args = fieldIds |> Array.map identAsExpr + // let baseExpr = + // if ent.IsFSharpExceptionDeclaration + // then libValue com ctx "Types" "FSharpException" |> Some + // elif ent.IsFSharpRecord || ent.IsValueType + // then libValue com ctx "Types" "Record" |> Some + // else None + // let body = [ + // if Option.isSome baseExpr then + // yield callSuperAsStatement [] + // yield! ent.FSharpFields |> Seq.mapi (fun i field -> + // let left = get None thisExpr field.Name + // let right = wrapIntExpression field.FieldType args.[i] + // assign None left right |> Statement.expr) + // |> Seq.toArray + // ] + // let typedPattern x = typedIdent com ctx x + // let args = fieldIds |> Array.map (typedPattern >> Pattern.Identifier) + // declareType com ctx ent entName args body baseExpr classMembers + + // let transformClassWithImplicitConstructor (com: IPythonCompiler) ctx (classDecl: Fable.ClassDecl) classMembers (cons: Fable.MemberDecl) = + // let classEnt = com.GetEntity(classDecl.Entity) + // let classIdent = Expression.identifier(classDecl.Name) + // let consArgs, consBody, returnType, typeParamDecl = + // getMemberArgsAndBody com ctx ClassConstructor cons.Info.HasSpread cons.Args cons.Body + + // let returnType, typeParamDecl = + // // change constructor's return type from void to entity type + // if com.Options.Language = TypeScript then + // let genParams = getEntityGenParams classEnt + // let returnType = getGenericTypeAnnotation com ctx classDecl.Name genParams + // let typeParamDecl = makeTypeParamDecl genParams |> mergeTypeParamDecls typeParamDecl + // returnType, typeParamDecl + // else + // returnType, typeParamDecl + + // let exposedCons = + // let argExprs = consArgs |> Array.map (fun p -> Expression.identifier(p.Name)) + // let exposedConsBody = Expression.newExpression(classIdent, argExprs) + // makeFunctionExpression None (consArgs, exposedConsBody, returnType, typeParamDecl) + + // let baseExpr, consBody = + // classDecl.BaseCall + // |> extractBaseExprFromBaseCall com ctx classEnt.BaseType + // |> Option.orElseWith (fun () -> + // if classEnt.IsValueType then Some(libValue com ctx "Types" "Record", []) + // else None) + // |> Option.map (fun (baseExpr, baseArgs) -> + // let consBody = + // consBody.Body + // |> List.append [ callSuperAsStatement baseArgs ] + // Some baseExpr, consBody) + // |> Option.defaultValue (None, consBody) + + // [ + // yield! declareType com ctx classEnt classDecl.Name consArgs consBody baseExpr classMembers + // yield declareModuleMember cons.Info.IsPublic cons.Name false exposedCons + // ] + + let rec transformDeclaration (com: IPythonCompiler) ctx decl = + let withCurrentScope ctx (usedNames: Set) f = + let ctx = { ctx with UsedNames = { ctx.UsedNames with CurrentDeclarationScope = HashSet usedNames } } + let result = f ctx + ctx.UsedNames.DeclarationScopes.UnionWith(ctx.UsedNames.CurrentDeclarationScope) + result + + match decl with + | Fable.ActionDeclaration decl -> + withCurrentScope ctx decl.UsedNames <| fun ctx -> + transformAction com ctx decl.Body + + | Fable.MemberDeclaration decl -> + withCurrentScope ctx decl.UsedNames <| fun ctx -> + let decls = + if decl.Info.IsValue then + let value, stmts = transformAsExpr com ctx decl.Body + stmts @ [declareModuleMember decl.Info.IsPublic decl.Name decl.Info.IsMutable value] + else + transformModuleFunction com ctx decl.Info decl.Name decl.Args decl.Body + + if decl.ExportDefault then + decls //@ [ ExportDefaultDeclaration(Choice2Of2(Expression.identifier(decl.Name))) ] + else decls + + // | Fable.ClassDeclaration decl -> + // let ent = decl.Entity + + // let classMembers = + // decl.AttachedMembers + // |> List.toArray + // |> Array.collect (fun memb -> + // withCurrentScope ctx memb.UsedNames <| fun ctx -> + // if memb.Info.IsGetter || memb.Info.IsSetter then + // transformAttachedProperty com ctx memb + // else + // transformAttachedMethod com ctx memb) + + // match decl.Constructor with + // | Some cons -> + // withCurrentScope ctx cons.UsedNames <| fun ctx -> + // transformClassWithImplicitConstructor com ctx decl classMembers cons + // | None -> + // let ent = com.GetEntity(ent) + // if ent.IsFSharpUnion then transformUnion com ctx ent decl.Name classMembers + // else transformClassWithCompilerGeneratedConstructor com ctx ent decl.Name classMembers + | _ -> failwith $"Declaration {decl} not implemented" + + // let transformImports (imports: Import seq): Module list = + // let statefulImports = ResizeArray() + // imports |> Seq.map (fun import -> + // let specifier = + // import.LocalIdent + // |> Option.map (fun localId -> + // let localId = Identifier.identifier(localId) + // match import.Selector with + // | "*" -> ImportNamespaceSpecifier(localId) + // | "default" | "" -> ImportDefaultSpecifier(localId) + // | memb -> ImportMemberSpecifier(localId, Identifier.identifier(memb))) + // import.Path, specifier) + // |> Seq.groupBy fst + // |> Seq.collect (fun (path, specifiers) -> + // let mems, defs, alls = + // (([], [], []), Seq.choose snd specifiers) + // ||> Seq.fold (fun (mems, defs, alls) x -> + // match x with + // | ImportNamespaceSpecifier(_) -> mems, defs, x::alls + // | ImportDefaultSpecifier(_) -> mems, x::defs, alls + // | _ -> x::mems, defs, alls) + // // We used to have trouble when mixing member, default and namespace imports, + // // issue an import statement for each kind just in case + // [mems; defs; alls] |> List.choose (function + // | [] -> None + // | specifiers -> + // ImportDeclaration(List.toArray specifiers, StringLiteral.stringLiteral(path)) + // |> Some) + // |> function + // | [] -> + // // If there are no specifiers, this is just an import for side effects, + // // put it after the other ones to match standard JS practices, see #2228 + // ImportDeclaration([||], StringLiteral.stringLiteral(path)) + // |> statefulImports.Add + // [] + // | decls -> decls + // ) + // |> fun staticImports -> [ + // yield! staticImports + // yield! statefulImports + // ] + + let getIdentForImport (ctx: Context) (path: string) (selector: string) = + if System.String.IsNullOrEmpty selector then None + else + match selector with + | "*" | "default" -> Path.GetFileNameWithoutExtension(path) + | _ -> selector + |> getUniqueNameInRootScope ctx + |> Some + +module Compiler = + open Util + + type BabelCompiler (com: Compiler) = + let onlyOnceWarnings = HashSet() + let imports = Dictionary() + + interface IPythonCompiler with + member _.WarnOnlyOnce(msg, ?range) = + if onlyOnceWarnings.Add(msg) then + addWarning com [] range msg + + member _.GetImportExpr(ctx, selector, path, r) = + let cachedName = path + "::" + selector + match imports.TryGetValue(cachedName) with + | true, i -> + match i.LocalIdent with + | Some localIdent -> Expression.identifier(localIdent) + | None -> Expression.none() + | false, _ -> + let localId = getIdentForImport ctx path selector + let i = + { Selector = + if selector = Naming.placeholder then + "`importMember` must be assigned to a variable" + |> addError com [] r; selector + else selector + Path = path + LocalIdent = localId } + imports.Add(cachedName, i) + match localId with + | Some localId -> Expression.identifier(localId) + | None -> Expression.none() + member _.GetAllImports() = imports.Values :> _ + member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e + member bcom.TransformAsStatements(ctx, ret, e) = transformAsStatements bcom ctx ret e + member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body + member bcom.TransformImport(ctx, selector, path) = transformImport bcom ctx None selector path + member bcom.GetIdentifier(ctx, name) = getIdentifier bcom ctx name + + interface Compiler with + member _.Options = com.Options + member _.Plugins = com.Plugins + member _.LibraryDir = com.LibraryDir + member _.CurrentFile = com.CurrentFile + member _.OutputDir = com.OutputDir + member _.ProjectFile = com.ProjectFile + member _.GetEntity(fullName) = com.GetEntity(fullName) + member _.GetImplementationFile(fileName) = com.GetImplementationFile(fileName) + member _.GetRootModule(fileName) = com.GetRootModule(fileName) + member _.GetOrAddInlineExpr(fullName, generate) = com.GetOrAddInlineExpr(fullName, generate) + member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) + member _.AddLog(msg, severity, ?range, ?fileName:string, ?tag: string) = + com.AddLog(msg, severity, ?range=range, ?fileName=fileName, ?tag=tag) + + let makeCompiler com = BabelCompiler(com) + + let transformFile (com: Compiler) (file: Fable.File) = + let com = makeCompiler com :> IPythonCompiler + let declScopes = + let hs = HashSet() + for decl in file.Declarations do + hs.UnionWith(decl.UsedNames) + hs + + let ctx = + { File = file + UsedNames = { RootScope = HashSet file.UsedNamesInRootScope + DeclarationScopes = declScopes + CurrentDeclarationScope = Unchecked.defaultof<_> } + DecisionTargets = [] + HoistVars = fun _ -> false + TailCallOpportunity = None + OptimizeTailCall = fun () -> () + ScopedTypeParams = Set.empty } + + let rootDecls = List.collect (transformDeclaration com ctx) file.Declarations + let importDecls = [] //com.GetAllImports() |> transformImports + let body = importDecls @ rootDecls + Module.module' (body) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index c6558ccbbd..08beba0e32 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -18,7 +18,10 @@ type Expression = | IfExp of IfExp | UnaryOp of UnaryOp | FormattedValue of FormattedValue - | Constant of Constant + /// A constant value. The value attribute of the Constant literal contains the Python object it represents. The + /// values represented can be simple types such as a number, string or None, but also immutable container types + /// (tuples and frozensets) if all of their elements are constant. + | Constant of value: obj * loc: SourceLocation option | Call of Call | Compare of Compare | Lambda of Lambda @@ -77,11 +80,11 @@ type ExpressionContext = | Store type Identifier = - Identifier of string - with - member this.Name = - let (Identifier name) = this - name + | Identifier of name: string + + member this.Name = + let (Identifier name) = this + name type Statement = | Pass @@ -305,7 +308,8 @@ type AsyncFor = type While = { Test: Expression Body: Statement list - Else: Statement list } + Else: Statement list + Loc: SourceLocation option } /// A class definition. /// @@ -386,7 +390,8 @@ type ClassDef = type If = { Test: Expression Body: Statement list - Else: Statement list } + Else: Statement list + Loc: SourceLocation option } /// A raise statement. exc is the exception object to be raised, normally a Call or Name, or None for a standalone /// raise. cause is the optional part for y in raise x from y. @@ -404,7 +409,7 @@ type Raise = { Exception: Expression Cause: Expression option } - static member Create(exc, ?cause): Statement = { Exception = exc; Cause = cause } |> Raise + static member Create(exc, ?cause) : Statement = { Exception = exc; Cause = cause } |> Raise /// A function definition. /// @@ -423,7 +428,7 @@ type FunctionDef = Returns: Expression option TypeComment: string option } - static member Create(name, args, body, ?decoratorList, ?returns, ?typeComment): Statement = + static member Create(name, args, body, ?decoratorList, ?returns, ?typeComment) : Statement = { Name = name Args = args Body = body @@ -566,7 +571,8 @@ type Attribute = type NamedExpr = { Target: Expression - Value: Expression } + Value: Expression + Loc: SourceLocation option } /// A subscript, such as l[1]. value is the subscripted object (usually sequence or mapping). slice is an index, slice /// or key. It can be a Tuple and contain a Slice. ctx is Load, Store or Del according to the action performed with the @@ -594,11 +600,13 @@ type Subscript = type BinOp = { Left: Expression Right: Expression - Operator: Operator } + Operator: Operator + Loc: SourceLocation option } type BoolOp = { Values: Expression list - Operator: BoolOperator } + Operator: BoolOperator + Loc: SourceLocation option } /// A comparison of two or more values. left is the first value in the comparison, ops the list of operators, and /// comparators the list of values after the first element in the comparison. @@ -618,7 +626,8 @@ type BoolOp = type Compare = { Left: Expression Comparators: Expression list - Ops: ComparisonOperator list } + Ops: ComparisonOperator list + Loc: SourceLocation option } /// A unary operation. op is the operator, and operand any expression node. type UnaryOp = @@ -626,17 +635,6 @@ type UnaryOp = Operand: Expression Loc: SourceLocation option } -/// A constant value. The value attribute of the Constant literal contains the Python object it represents. The values -/// represented can be simple types such as a number, string or None, but also immutable container types (tuples and -/// frozensets) if all of their elements are constant. -/// -/// ```py -/// >>> print(ast.dump(ast.parse('123', mode='eval'), indent=4)) -/// Expression( -/// body=Constant(value=123)) -/// ````` -type Constant = { Value: obj } - /// Node representing a single formatting field in an f-string. If the string contains a single formatting field and /// nothing else the node can be isolated otherwise it appears in JoinedStr. /// @@ -681,11 +679,13 @@ type FormattedValue = type Call = { Func: Expression Args: Expression list - Keywords: Keyword list } + Keywords: Keyword list + Loc: SourceLocation option } type Emit = { Value: string - Args: Expression list } + Args: Expression list + Loc: SourceLocation option } /// An expression such as a if b else c. Each field holds a single node, so in the following example, all three are Name nodes. /// @@ -700,7 +700,8 @@ type Emit = type IfExp = { Test: Expression Body: Expression - OrElse: Expression } + OrElse: Expression + Loc: SourceLocation option } /// lambda is a minimal function definition that can be used inside an expression. Unlike FunctionDef, body holds a /// single node. @@ -793,7 +794,8 @@ type Dict = /// A variable name. id holds the name as a string, and ctx is one of the following types. type Name = { Id: Identifier - Context: ExpressionContext } + Context: ExpressionContext + Loc: SourceLocation option } [] type AST = @@ -814,15 +816,16 @@ type AST = [] module PythonExtensions = type Statement with + static member break'() : Statement = Break + static member continue' (?loc) : Statement = Continue + static member import(names) : Statement = Import { Names = names } + static member expr(value) : Statement = { Expr.Value = value } |> Expr - static member import(names): Statement = Import { Names = names } - static member expr(value): Statement = { Expr.Value = value } |> Expr - - static member try'(body, ?handlers, ?orElse, ?finalBody, ?loc): Statement = + static member try'(body, ?handlers, ?orElse, ?finalBody, ?loc) : Statement = Try.try' (body, ?handlers = handlers, ?orElse = orElse, ?finalBody = finalBody, ?loc = loc) |> Try - static member classDef(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc): Statement = + static member classDef(name, ?bases, ?keywords, ?body, ?decoratorList, ?loc) : Statement = { Name = name Bases = defaultArg bases [] Keyword = defaultArg keywords [] @@ -831,103 +834,157 @@ module PythonExtensions = Loc = loc } |> ClassDef - static member assign(targets, value, ?typeComment): Statement = + static member assign(targets, value, ?typeComment) : Statement = { Targets = targets Value = value TypeComment = typeComment } |> Assign - static member return'(?value): Statement = Return { Value = value } + static member return'(?value) : Statement = Return { Value = value } - static member for'(target, iter, ?body, ?orelse, ?typeComment): Statement = + static member for'(target, iter, ?body, ?orelse, ?typeComment) : Statement = For.for' (target, iter, ?body = body, ?orelse = orelse, ?typeComment = typeComment) |> For - static member while'(test, body, ?orelse): Statement = + static member while'(test, body, ?orelse, ?loc) : Statement = { While.Test = test Body = body - Else = defaultArg orelse [] } + Else = defaultArg orelse [] + Loc = loc } |> While - static member if'(test, body, ?orelse): Statement = + static member if'(test, body, ?orelse, ?loc) : Statement = { Test = test Body = body - Else = defaultArg orelse [] } + Else = defaultArg orelse [] + Loc = loc } |> If static member importFrom(``module``, names, ?level) = ImportFrom.importFrom (``module``, names, ?level = level) |> ImportFrom + static member nonLocal(ids) = NonLocal.Create ids |> Statement.NonLocal type Expression with - static member name(id, ?ctx): Expression = - { Id = id - Context = defaultArg ctx Load } + static member name(identifier, ?ctx, ?loc) : Expression = + { Id = identifier + Context = defaultArg ctx Load + Loc = loc } |> Name - static member name(name, ?ctx): Expression = - Expression.name(Identifier(name), ?ctx=ctx) - static member dict(keys, values): Expression = { Keys = keys; Values = values } |> Dict - static member tuple(elts, ?loc): Expression = { Elements = elts; Loc = loc } |> Tuple + static member name(name, ?ctx) : Expression = Expression.name(Identifier(name), ?ctx = ctx) + static member identifier(name, ?ctx, ?loc) : Expression = Expression.name(Identifier(name), ?ctx = ctx, ?loc = loc) + static member identifier(identifier, ?ctx, ?loc) : Expression = Expression.name(identifier, ?ctx = ctx, ?loc = loc) - static member ifExp(test, body, orElse): Expression = + static member dict(keys, values) : Expression = { Keys = keys; Values = values } |> Dict + static member tuple(elts, ?loc) : Expression = { Elements = elts; Loc = loc } |> Tuple + + static member ifExp(test, body, orElse, ?loc) : Expression = { Test = test Body = body - OrElse = orElse } + OrElse = orElse + Loc = loc } |> IfExp - static member lambda(args, body): Expression = { Args = args; Body = body } |> Lambda + static member lambda(args, body) : Expression = { Args = args; Body = body } |> Lambda - static member emit(value, ?args): Expression = + static member emit(value, ?args, ?loc) : Expression = { Value = value - Args = defaultArg args [] } + Args = defaultArg args [] + Loc = loc } |> Emit - static member call(func, ?args, ?kw): Expression = + static member call(func, ?args, ?kw, ?loc) : Expression = { Func = func Args = defaultArg args [] - Keywords = defaultArg kw [] } + Keywords = defaultArg kw [] + Loc = loc } |> Call - static member compare(left, ops, comparators): Expression = + static member compare(left, ops, comparators, ?loc) : Expression = { Left = left Comparators = comparators - Ops = ops } + Ops = ops + Loc = loc } |> Compare + static member none() = + Expression.name (Identifier(name="None")) - static member attribute(value, attr, ?ctx): Expression = + static member attribute(value, attr, ?ctx) : Expression = { Value = value Attr = attr Ctx = defaultArg ctx Load } |> Attribute - static member unaryOp(op, operand, ?loc): Expression = + static member unaryOp(op, operand, ?loc) : Expression = + let op = + match op with + | UnaryMinus -> USub + | UnaryPlus -> UAdd + | UnaryNot -> Not + | UnaryNotBitwise -> Invert + | _ -> failwith $"Operator {op} not supported" + // | UnaryTypeof -> "typeof" + // | UnaryVoid -> "void" + // | UnaryDelete -> "delete" + + Expression.unaryOp(op, operand, ?loc=loc) + + static member unaryOp(op, operand, ?loc) : Expression = { Op = op Operand = operand Loc = loc } |> UnaryOp - static member namedExpr(target, value) = { Target = target; Value = value } |> NamedExpr + static member namedExpr(target, value, ?loc) = { Target = target; Value = value; Loc=loc } |> NamedExpr - static member subscript(value, slice, ?ctx): Expression = + static member subscript(value, slice, ?ctx) : Expression = { Value = value Slice = slice Ctx = defaultArg ctx Load } |> Subscript - static member binOp(left, op, right): Expression = + static member binOp(left, op, right, ?loc) : Expression = { Left = left Right = right - Operator = op } + Operator = op + Loc = loc } |> BinOp - static member boolOp(op, values): Expression = { Values = values; Operator = op } |> BoolOp - static member constant(value: obj): Expression = { Value = value } |> Constant + static member binOp(left, op, right, ?loc) : Expression = + let op = + match op with + | BinaryPlus -> Add + | BinaryMinus -> Sub + | BinaryMultiply -> Mult + | BinaryDivide -> Div + | BinaryModulus -> Mod + | BinaryOrBitwise -> BitOr + | BinaryAndBitwise -> BitAnd + | BinaryShiftLeft -> LShift + | BinaryShiftRightZeroFill -> RShift + | BinaryShiftRightSignPropagating -> RShift + | BinaryXorBitwise -> BitXor + | _ -> failwith $"Operator {op} not supported" + + Expression.binOp(left, op, right, ?loc=loc) + + static member boolOp(op, values, ?loc) : Expression = { Values = values; Operator = op; Loc=loc } |> BoolOp + static member boolOp(op, values, ?loc) : Expression = + let op = + match op with + | LogicalAnd -> And + | LogicalOr -> Or + + Expression.boolOp(op, values, ?loc=loc) + static member constant(value: obj, ?loc) : Expression = Constant (value=value, loc=loc) static member starred(value: Expression, ?ctx: ExpressionContext) : Expression = Starred(value, ctx |> Option.defaultValue Load) static member list(elts: Expression list, ?ctx: ExpressionContext) : Expression = List(elts, ctx |> Option.defaultValue Load) + type List with + static member list(elts) = { Elements = elts } type ExceptHandler with @@ -939,6 +996,7 @@ module PythonExtensions = Loc = loc } type Alias with + static member alias(name, ?asname) = { Name = name; AsName = asname } type Try with @@ -1019,10 +1077,7 @@ module PythonExtensions = { Module = ``module`` Names = names Level = level } + type Expr with - static member expr(value) : Expr = - { Value=value } - type Constant with - static member contant(value) : Expr = - { Value=value } \ No newline at end of file + static member expr(value) : Expr = { Value = value } diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index b11c8470a1..a2f9d56ebc 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -152,8 +152,6 @@ module PrinterExtensions = member printer.Print(arguments: Arguments) = let args = arguments.Args |> List.map AST.Arg let defaults = arguments.Defaults - if defaults.Length > 0 then - printfn "Got defaults. %A" defaults for i = 0 to args.Length - 1 do printer.Print(args.[i]) if i >= args.Length - defaults.Length then @@ -334,14 +332,6 @@ module PrinterExtensions = printer.ComplexExpressionWithParens(node.Operand) - member printer.Print(node: Constant) = - match box node.Value with - | :? string as str -> - printer.Print("\"") - printer.Print(Web.HttpUtility.JavaScriptStringEncode(string node.Value)) - printer.Print("\"") - | _ -> printer.Print(string node.Value) - member printer.Print(node: FormattedValue) = printer.Print("(FormattedValue)") member printer.Print(node: Call) = @@ -400,7 +390,7 @@ module PrinterExtensions = let i = int m.Groups.[1].Value match node.Args.[i] with - | Constant (c) -> m.Groups.[2].Value + | Constant (value=c) -> m.Groups.[2].Value | _ -> m.Groups.[3].Value) |> replace @@ -585,7 +575,14 @@ module PrinterExtensions = | Emit (ex) -> printer.Print(ex) | UnaryOp (ex) -> printer.Print(ex) | FormattedValue (ex) -> printer.Print(ex) - | Constant (ex) -> printer.Print(ex) + | Constant (value=value) -> + match box value with + | :? string as str -> + printer.Print("\"") + printer.Print(Web.HttpUtility.JavaScriptStringEncode(string value)) + printer.Print("\"") + | _ -> printer.Print(string value) + | IfExp (ex) -> printer.Print(ex) | Call (ex) -> printer.Print(ex) | Lambda (ex) -> printer.Print(ex) From ac25680615c23dc816c65df6cf46bae36eb2f6a6 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 28 May 2021 23:25:17 +0200 Subject: [PATCH 099/145] WIP --- src/Fable.Transforms/Python/Fable2Python.fs | 451 +++++++++----------- 1 file changed, 203 insertions(+), 248 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 13058f7df2..b9927a9f45 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -617,7 +617,8 @@ module Util = let varDeclaration (var: Expression) (isMutable: bool) value = Statement.assign([var], value) - let restElement (var: Expression) = + let restElement (var: Fable.Ident) = + let var = Expression.name (ident var) Expression.starred(var) let callSuper (args: Expression list) = @@ -724,12 +725,12 @@ module Util = let func = FunctionDef.Create(name = name, args = args, body = body) Expression.name (name), [ func ] - let makeFunctionExpression name (args, (body: Expression), returnType, typeParamDecl): Expression * Statement list= + let makeFunctionExpression name (args, (body: Expression)) : Expression * Statement list= let id = name |> Option.map Identifier let body = wrapExprInBlockWithReturn (body, []) let name = Helpers.getUniqueIdentifier "lifted" let func = - FunctionDef.Create(name = name, args = args, body = body) + FunctionDef.Create(name = name, args = Arguments.arguments args, body = body) //Expression.functionExpression(args, body, ?id=id, ?returnType=returnType, ?typeParameters=typeParamDecl) Expression.name (name), [ func ] @@ -1729,72 +1730,61 @@ module Util = // | _ -> None // ) -// let getUnionFieldsAsIdents (_com: IPythonCompiler) _ctx (_ent: Fable.Entity) = -// let tagId = makeTypedIdent (Fable.Number Int32) "tag" -// let fieldsId = makeTypedIdent (Fable.Array Fable.Any) "fields" -// [| tagId; fieldsId |] - -// let getEntityFieldsAsIdents _com (ent: Fable.Entity) = -// ent.FSharpFields -// |> Seq.map (fun field -> -// let name = field.Name |> Naming.sanitizeIdentForbiddenChars |> Naming.checkJsKeywords -// let typ = field.FieldType -// let id: Fable.Ident = { makeTypedIdent typ name with IsMutable = field.IsMutable } -// id) -// |> Seq.toArray - -// let getEntityFieldsAsProps (com: IPythonCompiler) ctx (ent: Fable.Entity) = -// if ent.IsFSharpUnion then -// getUnionFieldsAsIdents com ctx ent -// |> Array.map (fun id -> -// let prop = identAsExpr id -// let ta = typeAnnotation com ctx id.Type -// ObjectTypeProperty.objectTypeProperty(prop, ta)) -// else -// ent.FSharpFields -// |> Seq.map (fun field -> -// let prop, computed = memberFromName field.Name -// let ta = typeAnnotation com ctx field.FieldType -// let isStatic = if field.IsStatic then Some true else None -// ObjectTypeProperty.objectTypeProperty(prop, ta, computed_=computed, ?``static``=isStatic)) -// |> Seq.toArray - -// let declareClassType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Pattern[]) (consBody: BlockStatement) (baseExpr: Expression option) classMembers = -// let typeParamDecl = makeEntityTypeParamDecl com ctx ent -// let implements = -// if com.Options.Language = TypeScript then -// let implements = Util.getClassImplements com ctx ent |> Seq.toArray -// if Array.isEmpty implements then None else Some implements -// else None -// let classCons = makeClassConstructor consArgs consBody -// let classFields = -// if com.Options.Language = TypeScript then -// getEntityFieldsAsProps com ctx ent -// |> Array.map (fun (ObjectTypeProperty(key, value, _, _, ``static``, _, _, _)) -> -// let ta = value |> TypeAnnotation |> Some -// ClassMember.classProperty(key, ``static``=``static``, ?typeAnnotation=ta)) -// else Array.empty -// let classMembers = Array.append [| classCons |] classMembers -// let classBody = ClassBody.classBody([| yield! classFields; yield! classMembers |]) -// let classExpr = Expression.classExpression(classBody, ?superClass=baseExpr, ?typeParameters=typeParamDecl, ?implements=implements) -// classExpr |> declareModuleMember ent.IsPublic entName false - - // let declareType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Arg list) (consBody: Statement list) baseExpr classMembers: Module list = - // let typeDeclaration = declareClassType com ctx ent entName consArgs consBody baseExpr classMembers - // let reflectionDeclaration = - // let ta = - // if com.Options.Language = TypeScript then - // makeImportTypeAnnotation com ctx [] "Reflection" "TypeInfo" - // |> TypeAnnotation |> Some - // else None - // let genArgs = Array.init (ent.GenericParameters.Length) (fun i -> "gen" + string i |> makeIdent) - // let generics = genArgs |> Array.map identAsExpr - // let body = transformReflectionInfo com ctx None ent generics - // let args = genArgs |> Array.map (fun x -> Pattern.identifier(x.Name, ?typeAnnotation=ta)) - // let returnType = ta - // makeFunctionExpression None (args, body, returnType, None) - // |> declareModuleMember ent.IsPublic (entName + Naming.reflectionSuffix) false - // [typeDeclaration; reflectionDeclaration] + let getUnionFieldsAsIdents (_com: IPythonCompiler) _ctx (_ent: Fable.Entity) = + let tagId = makeTypedIdent (Fable.Number Int32) "tag" + let fieldsId = makeTypedIdent (Fable.Array Fable.Any) "fields" + [| tagId; fieldsId |] + + let getEntityFieldsAsIdents _com (ent: Fable.Entity) = + ent.FSharpFields + |> Seq.map (fun field -> + let name = field.Name |> Naming.sanitizeIdentForbiddenChars |> Naming.checkJsKeywords + let typ = field.FieldType + let id: Fable.Ident = { makeTypedIdent typ name with IsMutable = field.IsMutable } + id) + |> Seq.toArray + + let getEntityFieldsAsProps (com: IPythonCompiler) ctx (ent: Fable.Entity) = + if ent.IsFSharpUnion then + getUnionFieldsAsIdents com ctx ent + |> Array.map (fun id -> + let prop = identAsExpr id + //let ta = typeAnnotation com ctx id.Type + prop) + else + ent.FSharpFields + |> Seq.map (fun field -> + let prop, computed = memberFromName field.Name + //let ta = typeAnnotation com ctx field.FieldType + let isStatic = if field.IsStatic then Some true else None + prop) + |> Seq.toArray + + let declareClassType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Arg list) (consBody: Statement list) (baseExpr: Expression option) classMembers = + //let typeParamDecl = makeEntityTypeParamDecl com ctx ent + let classCons = makeClassConstructor consArgs consBody + let classFields = Array.empty + let classMembers = List.append [ classCons ] classMembers + let classBody = [ yield! classFields; yield! classMembers ] + let bases = baseExpr |> Option.toList + //let classExpr = Expression.classExpression(classBody, ?superClass=baseExpr, ?typeParameters=typeParamDecl, ?implements=implements) + let name = Helpers.getUniqueIdentifier "Lifted" + let classStmt = Statement.classDef(name, body = classBody, bases = bases) + let expr = Expression.name(name) + expr |> declareModuleMember ent.IsPublic entName false + + let declareType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Arg list) (consBody: Statement list) baseExpr classMembers : Statement list = + let typeDeclaration = declareClassType com ctx ent entName consArgs consBody baseExpr classMembers + let reflectionDeclaration, stmts = + let ta = None + let genArgs = Array.init (ent.GenericParameters.Length) (fun i -> "gen" + string i |> makeIdent) + let generics = genArgs |> Array.mapToList identAsExpr + let body, stmts = transformReflectionInfo com ctx None ent generics + let args = genArgs |> Array.mapToList (ident >> Arg.arg) + let returnType = ta + let expr, stmts' = makeFunctionExpression None (args, body) + expr |> declareModuleMember ent.IsPublic (entName + Naming.reflectionSuffix) false, stmts @ stmts' + stmts @ [typeDeclaration; reflectionDeclaration] let transformModuleFunction (com: IPythonCompiler) ctx (info: Fable.MemberInfo) (membName: string) args body = let args, body = @@ -1821,120 +1811,113 @@ module Util = //else statements - // let transformAttachedProperty (com: IPythonCompiler) ctx (memb: Fable.MemberDecl) = - // let isStatic = not memb.Info.IsInstance - // let kind = if memb.Info.IsGetter then ClassGetter else ClassSetter - // let args, body, _returnType, _typeParamDecl = - // getMemberArgsAndBody com ctx (Attached isStatic) false memb.Args memb.Body - // let key, computed = memberFromName memb.Name - // ClassMember.classMethod(kind, key, args, body, computed_=computed, ``static``=isStatic) - // |> Array.singleton - - // let transformAttachedMethod (com: IPythonCompiler) ctx (memb: Fable.MemberDecl) = - // let isStatic = not memb.Info.IsInstance - // let makeMethod name args body = - // let key, computed = memberFromName name - // ClassMember.classMethod(ClassFunction, key, args, body, computed_=computed, ``static``=isStatic) - // let args, body, _returnType, _typeParamDecl = - // getMemberArgsAndBody com ctx (Attached isStatic) memb.Info.HasSpread memb.Args memb.Body - // [| - // yield makeMethod memb.Name args body - // if memb.Info.IsEnumerator then - // yield makeMethod "Symbol.iterator" [||] (enumerator2iterator com ctx) - // |] - - // let transformUnion (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = - // let fieldIds = getUnionFieldsAsIdents com ctx ent - // let args = - // [ fieldIds.[0] - // fieldIds.[1] |> restElement ] - // let body = - // [ - // yield callSuperAsStatement [] - // yield! fieldIds |> Array.map (fun id -> - // let left = get None thisExpr id.Name - // let right = - // match id.Type with - // | Fable.Number _ -> - // Expression.binOp(identAsExpr id, BinaryOrBitwise, Expression.constant(0.)) - // | _ -> identAsExpr id - // assign None left right |> Statement.expr) - // ] - // let cases = - // let body = - // ent.UnionCases - // |> Seq.map (getUnionCaseName >> makeStrConst) - // |> Seq.toList - // |> makeArray com ctx - // |> Statement.return' - // |> List.singleton - // let name = Identifier("cases") - // FunctionDef.Create(name, Arguments.arguments [], body = body) - - // let baseExpr = libValue com ctx "Types" "Union" |> Some - // let classMembers = Array.append [|cases|] classMembers - // declareType com ctx ent entName args body baseExpr classMembers - - // let transformClassWithCompilerGeneratedConstructor (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = - // let fieldIds = getEntityFieldsAsIdents com ent - // let args = fieldIds |> Array.map identAsExpr - // let baseExpr = - // if ent.IsFSharpExceptionDeclaration - // then libValue com ctx "Types" "FSharpException" |> Some - // elif ent.IsFSharpRecord || ent.IsValueType - // then libValue com ctx "Types" "Record" |> Some - // else None - // let body = [ - // if Option.isSome baseExpr then - // yield callSuperAsStatement [] - // yield! ent.FSharpFields |> Seq.mapi (fun i field -> - // let left = get None thisExpr field.Name - // let right = wrapIntExpression field.FieldType args.[i] - // assign None left right |> Statement.expr) - // |> Seq.toArray - // ] - // let typedPattern x = typedIdent com ctx x - // let args = fieldIds |> Array.map (typedPattern >> Pattern.Identifier) - // declareType com ctx ent entName args body baseExpr classMembers - - // let transformClassWithImplicitConstructor (com: IPythonCompiler) ctx (classDecl: Fable.ClassDecl) classMembers (cons: Fable.MemberDecl) = - // let classEnt = com.GetEntity(classDecl.Entity) - // let classIdent = Expression.identifier(classDecl.Name) - // let consArgs, consBody, returnType, typeParamDecl = - // getMemberArgsAndBody com ctx ClassConstructor cons.Info.HasSpread cons.Args cons.Body - - // let returnType, typeParamDecl = - // // change constructor's return type from void to entity type - // if com.Options.Language = TypeScript then - // let genParams = getEntityGenParams classEnt - // let returnType = getGenericTypeAnnotation com ctx classDecl.Name genParams - // let typeParamDecl = makeTypeParamDecl genParams |> mergeTypeParamDecls typeParamDecl - // returnType, typeParamDecl - // else - // returnType, typeParamDecl - - // let exposedCons = - // let argExprs = consArgs |> Array.map (fun p -> Expression.identifier(p.Name)) - // let exposedConsBody = Expression.newExpression(classIdent, argExprs) - // makeFunctionExpression None (consArgs, exposedConsBody, returnType, typeParamDecl) - - // let baseExpr, consBody = - // classDecl.BaseCall - // |> extractBaseExprFromBaseCall com ctx classEnt.BaseType - // |> Option.orElseWith (fun () -> - // if classEnt.IsValueType then Some(libValue com ctx "Types" "Record", []) - // else None) - // |> Option.map (fun (baseExpr, baseArgs) -> - // let consBody = - // consBody.Body - // |> List.append [ callSuperAsStatement baseArgs ] - // Some baseExpr, consBody) - // |> Option.defaultValue (None, consBody) + let transformAttachedProperty (com: IPythonCompiler) ctx (memb: Fable.MemberDecl) = + let isStatic = not memb.Info.IsInstance + //let kind = if memb.Info.IsGetter then ClassGetter else ClassSetter + let args, body = + getMemberArgsAndBody com ctx (Attached isStatic) false memb.Args memb.Body + let key, computed = memberFromName memb.Name + let arguments = Arguments.arguments args + FunctionDef.Create(Identifier memb.Name, arguments, body = body) + |> List.singleton + //ClassMember.classMethod(kind, key, args, body, computed_=computed, ``static``=isStatic) + + let transformAttachedMethod (com: IPythonCompiler) ctx (memb: Fable.MemberDecl) = + let isStatic = not memb.Info.IsInstance + let makeMethod name args body = + let key, computed = memberFromName name + //ClassMember.classMethod(ClassFunction, key, args, body, computed_=computed, ``static``=isStatic) + FunctionDef.Create(Identifier name, args, body = body) + let args, body = + getMemberArgsAndBody com ctx (Attached isStatic) memb.Info.HasSpread memb.Args memb.Body + let arguments = Arguments.arguments args + [ + yield makeMethod memb.Name arguments body + if memb.Info.IsEnumerator then + yield makeMethod "Symbol.iterator" (Arguments.arguments []) (enumerator2iterator com ctx) + ] + + let transformUnion (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = + let fieldIds = getUnionFieldsAsIdents com ctx ent + let args = + [ fieldIds.[0] |> ident |> Arg.arg + fieldIds.[1] |> ident |> Arg.arg ] //restElement ] + let body = + [ + yield callSuperAsStatement [] + yield! fieldIds |> Array.map (fun id -> + let left = get None thisExpr id.Name + let right = + match id.Type with + | Fable.Number _ -> + Expression.binOp(identAsExpr id, BinaryOrBitwise, Expression.constant(0.)) + | _ -> identAsExpr id + assign None left right |> Statement.expr) + ] + let cases = + let expr, stmts = + ent.UnionCases + |> Seq.map (getUnionCaseName >> makeStrConst) + |> Seq.toList + |> makeArray com ctx + + let body = stmts @ [ Statement.return'(expr) ] + let name = Identifier("cases") + FunctionDef.Create(name, Arguments.arguments [], body = body) + + let baseExpr = libValue com ctx "Types" "Union" |> Some + let classMembers = List.append [ cases ] classMembers + declareType com ctx ent entName args body baseExpr classMembers + + let transformClassWithCompilerGeneratedConstructor (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = + let fieldIds = getEntityFieldsAsIdents com ent + let args = fieldIds |> Array.map identAsExpr + let baseExpr = + if ent.IsFSharpExceptionDeclaration + then libValue com ctx "Types" "FSharpException" |> Some + elif ent.IsFSharpRecord || ent.IsValueType + then libValue com ctx "Types" "Record" |> Some + else None + let body = [ + if Option.isSome baseExpr then + yield callSuperAsStatement [] + yield! ent.FSharpFields |> Seq.mapi (fun i field -> + let left = get None thisExpr field.Name + let right = wrapIntExpression field.FieldType args.[i] + assign None left right |> Statement.expr) + |> Seq.toArray + ] + let args = fieldIds |> Array.mapToList (ident >> Arg.arg) + declareType com ctx ent entName args body baseExpr classMembers + + let transformClassWithImplicitConstructor (com: IPythonCompiler) ctx (classDecl: Fable.ClassDecl) classMembers (cons: Fable.MemberDecl) = + let classEnt = com.GetEntity(classDecl.Entity) + let classIdent = Expression.identifier(classDecl.Name) + let consArgs, consBody = + getMemberArgsAndBody com ctx ClassConstructor cons.Info.HasSpread cons.Args cons.Body + + let exposedCons, stmts = + let argExprs = consArgs |> List.map (fun p -> Expression.identifier(p.Arg)) + let exposedConsBody = Expression.call(classIdent, argExprs) + makeFunctionExpression None (consArgs, exposedConsBody) + + let baseExpr, consBody = + classDecl.BaseCall + |> extractBaseExprFromBaseCall com ctx classEnt.BaseType + |> Option.orElseWith (fun () -> + if classEnt.IsValueType then Some(libValue com ctx "Types" "Record", ([], [])) + else None) + |> Option.map (fun (baseExpr, (baseArgs, stmts)) -> + let consBody = + stmts @ consBody + |> List.append [ callSuperAsStatement baseArgs ] + Some baseExpr, consBody) + |> Option.defaultValue (None, consBody) - // [ - // yield! declareType com ctx classEnt classDecl.Name consArgs consBody baseExpr classMembers - // yield declareModuleMember cons.Info.IsPublic cons.Name false exposedCons - // ] + [ + yield! declareType com ctx classEnt classDecl.Name consArgs consBody baseExpr classMembers + yield declareModuleMember cons.Info.IsPublic cons.Name false exposedCons + ] let rec transformDeclaration (com: IPythonCompiler) ctx decl = let withCurrentScope ctx (usedNames: Set) f = @@ -1961,70 +1944,41 @@ module Util = decls //@ [ ExportDefaultDeclaration(Choice2Of2(Expression.identifier(decl.Name))) ] else decls - // | Fable.ClassDeclaration decl -> - // let ent = decl.Entity - - // let classMembers = - // decl.AttachedMembers - // |> List.toArray - // |> Array.collect (fun memb -> - // withCurrentScope ctx memb.UsedNames <| fun ctx -> - // if memb.Info.IsGetter || memb.Info.IsSetter then - // transformAttachedProperty com ctx memb - // else - // transformAttachedMethod com ctx memb) - - // match decl.Constructor with - // | Some cons -> - // withCurrentScope ctx cons.UsedNames <| fun ctx -> - // transformClassWithImplicitConstructor com ctx decl classMembers cons - // | None -> - // let ent = com.GetEntity(ent) - // if ent.IsFSharpUnion then transformUnion com ctx ent decl.Name classMembers - // else transformClassWithCompilerGeneratedConstructor com ctx ent decl.Name classMembers - | _ -> failwith $"Declaration {decl} not implemented" - - // let transformImports (imports: Import seq): Module list = - // let statefulImports = ResizeArray() - // imports |> Seq.map (fun import -> - // let specifier = - // import.LocalIdent - // |> Option.map (fun localId -> - // let localId = Identifier.identifier(localId) - // match import.Selector with - // | "*" -> ImportNamespaceSpecifier(localId) - // | "default" | "" -> ImportDefaultSpecifier(localId) - // | memb -> ImportMemberSpecifier(localId, Identifier.identifier(memb))) - // import.Path, specifier) - // |> Seq.groupBy fst - // |> Seq.collect (fun (path, specifiers) -> - // let mems, defs, alls = - // (([], [], []), Seq.choose snd specifiers) - // ||> Seq.fold (fun (mems, defs, alls) x -> - // match x with - // | ImportNamespaceSpecifier(_) -> mems, defs, x::alls - // | ImportDefaultSpecifier(_) -> mems, x::defs, alls - // | _ -> x::mems, defs, alls) - // // We used to have trouble when mixing member, default and namespace imports, - // // issue an import statement for each kind just in case - // [mems; defs; alls] |> List.choose (function - // | [] -> None - // | specifiers -> - // ImportDeclaration(List.toArray specifiers, StringLiteral.stringLiteral(path)) - // |> Some) - // |> function - // | [] -> - // // If there are no specifiers, this is just an import for side effects, - // // put it after the other ones to match standard JS practices, see #2228 - // ImportDeclaration([||], StringLiteral.stringLiteral(path)) - // |> statefulImports.Add - // [] - // | decls -> decls - // ) - // |> fun staticImports -> [ - // yield! staticImports - // yield! statefulImports - // ] + | Fable.ClassDeclaration decl -> + let ent = decl.Entity + + let classMembers = + decl.AttachedMembers + |> List.collect (fun memb -> + withCurrentScope ctx memb.UsedNames <| fun ctx -> + if memb.Info.IsGetter || memb.Info.IsSetter then + transformAttachedProperty com ctx memb + else + transformAttachedMethod com ctx memb) + + match decl.Constructor with + | Some cons -> + withCurrentScope ctx cons.UsedNames <| fun ctx -> + transformClassWithImplicitConstructor com ctx decl classMembers cons + | None -> + let ent = com.GetEntity(ent) + if ent.IsFSharpUnion then transformUnion com ctx ent decl.Name classMembers + else transformClassWithCompilerGeneratedConstructor com ctx ent decl.Name classMembers + + | _ -> failwith $"Declaration {decl} not implemented" + + let transformImports (imports: Import seq) : Statement list = + let statefulImports = ResizeArray() + printfn "Imports: %A" imports + + [ + for import in imports do + match import with + | { Selector = selector; LocalIdent = local; Path = path } -> + let path = path |> Helpers.rewriteFableImport + let alias = Alias.alias(Identifier(selector), ?asname=None) + Statement.importFrom (Some(Identifier(path)), [ alias ]) + ] let getIdentForImport (ctx: Context) (path: string) (selector: string) = if System.String.IsNullOrEmpty selector then None @@ -2040,7 +1994,7 @@ module Compiler = type BabelCompiler (com: Compiler) = let onlyOnceWarnings = HashSet() - let imports = Dictionary() + let imports = Dictionary() interface IPythonCompiler with member _.WarnOnlyOnce(msg, ?range) = @@ -2068,6 +2022,7 @@ module Compiler = match localId with | Some localId -> Expression.identifier(localId) | None -> Expression.none() + member _.GetAllImports() = imports.Values :> _ member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e member bcom.TransformAsStatements(ctx, ret, e) = transformAsStatements bcom ctx ret e @@ -2112,6 +2067,6 @@ module Compiler = ScopedTypeParams = Set.empty } let rootDecls = List.collect (transformDeclaration com ctx) file.Declarations - let importDecls = [] //com.GetAllImports() |> transformImports + let importDecls = com.GetAllImports() |> transformImports let body = importDecls @ rootDecls Module.module' (body) From 0968fc00eb58976af08915b1913082c460f74263 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 29 May 2021 08:02:49 +0200 Subject: [PATCH 100/145] Fix printing of pos only args --- src/Fable.Transforms/Python/Fable2Python.fs | 4 ++-- src/Fable.Transforms/Python/PythonPrinter.fs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index b9927a9f45..be03654a1b 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -1797,7 +1797,7 @@ module Util = |> Seq.exists (fun att -> att.Entity.FullName = Atts.entryPoint) |> function | true -> [ stmt; declareEntryPoint com ctx expr ] - | false -> [ declareModuleMember info.IsPublic membName false expr ] + | false -> [ stmt; declareModuleMember info.IsPublic membName false expr ] let transformAction (com: IPythonCompiler) ctx expr = let statements = transformAsStatements com ctx None expr @@ -1965,7 +1965,7 @@ module Util = if ent.IsFSharpUnion then transformUnion com ctx ent decl.Name classMembers else transformClassWithCompilerGeneratedConstructor com ctx ent decl.Name classMembers - | _ -> failwith $"Declaration {decl} not implemented" + //| _ -> failwith $"Declaration {decl} not implemented" let transformImports (imports: Import seq) : Statement list = let statefulImports = ResizeArray() diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index a2f9d56ebc..e57b4feb5a 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -150,6 +150,8 @@ module PrinterExtensions = printer.Print(kw.Value) member printer.Print(arguments: Arguments) = + printer.PrintCommaSeparatedList(arguments.PosOnlyArgs) + let args = arguments.Args |> List.map AST.Arg let defaults = arguments.Defaults for i = 0 to args.Length - 1 do From 4dd84a78f8041ceda2f4b6f86196d6c459a8cc66 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 30 May 2021 09:57:13 +0200 Subject: [PATCH 101/145] Fix classes (wip) --- src/Fable.Transforms/Python/Fable2Python.fs | 317 +++++++++++--------- src/Fable.Transforms/Python/Python.fs | 7 +- 2 files changed, 174 insertions(+), 150 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index be03654a1b..560ec88c9c 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -283,7 +283,7 @@ module Reflection = let jsTypeof (primitiveType: string) (Util.TransformExpr com ctx (expr, stmts)): Expression * Statement list = let typeof = Expression.unaryOp(UnaryTypeof, expr) - Expression.binOp(typeof, BinaryEqualStrict, Expression.constant(primitiveType), ?loc=range), stmts + Expression.compare(typeof, [ Eq ], [ Expression.constant(primitiveType)], ?loc=range), stmts let jsInstanceof consExpr (Util.TransformExpr com ctx (expr, stmts)): Expression * Statement list= Expression.binOp(expr, BinaryInstanceOf, consExpr, ?loc=range), stmts @@ -629,9 +629,9 @@ module Util = Statement.expr(callSuper args) let makeClassConstructor args body = - // ClassMember.classMethod(ClassImplicitConstructor, Expression.identifier("constructor"), args, body) - let name = Python.Identifier("__init__") - let args = Arguments.arguments (args = args) + let name = Identifier("__init__") + let self = Arg.arg("self") + let args = Arguments.arguments (args = self::args) FunctionDef.Create(name, args, body = body) let callFunction r funcExpr (args: Expression list) = @@ -645,7 +645,7 @@ module Util = Expression.emit (txt, args, ?loc=range) let undefined range: Expression = - Expression.unaryOp(UnaryVoid, Expression.constant(0.), ?loc=range) + Expression.name(identifier = Identifier("None"), ?loc=range) let getGenericTypeParams (types: Fable.Type list) = let rec getGenParams = function @@ -725,14 +725,16 @@ module Util = let func = FunctionDef.Create(name = name, args = args, body = body) Expression.name (name), [ func ] - let makeFunctionExpression name (args, (body: Expression)) : Expression * Statement list= - let id = name |> Option.map Identifier + let makeFunction name (args, (body: Expression)) : Statement = let body = wrapExprInBlockWithReturn (body, []) - let name = Helpers.getUniqueIdentifier "lifted" - let func = - FunctionDef.Create(name = name, args = Arguments.arguments args, body = body) - //Expression.functionExpression(args, body, ?id=id, ?returnType=returnType, ?typeParameters=typeParamDecl) - Expression.name (name), [ func ] + FunctionDef.Create(name = name, args = Arguments.arguments args, body = body) + + let makeFunctionExpression name (args, (body: Expression)) : Expression * Statement list= + let name = + name |> Option.map Identifier + |> Option.defaultValue (Helpers.getUniqueIdentifier "lifted") + let func = makeFunction name (args, body) + Expression.name(name), [ func ] let optimizeTailCall (com: IPythonCompiler) (ctx: Context) range (tc: ITailCallOpportunity) args = let rec checkCrossRefs tempVars allArgs = function @@ -811,12 +813,7 @@ module Util = | Fable.BaseValue(Some boundIdent,_) -> identAsExpr boundIdent, [] | Fable.ThisValue _ -> Expression.identifier("self"), [] | Fable.TypeInfo t -> transformTypeInfo com ctx r Map.empty t - | Fable.Null _t -> - // if com.Options.typescript - // let ta = typeAnnotation com ctx t |> TypeAnnotation |> Some - // upcast Identifier("null", ?typeAnnotation=ta, ?loc=r) - // else - Expression.identifier("None", ?loc=r), [] + | Fable.Null _t -> Expression.identifier("None", ?loc=r), [] | Fable.UnitConstant -> undefined r, [] | Fable.BoolConstant x -> Expression.constant(x, ?loc=r), [] | Fable.CharConstant x -> Expression.constant(string x, ?loc=r), [] @@ -905,68 +902,78 @@ module Util = | None, _ -> None - // let transformObjectExpr (com: IPythonCompiler) ctx (members: Fable.MemberDecl list) baseCall: Expression * Statement list = - // let compileAsClass = - // Option.isSome baseCall || members |> List.exists (fun m -> - // // Optimization: Object literals with getters and setters are very slow in V8 - // // so use a class expression instead. See https://github.com/fable-compiler/Fable/pull/2165#issuecomment-695835444 - // m.Info.IsSetter || (m.Info.IsGetter && canHaveSideEffects m.Body)) - - // let makeMethod kind prop computed hasSpread args body = - // let args, body = - // getMemberArgsAndBody com ctx (Attached(isStatic=false)) hasSpread args body - // ObjectMember.objectMethod(kind, prop, args, body, computed_=computed, - // ?returnType=returnType, ?typeParameters=typeParamDecl) - - // let members = - // members |> List.collect (fun memb -> - // let info = memb.Info - // let prop, computed = memberFromName memb.Name - // // If compileAsClass is false, it means getters don't have side effects - // // and can be compiled as object fields (see condition above) - // if info.IsValue || (not compileAsClass && info.IsGetter) then - // [ObjectMember.objectProperty(prop, com.TransformAsExpr(ctx, memb.Body), computed_=computed)] - // elif info.IsGetter then - // [makeMethod ObjectGetter prop computed false memb.Args memb.Body] - // elif info.IsSetter then - // [makeMethod ObjectSetter prop computed false memb.Args memb.Body] - // elif info.IsEnumerator then - // let method = makeMethod ObjectMeth prop computed info.HasSpread memb.Args memb.Body - // let iterator = - // let prop, computed = memberFromName "Symbol.iterator" - // let body = enumerator2iterator com ctx - // ObjectMember.objectMethod(ObjectMeth, prop, [||], body, computed_=computed) - // [method; iterator] - // else - // [makeMethod ObjectMeth prop computed info.HasSpread memb.Args memb.Body] - // ) - - // let classMembers = - // members |> List.choose (function - // | ObjectProperty(key, value, computed) -> - // ClassMember.classProperty(key, value, computed_=computed) |> Some - // | ObjectMethod(kind, key, ``params``, body, computed, returnType, typeParameters, _) -> - // let kind = - // match kind with - // | "get" -> ClassGetter - // | "set" -> ClassSetter - // | _ -> ClassFunction - // ClassMember.classMethod(kind, key, ``params``, body, computed_=computed, - // ?returnType=returnType, ?typeParameters=typeParameters) |> Some) - - // let baseExpr, classMembers = - // baseCall - // |> extractBaseExprFromBaseCall com ctx None - // |> Option.map (fun (baseExpr, baseArgs) -> - // let consBody = [ callSuperAsStatement baseArgs ] - // let cons = makeClassConstructor [] consBody - // Some baseExpr, cons::classMembers - // ) - // |> Option.defaultValue (None, classMembers) - - // let classBody = ClassBody.classBody(List.toArray classMembers) - // let classExpr = Expression.classExpression(classBody, ?superClass=baseExpr) - // Expression.newExpression(classExpr, [||]) + let transformObjectExpr (com: IPythonCompiler) ctx (members: Fable.MemberDecl list) baseCall: Expression * Statement list = + let compileAsClass = + Option.isSome baseCall || members |> List.exists (fun m -> + // Optimization: Object literals with getters and setters are very slow in V8 + // so use a class expression instead. See https://github.com/fable-compiler/Fable/pull/2165#issuecomment-695835444 + m.Info.IsSetter || (m.Info.IsGetter && canHaveSideEffects m.Body)) + + let makeMethod prop computed hasSpread args body = + let args, body = + getMemberArgsAndBody com ctx (Attached(isStatic=false)) hasSpread args body + let name = Identifier("test") + let arguments = Arguments.arguments(args) + FunctionDef.Create(name, arguments, body) + + let members = + members |> List.collect (fun memb -> + let info = memb.Info + let prop, computed = memberFromName memb.Name + // If compileAsClass is false, it means getters don't have side effects + // and can be compiled as object fields (see condition above) + if info.IsValue || (not compileAsClass && info.IsGetter) then + let expr, stmts = com.TransformAsExpr(ctx, memb.Body) + stmts @ [ Statement.assign([prop], expr) ] + //[ObjectMember.objectProperty(prop, expr, computed_=computed)] + elif info.IsGetter then + let arguments = memb.Args + [ makeMethod prop computed false memb.Args memb.Body ] + elif info.IsSetter then + [makeMethod prop computed false memb.Args memb.Body] + elif info.IsEnumerator then + let method = makeMethod prop computed info.HasSpread memb.Args memb.Body + let iterator = + let body = enumerator2iterator com ctx + let name = com.GetIdentifier(ctx, "__iter__") + let args = Arguments.arguments() + FunctionDef.Create(name = name, args = args, body = body) + [method; iterator] + else + [makeMethod prop computed info.HasSpread memb.Args memb.Body] + ) + + // let classMembers = + // members |> List.choose (function + // | ObjectProperty(key, value, computed) -> + // ClassMember.classProperty(key, value, computed_=computed) |> Some + // | ObjectMethod(kind, key, ``params``, body, computed, returnType, typeParameters, _) -> + // let kind = + // match kind with + // | "get" -> ClassGetter + // | "set" -> ClassSetter + // | _ -> ClassFunction + // ClassMember.classMethod(kind, key, ``params``, body, computed_=computed, + // ?returnType=returnType, ?typeParameters=typeParameters) |> Some) + + let baseExpr, classMembers = + baseCall + |> extractBaseExprFromBaseCall com ctx None + |> Option.map (fun (baseExpr, (baseArgs, stmts)) -> + let consBody = [ callSuperAsStatement baseArgs ] + let cons = makeClassConstructor [] consBody + Some baseExpr, cons::members + ) + |> Option.defaultValue (None, members) + + let classBody = + match classMembers with + | [] -> [ Pass] + | _ -> classMembers + let name = Helpers.getUniqueIdentifier "lifted" + let stmt = Statement.classDef(name, body=classBody, bases=(baseExpr |> Option.toList) ) + Expression.name (name), [ stmt ] + //Expression.call(classExpr, []), [] let transformCallArgs (com: IPythonCompiler) ctx hasSpread args : Expression list * Statement list = match args with @@ -997,6 +1004,8 @@ module Util = | Fable.Unary(op, TransformExpr com ctx (expr, stmts)) -> Expression.unaryOp(op, expr, ?loc=range), stmts + | Fable.Binary(BinaryEqualStrict, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> + Expression.compare(left, [Eq], [right], ?loc=range), stmts @ stmts' | Fable.Binary(op, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> Expression.binOp(left, op, right, ?loc=range), stmts @ stmts' @@ -1153,34 +1162,34 @@ module Util = expr, stmts @ stmts' @ stmts'' @ stmts''' assign range ret value, stmts - // let transformBindingExprBody (com: IPythonCompiler) (ctx: Context) (var: Fable.Ident) (value: Fable.Expr) = - // match value with - // | Function(args, body) -> - // let name = Some var.Name - // transformFunction com ctx name args body - // |> makeArrowFunctionExpression name - // | _ -> - // if var.IsMutable then - // com.TransformAsExpr(ctx, value) - // else - // let expr, stmts = com.TransformAsExpr(ctx, value) - // expr |> wrapIntExpression value.Type, stmts - - // let transformBindingAsExpr (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = - // let expr, stmts = transformBindingExprBody com ctx var value - // expr |> assign None (identAsExpr var), stmts - - // let transformBindingAsStatements (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = - // if isJsStatement ctx false value then - // let varName, varExpr = Expression.name(var.Name), identAsExpr var - // let decl = Statement.assign([varName], varExpr) - // let body = com.TransformAsStatements(ctx, Some(Assign varExpr), value) - // List.append [ decl ] body - // else - // let value, stmts = transformBindingExprBody com ctx var value - // let varName = Expression.name(var.Name) - // let decl = varDeclaration varName var.IsMutable value - // [ decl ] + let transformBindingExprBody (com: IPythonCompiler) (ctx: Context) (var: Fable.Ident) (value: Fable.Expr) = + match value with + | Function(args, body) -> + let name = Some var.Name + let args, stmts = transformFunction com ctx name args body + makeArrowFunctionExpression args stmts + | _ -> + if var.IsMutable then + com.TransformAsExpr(ctx, value) + else + let expr, stmts = com.TransformAsExpr(ctx, value) + expr |> wrapIntExpression value.Type, stmts + + let transformBindingAsExpr (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = + let expr, stmts = transformBindingExprBody com ctx var value + expr |> assign None (identAsExpr var), stmts + + let transformBindingAsStatements (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = + if isJsStatement ctx false value then + let varName, varExpr = Expression.name(var.Name), identAsExpr var + let decl = Statement.assign([varName], varExpr) + let body = com.TransformAsStatements(ctx, Some(Assign varExpr), value) + List.append [ decl ] body + else + let value, stmts = transformBindingExprBody com ctx var value + let varName = Expression.name(var.Name) + let decl = varDeclaration varName var.IsMutable value + [ decl ] let transformTest (com: IPythonCompiler) ctx range kind expr: Expression * Statement list = match kind with @@ -1453,9 +1462,8 @@ module Util = transformFunction com ctx name args body ||> makeArrowFunctionExpression - // FIXME: - //| Fable.ObjectExpr (members, _, baseCall) -> - // transformObjectExpr com ctx members baseCall + | Fable.ObjectExpr (members, _, baseCall) -> + transformObjectExpr com ctx members baseCall | Fable.Call(callee, info, _, range) -> transformCall com ctx range callee info @@ -1483,11 +1491,13 @@ module Util = | Fable.Set(expr, kind, value, range) -> transformSet com ctx range expr value kind - // | Fable.Let(ident, value, body) -> - // if ctx.HoistVars [ident] then - // let assignment = transformBindingAsExpr com ctx ident value - // Expression.sequenceExpression([|assignment; com.TransformAsExpr(ctx, body)|]) - // else iife com ctx expr + | Fable.Let(ident, value, body) -> + if ctx.HoistVars [ident] then + let assignment, stmts = transformBindingAsExpr com ctx ident value + let expr, stmts' = com.TransformAsExpr(ctx, body) + //Expression.sequenceExpression([|assignment; |]) + expr, stmts @ stmts' + else iife com ctx expr // | Fable.LetRec(bindings, body) -> // if ctx.HoistVars(List.map fst bindings) then @@ -1545,9 +1555,9 @@ module Util = let expr', stmts = makeArrowFunctionExpression args body stmts @ [ expr' |> resolveExpr expr.Type returnStrategy ] - // | Fable.ObjectExpr (members, t, baseCall) -> - // let expr, stmts = transformObjectExpr com ctx members baseCall - // stmts @ [ expr |> resolveExpr t returnStrategy ] + | Fable.ObjectExpr (members, t, baseCall) -> + let expr, stmts = transformObjectExpr com ctx members baseCall + stmts @ [ expr |> resolveExpr t returnStrategy ] | Fable.Call(callee, info, typ, range) -> transformCallAsStatements com ctx range typ returnStrategy callee info @@ -1569,13 +1579,13 @@ module Util = let expr, stmts = transformGet com ctx range t expr kind stmts @ [ expr |> resolveExpr t returnStrategy ] - // | Fable.Let(ident, value, body) -> - // let binding = transformBindingAsStatements com ctx ident value - // List.append binding (transformAsStatements com ctx returnStrategy body) + | Fable.Let(ident, value, body) -> + let binding = transformBindingAsStatements com ctx ident value + List.append binding (transformAsStatements com ctx returnStrategy body) - // | Fable.LetRec(bindings, body) -> - // let bindings = bindings |> Seq.collect (fun (i, v) -> transformBindingAsStatements com ctx i v) |> Seq.toList - // List.append bindings (transformAsStatements com ctx returnStrategy body) + | Fable.LetRec(bindings, body) -> + let bindings = bindings |> Seq.collect (fun (i, v) -> transformBindingAsStatements com ctx i v) |> Seq.toList + List.append bindings (transformAsStatements com ctx returnStrategy body) | Fable.Set(expr, kind, value, range) -> let expr', stmts = transformSet com ctx range expr value kind @@ -1634,7 +1644,7 @@ module Util = // Expression.binOp(identAsExpr var, op1, limit), // Expression.updateExpression(op2, false, identAsExpr var), ?loc=range) ] - | _ -> failwith $"Expression {expr} not supported." + | _ -> failwith $"transformAsStatements: Expression {expr} not supported." let transformFunction com ctx name (args: Fable.Ident list) (body: Fable.Expr): Arg list * Statement list = let tailcallChance = @@ -1686,7 +1696,7 @@ module Util = // Statement.expr(emitExpression funcExpr.loc "process.exit($0)" [main], ?loc=funcExpr.loc) Statement.expr(main) - let declareModuleMember isPublic (membName: string) isMutable (expr: Expression) = + let declareModuleMember isPublic (membName: Identifier) isMutable (expr: Expression) = let membName = Expression.name(membName) match expr with // | ClassExpression(body, id, superClass, implements, superTypeParameters, typeParameters, loc) -> @@ -1703,7 +1713,7 @@ module Util = // ?returnType = returnType, // ?typeParameters = typeParameters) | _ -> - printfn $"declareModuleMember: Got {expr}" + //printfn $"declareModuleMember: Got {expr}" varDeclaration membName isMutable expr // if not isPublic then PrivateModuleDeclaration(decl |> Declaration) // else ExportNamedDeclaration(decl) @@ -1761,17 +1771,19 @@ module Util = |> Seq.toArray let declareClassType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Arg list) (consBody: Statement list) (baseExpr: Expression option) classMembers = - //let typeParamDecl = makeEntityTypeParamDecl com ctx ent let classCons = makeClassConstructor consArgs consBody let classFields = Array.empty let classMembers = List.append [ classCons ] classMembers - let classBody = [ yield! classFields; yield! classMembers ] + let classBody = + let body = [ yield! classFields; yield! classMembers ] + printfn "Body: %A" body + match body with + | [] -> [ Pass ] + | _ -> body let bases = baseExpr |> Option.toList - //let classExpr = Expression.classExpression(classBody, ?superClass=baseExpr, ?typeParameters=typeParamDecl, ?implements=implements) - let name = Helpers.getUniqueIdentifier "Lifted" + let name = com.GetIdentifier(ctx, entName) let classStmt = Statement.classDef(name, body = classBody, bases = bases) - let expr = Expression.name(name) - expr |> declareModuleMember ent.IsPublic entName false + classStmt let declareType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Arg list) (consBody: Statement list) baseExpr classMembers : Statement list = let typeDeclaration = declareClassType com ctx ent entName consArgs consBody baseExpr classMembers @@ -1781,23 +1793,24 @@ module Util = let generics = genArgs |> Array.mapToList identAsExpr let body, stmts = transformReflectionInfo com ctx None ent generics let args = genArgs |> Array.mapToList (ident >> Arg.arg) - let returnType = ta let expr, stmts' = makeFunctionExpression None (args, body) - expr |> declareModuleMember ent.IsPublic (entName + Naming.reflectionSuffix) false, stmts @ stmts' + let name = com.GetIdentifier(ctx, entName + Naming.reflectionSuffix) + expr |> declareModuleMember ent.IsPublic name false, stmts @ stmts' stmts @ [typeDeclaration; reflectionDeclaration] let transformModuleFunction (com: IPythonCompiler) ctx (info: Fable.MemberInfo) (membName: string) args body = let args, body = getMemberArgsAndBody com ctx (NonAttached membName) info.HasSpread args body - //let expr = Expression.functionExpression(args, body, ?returnType=returnType, ?typeParameters=typeParamDecl) - let name = Helpers.getUniqueIdentifier "lifted" + + //printfn "Arsg: %A" args + let name = com.GetIdentifier(ctx, membName) //Helpers.getUniqueIdentifier "lifted" let stmt = FunctionDef.Create(name = name, args = Arguments.arguments args, body = body) let expr = Expression.name (name) info.Attributes |> Seq.exists (fun att -> att.Entity.FullName = Atts.entryPoint) |> function | true -> [ stmt; declareEntryPoint com ctx expr ] - | false -> [ stmt; declareModuleMember info.IsPublic membName false expr ] + | false -> [ stmt ] //; declareModuleMember info.IsPublic membName false expr ] let transformAction (com: IPythonCompiler) ctx expr = let statements = transformAsStatements com ctx None expr @@ -1871,7 +1884,7 @@ module Util = let transformClassWithCompilerGeneratedConstructor (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = let fieldIds = getEntityFieldsAsIdents com ent - let args = fieldIds |> Array.map identAsExpr + let args = fieldIds |> Array.map (fun id -> com.GetIdentifier(ctx, id.Name) |> Expression.name) let baseExpr = if ent.IsFSharpExceptionDeclaration then libValue com ctx "Types" "FSharpException" |> Some @@ -1892,14 +1905,15 @@ module Util = let transformClassWithImplicitConstructor (com: IPythonCompiler) ctx (classDecl: Fable.ClassDecl) classMembers (cons: Fable.MemberDecl) = let classEnt = com.GetEntity(classDecl.Entity) - let classIdent = Expression.identifier(classDecl.Name) + let classIdent = Expression.name(com.GetIdentifier(ctx, classDecl.Name)) let consArgs, consBody = getMemberArgsAndBody com ctx ClassConstructor cons.Info.HasSpread cons.Args cons.Body - let exposedCons, stmts = + let exposedCons = let argExprs = consArgs |> List.map (fun p -> Expression.identifier(p.Arg)) let exposedConsBody = Expression.call(classIdent, argExprs) - makeFunctionExpression None (consArgs, exposedConsBody) + let name = com.GetIdentifier(ctx, cons.Name) + makeFunction name (consArgs, exposedConsBody) let baseExpr, consBody = classDecl.BaseCall @@ -1916,7 +1930,7 @@ module Util = [ yield! declareType com ctx classEnt classDecl.Name consArgs consBody baseExpr classMembers - yield declareModuleMember cons.Info.IsPublic cons.Name false exposedCons + yield exposedCons ] let rec transformDeclaration (com: IPythonCompiler) ctx decl = @@ -1935,14 +1949,18 @@ module Util = withCurrentScope ctx decl.UsedNames <| fun ctx -> let decls = if decl.Info.IsValue then + printfn "1" let value, stmts = transformAsExpr com ctx decl.Body - stmts @ [declareModuleMember decl.Info.IsPublic decl.Name decl.Info.IsMutable value] + let name = com.GetIdentifier(ctx, decl.Name) + stmts @ [declareModuleMember decl.Info.IsPublic name decl.Info.IsMutable value] else transformModuleFunction com ctx decl.Info decl.Name decl.Args decl.Body if decl.ExportDefault then + printfn "2" decls //@ [ ExportDefaultDeclaration(Choice2Of2(Expression.identifier(decl.Name))) ] - else decls + else + decls | Fable.ClassDeclaration decl -> let ent = decl.Entity @@ -1959,11 +1977,14 @@ module Util = match decl.Constructor with | Some cons -> withCurrentScope ctx cons.UsedNames <| fun ctx -> + printfn "1" transformClassWithImplicitConstructor com ctx decl classMembers cons | None -> let ent = com.GetEntity(ent) if ent.IsFSharpUnion then transformUnion com ctx ent decl.Name classMembers - else transformClassWithCompilerGeneratedConstructor com ctx ent decl.Name classMembers + else + printfn "2" + transformClassWithCompilerGeneratedConstructor com ctx ent decl.Name classMembers //| _ -> failwith $"Declaration {decl} not implemented" @@ -2054,7 +2075,7 @@ module Compiler = for decl in file.Declarations do hs.UnionWith(decl.UsedNames) hs - + //printfn "File: %A" file.Declarations let ctx = { File = file UsedNames = { RootScope = HashSet file.UsedNamesInRootScope diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 08beba0e32..2ffe4750be 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -925,10 +925,10 @@ module PythonExtensions = | UnaryPlus -> UAdd | UnaryNot -> Not | UnaryNotBitwise -> Invert - | _ -> failwith $"Operator {op} not supported" // | UnaryTypeof -> "typeof" - // | UnaryVoid -> "void" + //| UnaryVoid -> // | UnaryDelete -> "delete" + | _ -> failwith $"Operator {op} not supported" Expression.unaryOp(op, operand, ?loc=loc) @@ -1031,6 +1031,9 @@ module PythonExtensions = Annotation = annotation TypeComment = typeComment } + static member arg(arg, ?annotation, ?typeComment) = + Arg.arg(Identifier(arg), ?annotation=annotation, ?typeComment=typeComment) + type Keyword with static member keyword(arg, value) = From 97ec1a3038593d9e45f991ab85ffd44d32120700 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 30 May 2021 13:45:39 +0200 Subject: [PATCH 102/145] Fix class setters --- src/Fable.Transforms/Python/Fable2Python.fs | 85 ++++++++++++--------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 560ec88c9c..0144d486e2 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -489,7 +489,7 @@ module Util = | None -> failwithf "Cannot find DecisionTree target %i" targetIndex | Some(idents, target) -> idents, target - let rec isJsStatement ctx preferStatement (expr: Fable.Expr) = + let rec isPyStatement ctx preferStatement (expr: Fable.Expr) = match expr with | Fable.Value _ | Fable.Import _ | Fable.IdentExpr _ | Fable.Lambda _ | Fable.Delegate _ | Fable.ObjectExpr _ @@ -506,16 +506,16 @@ module Util = | Fable.DecisionTreeSuccess(targetIndex,_, _) -> getDecisionTarget ctx targetIndex - |> snd |> isJsStatement ctx preferStatement + |> snd |> isPyStatement ctx preferStatement // Make it also statement if we have more than, say, 3 targets? // That would increase the chances to convert it into a switch | Fable.DecisionTree(_,targets) -> preferStatement - || List.exists (snd >> (isJsStatement ctx false)) targets + || List.exists (snd >> (isPyStatement ctx false)) targets | Fable.IfThenElse(_,thenExpr,elseExpr,_) -> - preferStatement || isJsStatement ctx false thenExpr || isJsStatement ctx false elseExpr + preferStatement || isPyStatement ctx false thenExpr || isPyStatement ctx false elseExpr let addErrorAndReturnNull (com: Compiler) (range: SourceLocation option) (error: string) = @@ -544,7 +544,6 @@ module Util = match memberName with | "ToString" -> Expression.identifier("toString"), [] | n when n.StartsWith("Symbol.iterator") -> - //Expression.memberExpression(Expression.identifier("Symbol"), Expression.identifier(n.[7..]), false), true let name = Identifier "__iter__" Expression.name(name), [] | n when Naming.hasIdentForbiddenChars n -> Expression.constant(n), [] @@ -556,17 +555,16 @@ module Util = Expression.attribute (value = left, attr = expr, ctx = Load) let getExpr r (object: Expression) (expr: Expression) = - let attr, stmts = - match expr with - | Expression.Constant(value=value) -> - match value with - | :? string as str -> memberFromName str - | _ -> failwith "Need to be string" - | e -> e, [] - let func = Expression.name("getattr") - Expression.call(func=func, args=[object; attr]), stmts - //Expression.attribute (value = object, attr = expr, ctx = Load), stmts - //Expression.memberExpression(object, expr, computed, ?loc=r) + match expr with + | Expression.Constant(value=value) -> + match value with + | :? string as str -> memberFromName str + | _ -> failwith "Need to be string" + | Expression.Name({Id=id}) -> + Expression.attribute (value = object, attr = id, ctx = Load), [] + | e -> + let func = Expression.name("getattr") + Expression.call(func=func, args=[object; e]), [] let rec getParts (parts: string list) (expr: Expression) = match parts with @@ -675,7 +673,7 @@ module Util = let body = // TODO: If ident is not captured maybe we can just replace it with "this" if FableTransforms.isIdentUsed thisArg.Name body then - let thisKeyword = Fable.IdentExpr { thisArg with Name = "this" } + let thisKeyword = Fable.IdentExpr { thisArg with Name = "self" } Fable.Let(thisArg, thisKeyword, body) else body None, genTypeParams, args, body @@ -991,13 +989,14 @@ module Util = rest @ [ Expression.starred(expr) ], stmts @ stmts' | args -> List.map (fun e -> com.TransformAsExpr(ctx, e)) args |> Helpers.unzipArgs - let resolveExpr t strategy babelExpr: Statement = + let resolveExpr t strategy pyExpr: Statement = + printfn "resolveExpr: %A" pyExpr match strategy with - | None | Some ReturnUnit -> Statement.expr(babelExpr) + | None | Some ReturnUnit -> Statement.expr(pyExpr) // TODO: Where to put these int wrappings? Add them also for function arguments? - | Some Return -> Statement.return'(wrapIntExpression t babelExpr) - | Some(Assign left) -> Statement.expr(assign None left babelExpr) - | Some(Target left) -> Statement.expr(assign None (left |> Expression.identifier) babelExpr) + | Some Return -> Statement.return'(wrapIntExpression t pyExpr) + | Some(Assign left) -> Statement.expr(assign None left pyExpr) + | Some(Target left) -> Statement.expr(assign None (left |> Expression.identifier) pyExpr) let transformOperation com ctx range opKind: Expression * Statement list = match opKind with @@ -1150,6 +1149,7 @@ module Util = expr, stmts @ stmts' @ stmts'' let transformSet (com: IPythonCompiler) ctx range fableExpr (value: Fable.Expr) kind = + printfn "transformSet" let expr, stmts = com.TransformAsExpr(ctx, fableExpr) let value', stmts' = com.TransformAsExpr(ctx, value) let value = value' |> wrapIntExpression value.Type @@ -1160,6 +1160,7 @@ module Util = | Some(Fable.ExprKey(TransformExpr com ctx (e, stmts''))) -> let expr, stmts''' = getExpr None expr e expr, stmts @ stmts' @ stmts'' @ stmts''' + printfn "transformset, assign: %A" (range, ret, value, stmts) assign range ret value, stmts let transformBindingExprBody (com: IPythonCompiler) (ctx: Context) (var: Fable.Ident) (value: Fable.Expr) = @@ -1176,20 +1177,23 @@ module Util = expr |> wrapIntExpression value.Type, stmts let transformBindingAsExpr (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = + printfn "transformBindingAsExpr: %A" (var, value) let expr, stmts = transformBindingExprBody com ctx var value expr |> assign None (identAsExpr var), stmts let transformBindingAsStatements (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = - if isJsStatement ctx false value then + if isPyStatement ctx false value then + printfn "Py statement" let varName, varExpr = Expression.name(var.Name), identAsExpr var let decl = Statement.assign([varName], varExpr) let body = com.TransformAsStatements(ctx, Some(Assign varExpr), value) List.append [ decl ] body else + printfn "Not Py statement" let value, stmts = transformBindingExprBody com ctx var value let varName = Expression.name(var.Name) let decl = varDeclaration varName var.IsMutable value - [ decl ] + stmts @ [ decl ] let transformTest (com: IPythonCompiler) ctx range kind expr: Expression * Statement list = match kind with @@ -1489,6 +1493,7 @@ module Util = transformDecisionTreeSuccessAsExpr com ctx idx boundValues | Fable.Set(expr, kind, value, range) -> + printfn "A" transformSet com ctx range expr value kind | Fable.Let(ident, value, body) -> @@ -1588,18 +1593,23 @@ module Util = List.append bindings (transformAsStatements com ctx returnStrategy body) | Fable.Set(expr, kind, value, range) -> + printfn "B: %A" (kind, value, range) + let expr', stmts = transformSet com ctx range expr value kind - stmts @ [ expr' |> resolveExpr expr.Type returnStrategy ] + match expr' with + | Expression.NamedExpr({ Target = target; Value = value; Loc=loc }) -> + stmts @ [ Statement.assign([target], value) ] + | _ -> stmts @ [ expr' |> resolveExpr expr.Type returnStrategy ] | Fable.IfThenElse(guardExpr, thenExpr, elseExpr, r) -> let asStatement = match returnStrategy with | None | Some ReturnUnit -> true | Some(Target _) -> true // Compile as statement so values can be bound - | Some(Assign _) -> (isJsStatement ctx false thenExpr) || (isJsStatement ctx false elseExpr) + | Some(Assign _) -> (isPyStatement ctx false thenExpr) || (isPyStatement ctx false elseExpr) | Some Return -> Option.isSome ctx.TailCallOpportunity - || (isJsStatement ctx false thenExpr) || (isJsStatement ctx false elseExpr) + || (isPyStatement ctx false thenExpr) || (isPyStatement ctx false elseExpr) if asStatement then transformIfStatement com ctx r returnStrategy guardExpr thenExpr elseExpr else @@ -1660,7 +1670,7 @@ module Util = let body = if body.Type = Fable.Unit then transformBlock com ctx (Some ReturnUnit) body - elif isJsStatement ctx (Option.isSome tailcallChance) body then + elif isPyStatement ctx (Option.isSome tailcallChance) body then transformBlock com ctx (Some Return) body else transformAsExpr com ctx body |> wrapExprInBlockWithReturn @@ -1776,7 +1786,7 @@ module Util = let classMembers = List.append [ classCons ] classMembers let classBody = let body = [ yield! classFields; yield! classMembers ] - printfn "Body: %A" body + //printfn "Body: %A" body match body with | [] -> [ Pass ] | _ -> body @@ -1830,7 +1840,8 @@ module Util = let args, body = getMemberArgsAndBody com ctx (Attached isStatic) false memb.Args memb.Body let key, computed = memberFromName memb.Name - let arguments = Arguments.arguments args + let self = Arg.arg("self") + let arguments = Arguments.arguments (self::args) FunctionDef.Create(Identifier memb.Name, arguments, body = body) |> List.singleton //ClassMember.classMethod(kind, key, args, body, computed_=computed, ``static``=isStatic) @@ -1843,7 +1854,8 @@ module Util = FunctionDef.Create(Identifier name, args, body = body) let args, body = getMemberArgsAndBody com ctx (Attached isStatic) memb.Info.HasSpread memb.Args memb.Body - let arguments = Arguments.arguments args + let self = Arg.arg("self") + let arguments = Arguments.arguments (self::args) [ yield makeMethod memb.Name arguments body if memb.Info.IsEnumerator then @@ -1865,7 +1877,9 @@ module Util = | Fable.Number _ -> Expression.binOp(identAsExpr id, BinaryOrBitwise, Expression.constant(0.)) | _ -> identAsExpr id - assign None left right |> Statement.expr) + + Statement.assign([left], right)) + //assign None left right |> Statement.expr) ] let cases = let expr, stmts = @@ -1876,7 +1890,8 @@ module Util = let body = stmts @ [ Statement.return'(expr) ] let name = Identifier("cases") - FunctionDef.Create(name, Arguments.arguments [], body = body) + let self = Arg.arg("self") + FunctionDef.Create(name, Arguments.arguments [ self ], body = body) let baseExpr = libValue com ctx "Types" "Union" |> Some let classMembers = List.append [ cases ] classMembers @@ -1897,6 +1912,7 @@ module Util = yield! ent.FSharpFields |> Seq.mapi (fun i field -> let left = get None thisExpr field.Name let right = wrapIntExpression field.FieldType args.[i] + printfn "***" assign None left right |> Statement.expr) |> Seq.toArray ] @@ -1963,6 +1979,7 @@ module Util = decls | Fable.ClassDeclaration decl -> + printfn "Class: %A" decl let ent = decl.Entity let classMembers = @@ -1986,8 +2003,6 @@ module Util = printfn "2" transformClassWithCompilerGeneratedConstructor com ctx ent decl.Name classMembers - //| _ -> failwith $"Declaration {decl} not implemented" - let transformImports (imports: Import seq) : Statement list = let statefulImports = ResizeArray() printfn "Imports: %A" imports From a70f8ea56b061ffeac8ab19eea1b60eb74fffdb8 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 1 Jun 2021 07:20:52 +0200 Subject: [PATCH 103/145] Fable2Python (wip) --- src/Fable.Transforms/Python/Babel2Python.fs | 13 +- src/Fable.Transforms/Python/Fable2Python.fs | 238 +++++++++--------- src/Fable.Transforms/Python/Python.fs | 2 +- src/Fable.Transforms/Replacements.fs | 1 + .../fable/{asyncio.py => task.py} | 3 +- src/fable-library-py/fable/types.py | 9 +- src/fable-library-py/fable/util.py | 66 ++++- 7 files changed, 193 insertions(+), 139 deletions(-) rename src/fable-library-py/fable/{asyncio.py => task.py} (85%) diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs index a98e867456..b3693c0b8e 100644 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ b/src/Fable.Transforms/Python/Babel2Python.fs @@ -20,8 +20,7 @@ type ReturnStrategy = type UsedNames = { GlobalScope: HashSet EnclosingScope: HashSet - LocalScope: HashSet - NonLocals: HashSet } + LocalScope: HashSet } type Context = { ReturnStrategy: ReturnStrategy @@ -318,7 +317,7 @@ module Util = let enclosingScope = HashSet() enclosingScope.UnionWith(ctx.UsedNames.EnclosingScope) enclosingScope.UnionWith(ctx.UsedNames.LocalScope) - let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = enclosingScope; NonLocals = HashSet () }} + let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = enclosingScope }} let body = com.TransformAsStatements(ctx', ReturnStrategy.Return, body) let name = com.GetIdentifier(ctx, name) @@ -435,7 +434,7 @@ module Util = let body, stmts = com.TransformAsExpr(ctx, argument) Expression.lambda (arguments, body), stmts | _ -> - let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope; NonLocals = HashSet () }} + let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope }} let body = com.TransformAsStatements(ctx', ReturnStrategy.Return, body) let name = Helpers.getUniqueIdentifier "lifted" @@ -630,7 +629,7 @@ module Util = let body, stmts = com.TransformAsExpr(ctx, expr) Expression.lambda (arguments, body), stmts | _ -> - let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope; NonLocals = HashSet () }} + let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope }} let body = com.TransformAsStatements(ctx', ReturnStrategy.Return, body) let name = Helpers.getUniqueIdentifier "lifted" @@ -647,7 +646,7 @@ module Util = | Expression.Literal (Literal.NullLiteral (nl)) -> Expression.name (Python.Identifier("None")), [] | SequenceExpression (expressions = exprs) -> //XXX // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments - let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope; NonLocals = HashSet () }} + let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope }} let body = exprs @@ -715,7 +714,6 @@ module Util = let target = com.GetIdentifier(ctx, name) let stmts = if not (ctx.UsedNames.LocalScope.Contains name) && ctx.UsedNames.EnclosingScope.Contains name then - ctx.UsedNames.NonLocals.Add name |> ignore // printfn "**** Adding non-local: %A" target [ Statement.nonLocal [target] ] else @@ -1051,7 +1049,6 @@ module Compiler = GlobalScope = HashSet () EnclosingScope = HashSet () LocalScope = HashSet () - NonLocals = HashSet () } } diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 0144d486e2..5fe44239c6 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -29,9 +29,17 @@ type UsedNames = DeclarationScopes: HashSet CurrentDeclarationScope: HashSet } +/// Used for keeping track of existing variable bindings to know if we need to declare an +/// identifier as nonlocal or global. TODO: can we merge this with UsedNames? +type BoundVars = + { GlobalScope: HashSet + EnclosingScope: HashSet + LocalScope: HashSet } + type Context = { File: Fable.File UsedNames: UsedNames + BoundVars: BoundVars DecisionTargets: (Fable.Ident list * Fable.Expr) list HoistVars: Fable.Ident list -> bool TailCallOpportunity: ITailCallOpportunity option @@ -433,9 +441,9 @@ module Util = let name = Helpers.clean name // FIXME: - //match name with - //| "math" -> com.GetImportExpr(ctx, "math") |> ignore - //| _ -> () + // match name with + // | "math" -> com.GetImportExpr(ctx, "math") |> ignore + // | _ -> () Python.Identifier name @@ -599,18 +607,16 @@ module Util = Expression.call(afe, []), stmts let multiVarDeclaration (variables: (Identifier * Expression option) list) = - let varDeclarators = - // TODO: Log error if there're duplicated non-empty var declarations + let ids, values = variables |> List.distinctBy (fun (Identifier(name=name), _value) -> name) + |> List.map (function + | i, Some value -> Expression.name(i, Store), value + | i, _ -> Expression.name(i, Store), Expression.none ()) + |> List.unzip + |> fun (ids, values) -> (Expression.tuple(ids), Expression.tuple(values)) - [ - for id, init in varDeclarators do - let name = Expression.name(id, Store) - match init with - | Some value -> - Statement.assign ([name], value) - | None -> () ] + Statement.assign([ids], values) let varDeclaration (var: Expression) (isMutable: bool) value = Statement.assign([var], value) @@ -1149,7 +1155,7 @@ module Util = expr, stmts @ stmts' @ stmts'' let transformSet (com: IPythonCompiler) ctx range fableExpr (value: Fable.Expr) kind = - printfn "transformSet" + printfn "transformSet: %A" (fableExpr, value) let expr, stmts = com.TransformAsExpr(ctx, fableExpr) let value', stmts' = com.TransformAsExpr(ctx, value) let value = value' |> wrapIntExpression value.Type @@ -1160,6 +1166,7 @@ module Util = | Some(Fable.ExprKey(TransformExpr com ctx (e, stmts''))) -> let expr, stmts''' = getExpr None expr e expr, stmts @ stmts' @ stmts'' @ stmts''' + printfn "Used names: %A" ctx.UsedNames printfn "transformset, assign: %A" (range, ret, value, stmts) assign range ret value, stmts @@ -1216,30 +1223,31 @@ module Util = let actual, stmts = getUnionExprTag com ctx None expr Expression.compare(actual, [Eq], [ expected ], ?loc=range), stmts - // let transformSwitch (com: IPythonCompiler) ctx useBlocks returnStrategy evalExpr cases defaultCase: Statement = - // let cases = - // cases |> List.collect (fun (guards, expr) -> - // // Remove empty branches - // match returnStrategy, expr, guards with - // | None, Fable.Value(Fable.UnitConstant,_), _ - // | _, _, [] -> [] - // | _, _, guards -> - // let guards, lastGuard = List.splitLast guards - // let guards = guards |> List.map (fun e -> SwitchCase.switchCase([||], com.TransformAsExpr(ctx, e))) - // let caseBody = com.TransformAsStatements(ctx, returnStrategy, expr) - // let caseBody = - // match returnStrategy with - // | Some Return -> caseBody - // | _ -> Array.append caseBody [|Statement.break'()|] - // guards @ [SwitchCase.switchCase(caseBody, com.TransformAsExpr(ctx, lastGuard))] - // ) - // let cases = - // match defaultCase with - // | Some expr -> - // let defaultCaseBody = com.TransformAsStatements(ctx, returnStrategy, expr) - // cases @ [SwitchCase.switchCase(consequent defaultCaseBody)] - // | None -> cases - // Statement.switchStatement(com.TransformAsExpr(ctx, evalExpr), List.toArray cases) + let transformSwitch (com: IPythonCompiler) ctx useBlocks returnStrategy evalExpr cases defaultCase: Statement = + // let cases = + // cases |> List.collect (fun (guards, expr) -> + // // Remove empty branches + // match returnStrategy, expr, guards with + // | None, Fable.Value(Fable.UnitConstant,_), _ + // | _, _, [] -> [] + // | _, _, guards -> + // let guards, lastGuard = List.splitLast guards + // let guards = guards |> List.map (fun e -> SwitchCase.switchCase([||], com.TransformAsExpr(ctx, e))) + // let caseBody = com.TransformAsStatements(ctx, returnStrategy, expr) + // let caseBody = + // match returnStrategy with + // | Some Return -> caseBody + // | _ -> Array.append caseBody [|Statement.break'()|] + // guards @ [SwitchCase.switchCase(caseBody, com.TransformAsExpr(ctx, lastGuard))] + // ) + // let cases = + // match defaultCase with + // | Some expr -> + // let defaultCaseBody = com.TransformAsStatements(ctx, returnStrategy, expr) + // cases @ [SwitchCase.switchCase(consequent defaultCaseBody)] + // | None -> cases + // Statement.switchStatement(com.TransformAsExpr(ctx, evalExpr), List.toArray cases) + Statement.expr(Expression.name("Not implemented")) let matchTargetIdentAndValues idents values = if List.isEmpty idents then [] @@ -1371,76 +1379,76 @@ module Util = /// When several branches share target create first a switch to get the target index and bind value /// and another to execute the actual target - // let transformDecisionTreeWithTwoSwitches (com: IPythonCompiler) ctx returnStrategy - // (targets: (Fable.Ident list * Fable.Expr) list) treeExpr = - // // Declare target and bound idents - // let targetId = getUniqueNameInDeclarationScope ctx "pattern_matching_result" |> makeIdent |> ident - // let multiVarDecl = - // let boundIdents = - // targets |> List.collect (fun (idents,_) -> - // idents) |> List.map (fun id -> ident id, None) - // multiVarDeclaration ((targetId,None)::boundIdents) - // // Transform targets as switch - // let switch2 = - // // TODO: Declare the last case as the default case? - // let cases = targets |> List.mapi (fun i (_,target) -> [makeIntConst i], target) - // transformSwitch com ctx true returnStrategy (targetId |> Fable.IdentExpr) cases None - // // Transform decision tree - // let targetAssign = Target(ident targetId) - // let ctx = { ctx with DecisionTargets = targets } - // match transformDecisionTreeAsSwitch treeExpr with - // | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> - // let cases = groupSwitchCases (Fable.Number Int32) cases (defaultIndex, defaultBoundValues) - // let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, Fable.Number Int32) - // let switch1 = transformSwitch com ctx false (Some targetAssign) evalExpr cases (Some defaultCase) - // [ multiVarDecl; switch1; switch2 ] - // | None -> - // let decisionTree = com.TransformAsStatements(ctx, Some targetAssign, treeExpr) - // [ yield multiVarDecl; yield! decisionTree; yield switch2 ] - - // let transformDecisionTreeAsStatements (com: IPythonCompiler) (ctx: Context) returnStrategy - // (targets: (Fable.Ident list * Fable.Expr) list) (treeExpr: Fable.Expr): Statement list = - // // If some targets are referenced multiple times, hoist bound idents, - // // resolve the decision index and compile the targets as a switch - // let targetsWithMultiRefs = - // if com.Options.Language = TypeScript then [] // no hoisting when compiled with types - // else getTargetsWithMultipleReferences treeExpr - // match targetsWithMultiRefs with - // | [] -> - // let ctx = { ctx with DecisionTargets = targets } - // match transformDecisionTreeAsSwitch treeExpr with - // | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> - // let t = treeExpr.Type - // let cases = cases |> List.map (fun (caseExpr, targetIndex, boundValues) -> - // [caseExpr], Fable.DecisionTreeSuccess(targetIndex, boundValues, t)) - // let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, t) - // [ transformSwitch com ctx true returnStrategy evalExpr cases (Some defaultCase) ] - // | None -> - // com.TransformAsStatements(ctx, returnStrategy, treeExpr) - // | targetsWithMultiRefs -> - // // If the bound idents are not referenced in the target, remove them - // let targets = - // targets |> List.map (fun (idents, expr) -> - // idents - // |> List.exists (fun i -> FableTransforms.isIdentUsed i.Name expr) - // |> function - // | true -> idents, expr - // | false -> [], expr) - // let hasAnyTargetWithMultiRefsBoundValues = - // targetsWithMultiRefs |> List.exists (fun idx -> - // targets.[idx] |> fst |> List.isEmpty |> not) - // if not hasAnyTargetWithMultiRefsBoundValues then - // match transformDecisionTreeAsSwitch treeExpr with - // | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> - // let t = treeExpr.Type - // let cases = groupSwitchCases t cases (defaultIndex, defaultBoundValues) - // let ctx = { ctx with DecisionTargets = targets } - // let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, t) - // [ transformSwitch com ctx true returnStrategy evalExpr cases (Some defaultCase) ] - // | None -> - // transformDecisionTreeWithTwoSwitches com ctx returnStrategy targets treeExpr - // else - // transformDecisionTreeWithTwoSwitches com ctx returnStrategy targets treeExpr + let transformDecisionTreeWithTwoSwitches (com: IPythonCompiler) ctx returnStrategy + (targets: (Fable.Ident list * Fable.Expr) list) treeExpr = + // Declare target and bound idents + let targetId = getUniqueNameInDeclarationScope ctx "pattern_matching_result" |> makeIdent + let multiVarDecl = + let boundIdents = + targets |> List.collect (fun (idents,_) -> + idents) |> List.map (fun id -> ident id, None) + multiVarDeclaration ((ident targetId,None)::boundIdents) + // Transform targets as switch + let switch2 = + // TODO: Declare the last case as the default case? + let cases = targets |> List.mapi (fun i (_,target) -> [makeIntConst i], target) + transformSwitch com ctx true returnStrategy (targetId |> Fable.IdentExpr) cases None + // Transform decision tree + let targetAssign = Target(ident targetId) + let ctx = { ctx with DecisionTargets = targets } + match transformDecisionTreeAsSwitch treeExpr with + | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> + let cases = groupSwitchCases (Fable.Number Int32) cases (defaultIndex, defaultBoundValues) + let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, Fable.Number Int32) + let switch1 = transformSwitch com ctx false (Some targetAssign) evalExpr cases (Some defaultCase) + [ multiVarDecl; switch1; switch2 ] + | None -> + let decisionTree = com.TransformAsStatements(ctx, Some targetAssign, treeExpr) + [ yield multiVarDecl; yield! decisionTree; yield switch2 ] + + let transformDecisionTreeAsStatements (com: IPythonCompiler) (ctx: Context) returnStrategy + (targets: (Fable.Ident list * Fable.Expr) list) (treeExpr: Fable.Expr): Statement list = + // If some targets are referenced multiple times, hoist bound idents, + // resolve the decision index and compile the targets as a switch + let targetsWithMultiRefs = + if com.Options.Language = TypeScript then [] // no hoisting when compiled with types + else getTargetsWithMultipleReferences treeExpr + match targetsWithMultiRefs with + | [] -> + let ctx = { ctx with DecisionTargets = targets } + match transformDecisionTreeAsSwitch treeExpr with + | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> + let t = treeExpr.Type + let cases = cases |> List.map (fun (caseExpr, targetIndex, boundValues) -> + [caseExpr], Fable.DecisionTreeSuccess(targetIndex, boundValues, t)) + let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, t) + [ transformSwitch com ctx true returnStrategy evalExpr cases (Some defaultCase) ] + | None -> + com.TransformAsStatements(ctx, returnStrategy, treeExpr) + | targetsWithMultiRefs -> + // If the bound idents are not referenced in the target, remove them + let targets = + targets |> List.map (fun (idents, expr) -> + idents + |> List.exists (fun i -> FableTransforms.isIdentUsed i.Name expr) + |> function + | true -> idents, expr + | false -> [], expr) + let hasAnyTargetWithMultiRefsBoundValues = + targetsWithMultiRefs |> List.exists (fun idx -> + targets.[idx] |> fst |> List.isEmpty |> not) + if not hasAnyTargetWithMultiRefsBoundValues then + match transformDecisionTreeAsSwitch treeExpr with + | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> + let t = treeExpr.Type + let cases = groupSwitchCases t cases (defaultIndex, defaultBoundValues) + let ctx = { ctx with DecisionTargets = targets } + let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, t) + [ transformSwitch com ctx true returnStrategy evalExpr cases (Some defaultCase) ] + | None -> + transformDecisionTreeWithTwoSwitches com ctx returnStrategy targets treeExpr + else + transformDecisionTreeWithTwoSwitches com ctx returnStrategy targets treeExpr let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Fable.Expr): Expression * Statement list= match expr with @@ -1493,7 +1501,6 @@ module Util = transformDecisionTreeSuccessAsExpr com ctx idx boundValues | Fable.Set(expr, kind, value, range) -> - printfn "A" transformSet com ctx range expr value kind | Fable.Let(ident, value, body) -> @@ -1593,8 +1600,6 @@ module Util = List.append bindings (transformAsStatements com ctx returnStrategy body) | Fable.Set(expr, kind, value, range) -> - printfn "B: %A" (kind, value, range) - let expr', stmts = transformSet com ctx range expr value kind match expr' with | Expression.NamedExpr({ Target = target; Value = value; Loc=loc }) -> @@ -1686,7 +1691,7 @@ module Util = |> List.map (fun (id, tcArg) -> ident id, Some (Expression.identifier(tcArg))) |> multiVarDeclaration - let body = varDecls @ body + let body = varDecls :: body // Make sure we don't get trapped in an infinite loop, see #1624 let body = body @ [ Statement.break'() ] args', Statement.while'(Expression.constant(true), body) @@ -1696,7 +1701,7 @@ module Util = if declaredVars.Count = 0 then body else let varDeclStatement = multiVarDeclaration [for v in declaredVars -> ident v, None] - List.append varDeclStatement body + varDeclStatement :: body args |> List.map (ident >> Arg.arg), body let declareEntryPoint _com _ctx (funcExpr: Expression) = @@ -1723,7 +1728,6 @@ module Util = // ?returnType = returnType, // ?typeParameters = typeParameters) | _ -> - //printfn $"declareModuleMember: Got {expr}" varDeclaration membName isMutable expr // if not isPublic then PrivateModuleDeclaration(decl |> Declaration) // else ExportNamedDeclaration(decl) @@ -1912,7 +1916,6 @@ module Util = yield! ent.FSharpFields |> Seq.mapi (fun i field -> let left = get None thisExpr field.Name let right = wrapIntExpression field.FieldType args.[i] - printfn "***" assign None left right |> Statement.expr) |> Seq.toArray ] @@ -1965,7 +1968,6 @@ module Util = withCurrentScope ctx decl.UsedNames <| fun ctx -> let decls = if decl.Info.IsValue then - printfn "1" let value, stmts = transformAsExpr com ctx decl.Body let name = com.GetIdentifier(ctx, decl.Name) stmts @ [declareModuleMember decl.Info.IsPublic name decl.Info.IsMutable value] @@ -1973,7 +1975,6 @@ module Util = transformModuleFunction com ctx decl.Info decl.Name decl.Args decl.Body if decl.ExportDefault then - printfn "2" decls //@ [ ExportDefaultDeclaration(Choice2Of2(Expression.identifier(decl.Name))) ] else decls @@ -1994,13 +1995,11 @@ module Util = match decl.Constructor with | Some cons -> withCurrentScope ctx cons.UsedNames <| fun ctx -> - printfn "1" transformClassWithImplicitConstructor com ctx decl classMembers cons | None -> let ent = com.GetEntity(ent) if ent.IsFSharpUnion then transformUnion com ctx ent decl.Name classMembers else - printfn "2" transformClassWithCompilerGeneratedConstructor com ctx ent decl.Name classMembers let transformImports (imports: Import seq) : Statement list = @@ -2096,6 +2095,9 @@ module Compiler = UsedNames = { RootScope = HashSet file.UsedNamesInRootScope DeclarationScopes = declScopes CurrentDeclarationScope = Unchecked.defaultof<_> } + BoundVars = { GlobalScope = HashSet () + EnclosingScope = HashSet () + LocalScope = HashSet () } DecisionTargets = [] HoistVars = fun _ -> false TailCallOpportunity = None diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 2ffe4750be..7fe0c61b50 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -926,7 +926,7 @@ module PythonExtensions = | UnaryNot -> Not | UnaryNotBitwise -> Invert // | UnaryTypeof -> "typeof" - //| UnaryVoid -> + // | UnaryVoid -> // | UnaryDelete -> "delete" | _ -> failwith $"Operator {op} not supported" diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 7232fe0d18..32a838ccad 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -1192,6 +1192,7 @@ let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp | "typedArrays" -> makeBoolConst com.Options.TypedArrays |> Some | "extension" -> makeStrConst com.Options.FileExtension |> Some | _ -> None + | "Fable.Core.PyInterop", _ | "Fable.Core.JsInterop", _ -> match i.CompiledName, args with | "importDynamic", [path] -> diff --git a/src/fable-library-py/fable/asyncio.py b/src/fable-library-py/fable/task.py similarity index 85% rename from src/fable-library-py/fable/asyncio.py rename to src/fable-library-py/fable/task.py index 2cdd147fd8..ebaf0d147e 100644 --- a/src/fable-library-py/fable/asyncio.py +++ b/src/fable-library-py/fable/task.py @@ -21,5 +21,6 @@ sleep = aiotools.sleep start = aiotools.start runSynchronously = aiotools.run_synchronously +startImmediate = aiotools.start_immediate -__all__ = ["sleep", "start", "runSynchronously"] +__all__ = ["sleep", "start", "runSynchronously", "startImmediate"] diff --git a/src/fable-library-py/fable/types.py b/src/fable-library-py/fable/types.py index 3556dd7ced..a407e6864f 100644 --- a/src/fable-library-py/fable/types.py +++ b/src/fable-library-py/fable/types.py @@ -91,12 +91,7 @@ def recordEquals(self, other): if self is other: return True - else: - for name in self.keys(): - if self[name] != other.get(name): - return False - - return True + return False def recordCompareTo(self, other): @@ -137,7 +132,7 @@ def __lt__(self, other: Any) -> bool: raise NotImplementedError def __eq__(self, other: Any) -> bool: - return recordEquals(self, other) + return self.Equals(other) def __hash__(self) -> int: return recordGetHashCode(self) diff --git a/src/fable-library-py/fable/util.py b/src/fable-library-py/fable/util.py index 480608ee0c..fa5706700d 100644 --- a/src/fable-library-py/fable/util.py +++ b/src/fable-library-py/fable/util.py @@ -2,6 +2,8 @@ from threading import RLock from typing import Callable, Iterable, List, TypeVar, Optional +from libcst import Call + T = TypeVar("T") @@ -89,6 +91,30 @@ def equals(a, b): return a == b +def equal(a, b): + return a == b + + +def compare(a, b): + if a == b: + return 0 + if a < b: + return -1 + return 1 + + +def comparePrimitives(x, y) -> int: + return 0 if x == y else (-1 if x < y else 1) + + +def min(comparer, x, y): + return x if comparer(x, y) < 0 else y + + +def max(comparer, x, y): + return x if comparer(x, y) > 0 else y + + def assertEqual(actual, expected, msg=None) -> None: if actual != expected: raise Exception(msg or f"Expected: ${expected} - Actual: ${actual}") @@ -149,7 +175,6 @@ def clear(col): class IEnumerator(IDisposable): - @property @abstractmethod def Current(self): ... @@ -177,7 +202,7 @@ def __getattr__(self, name): class IEnumerable(Iterable): @abstractmethod - def GetEnumerator(): + def GetEnumerator(self): ... @@ -186,7 +211,6 @@ def __init__(self, iter) -> None: self.iter = iter self.current = None - @property def Current(self): if self.current is not None: return self.current @@ -210,7 +234,7 @@ def Dispose(self): def getEnumerator(o): attr = getattr(o, "GetEnumerator", None) if attr: - return attr + return attr() else: return Enumerator(iter(o)) @@ -242,6 +266,35 @@ def uncurry(arity: int, f: Callable): return uncurriedFn +def curry(arity: int, f: Callable) -> Callable: + if f is None or arity == 1: + return f + + if hasattr(f, CURRIED_KEY): + return getattr(f, CURRIED_KEY) + + if arity == 2: + return lambda a1: lambda a2: f(a1, a2) + elif arity == 3: + return lambda a1: lambda a2: lambda a3: f(a1, a2, a3) + elif arity == 4: + return lambda a1: lambda a2: lambda a3: lambda a4: f(a1, a2, a3, a4) + elif arity == 4: + return lambda a1: lambda a2: lambda a3: lambda a4: lambda a5: f(a1, a2, a3, a4, a5) + elif arity == 6: + return lambda a1: lambda a2: lambda a3: lambda a4: lambda a5: lambda a6: f(a1, a2, a3, a4, a5, a6) + elif arity == 7: + return lambda a1: lambda a2: lambda a3: lambda a4: lambda a5: lambda a6: lambda a7: f( + a1, a2, a3, a4, a5, a6, a7 + ) + elif arity == 8: + return lambda a1: lambda a2: lambda a3: lambda a4: lambda a5: lambda a6: lambda a7: lambda a8: f( + a1, a2, a3, a4, a5, a6, a7, a8 + ) + else: + raise Exception("Currying to more than 8-arity is not supported: %d" % arity) + + def isArrayLike(x): return hasattr(x, "__len__") @@ -251,8 +304,13 @@ def isDisposable(x): def toIterator(x): + print("toIterator: ", x) return iter(x) def structuralHash(x): return hash(x) + + +def physicalHash(x): + return hash(x) From 63826ba9cfda0cbec7a133b5852d4b3db9a304ae Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 5 Jun 2021 11:30:45 +0200 Subject: [PATCH 104/145] Initial non-local handling (wip) --- src/Fable.Transforms/Python/Fable2Python.fs | 132 ++++++++++++-------- 1 file changed, 81 insertions(+), 51 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 5fe44239c6..08347ea98b 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -29,13 +29,40 @@ type UsedNames = DeclarationScopes: HashSet CurrentDeclarationScope: HashSet } -/// Used for keeping track of existing variable bindings to know if we need to declare an -/// identifier as nonlocal or global. TODO: can we merge this with UsedNames? +/// Python specific, used for keeping track of existing variable bindings to +/// know if we need to declare an identifier as nonlocal or global. type BoundVars = { GlobalScope: HashSet EnclosingScope: HashSet LocalScope: HashSet } + member this.EnterScope () = + printfn "EnterScope" + let enclosingScope = HashSet() + enclosingScope.UnionWith(this.EnclosingScope) + enclosingScope.UnionWith(this.LocalScope) + { this with LocalScope = HashSet (); EnclosingScope = enclosingScope } + + member this.Bind(name: string) = + printfn "Bind: %A" name + this.LocalScope.Add name |> ignore + + member this.Bind(ids: Identifier list) = + printfn "Bind: %A" ids + for (Identifier name) in ids do + this.LocalScope.Add name |> ignore + + member this.NonLocals(idents: Identifier list) = + printfn "NonLocals: %A" (idents, this) + [ + for ident in idents do + let (Identifier name) = ident + if not (this.LocalScope.Contains name) && this.EnclosingScope.Contains name then + yield ident + else + this.Bind(name) + () + ] type Context = { File: Fable.File UsedNames: UsedNames @@ -412,8 +439,8 @@ module Helpers = let args = args |> List.map fst args, stmts - /// A few statements in the generated Babel AST do not produce any effect, and will not be printet. But they are - /// left in the AST and we need to skip them since they are not valid for Python (either). + /// A few statements in the generated Python AST do not produce any effect, + /// and should not be printet. let isProductiveStatement (stmt: Python.Statement) = let rec hasNoSideEffects (e: Python.Expression) = //printfn $"hasNoSideEffects: {e}" @@ -536,9 +563,6 @@ module Util = let identAsExpr (id: Fable.Ident) = Expression.identifier(id.Name, ?loc=id.Range) - //let identAsPattern (id: Fable.Ident) = - // Pattern.identifier(id.Name, ?loc=id.Range) - let thisExpr = Expression.name ("self") @@ -563,16 +587,19 @@ module Util = Expression.attribute (value = left, attr = expr, ctx = Load) let getExpr r (object: Expression) (expr: Expression) = + printfn "getExpr: %A" (object, expr) match expr with | Expression.Constant(value=value) -> match value with | :? string as str -> memberFromName str - | _ -> failwith "Need to be string" + | :? int + | :? float -> Expression.subscript(value = object, slice = expr, ctx = Load), [] + | _ -> failwith $"Value {value} needs to be string" | Expression.Name({Id=id}) -> Expression.attribute (value = object, attr = id, ctx = Load), [] | e -> - let func = Expression.name("getattr") - Expression.call(func=func, args=[object; e]), [] + let func = Expression.name("getattr") + Expression.call(func=func, args=[object; e]), [] let rec getParts (parts: string list) (expr: Expression) = match parts with @@ -606,20 +633,26 @@ module Util = let afe, stmts = makeArrowFunctionExpression [] body Expression.call(afe, []), stmts - let multiVarDeclaration (variables: (Identifier * Expression option) list) = + let multiVarDeclaration (ctx: Context) (variables: (Identifier * Expression option) list) = let ids, values = variables |> List.distinctBy (fun (Identifier(name=name), _value) -> name) |> List.map (function - | i, Some value -> Expression.name(i, Store), value - | i, _ -> Expression.name(i, Store), Expression.none ()) - |> List.unzip - |> fun (ids, values) -> (Expression.tuple(ids), Expression.tuple(values)) + | i, Some value -> Expression.name(i, Store), value, i + | i, _ -> Expression.name(i, Store), Expression.none (), i) + |> List.unzip3 + |> fun (ids, values, ids') -> + ctx.BoundVars.Bind(ids') + (Expression.tuple(ids), Expression.tuple(values)) + + [ Statement.assign([ids], values) ] - Statement.assign([ids], values) + let varDeclaration (ctx: Context) (var: Expression) (isMutable: bool) value = + match var with + | Name({Id=id}) -> ctx.BoundVars.Bind([id]) + | _ -> () - let varDeclaration (var: Expression) (isMutable: bool) value = - Statement.assign([var], value) + [ Statement.assign([var], value) ] let restElement (var: Fable.Ident) = let var = Expression.name (ident var) @@ -760,7 +793,7 @@ module Util = [ // First declare temp variables for (KeyValue(argId, tempVar)) in tempVars do - yield varDeclaration (Expression.identifier(tempVar)) false (Expression.identifier(argId)) + yield! varDeclaration ctx (Expression.identifier(tempVar)) false (Expression.identifier(argId)) // Then assign argument expressions to the original argument identifiers // See https://github.com/fable-compiler/Fable/issues/1368#issuecomment-434142713 for (argId, arg) in zippedArgs do @@ -827,8 +860,6 @@ module Util = | Fable.NewArray (values, typ) -> makeArray com ctx values //| Fable.NewArrayFrom (size, typ) -> makeAllocatedFrom com ctx size, [] | Fable.NewTuple vals -> makeArray com ctx vals - // | Fable.NewList (headAndTail, _) when List.contains "FABLE_LIBRARY" com.Options.Define -> - // makeList com ctx r headAndTail // Optimization for bundle size: compile list literals as List.ofArray | Fable.NewList (headAndTail, _) -> let rec getItems acc = function @@ -996,7 +1027,7 @@ module Util = | args -> List.map (fun e -> com.TransformAsExpr(ctx, e)) args |> Helpers.unzipArgs let resolveExpr t strategy pyExpr: Statement = - printfn "resolveExpr: %A" pyExpr + //printfn "resolveExpr: %A" pyExpr match strategy with | None | Some ReturnUnit -> Statement.expr(pyExpr) // TODO: Where to put these int wrappings? Add them also for function arguments? @@ -1155,7 +1186,7 @@ module Util = expr, stmts @ stmts' @ stmts'' let transformSet (com: IPythonCompiler) ctx range fableExpr (value: Fable.Expr) kind = - printfn "transformSet: %A" (fableExpr, value) + //printfn "transformSet: %A" (fableExpr, value) let expr, stmts = com.TransformAsExpr(ctx, fableExpr) let value', stmts' = com.TransformAsExpr(ctx, value) let value = value' |> wrapIntExpression value.Type @@ -1166,8 +1197,7 @@ module Util = | Some(Fable.ExprKey(TransformExpr com ctx (e, stmts''))) -> let expr, stmts''' = getExpr None expr e expr, stmts @ stmts' @ stmts'' @ stmts''' - printfn "Used names: %A" ctx.UsedNames - printfn "transformset, assign: %A" (range, ret, value, stmts) + //printfn "transformset, assign: %A" (range, ret, value, stmts) assign range ret value, stmts let transformBindingExprBody (com: IPythonCompiler) (ctx: Context) (var: Fable.Ident) (value: Fable.Expr) = @@ -1192,6 +1222,7 @@ module Util = if isPyStatement ctx false value then printfn "Py statement" let varName, varExpr = Expression.name(var.Name), identAsExpr var + ctx.BoundVars.Bind(var.Name) let decl = Statement.assign([varName], varExpr) let body = com.TransformAsStatements(ctx, Some(Assign varExpr), value) List.append [ decl ] body @@ -1199,8 +1230,8 @@ module Util = printfn "Not Py statement" let value, stmts = transformBindingExprBody com ctx var value let varName = Expression.name(var.Name) - let decl = varDeclaration varName var.IsMutable value - stmts @ [ decl ] + let decl = varDeclaration ctx varName var.IsMutable value + stmts @ decl let transformTest (com: IPythonCompiler) ctx range kind expr: Expression * Statement list = match kind with @@ -1387,7 +1418,7 @@ module Util = let boundIdents = targets |> List.collect (fun (idents,_) -> idents) |> List.map (fun id -> ident id, None) - multiVarDeclaration ((ident targetId,None)::boundIdents) + multiVarDeclaration ctx ((ident targetId,None)::boundIdents) // Transform targets as switch let switch2 = // TODO: Declare the last case as the default case? @@ -1401,10 +1432,10 @@ module Util = let cases = groupSwitchCases (Fable.Number Int32) cases (defaultIndex, defaultBoundValues) let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, Fable.Number Int32) let switch1 = transformSwitch com ctx false (Some targetAssign) evalExpr cases (Some defaultCase) - [ multiVarDecl; switch1; switch2 ] + [ yield! multiVarDecl; switch1; switch2 ] | None -> let decisionTree = com.TransformAsStatements(ctx, Some targetAssign, treeExpr) - [ yield multiVarDecl; yield! decisionTree; yield switch2 ] + [ yield! multiVarDecl; yield! decisionTree; yield switch2 ] let transformDecisionTreeAsStatements (com: IPythonCompiler) (ctx: Context) returnStrategy (targets: (Fable.Ident list * Fable.Expr) list) (treeExpr: Fable.Expr): Statement list = @@ -1603,7 +1634,11 @@ module Util = let expr', stmts = transformSet com ctx range expr value kind match expr' with | Expression.NamedExpr({ Target = target; Value = value; Loc=loc }) -> - stmts @ [ Statement.assign([target], value) ] + let nonLocals = + match target with + | Expression.Name({Id=id}) -> [ ctx.BoundVars.NonLocals([id]) |> Statement.nonLocal ] + | _ -> [] + nonLocals @ stmts @ [ Statement.assign([target], value) ] | _ -> stmts @ [ expr' |> resolveExpr expr.Type returnStrategy ] | Fable.IfThenElse(guardExpr, thenExpr, elseExpr, r) -> @@ -1671,7 +1706,9 @@ module Util = let ctx = { ctx with TailCallOpportunity = tailcallChance HoistVars = fun ids -> declaredVars.AddRange(ids); true - OptimizeTailCall = fun () -> isTailCallOptimized <- true } + OptimizeTailCall = fun () -> isTailCallOptimized <- true + BoundVars = ctx.BoundVars.EnterScope() } + let body = if body.Type = Fable.Unit then transformBlock com ctx (Some ReturnUnit) body @@ -1689,9 +1726,9 @@ module Util = let varDecls = List.zip args tc.Args |> List.map (fun (id, tcArg) -> ident id, Some (Expression.identifier(tcArg))) - |> multiVarDeclaration + |> multiVarDeclaration ctx - let body = varDecls :: body + let body = varDecls @ body // Make sure we don't get trapped in an infinite loop, see #1624 let body = body @ [ Statement.break'() ] args', Statement.while'(Expression.constant(true), body) @@ -1700,8 +1737,8 @@ module Util = let body = if declaredVars.Count = 0 then body else - let varDeclStatement = multiVarDeclaration [for v in declaredVars -> ident v, None] - varDeclStatement :: body + let varDeclStatement = multiVarDeclaration ctx [for v in declaredVars -> ident v, None] + varDeclStatement @ body args |> List.map (ident >> Arg.arg), body let declareEntryPoint _com _ctx (funcExpr: Expression) = @@ -1711,7 +1748,7 @@ module Util = // Statement.expr(emitExpression funcExpr.loc "process.exit($0)" [main], ?loc=funcExpr.loc) Statement.expr(main) - let declareModuleMember isPublic (membName: Identifier) isMutable (expr: Expression) = + let declareModuleMember ctx isPublic (membName: Identifier) isMutable (expr: Expression) = let membName = Expression.name(membName) match expr with // | ClassExpression(body, id, superClass, implements, superTypeParameters, typeParameters, loc) -> @@ -1728,7 +1765,7 @@ module Util = // ?returnType = returnType, // ?typeParameters = typeParameters) | _ -> - varDeclaration membName isMutable expr + varDeclaration ctx membName isMutable expr // if not isPublic then PrivateModuleDeclaration(decl |> Declaration) // else ExportNamedDeclaration(decl) @@ -1771,16 +1808,11 @@ module Util = let getEntityFieldsAsProps (com: IPythonCompiler) ctx (ent: Fable.Entity) = if ent.IsFSharpUnion then getUnionFieldsAsIdents com ctx ent - |> Array.map (fun id -> - let prop = identAsExpr id - //let ta = typeAnnotation com ctx id.Type - prop) + |> Array.map identAsExpr else ent.FSharpFields |> Seq.map (fun field -> let prop, computed = memberFromName field.Name - //let ta = typeAnnotation com ctx field.FieldType - let isStatic = if field.IsStatic then Some true else None prop) |> Seq.toArray @@ -1809,8 +1841,8 @@ module Util = let args = genArgs |> Array.mapToList (ident >> Arg.arg) let expr, stmts' = makeFunctionExpression None (args, body) let name = com.GetIdentifier(ctx, entName + Naming.reflectionSuffix) - expr |> declareModuleMember ent.IsPublic name false, stmts @ stmts' - stmts @ [typeDeclaration; reflectionDeclaration] + expr |> declareModuleMember ctx ent.IsPublic name false, stmts @ stmts' + stmts @ [typeDeclaration ] @ reflectionDeclaration let transformModuleFunction (com: IPythonCompiler) ctx (info: Fable.MemberInfo) (membName: string) args body = let args, body = @@ -1848,13 +1880,11 @@ module Util = let arguments = Arguments.arguments (self::args) FunctionDef.Create(Identifier memb.Name, arguments, body = body) |> List.singleton - //ClassMember.classMethod(kind, key, args, body, computed_=computed, ``static``=isStatic) let transformAttachedMethod (com: IPythonCompiler) ctx (memb: Fable.MemberDecl) = let isStatic = not memb.Info.IsInstance let makeMethod name args body = let key, computed = memberFromName name - //ClassMember.classMethod(ClassFunction, key, args, body, computed_=computed, ``static``=isStatic) FunctionDef.Create(Identifier name, args, body = body) let args, body = getMemberArgsAndBody com ctx (Attached isStatic) memb.Info.HasSpread memb.Args memb.Body @@ -1970,7 +2000,7 @@ module Util = if decl.Info.IsValue then let value, stmts = transformAsExpr com ctx decl.Body let name = com.GetIdentifier(ctx, decl.Name) - stmts @ [declareModuleMember decl.Info.IsPublic name decl.Info.IsMutable value] + stmts @ declareModuleMember ctx decl.Info.IsPublic name decl.Info.IsMutable value else transformModuleFunction com ctx decl.Info decl.Name decl.Args decl.Body @@ -2027,7 +2057,7 @@ module Util = module Compiler = open Util - type BabelCompiler (com: Compiler) = + type PythonCompiler (com: Compiler) = let onlyOnceWarnings = HashSet() let imports = Dictionary() @@ -2080,7 +2110,7 @@ module Compiler = member _.AddLog(msg, severity, ?range, ?fileName:string, ?tag: string) = com.AddLog(msg, severity, ?range=range, ?fileName=fileName, ?tag=tag) - let makeCompiler com = BabelCompiler(com) + let makeCompiler com = PythonCompiler(com) let transformFile (com: Compiler) (file: Fable.File) = let com = makeCompiler com :> IPythonCompiler From 9d597cacd7a4c767d8cfcf0363e8cd425f00ec23 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 5 Jun 2021 20:20:27 +0200 Subject: [PATCH 105/145] Import fixes --- src/Fable.Transforms/Python/Fable2Python.fs | 168 ++++++++++---------- 1 file changed, 86 insertions(+), 82 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index ccf1adbd23..205f15b9bf 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -15,9 +15,9 @@ type ReturnStrategy = | Target of Identifier type Import = - { Selector: string - LocalIdent: string option - Path: string } + { Module: string + LocalIdent: Identifier option + Name: string option } type ITailCallOpportunity = abstract Label: string @@ -76,8 +76,9 @@ type Context = type IPythonCompiler = inherit Compiler abstract GetIdentifier: ctx: Context * name: string -> Python.Identifier + abstract GetIdentifierAsExpr: ctx: Context * name: string -> Python.Expression abstract GetAllImports: unit -> seq - abstract GetImportExpr: Context * selector: string * path: string * SourceLocation option -> Expression + abstract GetImportExpr: Context * moduleName: string * ?name: string * ?loc: SourceLocation -> Expression abstract TransformAsExpr: Context * Fable.Expr -> Expression * Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Fable.Expr -> Statement list abstract TransformImport: Context * selector:string * path:string -> Expression @@ -467,10 +468,9 @@ module Util = let getIdentifier (com: IPythonCompiler) (ctx: Context) (name: string) = let name = Helpers.clean name - // FIXME: - // match name with - // | "math" -> com.GetImportExpr(ctx, "math") |> ignore - // | _ -> () + match name with + | "math" -> com.GetImportExpr(ctx, "math") |> ignore + | _ -> () Python.Identifier name @@ -552,19 +552,18 @@ module Util = | Fable.IfThenElse(_,thenExpr,elseExpr,_) -> preferStatement || isPyStatement ctx false thenExpr || isPyStatement ctx false elseExpr - let addErrorAndReturnNull (com: Compiler) (range: SourceLocation option) (error: string) = addError com [] range error Expression.name (Python.Identifier("None")) - let ident (id: Fable.Ident) = - Identifier(id.Name) + let ident (com: IPythonCompiler) (ctx: Context) (id: Fable.Ident) = + com.GetIdentifier(ctx, id.Name) - let identAsExpr (id: Fable.Ident) = - Expression.identifier(id.Name, ?loc=id.Range) + let identAsExpr (com: IPythonCompiler) (ctx: Context) (id: Fable.Ident) = + com.GetIdentifierAsExpr(ctx, id.Name) let thisExpr = - Expression.name ("self") + Expression.name("self") let ofInt (i: int) = Expression.constant(float i) @@ -654,8 +653,8 @@ module Util = [ Statement.assign([var], value) ] - let restElement (var: Fable.Ident) = - let var = Expression.name (ident var) + let restElement (var: Python.Identifier) = + let var = Expression.name(var) Expression.starred(var) let callSuper (args: Expression list) = @@ -743,16 +742,6 @@ module Util = let expr, stmts' = getExpr r expr (Expression.constant("tag")) expr, stmts @ stmts' - /// Wrap int expressions with `| 0` to help optimization of JS VMs - let wrapIntExpression typ (e: Expression) = - match e, typ with - | Expression.Constant(_), _ -> e - // TODO: Unsigned ints seem to cause problems, should we check only Int32 here? - | _, Fable.Number(Int8 | Int16 | Int32) - | _, Fable.Enum _ -> - Expression.binOp(e, BinaryOrBitwise, Expression.constant(0.)) - | _ -> e - let wrapExprInBlockWithReturn (e, stmts) = stmts @ [ Statement.return'(e) ] @@ -804,11 +793,11 @@ module Util = yield Statement.continue'(?loc=range) ] - let transformImport (com: IPythonCompiler) ctx r (selector: string) (path: string) = - let selector, parts = - let parts = Array.toList(selector.Split('.')) + let transformImport (com: IPythonCompiler) ctx (r: SourceLocation option) (name: string) (moduleName: string) = + let name, parts = + let parts = Array.toList(name.Split('.')) parts.Head, parts.Tail - com.GetImportExpr(ctx, selector, path, r) + com.GetImportExpr(ctx, moduleName, name) |> getParts parts let transformCast (com: IPythonCompiler) (ctx: Context) t tag e: Expression * Statement list = @@ -847,7 +836,7 @@ module Util = let transformValue (com: IPythonCompiler) (ctx: Context) r value: Expression * Statement list = match value with | Fable.BaseValue(None,_) -> Expression.identifier("super().__init__"), [] - | Fable.BaseValue(Some boundIdent,_) -> identAsExpr boundIdent, [] + | Fable.BaseValue(Some boundIdent,_) -> identAsExpr com ctx boundIdent, [] | Fable.ThisValue _ -> Expression.identifier("self"), [] | Fable.TypeInfo t -> transformTypeInfo com ctx r Map.empty t | Fable.Null _t -> Expression.identifier("None", ?loc=r), [] @@ -1031,7 +1020,7 @@ module Util = match strategy with | None | Some ReturnUnit -> Statement.expr(pyExpr) // TODO: Where to put these int wrappings? Add them also for function arguments? - | Some Return -> Statement.return'(wrapIntExpression t pyExpr) + | Some Return -> Statement.return'(pyExpr) | Some(Assign left) -> Statement.expr(assign None left pyExpr) | Some(Target left) -> Statement.expr(assign None (left |> Expression.identifier) pyExpr) @@ -1110,7 +1099,7 @@ module Util = catch |> Option.map (fun (param, body) -> let body = transformBlock com ctx returnStrategy body let exn = Expression.identifier("Exception") |> Some - let identifier = ident param + let identifier = ident com ctx param [ ExceptHandler.exceptHandler (``type`` = exn, name = identifier, body = body) ]) let finalizer = finalizer |> Option.map (transformBlock com ctx None) @@ -1188,8 +1177,7 @@ module Util = let transformSet (com: IPythonCompiler) ctx range fableExpr (value: Fable.Expr) kind = let expr, stmts = com.TransformAsExpr(ctx, fableExpr) - let value', stmts' = com.TransformAsExpr(ctx, value) - let value = value' |> wrapIntExpression value.Type + let value, stmts' = com.TransformAsExpr(ctx, value) let ret, stmts'' = match kind with | Fable.ValueSet -> expr, stmts @ stmts' @@ -1207,21 +1195,17 @@ module Util = let args, stmts = transformFunction com ctx name args body makeArrowFunctionExpression args stmts | _ -> - if var.IsMutable then - com.TransformAsExpr(ctx, value) - else - let expr, stmts = com.TransformAsExpr(ctx, value) - expr |> wrapIntExpression value.Type, stmts + com.TransformAsExpr(ctx, value) let transformBindingAsExpr (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = printfn "transformBindingAsExpr: %A" (var, value) let expr, stmts = transformBindingExprBody com ctx var value - expr |> assign None (identAsExpr var), stmts + expr |> assign None (identAsExpr com ctx var), stmts let transformBindingAsStatements (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = if isPyStatement ctx false value then printfn "Py statement" - let varName, varExpr = Expression.name(var.Name), identAsExpr var + let varName, varExpr = Expression.name(var.Name), identAsExpr com ctx var ctx.BoundVars.Bind(var.Name) let decl = Statement.assign([varName], varExpr) let body = com.TransformAsStatements(ctx, Some(Assign varExpr), value) @@ -1417,15 +1401,15 @@ module Util = let multiVarDecl = let boundIdents = targets |> List.collect (fun (idents,_) -> - idents) |> List.map (fun id -> ident id, None) - multiVarDeclaration ctx ((ident targetId,None)::boundIdents) + idents) |> List.map (fun id -> ident com ctx id, None) + multiVarDeclaration ctx ((ident com ctx targetId,None)::boundIdents) // Transform targets as switch let switch2 = // TODO: Declare the last case as the default case? let cases = targets |> List.mapi (fun i (_,target) -> [makeIntConst i], target) transformSwitch com ctx true returnStrategy (targetId |> Fable.IdentExpr) cases None // Transform decision tree - let targetAssign = Target(ident targetId) + let targetAssign = Target(ident com ctx targetId) let ctx = { ctx with DecisionTargets = targets } match transformDecisionTreeAsSwitch treeExpr with | Some(evalExpr, cases, (defaultIndex, defaultBoundValues)) -> @@ -1489,7 +1473,7 @@ module Util = | Fable.Value(kind, r) -> transformValue com ctx r kind - | Fable.IdentExpr id -> identAsExpr id, [] + | Fable.IdentExpr id -> identAsExpr com ctx id, [] | Fable.Import({ Selector = selector; Path = path }, _, r) -> transformImport com ctx r selector path, [] @@ -1579,7 +1563,7 @@ module Util = stmts @ [ expr |> resolveExpr kind.Type returnStrategy ] | Fable.IdentExpr id -> - [ identAsExpr id |> resolveExpr id.Type returnStrategy ] + [ identAsExpr com ctx id |> resolveExpr id.Type returnStrategy ] | Fable.Import({ Selector = selector; Path = path }, t, r) -> [ transformImport com ctx r selector path |> resolveExpr t returnStrategy ] @@ -1725,7 +1709,7 @@ module Util = |> List.map (fun (id, tcArg) -> id) let varDecls = List.zip args tc.Args - |> List.map (fun (id, tcArg) -> ident id, Some (Expression.identifier(tcArg))) + |> List.map (fun (id, tcArg) -> ident com ctx id, Some (Expression.identifier(tcArg))) |> multiVarDeclaration ctx let body = varDecls @ body @@ -1737,9 +1721,9 @@ module Util = let body = if declaredVars.Count = 0 then body else - let varDeclStatement = multiVarDeclaration ctx [for v in declaredVars -> ident v, None] + let varDeclStatement = multiVarDeclaration ctx [for v in declaredVars -> ident com ctx v, None] varDeclStatement @ body - args |> List.map (ident >> Arg.arg), body + args |> List.map (ident com ctx >> Arg.arg), body let declareEntryPoint _com _ctx (funcExpr: Expression) = let argv = emitExpression None "typeof process === 'object' ? process.argv.slice(2) : []" [] @@ -1808,7 +1792,7 @@ module Util = let getEntityFieldsAsProps (com: IPythonCompiler) ctx (ent: Fable.Entity) = if ent.IsFSharpUnion then getUnionFieldsAsIdents com ctx ent - |> Array.map identAsExpr + |> Array.map (identAsExpr com ctx) else ent.FSharpFields |> Seq.map (fun field -> @@ -1836,9 +1820,9 @@ module Util = let reflectionDeclaration, stmts = let ta = None let genArgs = Array.init (ent.GenericParameters.Length) (fun i -> "gen" + string i |> makeIdent) - let generics = genArgs |> Array.mapToList identAsExpr + let generics = genArgs |> Array.mapToList (identAsExpr com ctx) let body, stmts = transformReflectionInfo com ctx None ent generics - let args = genArgs |> Array.mapToList (ident >> Arg.arg) + let args = genArgs |> Array.mapToList (ident com ctx >> Arg.arg) let expr, stmts' = makeFunctionExpression None (args, body) let name = com.GetIdentifier(ctx, entName + Naming.reflectionSuffix) expr |> declareModuleMember ctx ent.IsPublic name false, stmts @ stmts' @@ -1899,8 +1883,8 @@ module Util = let transformUnion (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = let fieldIds = getUnionFieldsAsIdents com ctx ent let args = - [ fieldIds.[0] |> ident |> Arg.arg - fieldIds.[1] |> ident |> Arg.arg ] //restElement ] + [ fieldIds.[0] |> ident com ctx |> Arg.arg + fieldIds.[1] |> ident com ctx |> Arg.arg ] //restElement ] let body = [ yield callSuperAsStatement [] @@ -1909,8 +1893,8 @@ module Util = let right = match id.Type with | Fable.Number _ -> - Expression.binOp(identAsExpr id, BinaryOrBitwise, Expression.constant(0.)) - | _ -> identAsExpr id + Expression.binOp(identAsExpr com ctx id, BinaryOrBitwise, Expression.constant(0.)) + | _ -> identAsExpr com ctx id Statement.assign([left], right)) //assign None left right |> Statement.expr) @@ -1945,11 +1929,11 @@ module Util = yield callSuperAsStatement [] yield! ent.FSharpFields |> Seq.mapi (fun i field -> let left = get None thisExpr field.Name - let right = wrapIntExpression field.FieldType args.[i] + let right = args.[i] assign None left right |> Statement.expr) |> Seq.toArray ] - let args = fieldIds |> Array.mapToList (ident >> Arg.arg) + let args = fieldIds |> Array.mapToList (ident com ctx >> Arg.arg) declareType com ctx ent entName args body baseExpr classMembers let transformClassWithImplicitConstructor (com: IPythonCompiler) ctx (classDecl: Fable.ClassDecl) classMembers (cons: Fable.MemberDecl) = @@ -2042,19 +2026,28 @@ module Util = [ for import in imports do match import with - | { Selector = selector; LocalIdent = local; Path = path } -> - let path = path |> Helpers.rewriteFableImport - let alias = Alias.alias(Identifier(selector), ?asname=None) - Statement.importFrom (Some(Identifier(path)), [ alias ]) + | { Name = name; LocalIdent = local; Module = moduleName } -> + let moduleName = moduleName |> Helpers.rewriteFableImport + match name with + | Some name -> + let alias = Alias.alias(Identifier(name), ?asname=None) + Statement.importFrom (Some(Identifier(moduleName)), [ alias ]) + | None -> + let alias = Alias.alias(Identifier(moduleName), ?asname=None) + Statement.import([alias]) ] - let getIdentForImport (ctx: Context) (path: string) (selector: string) = - if System.String.IsNullOrEmpty selector then None - else - match selector with - | "*" | "default" -> Path.GetFileNameWithoutExtension(path) - | _ -> selector - |> getUniqueNameInRootScope ctx + let getIdentForImport (ctx: Context) (moduleName: string) (name: string option) = + match name with + | None -> + Path.GetFileNameWithoutExtension(moduleName) + |> Python.Identifier + |> Some + | Some name -> + match name with + | "*" + | _ -> name + |> Python.Identifier |> Some module Compiler = @@ -2069,24 +2062,34 @@ module Compiler = if onlyOnceWarnings.Add(msg) then addWarning com [] range msg - member _.GetImportExpr(ctx, selector, path, r) = - let cachedName = path + "::" + selector + member _.GetImportExpr(ctx, moduleName, ?name, ?r) = + let cachedName = moduleName + "::" + defaultArg name "module" match imports.TryGetValue(cachedName) with | true, i -> match i.LocalIdent with | Some localIdent -> Expression.identifier(localIdent) | None -> Expression.none() | false, _ -> - let localId = getIdentForImport ctx path selector - let i = - { Selector = - if selector = Naming.placeholder then - "`importMember` must be assigned to a variable" - |> addError com [] r; selector - else selector - Path = path - LocalIdent = localId } - imports.Add(cachedName, i) + let localId = getIdentForImport ctx moduleName name + match name with + | Some name -> + let i = + { Name = + if name = Naming.placeholder then + "`importMember` must be assigned to a variable" + |> addError com [] r; name + else name + |> Some + Module = moduleName + LocalIdent = localId } + imports.Add(cachedName, i) + | None -> + let i = + { Name = None + Module = moduleName + LocalIdent = localId } + imports.Add(cachedName, i) + match localId with | Some localId -> Expression.identifier(localId) | None -> Expression.none() @@ -2097,6 +2100,7 @@ module Compiler = member bcom.TransformFunction(ctx, name, args, body) = transformFunction bcom ctx name args body member bcom.TransformImport(ctx, selector, path) = transformImport bcom ctx None selector path member bcom.GetIdentifier(ctx, name) = getIdentifier bcom ctx name + member bcom.GetIdentifierAsExpr(ctx, name) = getIdentifier bcom ctx name |> Expression.name interface Compiler with member _.Options = com.Options From a818b3e85058ddf63fdd925ffcbf69494033547d Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 12 Jun 2021 15:35:29 +0200 Subject: [PATCH 106/145] Fixes for sequence expressions, for-loops --- src/Fable.Transforms/Python/Fable2Python.fs | 164 ++++++++++++-------- src/Fable.Transforms/Python/Python.fs | 19 ++- 2 files changed, 120 insertions(+), 63 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 205f15b9bf..1766eff472 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -37,23 +37,23 @@ type BoundVars = LocalScope: HashSet } member this.EnterScope () = - printfn "EnterScope" + // printfn "EnterScope" let enclosingScope = HashSet() enclosingScope.UnionWith(this.EnclosingScope) enclosingScope.UnionWith(this.LocalScope) { this with LocalScope = HashSet (); EnclosingScope = enclosingScope } member this.Bind(name: string) = - printfn "Bind: %A" name + // printfn "Bind: %A" name this.LocalScope.Add name |> ignore member this.Bind(ids: Identifier list) = - printfn "Bind: %A" ids + // printfn "Bind: %A" ids for (Identifier name) in ids do this.LocalScope.Add name |> ignore member this.NonLocals(idents: Identifier list) = - printfn "NonLocals: %A" (idents, this) + // printfn "NonLocals: %A" (idents, this) [ for ident in idents do let (Identifier name) = ident @@ -61,7 +61,6 @@ type BoundVars = yield ident else this.Bind(name) - () ] type Context = { File: Fable.File @@ -322,7 +321,9 @@ module Reflection = Expression.compare(typeof, [ Eq ], [ Expression.constant(primitiveType)], ?loc=range), stmts let jsInstanceof consExpr (Util.TransformExpr com ctx (expr, stmts)): Expression * Statement list= - Expression.binOp(expr, BinaryInstanceOf, consExpr, ?loc=range), stmts + let func = Expression.name (Python.Identifier("isinstance")) + let args = [ expr; consExpr ] + Expression.call (func, args), stmts match typ with | Fable.Any -> Expression.constant(true), [] @@ -580,7 +581,6 @@ module Util = | n when Naming.hasIdentForbiddenChars n -> Expression.constant(n), [] | n -> Expression.identifier(n), [] - let get r left memberName = let expr = Identifier memberName Expression.attribute (value = left, attr = expr, ctx = Load) @@ -847,7 +847,9 @@ module Util = | Fable.NumberConstant (x,_) -> Expression.constant(x, ?loc=r), [] //| Fable.RegexConstant (source, flags) -> Expression.regExpLiteral(source, flags, ?loc=r) | Fable.NewArray (values, typ) -> makeArray com ctx values - //| Fable.NewArrayFrom (size, typ) -> makeAllocatedFrom com ctx size, [] + | Fable.NewArrayFrom (size, typ) -> + let array, stmts = makeArray com ctx [] + Expression.binOp(array, Mult, Expression.constant(size)), stmts | Fable.NewTuple vals -> makeArray com ctx vals // Optimization for bundle size: compile list literals as List.ofArray | Fable.NewList (headAndTail, _) -> @@ -997,7 +999,6 @@ module Util = let name = Helpers.getUniqueIdentifier "lifted" let stmt = Statement.classDef(name, body=classBody, bases=(baseExpr |> Option.toList) ) Expression.name (name), [ stmt ] - //Expression.call(classExpr, []), [] let transformCallArgs (com: IPythonCompiler) ctx hasSpread args : Expression list * Statement list = match args with @@ -1026,13 +1027,29 @@ module Util = let transformOperation com ctx range opKind: Expression * Statement list = match opKind with + | Fable.Unary(UnaryVoid, TransformExpr com ctx (expr, stmts)) -> + expr, stmts | Fable.Unary(op, TransformExpr com ctx (expr, stmts)) -> Expression.unaryOp(op, expr, ?loc=range), stmts - | Fable.Binary(BinaryEqualStrict, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> - Expression.compare(left, [Eq], [right], ?loc=range), stmts @ stmts' + | Fable.Binary(BinaryInstanceOf, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> + let func = Expression.name (Python.Identifier("isinstance")) + let args = [ left; right ] + Expression.call (func, args), stmts' @ stmts + | Fable.Binary(op, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> - Expression.binOp(left, op, right, ?loc=range), stmts @ stmts' + match op with + | BinaryEqual + | BinaryEqualStrict // FIXME: should use ´is` for objects + | BinaryUnequal + | BinaryUnequalStrict // FIXME: should use ´is not` for objects + | BinaryLess + | BinaryLessOrEqual + | BinaryGreater + | BinaryGreaterOrEqual -> + Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' + | _ -> + Expression.binOp(left, op, right, ?loc=range), stmts @ stmts' | Fable.Logical(op, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> Expression.boolOp(op, [left; right], ?loc=range), stmts @ stmts' @@ -1090,7 +1107,7 @@ module Util = // When expecting a block, it's usually not necessary to wrap it // in a lambda to isolate its variable context let transformBlock (com: IPythonCompiler) ctx ret expr: Statement list = - com.TransformAsStatements(ctx, ret, expr) + com.TransformAsStatements(ctx, ret, expr) |> List.choose Helpers.isProductiveStatement let transformTryCatch com ctx r returnStrategy (body, (catch: option), finalizer) = // try .. catch statements cannot be tail call optimized @@ -1204,14 +1221,12 @@ module Util = let transformBindingAsStatements (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = if isPyStatement ctx false value then - printfn "Py statement" let varName, varExpr = Expression.name(var.Name), identAsExpr com ctx var ctx.BoundVars.Bind(var.Name) let decl = Statement.assign([varName], varExpr) let body = com.TransformAsStatements(ctx, Some(Assign varExpr), value) List.append [ decl ] body else - printfn "Not Py statement" let value, stmts = transformBindingExprBody com ctx var value let varName = Expression.name(var.Name) let decl = varDeclaration ctx varName var.IsMutable value @@ -1224,7 +1239,7 @@ module Util = | Fable.OptionTest nonEmpty -> let op = if nonEmpty then BinaryUnequal else BinaryEqual let expr, stmts = com.TransformAsExpr(ctx, expr) - Expression.binOp(expr, op, Expression.none(), ?loc=range), stmts + Expression.compare(expr, op, [Expression.none()], ?loc=range), stmts | Fable.ListTest nonEmpty -> let expr, stmts = com.TransformAsExpr(ctx, expr) // let op = if nonEmpty then BinaryUnequal else BinaryEqual @@ -1293,21 +1308,21 @@ module Util = let target = List.rev bindings |> List.fold (fun e (i,v) -> Fable.Let(i,v,e)) target com.TransformAsExpr(ctx, target) - // let transformDecisionTreeSuccessAsStatements (com: IPythonCompiler) (ctx: Context) returnStrategy targetIndex boundValues: Statement list = - // match returnStrategy with - // | Some(Target targetId) as target -> - // let idents, _ = getDecisionTarget ctx targetIndex - // let assignments = - // matchTargetIdentAndValues idents boundValues - // |> List.collect (fun (id, TransformExpr com ctx (value, stmts)) -> - // let stmt = assign None (identAsExpr id) value |> Statement.expr - // stmts @ [ stmt ]) - // let targetAssignment = assign None (targetId |> Expression.name) (ofInt targetIndex) |> Statement.expr - // [ targetAssignment ] @ assignments - // | ret -> - // let bindings, target = getDecisionTargetAndBindValues com ctx targetIndex boundValues - // let bindings = bindings |> Seq.collect (fun (i, v) -> transformBindingAsStatements com ctx i v) |> Seq.toList - // bindings @ (com.TransformAsStatements(ctx, ret, target)) + let transformDecisionTreeSuccessAsStatements (com: IPythonCompiler) (ctx: Context) returnStrategy targetIndex boundValues: Statement list = + match returnStrategy with + | Some(Target targetId) as target -> + let idents, _ = getDecisionTarget ctx targetIndex + let assignments = + matchTargetIdentAndValues idents boundValues + |> List.collect (fun (id, TransformExpr com ctx (value, stmts)) -> + let stmt = assign None (identAsExpr com ctx id) value |> Statement.expr + stmts @ [ stmt ]) + let targetAssignment = assign None (targetId |> Expression.name) (ofInt targetIndex) |> Statement.expr + [ targetAssignment ] @ assignments + | ret -> + let bindings, target = getDecisionTargetAndBindValues com ctx targetIndex boundValues + let bindings = bindings |> Seq.collect (fun (i, v) -> transformBindingAsStatements com ctx i v) |> Seq.toList + bindings @ (com.TransformAsStatements(ctx, ret, target)) let transformDecisionTreeAsSwitch expr = let (|Equals|_|) = function @@ -1465,6 +1480,27 @@ module Util = else transformDecisionTreeWithTwoSwitches com ctx returnStrategy targets treeExpr + let transformSequenceExpr (com: IPythonCompiler) ctx (exprs: Expression list) : Expression * Statement list = + let body = + exprs + |> List.mapi + (fun i expr -> + // Return the last expression + if i = exprs.Length - 1 then + [ Statement.return' (expr)] + else + [ Statement.expr(expr) ]) + |> List.collect id + //|> transformBody ReturnStrategy.Return + + + let name = Helpers.getUniqueIdentifier ("lifted") + let func = FunctionDef.Create(name = name, args = Arguments.arguments [], body = body) + + let name = Expression.name (name) + Expression.call (name), [ func ] + + let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Fable.Expr): Expression * Statement list= match expr with | Fable.TypeCast(e,t,tag) -> transformCast com ctx t tag e @@ -1519,23 +1555,31 @@ module Util = transformSet com ctx range expr value kind | Fable.Let(ident, value, body) -> + printfn "Fable.Let: %A" (ident, value, body) if ctx.HoistVars [ident] then let assignment, stmts = transformBindingAsExpr com ctx ident value let expr, stmts' = com.TransformAsExpr(ctx, body) - //Expression.sequenceExpression([|assignment; |]) - expr, stmts @ stmts' + let expr, stmts'' = transformSequenceExpr com ctx [ assignment; expr ] + expr, stmts @ stmts' @ stmts'' else iife com ctx expr - // | Fable.LetRec(bindings, body) -> - // if ctx.HoistVars(List.map fst bindings) then - // let values = bindings |> List.mapToArray (fun (id, value) -> - // transformBindingAsExpr com ctx id value) - // Expression.sequenceExpression(Array.append values [|com.TransformAsExpr(ctx, body)|]) - // else iife com ctx expr + | Fable.LetRec(bindings, body) -> + if ctx.HoistVars(List.map fst bindings) then + let values, stmts = + bindings + |> List.map (fun (id, value) -> transformBindingAsExpr com ctx id value) + |> List.unzip + |> (fun (e, s) -> (e, List.collect id s)) + + let expr, stmts' = com.TransformAsExpr(ctx, body) + let expr, stmts'' = transformSequenceExpr com ctx (values @ [expr]) + expr, stmts @ stmts' @ stmts'' + else iife com ctx expr - // | Fable.Sequential exprs -> - // List.mapToArray (fun e -> com.TransformAsExpr(ctx, e)) exprs - // |> Expression.sequenceExpression + | Fable.Sequential exprs -> + let exprs, stmts = List.map (fun e -> com.TransformAsExpr(ctx, e)) exprs |> List.unzip + printfn "Sequential: %A" (exprs, stmts) + Expression.none(), stmts |> List.collect id | Fable.Emit(info, _, range) -> if info.IsJsStatement then iife com ctx expr @@ -1545,8 +1589,6 @@ module Util = | Fable.WhileLoop _ | Fable.ForLoop _ | Fable.TryCatch _ -> iife com ctx expr - | _ -> failwith $"Expression {expr} not supported." - let rec transformAsStatements (com: IPythonCompiler) ctx returnStrategy (expr: Fable.Expr): Statement list = match expr with @@ -1655,30 +1697,30 @@ module Util = | Fable.TryCatch (body, catch, finalizer, r) -> transformTryCatch com ctx r returnStrategy (body, catch, finalizer) - // | Fable.DecisionTree(expr, targets) -> - // transformDecisionTreeAsStatements com ctx returnStrategy targets expr + | Fable.DecisionTree(expr, targets) -> + transformDecisionTreeAsStatements com ctx returnStrategy targets expr - // | Fable.DecisionTreeSuccess(idx, boundValues, _) -> - // transformDecisionTreeSuccessAsStatements com ctx returnStrategy idx boundValues + | Fable.DecisionTreeSuccess(idx, boundValues, _) -> + transformDecisionTreeSuccessAsStatements com ctx returnStrategy idx boundValues | Fable.WhileLoop(TransformExpr com ctx (guard, stmts), body, range) -> stmts @ [ Statement.while'(guard, transformBlock com ctx None body, ?loc=range) ] - // | Fable.ForLoop (var, TransformExpr com ctx (start, stmts), TransformExpr com ctx (limit, stmts'), body, isUp, range) -> - // let op1, op2 = - // if isUp - // then BinaryOperator.BinaryLessOrEqual, UpdateOperator.UpdatePlus - // else BinaryOperator.BinaryGreaterOrEqual, UpdateOperator.UpdateMinus - - // let a = start |> varDeclaration (typedIdent com ctx var |> Pattern.Identifier) true + | Fable.ForLoop (var, TransformExpr com ctx (start, stmts), TransformExpr com ctx (limit, stmts'), body, isUp, range) -> + let limit, step = + if isUp + then + let limit = Expression.binOp (limit, Add, Expression.constant (1)) // Python `range` has exclusive end. + limit, 1 + else + limit, -1 - // [ Statement.for'( - // transformBlock com ctx None body, - // start |> varDeclaration (Expression.identifier(ident var)) true, - // Expression.binOp(identAsExpr var, op1, limit), - // Expression.updateExpression(op2, false, identAsExpr var), ?loc=range) ] + let step = Expression.constant(step) + let iter = Expression.call (Expression.name (Python.Identifier "range"), args = [ start; limit; step ]) + let body = transformBlock com ctx None body + let target = com.GetIdentifierAsExpr(ctx, var.Name) - | _ -> failwith $"transformAsStatements: Expression {expr} not supported." + [ Statement.for'(target = target, iter = iter, body = body) ] let transformFunction com ctx name (args: Fable.Ident list) (body: Fable.Expr): Arg list * Statement list = let tailcallChance = diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 7fe0c61b50..05c018a4d6 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -909,6 +909,21 @@ module PythonExtensions = Ops = ops Loc = loc } |> Compare + + static member compare(left, op, comparators, ?loc) : Expression = + let op = + match op with + | BinaryEqual + | BinaryEqualStrict -> Eq + | BinaryUnequal + | BinaryUnequalStrict -> NotEq + | BinaryLess -> Lt + | BinaryLessOrEqual -> LtE + | BinaryGreater -> Gt + | BinaryGreaterOrEqual -> GtE + | _ -> failwith $"compare: Operator {op} not supported" + Expression.compare(left, [op], comparators) + static member none() = Expression.name (Identifier(name="None")) @@ -928,7 +943,7 @@ module PythonExtensions = // | UnaryTypeof -> "typeof" // | UnaryVoid -> // | UnaryDelete -> "delete" - | _ -> failwith $"Operator {op} not supported" + | _ -> failwith $"unaryOp: Operator {op} not supported" Expression.unaryOp(op, operand, ?loc=loc) @@ -967,7 +982,7 @@ module PythonExtensions = | BinaryShiftRightZeroFill -> RShift | BinaryShiftRightSignPropagating -> RShift | BinaryXorBitwise -> BitXor - | _ -> failwith $"Operator {op} not supported" + | _ -> failwith $"binOp: Operator {op} not supported" Expression.binOp(left, op, right, ?loc=loc) From 0812d222d581ea345c81eb6e301fd1dce6214467 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 19 Jun 2021 19:05:11 +0200 Subject: [PATCH 107/145] Clean identifiers - Don't print empty nonlocals - Use `Is` for strict equal --- src/Fable.Transforms/Python/Fable2Python.fs | 161 ++++++++++++------- src/Fable.Transforms/Python/Python.fs | 8 +- src/Fable.Transforms/Python/PythonPrinter.fs | 5 +- 3 files changed, 110 insertions(+), 64 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 1766eff472..837cfafa08 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -404,6 +404,7 @@ module Helpers = | "toString" -> "str" | "len" -> "len_" | "Map" -> "dict" + | "Int32Array" -> "list" | _ -> name.Replace('$', '_').Replace('.', '_').Replace('`', '_') @@ -572,25 +573,25 @@ module Util = let ofString (s: string) = Expression.constant(s) - let memberFromName (memberName: string): Expression * Statement list = + let memberFromName (com: IPythonCompiler) (ctx: Context) (memberName: string): Expression * Statement list = match memberName with | "ToString" -> Expression.identifier("toString"), [] | n when n.StartsWith("Symbol.iterator") -> let name = Identifier "__iter__" Expression.name(name), [] | n when Naming.hasIdentForbiddenChars n -> Expression.constant(n), [] - | n -> Expression.identifier(n), [] + | n -> com.GetIdentifierAsExpr(ctx, n), [] - let get r left memberName = - let expr = Identifier memberName + let get (com: IPythonCompiler) ctx r left memberName = + let expr = com.GetIdentifier(ctx, memberName) Expression.attribute (value = left, attr = expr, ctx = Load) - let getExpr r (object: Expression) (expr: Expression) = - printfn "getExpr: %A" (object, expr) + let getExpr com ctx r (object: Expression) (expr: Expression) = + // printfn "getExpr: %A" (object, expr) match expr with | Expression.Constant(value=value) -> match value with - | :? string as str -> memberFromName str + | :? string as str -> memberFromName com ctx str | :? int | :? float -> Expression.subscript(value = object, slice = expr, ctx = Load), [] | _ -> failwith $"Value {value} needs to be string" @@ -600,10 +601,10 @@ module Util = let func = Expression.name("getattr") Expression.call(func=func, args=[object; e]), [] - let rec getParts (parts: string list) (expr: Expression) = + let rec getParts com ctx (parts: string list) (expr: Expression) = match parts with | [] -> expr - | m::ms -> get None expr m |> getParts ms + | m::ms -> get com ctx None expr m |> getParts com ctx ms let makeArray (com: IPythonCompiler) ctx exprs = let expr, stmts = exprs |> List.map (fun e -> com.TransformAsExpr(ctx, e)) |> Helpers.unzipArgs @@ -614,9 +615,9 @@ module Util = |> List.map (fun x -> Expression.constant(x)) |> Expression.list - let makeJsObject (pairs: seq) = + let makeJsObject com ctx (pairs: seq) = pairs |> Seq.map (fun (name, value) -> - let prop, computed = memberFromName name + let prop, computed = memberFromName com ctx name prop, value) |> Seq.toList |> List.unzip @@ -673,12 +674,21 @@ module Util = let callFunction r funcExpr (args: Expression list) = Expression.call(funcExpr, args, ?loc=r) - let callFunctionWithThisContext r funcExpr (args: Expression list) = + let callFunctionWithThisContext com ctx r funcExpr (args: Expression list) = let args = thisExpr::args - Expression.call(get None funcExpr "call", args, ?loc=r) + Expression.call(get com ctx None funcExpr "call", args, ?loc=r) let emitExpression range (txt: string) args = - Expression.emit (txt, args, ?loc=range) + let value = + match txt with + | "$0.join('')" -> + "''.join($0)" + | "throw $0" -> + "raise $0" + | Naming.StartsWith("void ") value + | Naming.StartsWith("new ") value -> value + | _ -> txt + Expression.emit (value, args, ?loc=range) let undefined range: Expression = Expression.name(identifier = Identifier("None"), ?loc=range) @@ -739,7 +749,7 @@ module Util = let getUnionExprTag (com: IPythonCompiler) ctx r (fableExpr: Fable.Expr) = let expr, stmts = com.TransformAsExpr(ctx, fableExpr) - let expr, stmts' = getExpr r expr (Expression.constant("tag")) + let expr, stmts' = getExpr com ctx r expr (Expression.constant("tag")) expr, stmts @ stmts' let wrapExprInBlockWithReturn (e, stmts) = @@ -755,9 +765,9 @@ module Util = let body = wrapExprInBlockWithReturn (body, []) FunctionDef.Create(name = name, args = Arguments.arguments args, body = body) - let makeFunctionExpression name (args, (body: Expression)) : Expression * Statement list= + let makeFunctionExpression (com: IPythonCompiler) ctx name (args, (body: Expression)) : Expression * Statement list= let name = - name |> Option.map Identifier + name |> Option.map (fun name -> com.GetIdentifier(ctx, name)) |> Option.defaultValue (Helpers.getUniqueIdentifier "lifted") let func = makeFunction name (args, body) Expression.name(name), [ func ] @@ -782,14 +792,14 @@ module Util = [ // First declare temp variables for (KeyValue(argId, tempVar)) in tempVars do - yield! varDeclaration ctx (Expression.identifier(tempVar)) false (Expression.identifier(argId)) + yield! varDeclaration ctx (com.GetIdentifierAsExpr(ctx, tempVar)) false (com.GetIdentifierAsExpr(ctx, argId)) // Then assign argument expressions to the original argument identifiers // See https://github.com/fable-compiler/Fable/issues/1368#issuecomment-434142713 for (argId, arg) in zippedArgs do let arg = FableTransforms.replaceValues tempVarReplacements arg let arg, stmts = com.TransformAsExpr(ctx, arg) yield! stmts - yield assign None (Expression.identifier(argId)) arg |> Statement.expr + yield assign None (com.GetIdentifierAsExpr(ctx, argId)) arg |> exprAsStatement yield Statement.continue'(?loc=range) ] @@ -798,7 +808,7 @@ module Util = let parts = Array.toList(name.Split('.')) parts.Head, parts.Tail com.GetImportExpr(ctx, moduleName, name) - |> getParts parts + |> getParts com ctx parts let transformCast (com: IPythonCompiler) (ctx: Context) t tag e: Expression * Statement list = // HACK: Try to optimize some patterns after FableTransforms @@ -888,7 +898,7 @@ module Util = Expression.call(consRef, values, ?loc=r), stmts @ stmts' | Fable.NewAnonymousRecord(values, fieldNames, _genArgs) -> let values, stmts = values |> List.map (fun x -> com.TransformAsExpr(ctx, x)) |> Helpers.unzipArgs - List.zip (List.ofArray fieldNames) values |> makeJsObject, stmts + List.zip (List.ofArray fieldNames) values |> makeJsObject com ctx , stmts | Fable.NewUnion(values, tag, ent, genArgs) -> let ent = com.GetEntity(ent) let values, stmts = List.map (fun x -> com.TransformAsExpr(ctx, x)) values |> Helpers.unzipArgs @@ -899,7 +909,7 @@ module Util = | _ -> failwith $"transformValue: value {value} not supported!" let enumerator2iterator com ctx = - let enumerator = Expression.call(get None (Expression.identifier("self")) "GetEnumerator", []) + let enumerator = Expression.call(get com ctx None (Expression.identifier("self")) "GetEnumerator", []) [ Statement.return'(libCall com ctx None "Util" "toIterator" [ enumerator ]) ] let extractBaseExprFromBaseCall (com: IPythonCompiler) (ctx: Context) (baseType: Fable.DeclaredType option) baseCall = @@ -907,7 +917,7 @@ module Util = | Some (Fable.Call(baseRef, info, _, _)), _ -> let baseExpr, stmts = match baseRef with - | Fable.IdentExpr id -> Expression.identifier id.Name, [] + | Fable.IdentExpr id -> com.GetIdentifierAsExpr(ctx, id.Name), [] | _ -> transformAsExpr com ctx baseRef let args = transformCallArgs com ctx info.HasSpread info.Args Some (baseExpr, args) @@ -945,7 +955,7 @@ module Util = let members = members |> List.collect (fun memb -> let info = memb.Info - let prop, computed = memberFromName memb.Name + let prop, computed = memberFromName com ctx memb.Name // If compileAsClass is false, it means getters don't have side effects // and can be compiled as object fields (see condition above) if info.IsValue || (not compileAsClass && info.IsGetter) then @@ -1017,13 +1027,13 @@ module Util = | args -> List.map (fun e -> com.TransformAsExpr(ctx, e)) args |> Helpers.unzipArgs let resolveExpr t strategy pyExpr: Statement = - //printfn "resolveExpr: %A" pyExpr + printfn "resolveExpr: %A" pyExpr match strategy with - | None | Some ReturnUnit -> Statement.expr(pyExpr) + | None | Some ReturnUnit -> exprAsStatement(pyExpr) // TODO: Where to put these int wrappings? Add them also for function arguments? | Some Return -> Statement.return'(pyExpr) - | Some(Assign left) -> Statement.expr(assign None left pyExpr) - | Some(Target left) -> Statement.expr(assign None (left |> Expression.identifier) pyExpr) + | Some(Assign left) -> exprAsStatement(assign None left pyExpr) + | Some(Target left) -> exprAsStatement(assign None (left |> Expression.identifier) pyExpr) let transformOperation com ctx range opKind: Expression * Statement list = match opKind with @@ -1039,10 +1049,36 @@ module Util = | Fable.Binary(op, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> match op with - | BinaryEqual - | BinaryEqualStrict // FIXME: should use ´is` for objects - | BinaryUnequal - | BinaryUnequalStrict // FIXME: should use ´is not` for objects + | BinaryEqualStrict -> + match right with + | Expression.Constant(_) -> + let op = BinaryEqual + Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' + | _ -> + Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' + | BinaryUnequalStrict -> + match right with + | Expression.Constant(_) -> + let op = BinaryUnequal + Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' + | _ -> + Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' + | BinaryEqual -> + match right with + | Expression.Name({Id=Identifier("None")}) -> + let op = BinaryEqualStrict + Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' + | _ -> + Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' + | BinaryUnequal -> + printfn "Right: %A" right + match right with + | Expression.Name({Id=Identifier("None")}) -> + let op = BinaryUnequalStrict + Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' + | _ -> + Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' + | BinaryLess | BinaryLessOrEqual | BinaryGreater @@ -1144,7 +1180,7 @@ module Util = match kind with | Fable.ExprGet(TransformExpr com ctx (prop, stmts)) -> let expr, stmts' = com.TransformAsExpr(ctx, fableExpr) - let expr, stmts'' = getExpr range expr prop + let expr, stmts'' = getExpr com ctx range expr prop expr, stmts @ stmts' @ stmts'' | Fable.FieldGet(fieldName,_) -> @@ -1155,7 +1191,7 @@ module Util = | Fable.Value(Fable.BaseValue(_,t), r) -> Fable.Value(Fable.BaseValue(None, t), r) | _ -> fableExpr let expr, stmts = com.TransformAsExpr(ctx, fableExpr) - get range expr fieldName, stmts + get com ctx range expr fieldName, stmts | Fable.ListHead -> // get range (com.TransformAsExpr(ctx, fableExpr)) "head" @@ -1173,7 +1209,7 @@ module Util = | Fable.Value(Fable.NewTuple exprs, _) -> com.TransformAsExpr(ctx, List.item index exprs) | TransformExpr com ctx (expr, stmts) -> - let expr, stmts' = getExpr range expr (ofInt index) + let expr, stmts' = getExpr com ctx range expr (ofInt index) expr, stmts @ stmts' | Fable.OptionValue -> @@ -1188,21 +1224,22 @@ module Util = | Fable.UnionField(index, _) -> let expr, stmts = com.TransformAsExpr(ctx, fableExpr) - let expr, stmts' = getExpr None expr (Expression.constant("fields")) - let expr, stmts'' = getExpr range expr (ofInt index) + let expr, stmts' = getExpr com ctx None expr (Expression.constant("fields")) + let expr, stmts'' = getExpr com ctx range expr (ofInt index) expr, stmts @ stmts' @ stmts'' let transformSet (com: IPythonCompiler) ctx range fableExpr (value: Fable.Expr) kind = + printfn "transformSet: %A" (fableExpr, value) let expr, stmts = com.TransformAsExpr(ctx, fableExpr) let value, stmts' = com.TransformAsExpr(ctx, value) let ret, stmts'' = match kind with | Fable.ValueSet -> expr, stmts @ stmts' | Fable.ExprSet(TransformExpr com ctx (e, stmts'')) -> - let expr, stmts''' = getExpr None expr e + let expr, stmts''' = getExpr com ctx None expr e expr, stmts @ stmts' @ stmts'' @ stmts''' | Fable.FieldSet(fieldName, _) -> - get None expr fieldName, stmts @ stmts' + get com ctx None expr fieldName, stmts @ stmts' assign range ret value, stmts'' let transformBindingExprBody (com: IPythonCompiler) (ctx: Context) (var: Fable.Ident) (value: Fable.Expr) = @@ -1228,7 +1265,7 @@ module Util = List.append [ decl ] body else let value, stmts = transformBindingExprBody com ctx var value - let varName = Expression.name(var.Name) + let varName = com.GetIdentifierAsExpr(ctx, var.Name) // Expression.name(var.Name) let decl = varDeclaration ctx varName var.IsMutable value stmts @ decl @@ -1237,7 +1274,7 @@ module Util = | Fable.TypeTest t -> transformTypeTest com ctx range expr t | Fable.OptionTest nonEmpty -> - let op = if nonEmpty then BinaryUnequal else BinaryEqual + let op = if nonEmpty then BinaryUnequalStrict else BinaryEqualStrict let expr, stmts = com.TransformAsExpr(ctx, expr) Expression.compare(expr, op, [Expression.none()], ?loc=range), stmts | Fable.ListTest nonEmpty -> @@ -1308,6 +1345,12 @@ module Util = let target = List.rev bindings |> List.fold (fun e (i,v) -> Fable.Let(i,v,e)) target com.TransformAsExpr(ctx, target) + let exprAsStatement (expr: Expression) : Statement = + match expr with + | NamedExpr({Target=target; Value=value; Loc=loc }) -> + Statement.assign([target], value) + | _ -> Statement.expr(expr) + let transformDecisionTreeSuccessAsStatements (com: IPythonCompiler) (ctx: Context) returnStrategy targetIndex boundValues: Statement list = match returnStrategy with | Some(Target targetId) as target -> @@ -1315,9 +1358,9 @@ module Util = let assignments = matchTargetIdentAndValues idents boundValues |> List.collect (fun (id, TransformExpr com ctx (value, stmts)) -> - let stmt = assign None (identAsExpr com ctx id) value |> Statement.expr + let stmt = assign None (identAsExpr com ctx id) value |> exprAsStatement stmts @ [ stmt ]) - let targetAssignment = assign None (targetId |> Expression.name) (ofInt targetIndex) |> Statement.expr + let targetAssignment = assign None (targetId |> Expression.name) (ofInt targetIndex) |> exprAsStatement [ targetAssignment ] @ assignments | ret -> let bindings, target = getDecisionTargetAndBindValues com ctx targetIndex boundValues @@ -1489,7 +1532,7 @@ module Util = if i = exprs.Length - 1 then [ Statement.return' (expr)] else - [ Statement.expr(expr) ]) + [ exprAsStatement expr ]) |> List.collect id //|> transformBody ReturnStrategy.Return @@ -1555,7 +1598,7 @@ module Util = transformSet com ctx range expr value kind | Fable.Let(ident, value, body) -> - printfn "Fable.Let: %A" (ident, value, body) + // printfn "Fable.Let: %A" (ident, value, body) if ctx.HoistVars [ident] then let assignment, stmts = transformBindingAsExpr com ctx ident value let expr, stmts' = com.TransformAsExpr(ctx, body) @@ -1578,7 +1621,7 @@ module Util = | Fable.Sequential exprs -> let exprs, stmts = List.map (fun e -> com.TransformAsExpr(ctx, e)) exprs |> List.unzip - printfn "Sequential: %A" (exprs, stmts) + // printfn "Sequential: %A" (exprs, stmts) Expression.none(), stmts |> List.collect id | Fable.Emit(info, _, range) -> @@ -1751,7 +1794,7 @@ module Util = |> List.map (fun (id, tcArg) -> id) let varDecls = List.zip args tc.Args - |> List.map (fun (id, tcArg) -> ident com ctx id, Some (Expression.identifier(tcArg))) + |> List.map (fun (id, tcArg) -> ident com ctx id, Some (com.GetIdentifierAsExpr(ctx, tcArg))) |> multiVarDeclaration ctx let body = varDecls @ body @@ -1765,6 +1808,7 @@ module Util = else let varDeclStatement = multiVarDeclaration ctx [for v in declaredVars -> ident com ctx v, None] varDeclStatement @ body + printfn "Args: %A" (args, body) args |> List.map (ident com ctx >> Arg.arg), body let declareEntryPoint _com _ctx (funcExpr: Expression) = @@ -1838,7 +1882,7 @@ module Util = else ent.FSharpFields |> Seq.map (fun field -> - let prop, computed = memberFromName field.Name + let prop, computed = memberFromName com ctx field.Name prop) |> Seq.toArray @@ -1865,7 +1909,7 @@ module Util = let generics = genArgs |> Array.mapToList (identAsExpr com ctx) let body, stmts = transformReflectionInfo com ctx None ent generics let args = genArgs |> Array.mapToList (ident com ctx >> Arg.arg) - let expr, stmts' = makeFunctionExpression None (args, body) + let expr, stmts' = makeFunctionExpression com ctx None (args, body) let name = com.GetIdentifier(ctx, entName + Naming.reflectionSuffix) expr |> declareModuleMember ctx ent.IsPublic name false, stmts @ stmts' stmts @ [typeDeclaration ] @ reflectionDeclaration @@ -1901,17 +1945,17 @@ module Util = //let kind = if memb.Info.IsGetter then ClassGetter else ClassSetter let args, body = getMemberArgsAndBody com ctx (Attached isStatic) false memb.Args memb.Body - let key, computed = memberFromName memb.Name + let key, computed = memberFromName com ctx memb.Name let self = Arg.arg("self") let arguments = Arguments.arguments (self::args) - FunctionDef.Create(Identifier memb.Name, arguments, body = body) + FunctionDef.Create(com.GetIdentifier(ctx, memb.Name), arguments, body = body) |> List.singleton let transformAttachedMethod (com: IPythonCompiler) ctx (memb: Fable.MemberDecl) = let isStatic = not memb.Info.IsInstance let makeMethod name args body = - let key, computed = memberFromName name - FunctionDef.Create(Identifier name, args, body = body) + let key, computed = memberFromName com ctx name + FunctionDef.Create(com.GetIdentifier(ctx, name), args, body = body) let args, body = getMemberArgsAndBody com ctx (Attached isStatic) memb.Info.HasSpread memb.Args memb.Body let self = Arg.arg("self") @@ -1931,7 +1975,7 @@ module Util = [ yield callSuperAsStatement [] yield! fieldIds |> Array.map (fun id -> - let left = get None thisExpr id.Name + let left = get com ctx None thisExpr id.Name let right = match id.Type with | Fable.Number _ -> @@ -1970,9 +2014,9 @@ module Util = if Option.isSome baseExpr then yield callSuperAsStatement [] yield! ent.FSharpFields |> Seq.mapi (fun i field -> - let left = get None thisExpr field.Name + let left = get com ctx None thisExpr field.Name let right = args.[i] - assign None left right |> Statement.expr) + assign None left right |> exprAsStatement) |> Seq.toArray ] let args = fieldIds |> Array.mapToList (ident com ctx >> Arg.arg) @@ -2009,6 +2053,7 @@ module Util = ] let rec transformDeclaration (com: IPythonCompiler) ctx decl = + printfn "transformDeclaration: %A" decl let withCurrentScope ctx (usedNames: Set) f = let ctx = { ctx with UsedNames = { ctx.UsedNames with CurrentDeclarationScope = HashSet usedNames } } let result = f ctx @@ -2039,7 +2084,7 @@ module Util = decls | Fable.ClassDeclaration decl -> - printfn "Class: %A" decl + // printfn "Class: %A" decl let ent = decl.Entity let classMembers = @@ -2063,7 +2108,7 @@ module Util = let transformImports (imports: Import seq) : Statement list = let statefulImports = ResizeArray() - printfn "Imports: %A" imports + // printfn "Imports: %A" imports [ for import in imports do diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index 05c018a4d6..c160878e65 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -913,10 +913,10 @@ module PythonExtensions = static member compare(left, op, comparators, ?loc) : Expression = let op = match op with - | BinaryEqual - | BinaryEqualStrict -> Eq - | BinaryUnequal - | BinaryUnequalStrict -> NotEq + | BinaryEqual -> Eq + | BinaryEqualStrict -> Is + | BinaryUnequal -> NotEq + | BinaryUnequalStrict -> IsNot | BinaryLess -> Lt | BinaryLessOrEqual -> LtE | BinaryGreater -> Gt diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index e57b4feb5a..ce185163d2 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -253,8 +253,9 @@ module PrinterExtensions = member printer.Print(gl: Global) = printer.Print("(Global)") member printer.Print(nl: NonLocal) = - printer.Print("nonlocal ") - printer.PrintCommaSeparatedList nl.Names + if List.length nl.Names > 0 then + printer.Print("nonlocal ") + printer.PrintCommaSeparatedList nl.Names member printer.Print(af: AsyncFunctionDef) = printer.Print("(AsyncFunctionDef)") From 586f5384414e96a70a285e365c54cf58a9ef41b8 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 20 Jun 2021 09:00:40 +0200 Subject: [PATCH 108/145] Fix errors with empty body and array create --- src/Fable.Transforms/Python/Fable2Python.fs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 837cfafa08..f19c7a364c 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -669,6 +669,11 @@ module Util = let name = Identifier("__init__") let self = Arg.arg("self") let args = Arguments.arguments (args = self::args) + let body = + match body with + | [] -> [ Pass ] + | _ -> body + FunctionDef.Create(name, args, body = body) let callFunction r funcExpr (args: Expression list) = @@ -859,7 +864,8 @@ module Util = | Fable.NewArray (values, typ) -> makeArray com ctx values | Fable.NewArrayFrom (size, typ) -> let array, stmts = makeArray com ctx [] - Expression.binOp(array, Mult, Expression.constant(size)), stmts + let size, stmts' = com.TransformAsExpr(ctx, size) + Expression.binOp(array, Mult, size), stmts @ stmts' | Fable.NewTuple vals -> makeArray com ctx vals // Optimization for bundle size: compile list literals as List.ofArray | Fable.NewList (headAndTail, _) -> @@ -1143,7 +1149,11 @@ module Util = // When expecting a block, it's usually not necessary to wrap it // in a lambda to isolate its variable context let transformBlock (com: IPythonCompiler) ctx ret expr: Statement list = - com.TransformAsStatements(ctx, ret, expr) |> List.choose Helpers.isProductiveStatement + let block = + com.TransformAsStatements(ctx, ret, expr) |> List.choose Helpers.isProductiveStatement + match block with + | [] -> [ Pass ] + | _ -> block let transformTryCatch com ctx r returnStrategy (body, (catch: option), finalizer) = // try .. catch statements cannot be tail call optimized From 5eadd5fc708cab633aa72362e857b2d7b438f0b5 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 20 Jun 2021 10:41:25 +0200 Subject: [PATCH 109/145] Fix import identifier cleaning --- src/Fable.Transforms/Python/Fable2Python.fs | 3 ++- src/Fable.Transforms/Python/PythonPrinter.fs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index f19c7a364c..fb5d28bcb5 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -2127,7 +2127,7 @@ module Util = let moduleName = moduleName |> Helpers.rewriteFableImport match name with | Some name -> - let alias = Alias.alias(Identifier(name), ?asname=None) + let alias = Alias.alias(Identifier(Helpers.clean name), ?asname=local) Statement.importFrom (Some(Identifier(moduleName)), [ alias ]) | None -> let alias = Alias.alias(Identifier(moduleName), ?asname=None) @@ -2144,6 +2144,7 @@ module Util = match name with | "*" | _ -> name + |> Helpers.clean |> Python.Identifier |> Some diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index ce185163d2..f6ca1aa925 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -505,7 +505,7 @@ module PrinterExtensions = printer.Print(node.Name) match node.AsName with - | Some (Identifier alias) -> + | Some (Identifier alias) when Identifier alias <> node.Name-> printer.Print(" as ") printer.Print(alias) | _ -> () From 956968fb15b354b79bc9fbfd0c3998f583274f20 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 20 Jun 2021 13:24:03 +0200 Subject: [PATCH 110/145] Transform x.toString() into str(x) --- src/Fable.Transforms/Python/Fable2Python.fs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index fb5d28bcb5..57b2f849a9 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -1581,6 +1581,11 @@ module Util = | Fable.ObjectExpr (members, _, baseCall) -> transformObjectExpr com ctx members baseCall + | Fable.Call(Fable.Get(expr, Fable.FieldGet(fieldName="toString"), _, _), info, _, range) -> + let func = Expression.name("str") + let left, stmts = com.TransformAsExpr(ctx, expr) + Expression.call (func, [ left ]), stmts + | Fable.Call(callee, info, _, range) -> transformCall com ctx range callee info From 484608acd0c50aa1798f2e23a18df473818a575c Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 20 Jun 2021 14:38:42 +0200 Subject: [PATCH 111/145] Sort nonlocals --- src/Fable.Transforms/Python/Fable2Python.fs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 57b2f849a9..d8540a8aa2 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -1153,7 +1153,10 @@ module Util = com.TransformAsStatements(ctx, ret, expr) |> List.choose Helpers.isProductiveStatement match block with | [] -> [ Pass ] - | _ -> block + | _ -> + block + |> List.sortBy (function | Statement.NonLocal _ -> 0 | _ -> 1) + let transformTryCatch com ctx r returnStrategy (body, (catch: option), finalizer) = // try .. catch statements cannot be tail call optimized From e73fa6def2743ca2573795b7ab3b3b838c726407 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 20 Jun 2021 15:01:15 +0200 Subject: [PATCH 112/145] Fix emit expressions --- src/Fable.Transforms/Python/Fable2Python.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index d8540a8aa2..75f3c851ed 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -1105,7 +1105,7 @@ module Util = let args = exprs |> List.append thisArg - Expression.emit(macro, args, ?loc=range), stmts @ stmts' + emitExpression range macro args, stmts @ stmts' let transformCall (com: IPythonCompiler) ctx range callee (callInfo: Fable.CallInfo) : Expression * Statement list = let callee, stmts = com.TransformAsExpr(ctx, callee) From 0115f6d8a027ec2451daf075a2540ce7ee8e4621 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 20 Jun 2021 16:28:17 +0200 Subject: [PATCH 113/145] Propagate nonlocals out of if and else blocks --- src/Fable.Transforms/Python/Fable2Python.fs | 57 ++++++++++++--------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 75f3c851ed..37f7ee27e6 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -588,18 +588,19 @@ module Util = let getExpr com ctx r (object: Expression) (expr: Expression) = // printfn "getExpr: %A" (object, expr) - match expr with - | Expression.Constant(value=value) -> - match value with - | :? string as str -> memberFromName com ctx str - | :? int - | :? float -> Expression.subscript(value = object, slice = expr, ctx = Load), [] - | _ -> failwith $"Value {value} needs to be string" - | Expression.Name({Id=id}) -> - Expression.attribute (value = object, attr = id, ctx = Load), [] - | e -> - let func = Expression.name("getattr") - Expression.call(func=func, args=[object; e]), [] + // match expr with + // | Expression.Constant(value=value) -> + // match value with + // | :? string as str -> memberFromName com ctx str + // | :? int + // | :? float -> Expression.subscript(value = object, slice = expr, ctx = Load), [] + // | _ -> failwith $"Value {value} needs to be string" + // | Expression.Name({Id=id}) -> + // Expression.attribute (value = object, attr = id, ctx = Load), [] + // | e -> + // let func = Expression.name("getattr") + // Expression.call(func=func, args=[object; e]), [] + Expression.subscript(value = object, slice = expr, ctx = Load), [] let rec getParts com ctx (parts: string list) (expr: Expression) = match parts with @@ -1167,10 +1168,16 @@ module Util = let exn = Expression.identifier("Exception") |> Some let identifier = ident com ctx param [ ExceptHandler.exceptHandler (``type`` = exn, name = identifier, body = body) ]) - let finalizer = - finalizer |> Option.map (transformBlock com ctx None) + let finalizer, stmts = + match finalizer with + | Some finalizer -> + finalizer |> + transformBlock com ctx None + |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) + | None -> [], [] + [ Statement.try'(transformBlock com ctx returnStrategy body, - ?handlers=handlers, ?finalBody=finalizer, ?loc=r) ] + ?handlers=handlers, finalBody=finalizer, ?loc=r) ] let rec transformIfStatement (com: IPythonCompiler) ctx r ret guardExpr thenStmnt elseStmnt = let expr, stmts = com.TransformAsExpr(ctx, guardExpr) @@ -1180,14 +1187,18 @@ module Util = | :? bool as value when value -> stmts @ com.TransformAsStatements(ctx, ret, thenStmnt) | _ -> stmts @ com.TransformAsStatements(ctx, ret, elseStmnt) | guardExpr -> - let thenStmnt = transformBlock com ctx ret thenStmnt - let ifStatement = - match com.TransformAsStatements(ctx, ret, elseStmnt) with - | [ ] -> Statement.if'(guardExpr, thenStmnt, ?loc=r) - | [ elseStmnt ] -> Statement.if'(guardExpr, thenStmnt, [ elseStmnt ], ?loc=r) - | statements -> Statement.if'(guardExpr, thenStmnt, statements, ?loc=r) - |> List.singleton - stmts @ ifStatement + let thenStmnt, stmts' = + transformBlock com ctx ret thenStmnt + |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) + let ifStatement, stmts'' = + let block, stmts = + com.TransformAsStatements(ctx, ret, elseStmnt) + |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) + match block with + | [ ] -> Statement.if'(guardExpr, thenStmnt, ?loc=r), stmts + | [ elseStmnt ] -> Statement.if'(guardExpr, thenStmnt, [ elseStmnt ], ?loc=r), stmts + | statements -> Statement.if'(guardExpr, thenStmnt, statements, ?loc=r), stmts + stmts @ stmts' @ stmts'' @ [ ifStatement ] let transformGet (com: IPythonCompiler) ctx range typ fableExpr kind = match kind with From 705d6e37119e98233ba59a173f12d74466ffd613 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 20 Jun 2021 22:02:41 +0200 Subject: [PATCH 114/145] Enable Fable2Python instead of Babel2Python --- build.fsx | 2 + src/Fable.Cli/Main.fs | 15 ++- src/Fable.Core/Fable.Core.JsInterop.fs | 4 + .../fable/Fable.Library.fsproj | 11 +- src/fable-library-py/fable/Native.fs | 2 +- src/fable-library-py/fable/asyncbuilder.py | 66 ---------- src/fable-library-py/fable/list.py | 39 ------ src/fable-library-py/fable/reflection.py | 2 + src/fable-library-py/fable/seq.py | 60 --------- src/fable-library/Array.fs | 116 +----------------- src/fable-library/Fable.Library.fsproj | 1 + tests/Python/TestOption.fs | 16 +-- tests/Python/TestSeq.fs | 5 + 13 files changed, 44 insertions(+), 295 deletions(-) delete mode 100644 src/fable-library-py/fable/asyncbuilder.py delete mode 100644 src/fable-library-py/fable/list.py delete mode 100644 src/fable-library-py/fable/seq.py diff --git a/build.fsx b/build.fsx index 13b60be1fd..cf2622f097 100644 --- a/build.fsx +++ b/build.fsx @@ -188,6 +188,8 @@ let buildLibraryPy() = copyDirNonRecursive (buildDirPy "fable/fable-library") (buildDirPy "fable") //copyFile (buildDirPy "fable/fable-library/*.py") (buildDirPy "fable") copyFile (buildDirPy "fable/system.text.py") (buildDirPy "fable/system_text.py") + copyFile (buildDirPy "fable/fsharp.core.py") (buildDirPy "fable/fsharp_core.py") + copyFile (buildDirPy "fable/fsharp.collections.py") (buildDirPy "fable/fsharp_collections.py") //copyFile (buildDirPy "fable/async.py") (buildDirPy "fable/async_.py") removeFile (buildDirPy "fable/system.text.py") diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index 66a1ce592b..6b6b3bd931 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -222,15 +222,22 @@ module private Util = PhpPrinter.Output.writeFile ctx php w.Flush() | Python -> - logger("Generating Python") - let babel = fable |> Fable2Babel.Compiler.transformFile com - let python = babel |> Babel2Python.Compiler.transformFile com - + logger("Generating Python") // From Fable AST + let python = fable |> Fable2Python.Compiler.transformFile com let map = { new PythonPrinter.SourceMapGenerator with member _.AddMapping(_,_,_,_,_) = () } let writer = new PythonFileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) do! PythonPrinter.run writer map python + // logger("Generating Python from Babel") + // let babel = fable |> Fable2Babel.Compiler.transformFile com + // let python = babel |> Babel2Python.Compiler.transformFile com + // let map = { new PythonPrinter.SourceMapGenerator with + // member _.AddMapping(_,_,_,_,_) = () } + // let outPath = outPath.Replace(".fs", "2.fs") + // let writer = new PythonFileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) + // do! PythonPrinter.run writer map python + logger("Compiled " + File.getRelativePathFromCwd com.CurrentFile) return Ok {| File = com.CurrentFile diff --git a/src/Fable.Core/Fable.Core.JsInterop.fs b/src/Fable.Core/Fable.Core.JsInterop.fs index 09410530b6..a60d3ad98f 100644 --- a/src/Fable.Core/Fable.Core.JsInterop.fs +++ b/src/Fable.Core/Fable.Core.JsInterop.fs @@ -70,6 +70,10 @@ let jsTypeof (x: obj): string = jsNative [] let jsInstanceof (x: obj) (cons: obj): bool = jsNative +/// Check if object is callable, i.e a function. +[] +let inline callable(x: obj) = jsNative + [] let jsThis<'T> : 'T = jsNative diff --git a/src/fable-library-py/fable/Fable.Library.fsproj b/src/fable-library-py/fable/Fable.Library.fsproj index 3235eb006d..32d25e681d 100644 --- a/src/fable-library-py/fable/Fable.Library.fsproj +++ b/src/fable-library-py/fable/Fable.Library.fsproj @@ -18,14 +18,15 @@ - - + + + - - - + + + diff --git a/src/fable-library-py/fable/Native.fs b/src/fable-library-py/fable/Native.fs index e84628a615..8f02cd1bd4 100644 --- a/src/fable-library-py/fable/Native.fs +++ b/src/fable-library-py/fable/Native.fs @@ -9,7 +9,7 @@ open Fable.Core.PyInterop open Fable.Import type Cons<'T> = - [] + [] abstract Allocate : len: int -> 'T [] module Helpers = diff --git a/src/fable-library-py/fable/asyncbuilder.py b/src/fable-library-py/fable/asyncbuilder.py deleted file mode 100644 index 38841e0b73..0000000000 --- a/src/fable-library-py/fable/asyncbuilder.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Any, Callable, Optional, TypeVar, Awaitable -from expression.core.aiotools import from_result - -T = TypeVar("T") -U = TypeVar("U") - - -class AsyncBuilder: - def Bind(self, computation: Awaitable[T], binder: Callable[[T], Awaitable[U]]) -> Awaitable[U]: - async def bind() -> U: - t = await computation - return await binder(t) - - return bind() - - def Combine(self, computation1: Awaitable[None], computation2: Awaitable[T]) -> Awaitable[T]: - return self.Bind(computation1, lambda _: computation2) - - def Delay(self, generator: Callable[[], Awaitable[T]]) -> Awaitable[T]: - async def deferred() -> T: - return await generator() - - return deferred() - - def Return(self, value: Optional[T] = None) -> Awaitable[Optional[T]]: - return from_result(value) - - def ReturnFrom(self, computation: Awaitable[T]) -> Awaitable[T]: - return computation - - def TryFinally(self, computation: Awaitable[T], compensation: Callable[[], None]) -> Awaitable[T]: - async def try_finally() -> T: - try: - t = await computation - finally: - compensation() - return t - - return try_finally() - - def TryWith(self, computation: Awaitable[T], catchHandler: Callable[[Any], Awaitable[T]]) -> Awaitable[T]: - async def try_with() -> T: - try: - t = await computation - except Exception as exn: - t = await catchHandler(exn) - return t - - return try_with() - - def Using(self, resource: T, binder: Callable[[T], Awaitable[U]]) -> Awaitable[U]: - return self.TryFinally(binder(resource), lambda: resource.Dispose()) - - def While(self, guard: Callable[[], bool], computation: Awaitable[None]) -> Awaitable[None]: - if guard(): - return self.Bind(computation, lambda _: self.While(guard, computation)) - else: - return self.Return() - - def Zero(self) -> Awaitable[None]: - return from_result(None) - - -singleton = AsyncBuilder() - -__all__ = ["singleton"] diff --git a/src/fable-library-py/fable/list.py b/src/fable-library-py/fable/list.py deleted file mode 100644 index 8cdf4dbb64..0000000000 --- a/src/fable-library-py/fable/list.py +++ /dev/null @@ -1,39 +0,0 @@ -# flake8: noqa - -from typing import Any, Callable, TypeVar - -from expression.collections import FrozenList - -A = TypeVar("A") -B = TypeVar("B") - - -def collect(mapper: Callable[[A], FrozenList[B]], lst: FrozenList[A]) -> FrozenList[B]: - return lst.collect(mapper) - - -def empty() -> FrozenList[Any]: - return FrozenList.empty() - - -def filter(predicate: Callable[[A], bool], lst: FrozenList[A]) -> FrozenList[A]: - return lst.filter(predicate) - - -def forAll(predicate, source): - return source.forall(predicate) - - -def length(xs): - return len(xs) - - -def map(mapper: Callable[[A], B], lst: FrozenList[A]) -> FrozenList[B]: - return lst.map(mapper) - - -ofArray = FrozenList.of_seq -ofSeq = FrozenList.of_seq -singleton = FrozenList.singleton - -__all__ = ["collect", "empty", "forAll", "length", "map", "ofArray", "ofSeq", "singleton"] diff --git a/src/fable-library-py/fable/reflection.py b/src/fable-library-py/fable/reflection.py index 49f1e95869..2fe8347792 100644 --- a/src/fable-library-py/fable/reflection.py +++ b/src/fable-library-py/fable/reflection.py @@ -107,6 +107,8 @@ def tuple_type(*generics: TypeInfo) -> TypeInfo: def equals(t1: TypeInfo, t2: TypeInfo) -> bool: return t1 == t2 + + # if (t1.fullname === "") { // Anonymous records # return t2.fullname === "" # && equalArraysWith(getRecordElements(t1), diff --git a/src/fable-library-py/fable/seq.py b/src/fable-library-py/fable/seq.py deleted file mode 100644 index 3064d2b8ac..0000000000 --- a/src/fable-library-py/fable/seq.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import Callable, Iterable, TypeVar -from expression.collections import Seq, seq, frozenlist - -A = TypeVar("A") -B = TypeVar("B") - - -def map(mapper: Callable[[A], B], xs: Seq[A]) -> Seq[B]: - return Seq(xs).map(mapper) - - -def filter(predicate: Callable[[A], bool], xs: Seq[A]) -> Seq[A]: - return Seq(xs).filter(predicate) - - -def length(xs): - return Seq(xs).length() - - -def empty(): - return seq.empty - - -def collect(mapper: Callable[[A], Seq[B]], source: Seq[A]) -> Seq[B]: - return Seq(source).collect(mapper) - - -def skip(count: int, xs: Seq[A]) -> Seq[A]: - return Seq(xs).skip(count) - - -def sum(source: Iterable[A]) -> A: - return Seq(source).sum() - - -def sumBy(projection: Callable[[A], B], source: Iterable[A], _) -> B: - return Seq(source).sum_by(projection) - - -delay = seq.delay -head = seq.head -rangeNumber = seq.range -singleton = seq.singleton -append = seq.concat -ofList = seq.of_list -toList = frozenlist.of_seq -concat = seq.concat -tail = seq.tail - -__all__ = [ - "delay", - "empty", - "head", - "map", - "length", - "rangeNumber", - "singleton", - "skip", - "tail", -] diff --git a/src/fable-library/Array.fs b/src/fable-library/Array.fs index 31467ed6f0..1f34b6c422 100644 --- a/src/fable-library/Array.fs +++ b/src/fable-library/Array.fs @@ -5,119 +5,11 @@ module ArrayModule open System.Collections.Generic open Fable.Core -open Fable.Core.JsInterop +//open Fable.Core.JsInterop open Fable.Import -type Cons<'T> = - [] - abstract Allocate: len: int -> 'T[] - -module Helpers = - [] - let arrayFrom (xs: 'T seq): 'T[] = jsNative - - [] - let allocateArray (len: int): 'T[] = jsNative - - [] - let allocateArrayFrom (xs: 'T[]) (len: int): 'T[] = jsNative - - let allocateArrayFromCons (cons: Cons<'T>) (len: int): 'T[] = - if jsTypeof cons = "function" - then cons.Allocate(len) - else JS.Constructors.Array.Create(len) - - let inline isDynamicArrayImpl arr = - JS.Constructors.Array.isArray arr - - let inline isTypedArrayImpl arr = - JS.Constructors.ArrayBuffer.isView arr - - // let inline typedArraySetImpl (target: obj) (source: obj) (offset: int): unit = - // !!target?set(source, offset) - - [] - let inline concatImpl (array1: 'T[]) (arrays: 'T[] seq): 'T[] = - jsNative - - let inline fillImpl (array: 'T[]) (value: 'T) (start: int) (count: int): 'T[] = - !!array?fill(value, start, start + count) - - let inline foldImpl (folder: 'State -> 'T -> 'State) (state: 'State) (array: 'T[]): 'State = - !!array?reduce(System.Func<'State, 'T, 'State>(folder), state) - - let inline foldIndexedImpl (folder: 'State -> 'T -> int -> 'State) (state: 'State) (array: 'T[]): 'State = - !!array?reduce(System.Func<'State, 'T, int, 'State>(folder), state) - - let inline foldBackImpl (folder: 'State -> 'T -> 'State) (state: 'State) (array: 'T[]): 'State = - !!array?reduceRight(System.Func<'State, 'T, 'State>(folder), state) - - let inline foldBackIndexedImpl (folder: 'State -> 'T -> int -> 'State) (state: 'State) (array: 'T[]): 'State = - !!array?reduceRight(System.Func<'State, 'T, int, 'State>(folder), state) - - // Typed arrays not supported, only dynamic ones do - let inline pushImpl (array: 'T[]) (item: 'T): int = - !!array?push(item) - - // Typed arrays not supported, only dynamic ones do - let inline insertImpl (array: 'T[]) (index: int) (item: 'T): 'T[] = - !!array?splice(index, 0, item) - - // Typed arrays not supported, only dynamic ones do - let inline spliceImpl (array: 'T[]) (start: int) (deleteCount: int): 'T[] = - !!array?splice(start, deleteCount) - - let inline reverseImpl (array: 'T[]): 'T[] = - !!array?reverse() - - let inline copyImpl (array: 'T[]): 'T[] = - !!array?slice() - - let inline skipImpl (array: 'T[]) (count: int): 'T[] = - !!array?slice(count) - - let inline subArrayImpl (array: 'T[]) (start: int) (count: int): 'T[] = - !!array?slice(start, start + count) - - let inline indexOfImpl (array: 'T[]) (item: 'T) (start: int): int = - !!array?indexOf(item, start) - - let inline findImpl (predicate: 'T -> bool) (array: 'T[]): 'T option = - !!array?find(predicate) - - let inline findIndexImpl (predicate: 'T -> bool) (array: 'T[]): int = - !!array?findIndex(predicate) - - let inline collectImpl (mapping: 'T -> 'U[]) (array: 'T[]): 'U[] = - !!array?flatMap(mapping) - - let inline containsImpl (predicate: 'T -> bool) (array: 'T[]): bool = - !!array?filter(predicate) - - let inline existsImpl (predicate: 'T -> bool) (array: 'T[]): bool = - !!array?some(predicate) - - let inline forAllImpl (predicate: 'T -> bool) (array: 'T[]): bool = - !!array?every(predicate) - - let inline filterImpl (predicate: 'T -> bool) (array: 'T[]): 'T[] = - !!array?filter(predicate) - - let inline reduceImpl (reduction: 'T -> 'T -> 'T) (array: 'T[]): 'T = - !!array?reduce(reduction) - - let inline reduceBackImpl (reduction: 'T -> 'T -> 'T) (array: 'T[]): 'T = - !!array?reduceRight(reduction) - - // Inlining in combination with dynamic application may cause problems with uncurrying - // Using Emit keeps the argument signature - [] - let sortInPlaceWithImpl (comparer: 'T -> 'T -> int) (array: 'T[]): unit = jsNative //!!array?sort(comparer) - - [] - let copyToTypedArray (src: 'T[]) (srci: int) (trg: 'T[]) (trgi: int) (cnt: int): unit = jsNative - -open Helpers +open Native +open Native.Helpers let private indexNotFound() = failwith "An index satisfying the predicate was not found in the collection." @@ -539,7 +431,7 @@ let choose (chooser: 'T->'U option) (array: 'T[]) ([] cons: Cons<'U>) = match chooser array.[i] with | None -> () | Some y -> pushImpl res y |> ignore - if jsTypeof cons = "function" + if callable cons then map id res cons else res // avoid extra copy diff --git a/src/fable-library/Fable.Library.fsproj b/src/fable-library/Fable.Library.fsproj index 8c638e193e..3126638077 100644 --- a/src/fable-library/Fable.Library.fsproj +++ b/src/fable-library/Fable.Library.fsproj @@ -19,6 +19,7 @@ + diff --git a/tests/Python/TestOption.fs b/tests/Python/TestOption.fs index 7cb19c91c9..d69f823b38 100644 --- a/tests/Python/TestOption.fs +++ b/tests/Python/TestOption.fs @@ -55,14 +55,14 @@ let ``test Option.IsSome/IsNone works II`` () = o2.IsNone |> equal false o2.IsSome |> equal true -// [] -// let ``test Option.iter works`` () = -// let mutable res = false -// let getOnlyOnce = -// let mutable value = Some "Hello" -// fun () -> match value with Some x -> value <- None; Some x | None -> None -// getOnlyOnce() |> Option.iter (fun s -> if s = "Hello" then res <- true) -// equal true res +[] +let ``test Option.iter works`` () = + let mutable res = false + let getOnlyOnce = + let mutable value = Some "Hello" + fun () -> match value with Some x -> value <- None; Some x | None -> None + getOnlyOnce() |> Option.iter (fun s -> if s = "Hello" then res <- true) + equal true res [] let ``test Option.map works`` () = diff --git a/tests/Python/TestSeq.fs b/tests/Python/TestSeq.fs index 2eea20aaca..3946547f9e 100644 --- a/tests/Python/TestSeq.fs +++ b/tests/Python/TestSeq.fs @@ -8,6 +8,11 @@ let sumFirstTwo (zs: seq) = printfn "sumFirstTwo: %A" (first, second) first + second +let rec sumFirstSeq (zs: seq) (n: int): float = + match n with + | 0 -> 0. + | 1 -> Seq.head zs + | _ -> (Seq.head zs) + sumFirstSeq (Seq.skip 1 zs) (n-1) [] let ``test Seq.empty works`` () = From 2ef3a008656603cea4fe0d6264253d22e0e9a171 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 21 Jun 2021 06:48:31 +0200 Subject: [PATCH 115/145] Better cleaning of identifiers --- src/Fable.Transforms/Python/Fable2Python.fs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 37f7ee27e6..4236285bb6 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -406,7 +406,7 @@ module Helpers = | "Map" -> "dict" | "Int32Array" -> "list" | _ -> - name.Replace('$', '_').Replace('.', '_').Replace('`', '_') + name |> String.map(fun c -> if List.contains c ['.'; '$'; '`'; '*'; ' '] then '_' else c) let rewriteFableImport moduleName = //printfn "ModuleName: %s" moduleName @@ -645,7 +645,6 @@ module Util = |> fun (ids, values, ids') -> ctx.BoundVars.Bind(ids') (Expression.tuple(ids), Expression.tuple(values)) - [ Statement.assign([ids], values) ] let varDeclaration (ctx: Context) (var: Expression) (isMutable: bool) value = From 146359b7cacb31ab48980c90cbcacd3c354cf8dd Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 3 Jul 2021 11:48:36 +0200 Subject: [PATCH 116/145] Fixes for push vs append, indexOf vs find, length vs len, ... --- src/Fable.Transforms/Python/Fable2Python.fs | 29 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 4236285bb6..cc3c43e11f 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -645,13 +645,13 @@ module Util = |> fun (ids, values, ids') -> ctx.BoundVars.Bind(ids') (Expression.tuple(ids), Expression.tuple(values)) + [ Statement.assign([ids], values) ] let varDeclaration (ctx: Context) (var: Expression) (isMutable: bool) value = match var with | Name({Id=id}) -> ctx.BoundVars.Bind([id]) | _ -> () - [ Statement.assign([var], value) ] let restElement (var: Python.Identifier) = @@ -1077,7 +1077,7 @@ module Util = | _ -> Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' | BinaryUnequal -> - printfn "Right: %A" right + //printfn "Right: %A" right match right with | Expression.Name({Id=Identifier("None")}) -> let op = BinaryUnequalStrict @@ -1283,7 +1283,8 @@ module Util = if isPyStatement ctx false value then let varName, varExpr = Expression.name(var.Name), identAsExpr com ctx var ctx.BoundVars.Bind(var.Name) - let decl = Statement.assign([varName], varExpr) + + let decl = Statement.assign([varName], Expression.none ()) let body = com.TransformAsStatements(ctx, Some(Assign varExpr), value) List.append [ decl ] body else @@ -1599,6 +1600,11 @@ module Util = let left, stmts = com.TransformAsExpr(ctx, expr) Expression.call (func, [ left ]), stmts + | Fable.Call(Fable.Get(expr, Fable.FieldGet(fieldName="charCodeAt"), _, _), info, _, range) -> + let func = Expression.name("ord") + let value, stmts = com.TransformAsExpr(ctx, expr) + Expression.call (func, [ value ]), stmts + | Fable.Call(callee, info, _, range) -> transformCall com ctx range callee info @@ -1608,6 +1614,21 @@ module Util = | Fable.Operation(kind, _, range) -> transformOperation com ctx range kind + | Fable.Get(expr, Fable.FieldGet(fieldName="push"), _, _) -> + let attr = Python.Identifier("append") + let value, stmts = com.TransformAsExpr(ctx, expr) + Expression.attribute (value = value, attr = attr, ctx = Load), stmts + + | Fable.Get(expr, Fable.FieldGet(fieldName="indexOf"), _, _) -> + let attr = Python.Identifier("find") + let value, stmts = com.TransformAsExpr(ctx, expr) + Expression.attribute (value = value, attr = attr, ctx = Load), stmts + + | Fable.Get(expr, Fable.FieldGet(fieldName="length"), _, _) -> + let func = Expression.name("len") + let left, stmts = com.TransformAsExpr(ctx, expr) + Expression.call (func, [ left ]), stmts + | Fable.Get(expr, kind, typ, range) -> transformGet com ctx range typ expr kind @@ -1836,7 +1857,7 @@ module Util = else let varDeclStatement = multiVarDeclaration ctx [for v in declaredVars -> ident com ctx v, None] varDeclStatement @ body - printfn "Args: %A" (args, body) + //printfn "Args: %A" (args, body) args |> List.map (ident com ctx >> Arg.arg), body let declareEntryPoint _com _ctx (funcExpr: Expression) = From 9a6068907a51e2a5b674a8bf85fcab3bb818954f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 4 Jul 2021 09:34:43 +0200 Subject: [PATCH 117/145] Fix tc arg names --- src/Fable.Transforms/Python/Fable2Python.fs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index cc3c43e11f..1912f349b2 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -686,10 +686,8 @@ module Util = let emitExpression range (txt: string) args = let value = match txt with - | "$0.join('')" -> - "''.join($0)" - | "throw $0" -> - "raise $0" + | "$0.join('')" -> "''.join($0)" + | "throw $0" -> "raise $0" | Naming.StartsWith("void ") value | Naming.StartsWith("new ") value -> value | _ -> txt @@ -1033,7 +1031,7 @@ module Util = | args -> List.map (fun e -> com.TransformAsExpr(ctx, e)) args |> Helpers.unzipArgs let resolveExpr t strategy pyExpr: Statement = - printfn "resolveExpr: %A" pyExpr + //printfn "resolveExpr: %A" pyExpr match strategy with | None | Some ReturnUnit -> exprAsStatement(pyExpr) // TODO: Where to put these int wrappings? Add them also for function arguments? @@ -1252,7 +1250,7 @@ module Util = expr, stmts @ stmts' @ stmts'' let transformSet (com: IPythonCompiler) ctx range fableExpr (value: Fable.Expr) kind = - printfn "transformSet: %A" (fableExpr, value) + //printfn "transformSet: %A" (fableExpr, value) let expr, stmts = com.TransformAsExpr(ctx, fableExpr) let value, stmts' = com.TransformAsExpr(ctx, value) let ret, stmts'' = @@ -1275,7 +1273,7 @@ module Util = com.TransformAsExpr(ctx, value) let transformBindingAsExpr (com: IPythonCompiler) ctx (var: Fable.Ident) (value: Fable.Expr) = - printfn "transformBindingAsExpr: %A" (var, value) + //printfn "transformBindingAsExpr: %A" (var, value) let expr, stmts = transformBindingExprBody com ctx var value expr |> assign None (identAsExpr com ctx var), stmts @@ -1840,7 +1838,7 @@ module Util = // Replace args, see NamedTailCallOpportunity constructor let args' = List.zip args tc.Args - |> List.map (fun (id, tcArg) -> id) + |> List.map (fun (id, tcArg) -> com.GetIdentifier(ctx, tcArg)) let varDecls = List.zip args tc.Args |> List.map (fun (id, tcArg) -> ident com ctx id, Some (com.GetIdentifierAsExpr(ctx, tcArg))) @@ -1851,14 +1849,14 @@ module Util = let body = body @ [ Statement.break'() ] args', Statement.while'(Expression.constant(true), body) |> List.singleton - | _ -> args |> List.map id, body + | _ -> args |> List.map (ident com ctx), body let body = if declaredVars.Count = 0 then body else let varDeclStatement = multiVarDeclaration ctx [for v in declaredVars -> ident com ctx v, None] varDeclStatement @ body //printfn "Args: %A" (args, body) - args |> List.map (ident com ctx >> Arg.arg), body + args |> List.map Arg.arg, body let declareEntryPoint _com _ctx (funcExpr: Expression) = let argv = emitExpression None "typeof process === 'object' ? process.argv.slice(2) : []" [] @@ -2102,7 +2100,7 @@ module Util = ] let rec transformDeclaration (com: IPythonCompiler) ctx decl = - printfn "transformDeclaration: %A" decl + //printfn "transformDeclaration: %A" decl let withCurrentScope ctx (usedNames: Set) f = let ctx = { ctx with UsedNames = { ctx.UsedNames with CurrentDeclarationScope = HashSet usedNames } } let result = f ctx From eee5696a06327230777069338a36e1c39b08cc35 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 8 Jul 2021 12:59:48 +0200 Subject: [PATCH 118/145] Fixes for sequence expressions + get dict --- src/Fable.Transforms/Python/Fable2Python.fs | 156 +++++++++++++------- 1 file changed, 105 insertions(+), 51 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 545b95d1bc..e12b69ca00 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -808,7 +808,7 @@ module Util = let arg = FableTransforms.replaceValues tempVarReplacements arg let arg, stmts = com.TransformAsExpr(ctx, arg) yield! stmts - yield assign None (com.GetIdentifierAsExpr(ctx, argId)) arg |> exprAsStatement + yield! assign None (com.GetIdentifierAsExpr(ctx, argId)) arg |> exprAsStatement ctx yield Statement.continue'(?loc=range) ] @@ -1018,14 +1018,14 @@ module Util = rest @ [ Expression.starred(expr) ], stmts @ stmts' | args -> List.map (fun e -> com.TransformAsExpr(ctx, e)) args |> Helpers.unzipArgs - let resolveExpr t strategy pyExpr: Statement = + let resolveExpr (ctx: Context) t strategy pyExpr: Statement list = //printfn "resolveExpr: %A" pyExpr match strategy with - | None | Some ReturnUnit -> exprAsStatement(pyExpr) + | None | Some ReturnUnit -> exprAsStatement ctx pyExpr // TODO: Where to put these int wrappings? Add them also for function arguments? - | Some Return -> Statement.return'(pyExpr) - | Some(Assign left) -> exprAsStatement(assign None left pyExpr) - | Some(Target left) -> exprAsStatement(assign None (left |> Expression.identifier) pyExpr) + | Some Return -> [ Statement.return'(pyExpr) ] + | Some(Assign left) -> exprAsStatement ctx (assign None left pyExpr) + | Some(Target left) -> exprAsStatement ctx (assign None (left |> Expression.identifier) pyExpr) let transformOperation com ctx range opKind: Expression * Statement list = match opKind with @@ -1120,7 +1120,7 @@ module Util = optimizeTailCall com ctx range tc args | _ -> let expr, stmts = transformCall com ctx range callee callInfo - stmts @ [ expr |> resolveExpr t returnStrategy ] + stmts @ (expr |> resolveExpr ctx t returnStrategy) let transformCurriedApplyAsStatements com ctx range t returnStrategy callee args = // Warn when there's a recursive call that couldn't be optimized? @@ -1130,7 +1130,7 @@ module Util = optimizeTailCall com ctx range tc args | _ -> let expr, stmts = transformCurriedApply com ctx range callee args - stmts @ [ expr |> resolveExpr t returnStrategy ] + stmts @ (expr |> resolveExpr ctx t returnStrategy) // When expecting a block, it's usually not necessary to wrap it // in a lambda to isolate its variable context @@ -1355,11 +1355,16 @@ module Util = let target = List.rev bindings |> List.fold (fun e (i,v) -> Fable.Let(i,v,e)) target com.TransformAsExpr(ctx, target) - let exprAsStatement (expr: Expression) : Statement = + let exprAsStatement (ctx: Context) (expr: Expression) : Statement list = match expr with | NamedExpr({Target=target; Value=value; Loc=loc }) -> - Statement.assign([target], value) - | _ -> Statement.expr(expr) + let nonLocals = + match target with + | Expression.Name({Id=id}) -> [ ctx.BoundVars.NonLocals([id]) |> Statement.nonLocal ] + | _ -> [] + + nonLocals @ [ Statement.assign([target], value) ] + | _ -> [ Statement.expr(expr) ] let transformDecisionTreeSuccessAsStatements (com: IPythonCompiler) (ctx: Context) returnStrategy targetIndex boundValues: Statement list = match returnStrategy with @@ -1368,10 +1373,10 @@ module Util = let assignments = matchTargetIdentAndValues idents boundValues |> List.collect (fun (id, TransformExpr com ctx (value, stmts)) -> - let stmt = assign None (identAsExpr com ctx id) value |> exprAsStatement - stmts @ [ stmt ]) - let targetAssignment = assign None (targetId |> Expression.name) (ofInt targetIndex) |> exprAsStatement - [ targetAssignment ] @ assignments + let stmts' = assign None (identAsExpr com ctx id) value |> exprAsStatement ctx + stmts @ stmts') + let targetAssignment = assign None (targetId |> Expression.name) (ofInt targetIndex) |> exprAsStatement ctx + targetAssignment @ assignments | ret -> let bindings, target = getDecisionTargetAndBindValues com ctx targetIndex boundValues let bindings = bindings |> Seq.collect (fun (i, v) -> transformBindingAsStatements com ctx i v) |> Seq.toList @@ -1533,19 +1538,25 @@ module Util = else transformDecisionTreeWithTwoSwitches com ctx returnStrategy targets treeExpr - let transformSequenceExpr (com: IPythonCompiler) ctx (exprs: Expression list) : Expression * Statement list = + let transformSequenceExpr (com: IPythonCompiler) ctx (exprs: Fable.Expr list) : Expression * Statement list = + //printfn "transformSequenceExpr, exprs: %A" exprs.Length let body = exprs - |> List.mapi - (fun i expr -> - // Return the last expression + |> List.collecti + (fun i e -> + let expr, stmts = com.TransformAsExpr(ctx, e) + + // Return the last expression if i = exprs.Length - 1 then - [ Statement.return' (expr)] + Statement.return' (expr) :: stmts else - [ exprAsStatement expr ]) - |> List.collect id - //|> transformBody ReturnStrategy.Return - + exprAsStatement ctx expr @ stmts) + // let expr = + // Expression.subscript( + // Expression.tuple(exprs), + // Expression.constant(-1)) + // expr, [] + //printfn "transformSequenceExpr, body: %A" body let name = Helpers.getUniqueIdentifier ("lifted") let func = FunctionDef.Create(name = name, args = Arguments.arguments [], body = body) @@ -1553,6 +1564,23 @@ module Util = let name = Expression.name (name) Expression.call (name), [ func ] + let transformSequenceExpr' (com: IPythonCompiler) ctx (exprs: Expression list) (stmts: Statement list) : Expression * Statement list = + //printfn "transformSequenceExpr', exprs: %A" exprs.Length + let body = + exprs + |> List.collecti + (fun i expr -> + // Return the last expression + if i = exprs.Length - 1 then + stmts @ [ Statement.return' (expr) ] + else + exprAsStatement ctx expr) + + let name = Helpers.getUniqueIdentifier ("lifted") + let func = FunctionDef.Create(name = name, args = Arguments.arguments [], body = body) + + let name = Expression.name (name) + Expression.call (name), [ func ] let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Fable.Expr): Expression * Statement list= match expr with @@ -1584,6 +1612,11 @@ module Util = let left, stmts = com.TransformAsExpr(ctx, expr) Expression.call (func, [ left ]), stmts + | Fable.Call(Fable.Get(expr, Fable.FieldGet(fieldName="split"), _, _), { Args=[Fable.Value(kind=Fable.StringConstant(""))]}, _, range) -> + let func = Expression.name("list") + let value, stmts = com.TransformAsExpr(ctx, expr) + Expression.call (func, [ value ]), stmts + | Fable.Call(Fable.Get(expr, Fable.FieldGet(fieldName="charCodeAt"), _, _), info, _, range) -> let func = Expression.name("ord") let value, stmts = com.TransformAsExpr(ctx, expr) @@ -1603,16 +1636,32 @@ module Util = let value, stmts = com.TransformAsExpr(ctx, expr) Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | Fable.Get(expr, Fable.FieldGet(fieldName="toLocaleUpperCase"), _, _) -> + let attr = Python.Identifier("upper") + let value, stmts = com.TransformAsExpr(ctx, expr) + Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | Fable.Get(expr, Fable.FieldGet(fieldName="toLocaleLowerCase"), _, _) -> + let attr = Python.Identifier("lower") + let value, stmts = com.TransformAsExpr(ctx, expr) + Expression.attribute (value = value, attr = attr, ctx = Load), stmts | Fable.Get(expr, Fable.FieldGet(fieldName="indexOf"), _, _) -> let attr = Python.Identifier("find") let value, stmts = com.TransformAsExpr(ctx, expr) Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | Fable.Get(Fable.IdentExpr({Name = "String"}), Fable.FieldGet(fieldName="fromCharCode"), _, _) -> + let func = Expression.name("chr") + func, [] | Fable.Get(expr, Fable.FieldGet(fieldName="length"), _, _) -> let func = Expression.name("len") let left, stmts = com.TransformAsExpr(ctx, expr) Expression.call (func, [ left ]), stmts + | Fable.Get(Fable.IdentExpr({Name=name;Type=Fable.AnonymousRecordType(_)}) as expr, Fable.FieldGet(fieldName=index), _, _) -> + let left, stmts = com.TransformAsExpr(ctx, expr) + let index = Expression.constant(index) + Expression.subscript (left, index), stmts + | Fable.Get(expr, kind, typ, range) -> transformGet com ctx range typ expr kind @@ -1628,15 +1677,23 @@ module Util = transformDecisionTreeSuccessAsExpr com ctx idx boundValues | Fable.Set(expr, kind, typ, value, range) -> - transformSet com ctx range expr typ value kind + let expr', stmts = transformSet com ctx range expr typ value kind + match expr' with + | Expression.NamedExpr({ Target = target; Value = _; Loc=_ }) -> + let nonLocals = + match target with + | Expression.Name({Id=id}) -> [ ctx.BoundVars.NonLocals([id]) |> Statement.nonLocal ] + | _ -> [] + expr', nonLocals @ stmts + | _ -> expr', stmts | Fable.Let(ident, value, body) -> - // printfn "Fable.Let: %A" (ident, value, body) + //printfn "Fable.Let: %A" (ident, value, body) if ctx.HoistVars [ident] then let assignment, stmts = transformBindingAsExpr com ctx ident value - let expr, stmts' = com.TransformAsExpr(ctx, body) - let expr, stmts'' = transformSequenceExpr com ctx [ assignment; expr ] - expr, stmts @ stmts' @ stmts'' + let bodyExpr, stmts' = com.TransformAsExpr(ctx, body) + let expr, stmts'' = transformSequenceExpr' com ctx [ assignment; bodyExpr ] (stmts @ stmts') + expr, stmts'' else iife com ctx expr | Fable.LetRec(bindings, body) -> @@ -1648,14 +1705,11 @@ module Util = |> (fun (e, s) -> (e, List.collect id s)) let expr, stmts' = com.TransformAsExpr(ctx, body) - let expr, stmts'' = transformSequenceExpr com ctx (values @ [expr]) + let expr, stmts'' = transformSequenceExpr' com ctx (values @ [expr]) [] expr, stmts @ stmts' @ stmts'' else iife com ctx expr - | Fable.Sequential exprs -> - let exprs, stmts = List.map (fun e -> com.TransformAsExpr(ctx, e)) exprs |> List.unzip - // printfn "Sequential: %A" (exprs, stmts) - Expression.none(), stmts |> List.collect id + | Fable.Sequential exprs -> transformSequenceExpr com ctx exprs | Fable.Emit(info, _, range) -> if info.IsStatement then iife com ctx expr @@ -1677,7 +1731,7 @@ module Util = match kind with | Fable.Curry(e, arity) -> let expr, stmts = transformCurry com ctx e arity - stmts @ [ expr |> resolveExpr e.Type returnStrategy ] + stmts @ (expr |> resolveExpr ctx e.Type returnStrategy) | Fable.Throw(TransformExpr com ctx (e, stmts), _) -> stmts @ [Statement.raise(e) ] | Fable.Return(TransformExpr com ctx (e, stmts)) -> stmts @ [ Statement.return'(e)] | Fable.Debugger -> [] @@ -1685,35 +1739,35 @@ module Util = | Fable.TypeCast(e, t) -> let expr, stmts = transformCast com ctx t e - stmts @ [ expr |> resolveExpr t returnStrategy ] + stmts @ (expr |> resolveExpr ctx t returnStrategy) | Fable.Value(kind, r) -> let expr, stmts = transformValue com ctx r kind - stmts @ [ expr |> resolveExpr kind.Type returnStrategy ] + stmts @ (expr |> resolveExpr ctx kind.Type returnStrategy) | Fable.IdentExpr id -> - [ identAsExpr com ctx id |> resolveExpr id.Type returnStrategy ] + identAsExpr com ctx id |> resolveExpr ctx id.Type returnStrategy | Fable.Import({ Selector = selector; Path = path }, t, r) -> - [ transformImport com ctx r selector path |> resolveExpr t returnStrategy ] + transformImport com ctx r selector path |> resolveExpr ctx t returnStrategy | Fable.Test(expr, kind, range) -> let expr, stmts = transformTest com ctx range kind expr - stmts @ [ expr |> resolveExpr Fable.Boolean returnStrategy ] + stmts @ (expr |> resolveExpr ctx Fable.Boolean returnStrategy) | Fable.Lambda(arg, body, name) -> let args, body = transformFunction com ctx name [arg] body let expr', stmts = makeArrowFunctionExpression args body - stmts @ [ expr' |> resolveExpr expr.Type returnStrategy ] + stmts @ (expr' |> resolveExpr ctx expr.Type returnStrategy) | Fable.Delegate(args, body, name) -> let args, body = transformFunction com ctx name args body let expr', stmts = makeArrowFunctionExpression args body - stmts @ [ expr' |> resolveExpr expr.Type returnStrategy ] + stmts @ (expr' |> resolveExpr ctx expr.Type returnStrategy) | Fable.ObjectExpr (members, t, baseCall) -> let expr, stmts = transformObjectExpr com ctx members baseCall - stmts @ [ expr |> resolveExpr t returnStrategy ] + stmts @ (expr |> resolveExpr ctx t returnStrategy) | Fable.Call(callee, info, typ, range) -> transformCallAsStatements com ctx range typ returnStrategy callee info @@ -1725,15 +1779,15 @@ module Util = let e, stmts = transformEmit com ctx range info if info.IsStatement then stmts @ [ Statement.expr(e) ] // Ignore the return strategy - else stmts @ [ resolveExpr t returnStrategy e ] + else stmts @ resolveExpr ctx t returnStrategy e | Fable.Operation(kind, t, range) -> let expr, stmts = transformOperation com ctx range kind - stmts @ [ expr |> resolveExpr t returnStrategy ] + stmts @ (expr |> resolveExpr ctx t returnStrategy) | Fable.Get(expr, kind, t, range) -> let expr, stmts = transformGet com ctx range t expr kind - stmts @ [ expr |> resolveExpr t returnStrategy ] + stmts @ (expr |> resolveExpr ctx t returnStrategy) | Fable.Let(ident, value, body) -> let binding = transformBindingAsStatements com ctx ident value @@ -1752,7 +1806,7 @@ module Util = | Expression.Name({Id=id}) -> [ ctx.BoundVars.NonLocals([id]) |> Statement.nonLocal ] | _ -> [] nonLocals @ stmts @ [ Statement.assign([target], value) ] - | _ -> stmts @ [ expr' |> resolveExpr expr.Type returnStrategy ] + | _ -> stmts @ (expr' |> resolveExpr ctx expr.Type returnStrategy) | Fable.IfThenElse(guardExpr, thenExpr, elseExpr, r) -> let asStatement = @@ -1772,7 +1826,7 @@ module Util = stmts @ stmts' @ stmts'' - @ [ Expression.ifExp(guardExpr', thenExpr', elseExpr', ?loc=r) |> resolveExpr thenExpr.Type returnStrategy ] + @ (Expression.ifExp(guardExpr', thenExpr', elseExpr', ?loc=r) |> resolveExpr ctx thenExpr.Type returnStrategy) | Fable.Sequential statements -> let lasti = (List.length statements) - 1 @@ -2057,11 +2111,11 @@ module Util = let body = [ if Option.isSome baseExpr then yield callSuperAsStatement [] - yield! ent.FSharpFields |> Seq.mapi (fun i field -> + + yield! (ent.FSharpFields |> List.collecti (fun i field -> let left = get com ctx None thisExpr field.Name let right = args.[i] - assign None left right |> exprAsStatement) - |> Seq.toArray + assign None left right |> exprAsStatement ctx)) ] let args = fieldIds |> Array.mapToList (ident com ctx >> Arg.arg) declareType com ctx ent entName args body baseExpr classMembers From a7294ede9a11a695adb1bce75e832521d5b62a8f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 10 Jul 2021 09:40:18 +0200 Subject: [PATCH 119/145] Fixed var args for union type --- src/Fable.Transforms/Python/Fable2Python.fs | 105 +++++++++++++------- src/Fable.Transforms/Python/Python.fs | 5 +- 2 files changed, 70 insertions(+), 40 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index e12b69ca00..4e396ea9ed 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -53,7 +53,7 @@ type BoundVars = this.LocalScope.Add name |> ignore member this.NonLocals(idents: Identifier list) = - // printfn "NonLocals: %A" (idents, this) + printfn "NonLocals: %A" (idents, this) [ for ident in idents do let (Identifier name) = ident @@ -81,7 +81,7 @@ type IPythonCompiler = abstract TransformAsExpr: Context * Fable.Expr -> Expression * Statement list abstract TransformAsStatements: Context * ReturnStrategy option * Fable.Expr -> Statement list abstract TransformImport: Context * selector:string * path:string -> Expression - abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> Arg list * Statement list + abstract TransformFunction: Context * string option * Fable.Ident list * Fable.Expr -> Arguments * Statement list abstract WarnOnlyOnce: string * ?range: SourceLocation -> unit @@ -637,7 +637,8 @@ module Util = let iife (com: IPythonCompiler) ctx (expr: Fable.Expr) = let _, body = com.TransformFunction(ctx, None, [], expr) // Use an arrow function in case we need to capture `this` - let afe, stmts = makeArrowFunctionExpression [] body + let args = Arguments.arguments () + let afe, stmts = makeArrowFunctionExpression args body Expression.call(afe, []), stmts let multiVarDeclaration (ctx: Context) (variables: (Identifier * Expression option) list) = @@ -671,10 +672,10 @@ module Util = let callSuperAsStatement (args: Expression list) = Statement.expr(callSuper args) - let makeClassConstructor args body = + let makeClassConstructor (args: Arguments) body = let name = Identifier("__init__") let self = Arg.arg("self") - let args = Arguments.arguments (args = self::args) + let args = { args with Args = self::args.Args } let body = match body with | [] -> [ Pass ] @@ -723,6 +724,7 @@ module Util = | Attached of isStatic: bool let getMemberArgsAndBody (com: IPythonCompiler) ctx kind hasSpread (args: Fable.Ident list) (body: Fable.Expr) = + printfn "getMemberArgsAndBody" let funcName, genTypeParams, args, body = match kind, args with | Attached(isStatic=false), (thisArg::args) -> @@ -742,14 +744,15 @@ module Util = let ctx = { ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams genTypeParams } let args, body = transformFunction com ctx funcName args body - let args = - let len = List.length args - if not hasSpread || len = 0 then args - else [ - if len > 1 then - yield! args.[..len-2] - // FIXME: yield restElement args.[len-1] - ] + printfn "hasSpread: %A" hasSpread + // let args = + // let len = args.Args.Length + // if not hasSpread || len = 0 then args + // else [ + // if len > 1 then + // yield! args.[..len-2] + // // FIXME: yield restElement args.[len-1] + // ] args, body @@ -764,15 +767,14 @@ module Util = let wrapExprInBlockWithReturn (e, stmts) = stmts @ [ Statement.return'(e) ] - let makeArrowFunctionExpression (args: Arg list) (body: Statement list) : Expression * Statement list = + let makeArrowFunctionExpression (args: Arguments) (body: Statement list) : Expression * Statement list = let name = Helpers.getUniqueIdentifier "lifted" - let args = Arguments.arguments(args) let func = FunctionDef.Create(name = name, args = args, body = body) Expression.name (name), [ func ] let makeFunction name (args, (body: Expression)) : Statement = let body = wrapExprInBlockWithReturn (body, []) - FunctionDef.Create(name = name, args = Arguments.arguments args, body = body) + FunctionDef.Create(name = name, args = args, body = body) let makeFunctionExpression (com: IPythonCompiler) ctx name (args, (body: Expression)) : Expression * Statement list= let name = @@ -941,8 +943,7 @@ module Util = let args, body = getMemberArgsAndBody com ctx (Attached(isStatic=false)) hasSpread args body let name = Identifier("test") - let arguments = Arguments.arguments(args) - FunctionDef.Create(name, arguments, body) + FunctionDef.Create(name, args, body) let members = members |> List.collect (fun memb -> @@ -989,7 +990,8 @@ module Util = |> extractBaseExprFromBaseCall com ctx None |> Option.map (fun (baseExpr, (baseArgs, stmts)) -> let consBody = [ callSuperAsStatement baseArgs ] - let cons = makeClassConstructor [] consBody + let args = Arguments.empty + let cons = makeClassConstructor args consBody Some baseExpr, cons::members ) |> Option.defaultValue (None, members) @@ -1132,17 +1134,30 @@ module Util = let expr, stmts = transformCurriedApply com ctx range callee args stmts @ (expr |> resolveExpr ctx t returnStrategy) + let transformBody (com: IPythonCompiler) ctx ret (body: Statement list) : Statement list = + match body with + | [] -> [ Pass ] + | _ -> + let body, nonLocals = + body + |> List.partition (function | Statement.NonLocal _ -> false | _ -> true) + + let nonLocal = + nonLocals + |> List.collect (function | Statement.NonLocal(nl) -> nl.Names | _ -> []) + |> Statement.nonLocal + nonLocal :: body + // When expecting a block, it's usually not necessary to wrap it // in a lambda to isolate its variable context - let transformBlock (com: IPythonCompiler) ctx ret expr: Statement list = + let transformBlock (com: IPythonCompiler) ctx ret (expr: Fable.Expr) : Statement list = let block = com.TransformAsStatements(ctx, ret, expr) |> List.choose Helpers.isProductiveStatement match block with | [] -> [ Pass ] | _ -> block - |> List.sortBy (function | Statement.NonLocal _ -> 0 | _ -> 1) - + |> transformBody com ctx ret let transformTryCatch com ctx r returnStrategy (body, (catch: option), finalizer) = // try .. catch statements cannot be tail call optimized @@ -1360,9 +1375,12 @@ module Util = | NamedExpr({Target=target; Value=value; Loc=loc }) -> let nonLocals = match target with - | Expression.Name({Id=id}) -> [ ctx.BoundVars.NonLocals([id]) |> Statement.nonLocal ] + | Expression.Name({ Id=id }) -> + //printfn "Adding nonlocal for: %A" id + [ ctx.BoundVars.NonLocals([ id ]) |> Statement.nonLocal ] | _ -> [] + //printfn "Nonlocals: %A" nonLocals nonLocals @ [ Statement.assign([target], value) ] | _ -> [ Statement.expr(expr) ] @@ -1539,18 +1557,20 @@ module Util = transformDecisionTreeWithTwoSwitches com ctx returnStrategy targets treeExpr let transformSequenceExpr (com: IPythonCompiler) ctx (exprs: Fable.Expr list) : Expression * Statement list = - //printfn "transformSequenceExpr, exprs: %A" exprs.Length + //printfn "transformSequenceExpr" + let ctx = { ctx with BoundVars = ctx.BoundVars.EnterScope() } let body = exprs |> List.collecti (fun i e -> let expr, stmts = com.TransformAsExpr(ctx, e) - // Return the last expression if i = exprs.Length - 1 then Statement.return' (expr) :: stmts else exprAsStatement ctx expr @ stmts) + |> transformBody com ctx None + // let expr = // Expression.subscript( // Expression.tuple(exprs), @@ -1566,6 +1586,8 @@ module Util = let transformSequenceExpr' (com: IPythonCompiler) ctx (exprs: Expression list) (stmts: Statement list) : Expression * Statement list = //printfn "transformSequenceExpr', exprs: %A" exprs.Length + let ctx = { ctx with BoundVars = ctx.BoundVars.EnterScope() } + let body = exprs |> List.collecti @@ -1723,7 +1745,6 @@ module Util = | Fable.Curry(e, arity) -> transformCurry com ctx e arity | Fable.Throw _ | Fable.Return _ | Fable.Break _ | Fable.Debugger -> iife com ctx expr - let rec transformAsStatements (com: IPythonCompiler) ctx returnStrategy (expr: Fable.Expr): Statement list = match expr with @@ -1863,7 +1884,7 @@ module Util = [ Statement.for'(target = target, iter = iter, body = body) ] - let transformFunction com ctx name (args: Fable.Ident list) (body: Fable.Expr): Arg list * Statement list = + let transformFunction com ctx name (args: Fable.Ident list) (body: Fable.Expr): Arguments * Statement list = let tailcallChance = Option.map (fun name -> NamedTailCallOpportunity(com, ctx, name, args) :> ITailCallOpportunity) name @@ -1907,7 +1928,8 @@ module Util = let varDeclStatement = multiVarDeclaration ctx [for v in declaredVars -> ident com ctx v, None] varDeclStatement @ body //printfn "Args: %A" (args, body) - args |> List.map Arg.arg, body + let args = Arguments.arguments(args |> List.map Arg.arg) + args, body let declareEntryPoint _com _ctx (funcExpr: Expression) = let argv = emitExpression None "typeof process === 'object' ? process.argv.slice(2) : []" [] @@ -1984,10 +2006,11 @@ module Util = prop) |> Seq.toArray - let declareClassType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Arg list) (consBody: Statement list) (baseExpr: Expression option) classMembers = + let declareClassType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Arguments) (consBody: Statement list) (baseExpr: Expression option) classMembers = let classCons = makeClassConstructor consArgs consBody let classFields = Array.empty let classMembers = List.append [ classCons ] classMembers + printfn "ClassMembers: %A" classMembers let classBody = let body = [ yield! classFields; yield! classMembers ] //printfn "Body: %A" body @@ -1999,14 +2022,14 @@ module Util = let classStmt = Statement.classDef(name, body = classBody, bases = bases) classStmt - let declareType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Arg list) (consBody: Statement list) baseExpr classMembers : Statement list = + let declareType (com: IPythonCompiler) ctx (ent: Fable.Entity) entName (consArgs: Arguments) (consBody: Statement list) baseExpr classMembers : Statement list = let typeDeclaration = declareClassType com ctx ent entName consArgs consBody baseExpr classMembers let reflectionDeclaration, stmts = let ta = None let genArgs = Array.init (ent.GenericParameters.Length) (fun i -> "gen" + string i |> makeIdent) let generics = genArgs |> Array.mapToList (identAsExpr com ctx) let body, stmts = transformReflectionInfo com ctx None ent generics - let args = genArgs |> Array.mapToList (ident com ctx >> Arg.arg) + let args = Arguments.arguments(genArgs |> Array.mapToList (ident com ctx >> Arg.arg)) let expr, stmts' = makeFunctionExpression com ctx None (args, body) let name = com.GetIdentifier(ctx, entName + Naming.reflectionSuffix) expr |> declareModuleMember ctx ent.IsPublic name false, stmts @ stmts' @@ -2018,7 +2041,7 @@ module Util = //printfn "Arsg: %A" args let name = com.GetIdentifier(ctx, membName) //Helpers.getUniqueIdentifier "lifted" - let stmt = FunctionDef.Create(name = name, args = Arguments.arguments args, body = body) + let stmt = FunctionDef.Create(name = name, args = args, body = body) let expr = Expression.name (name) info.Attributes |> Seq.exists (fun att -> att.Entity.FullName = Atts.entryPoint) @@ -2045,11 +2068,12 @@ module Util = getMemberArgsAndBody com ctx (Attached isStatic) false memb.Args memb.Body let key, computed = memberFromName com ctx memb.Name let self = Arg.arg("self") - let arguments = Arguments.arguments (self::args) + let arguments = { args with Args = self::args.Args } FunctionDef.Create(com.GetIdentifier(ctx, memb.Name), arguments, body = body) |> List.singleton let transformAttachedMethod (com: IPythonCompiler) ctx (memb: Fable.MemberDecl) = + printfn "transformAttachedMethod" let isStatic = not memb.Info.IsInstance let makeMethod name args body = let key, computed = memberFromName com ctx name @@ -2057,7 +2081,7 @@ module Util = let args, body = getMemberArgsAndBody com ctx (Attached isStatic) memb.Info.HasSpread memb.Args memb.Body let self = Arg.arg("self") - let arguments = Arguments.arguments (self::args) + let arguments = { args with Args = self::args.Args } [ yield makeMethod memb.Name arguments body if memb.Info.IsEnumerator then @@ -2067,8 +2091,10 @@ module Util = let transformUnion (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = let fieldIds = getUnionFieldsAsIdents com ctx ent let args = - [ fieldIds.[0] |> ident com ctx |> Arg.arg - fieldIds.[1] |> ident com ctx |> Arg.arg ] //restElement ] + let args=fieldIds.[0] |> ident com ctx |> Arg.arg |> List.singleton + let varargs = fieldIds.[1] |> ident com ctx |> Arg.arg + Arguments.arguments(args=args, vararg=varargs) + let body = [ yield callSuperAsStatement [] @@ -2117,7 +2143,10 @@ module Util = let right = args.[i] assign None left right |> exprAsStatement ctx)) ] - let args = fieldIds |> Array.mapToList (ident com ctx >> Arg.arg) + let args = + fieldIds + |> Array.mapToList (ident com ctx >> Arg.arg) + |> (fun args -> Arguments.arguments(args=args)) declareType com ctx ent entName args body baseExpr classMembers let transformClassWithImplicitConstructor (com: IPythonCompiler) ctx (classDecl: Fable.ClassDecl) classMembers (cons: Fable.MemberDecl) = @@ -2127,7 +2156,7 @@ module Util = getMemberArgsAndBody com ctx ClassConstructor cons.Info.HasSpread cons.Args cons.Body let exposedCons = - let argExprs = consArgs |> List.map (fun p -> Expression.identifier(p.Arg)) + let argExprs = consArgs.Args |> List.map (fun p -> Expression.identifier(p.Arg)) let exposedConsBody = Expression.call(classIdent, argExprs) let name = com.GetIdentifier(ctx, cons.Name) makeFunction name (consArgs, exposedConsBody) diff --git a/src/Fable.Transforms/Python/Python.fs b/src/Fable.Transforms/Python/Python.fs index b752d92198..1870d5bf8e 100644 --- a/src/Fable.Transforms/Python/Python.fs +++ b/src/Fable.Transforms/Python/Python.fs @@ -182,7 +182,7 @@ type Keyword = /// - defaults is a list of default values for arguments that can be passed positionally. If there are fewer defaults, /// they correspond to the last n arguments. type Arguments = - { PosOnlyArgs: Arg list + { PosOnlyArgs: Arg list // https://www.python.org/dev/peps/pep-0570/ Args: Arg list VarArg: Arg option KwOnlyArgs: Arg list @@ -1063,7 +1063,7 @@ module PythonExtensions = type Arguments with - static member arguments(?posonlyargs, ?args, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ?defaults) = + static member arguments(?args, ?posonlyargs, ?vararg, ?kwonlyargs, ?kwDefaults, ?kwarg, ?defaults) = { PosOnlyArgs = defaultArg posonlyargs [] Args = defaultArg args [] VarArg = vararg @@ -1071,6 +1071,7 @@ module PythonExtensions = KwDefaults = defaultArg kwDefaults [] KwArg = kwarg Defaults = defaultArg defaults [] } + static member empty = Arguments.arguments() type For with From 516d9eb07aa62f99df4781e24772af8997f9b9e7 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sun, 11 Jul 2021 10:18:30 +0200 Subject: [PATCH 120/145] Fix get attribute and exception raising --- src/Fable.Transforms/Python/Fable2Python.fs | 21 +++++++++----------- src/Fable.Transforms/Python/PythonPrinter.fs | 14 +++++++++---- src/fable-library-py/fable/util.py | 8 ++++---- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 4e396ea9ed..49422c78f2 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -594,19 +594,16 @@ module Util = let getExpr com ctx r (object: Expression) (expr: Expression) = // printfn "getExpr: %A" (object, expr) - // match expr with - // | Expression.Constant(value=value) -> - // match value with - // | :? string as str -> memberFromName com ctx str - // | :? int - // | :? float -> Expression.subscript(value = object, slice = expr, ctx = Load), [] - // | _ -> failwith $"Value {value} needs to be string" - // | Expression.Name({Id=id}) -> - // Expression.attribute (value = object, attr = id, ctx = Load), [] - // | e -> + match expr with + | Expression.Constant(value=name) when (name :? string) -> + let name = name :?> string |> Identifier + Expression.attribute (value = object, attr = name, ctx = Load), [] + | Expression.Name({Id=name}) -> + Expression.attribute (value = object, attr = name, ctx = Load), [] + | e -> // let func = Expression.name("getattr") // Expression.call(func=func, args=[object; e]), [] - Expression.subscript(value = object, slice = expr, ctx = Load), [] + Expression.subscript(value = object, slice = e, ctx = Load), [] let rec getParts com ctx (parts: string list) (expr: Expression) = match parts with @@ -2010,7 +2007,7 @@ module Util = let classCons = makeClassConstructor consArgs consBody let classFields = Array.empty let classMembers = List.append [ classCons ] classMembers - printfn "ClassMembers: %A" classMembers + //printfn "ClassMembers: %A" classMembers let classBody = let body = [ yield! classFields; yield! classMembers ] //printfn "Body: %A" body diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index f6ca1aa925..3057a9151d 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -150,7 +150,9 @@ module PrinterExtensions = printer.Print(kw.Value) member printer.Print(arguments: Arguments) = - printer.PrintCommaSeparatedList(arguments.PosOnlyArgs) + if not arguments.PosOnlyArgs.IsEmpty then + printer.PrintCommaSeparatedList(arguments.PosOnlyArgs) + printer.Print(", /") let args = arguments.Args |> List.map AST.Arg let defaults = arguments.Defaults @@ -244,16 +246,20 @@ module PrinterExtensions = printer.PrintBlock(ifElse.Body) printElse ifElse.Else - member printer.Print(ri: Raise) = printer.Print("(Raise)") + member printer.Print(ri: Raise) = + printer.Print("raise ") + printer.Print(ri.Exception) member printer.Print(func: FunctionDef) = printer.PrintFunction(Some func.Name, func.Args, func.Body, func.Returns, func.DecoratorList, isDeclaration = true) printer.PrintNewLine() - member printer.Print(gl: Global) = printer.Print("(Global)") + member printer.Print(gl: Global) = + printer.Print("global ") + printer.PrintCommaSeparatedList(gl.Names) member printer.Print(nl: NonLocal) = - if List.length nl.Names > 0 then + if not (List.isEmpty nl.Names) then printer.Print("nonlocal ") printer.PrintCommaSeparatedList nl.Names diff --git a/src/fable-library-py/fable/util.py b/src/fable-library-py/fable/util.py index fa5706700d..e41e161ca4 100644 --- a/src/fable-library-py/fable/util.py +++ b/src/fable-library-py/fable/util.py @@ -193,10 +193,10 @@ def Dispose(self): def __getattr__(self, name): return { - "System.Collections.Generic.IEnumerator`1.get_Current": self.Current, - "System.Collections.IEnumerator.get_Current": self.Current, - "System.Collections.IEnumerator.MoveNext": self.MoveNext, - "System.Collections.IEnumerator.Reset": self.Reset, + "System_Collections_Generic_IEnumerator_1_get_Current": self.Current, + "System_Collections.IEnumerator_get_Current": self.Current, + "System_Collections_IEnumerator_MoveNext": self.MoveNext, + "System_Collections.IEnumerator_Reset": self.Reset, }[name] From ba679e5e1f573dbdae70b07553a9967f3b2136aa Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 12 Jul 2021 04:57:37 +0200 Subject: [PATCH 121/145] Fix for dict getters Also transform `~(~(a/b))` to `a // b` --- src/Fable.Transforms/Python/Fable2Python.fs | 24 +++++++++++++++----- src/Fable.Transforms/Python/PythonPrinter.fs | 2 -- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 49422c78f2..5de0347ee1 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -53,7 +53,7 @@ type BoundVars = this.LocalScope.Add name |> ignore member this.NonLocals(idents: Identifier list) = - printfn "NonLocals: %A" (idents, this) + // printfn "NonLocals: %A" (idents, this) [ for ident in idents do let (Identifier name) = ident @@ -589,8 +589,14 @@ module Util = | n -> com.GetIdentifierAsExpr(ctx, n), [] let get (com: IPythonCompiler) ctx r left memberName = - let expr = com.GetIdentifier(ctx, memberName) - Expression.attribute (value = left, attr = expr, ctx = Load) + // printfn "get: %A" (left, memberName) + match left with + | Expression.Dict(_) -> + let expr = Expression.constant(memberName) + Expression.subscript (value = left, slice = expr, ctx = Load) + | _ -> + let expr = com.GetIdentifier(ctx, memberName) + Expression.attribute (value = left, attr = expr, ctx = Load) let getExpr com ctx r (object: Expression) (expr: Expression) = // printfn "getExpr: %A" (object, expr) @@ -598,14 +604,15 @@ module Util = | Expression.Constant(value=name) when (name :? string) -> let name = name :?> string |> Identifier Expression.attribute (value = object, attr = name, ctx = Load), [] - | Expression.Name({Id=name}) -> - Expression.attribute (value = object, attr = name, ctx = Load), [] + //| Expression.Name({Id=name}) -> + // Expression.attribute (value = object, attr = name, ctx = Load), [] | e -> // let func = Expression.name("getattr") // Expression.call(func=func, args=[object; e]), [] Expression.subscript(value = object, slice = e, ctx = Load), [] let rec getParts com ctx (parts: string list) (expr: Expression) = + printfn "getParts" match parts with | [] -> expr | m::ms -> get com ctx None expr m |> getParts com ctx ms @@ -621,7 +628,8 @@ module Util = let makeJsObject com ctx (pairs: seq) = pairs |> Seq.map (fun (name, value) -> - let prop, computed = memberFromName com ctx name + //let prop, computed = memberFromName com ctx name + let prop = Expression.constant(name) prop, value) |> Seq.toList |> List.unzip @@ -1027,9 +1035,13 @@ module Util = | Some(Target left) -> exprAsStatement ctx (assign None (left |> Expression.identifier) pyExpr) let transformOperation com ctx range opKind: Expression * Statement list = + //printfn "transformOperation: %A" opKind match opKind with | Fable.Unary(UnaryVoid, TransformExpr com ctx (expr, stmts)) -> expr, stmts + // Transform `~(~(a/b))` to `a // b` + | Fable.Unary(UnaryOperator.UnaryNotBitwise, Fable.Operation(kind=Fable.Unary(UnaryOperator.UnaryNotBitwise, Fable.Operation(kind=Fable.Binary(BinaryOperator.BinaryDivide, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')))))) -> + Expression.binOp(left, FloorDiv, right), stmts @ stmts' | Fable.Unary(op, TransformExpr com ctx (expr, stmts)) -> Expression.unaryOp(op, expr, ?loc=range), stmts diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index 3057a9151d..15e667eb3e 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -479,9 +479,7 @@ module PrinterExtensions = |> List.mapi (fun i n -> (i, n)) for i, (key, value) in nodes do - printer.Print("\"") printer.Print(key) - printer.Print("\"") printer.Print(": ") printer.Print(value) From fcd1d6aadd96752163b3056a152572a54fadc18d Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 12 Jul 2021 05:20:19 +0200 Subject: [PATCH 122/145] Rewrite `.message` to `str` --- src/Fable.Transforms/Python/Fable2Python.fs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 5de0347ee1..721384ba8f 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -1688,6 +1688,11 @@ module Util = let left, stmts = com.TransformAsExpr(ctx, expr) Expression.call (func, [ left ]), stmts + | Fable.Get(expr, Fable.FieldGet(fieldName="message"), _, _) -> + let func = Expression.name("str") + let left, stmts = com.TransformAsExpr(ctx, expr) + Expression.call (func, [ left ]), stmts + | Fable.Get(Fable.IdentExpr({Name=name;Type=Fable.AnonymousRecordType(_)}) as expr, Fable.FieldGet(fieldName=index), _, _) -> let left, stmts = com.TransformAsExpr(ctx, expr) let index = Expression.constant(index) From 5cc053fc9c7177107ad4dc4329b72cf033b4976f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 14 Jul 2021 06:36:18 +0200 Subject: [PATCH 123/145] Fix seq tests --- src/Fable.Core/Fable.Core.PY.fs | 559 ++++++++++++++++++++ src/Fable.Core/Fable.Core.fsproj | 1 + src/Fable.Transforms/Python/Fable2Python.fs | 84 +-- src/Fable.Transforms/Replacements.fs | 3 +- src/fable-library-py/fable/Native.fs | 6 +- 5 files changed, 609 insertions(+), 44 deletions(-) create mode 100644 src/Fable.Core/Fable.Core.PY.fs diff --git a/src/Fable.Core/Fable.Core.PY.fs b/src/Fable.Core/Fable.Core.PY.fs new file mode 100644 index 0000000000..b4457bbe81 --- /dev/null +++ b/src/Fable.Core/Fable.Core.PY.fs @@ -0,0 +1,559 @@ +namespace Fable.Import + +namespace Fable.Core + +open System +open System.Text.RegularExpressions + +module PY = + type [] PropertyDescriptor = + abstract configurable: bool option with get, set + abstract enumerable: bool option with get, set + abstract value: obj option with get, set + abstract writable: bool option with get, set + abstract get: unit -> obj + abstract set: v: obj -> unit + + and [] ArrayConstructor = + [] abstract Create: size: int -> 'T[] + abstract isArray: arg: obj -> bool + abstract from: arg: obj -> 'T[] + + and [] NumberConstructor = + abstract isNaN: float -> bool + + and [] Object = + abstract toString: unit -> string + abstract toLocaleString: unit -> string + abstract valueOf: unit -> obj + abstract hasOwnProperty: v: string -> bool + abstract isPrototypeOf: v: obj -> bool + abstract propertyIsEnumerable: v: string -> bool + abstract hasOwnProperty: v: obj -> bool + abstract propertyIsEnumerable: v: obj -> bool + + and [] ObjectConstructor = + abstract getPrototypeOf: o: obj -> obj + abstract getOwnPropertyDescriptor: o: obj * p: string -> PropertyDescriptor + abstract getOwnPropertyNames: o: obj -> ResizeArray + abstract create: o: obj * ?properties: obj -> obj + abstract defineProperty: o: obj * p: string * attributes: PropertyDescriptor -> obj + abstract defineProperties: o: obj * properties: obj -> obj + abstract seal: o: 'T -> 'T + abstract freeze: o: 'T -> 'T + abstract preventExtensions: o: 'T -> 'T + abstract isSealed: o: obj -> bool + abstract isFrozen: o: obj -> bool + abstract isExtensible: o: obj -> bool + abstract keys: o: obj -> ResizeArray + abstract values: o: obj -> ResizeArray + abstract entries: o: obj -> ResizeArray + abstract assign: target: 'T * source: 'U -> obj + abstract assign: target: 'T * source1: 'U * source2: 'V -> obj + abstract assign: target: 'T * source1: 'U * source2: 'V * source3: 'W -> obj + abstract assign: target: obj * [] sources: obj[] -> obj + // abstract getOwnPropertySymbols: o: obj -> ResizeArray + abstract is: value1: obj * value2: obj -> bool + abstract setPrototypeOf: o: obj * proto: obj -> obj + abstract getOwnPropertyDescriptor: o: obj * propertyKey: obj -> PropertyDescriptor + abstract defineProperty: o: obj * propertyKey: obj * attributes: PropertyDescriptor -> obj + + and [] Math = + abstract E: float + abstract LN10: float + abstract LN2: float + abstract LOG2E: float + abstract LOG10E: float + abstract PI: float + abstract SQRT1_2: float + abstract SQRT2: float + abstract abs: x: float -> float + abstract acos: x: float -> float + abstract asin: x: float -> float + abstract atan: x: float -> float + abstract atan2: y: float * x: float -> float + abstract ceil: x: float -> float + abstract cos: x: float -> float + abstract exp: x: float -> float + abstract floor: x: float -> float + abstract log: x: float -> float + abstract max: [] values: float[] -> float + abstract min: [] values: float[] -> float + abstract pow: x: float * y: float -> float + abstract random: unit -> float + abstract round: x: float -> float + abstract sin: x: float -> float + abstract sqrt: x: float -> float + abstract tan: x: float -> float + abstract clz32: x: float -> float + abstract imul: x: float * y: float -> float + abstract sign: x: float -> float + abstract log10: x: float -> float + abstract log2: x: float -> float + abstract log1p: x: float -> float + abstract expm1: x: float -> float + abstract cosh: x: float -> float + abstract sinh: x: float -> float + abstract tanh: x: float -> float + abstract acosh: x: float -> float + abstract asinh: x: float -> float + abstract atanh: x: float -> float + abstract hypot: [] values: float[] -> float + abstract trunc: x: float -> float + abstract fround: x: float -> float + abstract cbrt: x: float -> float + + and [] Date = + abstract toString: unit -> string + abstract toDateString: unit -> string + abstract toTimeString: unit -> string + abstract toLocaleString: unit -> string + abstract toLocaleDateString: unit -> string + abstract toLocaleTimeString: unit -> string + abstract valueOf: unit -> float + abstract getTime: unit -> float + abstract getFullYear: unit -> float + abstract getUTCFullYear: unit -> float + abstract getMonth: unit -> float + abstract getUTCMonth: unit -> float + abstract getDate: unit -> float + abstract getUTCDate: unit -> float + abstract getDay: unit -> float + abstract getUTCDay: unit -> float + abstract getHours: unit -> float + abstract getUTCHours: unit -> float + abstract getMinutes: unit -> float + abstract getUTCMinutes: unit -> float + abstract getSeconds: unit -> float + abstract getUTCSeconds: unit -> float + abstract getMilliseconds: unit -> float + abstract getUTCMilliseconds: unit -> float + abstract getTimezoneOffset: unit -> float + abstract setTime: time: float -> float + abstract setMilliseconds: ms: float -> float + abstract setUTCMilliseconds: ms: float -> float + abstract setSeconds: sec: float * ?ms: float -> float + abstract setUTCSeconds: sec: float * ?ms: float -> float + abstract setMinutes: min: float * ?sec: float * ?ms: float -> float + abstract setUTCMinutes: min: float * ?sec: float * ?ms: float -> float + abstract setHours: hours: float * ?min: float * ?sec: float * ?ms: float -> float + abstract setUTCHours: hours: float * ?min: float * ?sec: float * ?ms: float -> float + abstract setDate: date: float -> float + abstract setUTCDate: date: float -> float + abstract setMonth: month: float * ?date: float -> float + abstract setUTCMonth: month: float * ?date: float -> float + abstract setFullYear: year: float * ?month: float * ?date: float -> float + abstract setUTCFullYear: year: float * ?month: float * ?date: float -> float + abstract toUTCString: unit -> string + abstract toISOString: unit -> string + abstract toJSON: ?key: obj -> string + + and [] DateConstructor = + [] abstract Create: unit -> DateTime + [] abstract Create: value: float -> DateTime + [] abstract Create: value: string -> DateTime + [] abstract Create: year: float * month: float * ?date: float * ?hours: float * ?minutes: float * ?seconds: float * ?ms: float -> DateTime + [] abstract Invoke: unit -> string + abstract parse: s: string -> float + abstract UTC: year: float * month: float * ?date: float * ?hours: float * ?minutes: float * ?seconds: float * ?ms: float -> float + abstract now: unit -> float + + and [] JSON = + abstract parse: text: string * ?reviver: (obj->obj->obj) -> obj + abstract stringify: value: obj * ?replacer: (string->obj->obj) * ?space: obj -> string + + and [] Map<'K, 'V> = + abstract size: int + abstract clear: unit -> unit + abstract delete: key: 'K -> bool + abstract entries: unit -> seq<'K * 'V> + abstract forEach: callbackfn: ('V->'K->Map<'K, 'V>->unit) * ?thisArg: obj -> unit + abstract get: key: 'K -> 'V + abstract has: key: 'K -> bool + abstract keys: unit -> seq<'K> + abstract set: key: 'K * value: 'V -> Map<'K, 'V> + abstract values: unit -> seq<'V> + + and [] MapConstructor = + [] abstract Create: ?iterable: seq<'K * 'V> -> Map<'K, 'V> + + and [] WeakMap<'K, 'V> = + abstract clear: unit -> unit + abstract delete: key: 'K -> bool + abstract get: key: 'K -> 'V + abstract has: key: 'K -> bool + abstract set: key: 'K * value: 'V -> WeakMap<'K, 'V> + + and [] WeakMapConstructor = + [] abstract Create: ?iterable: seq<'K * 'V> -> WeakMap<'K, 'V> + + and [] Set<'T> = + abstract size: int + abstract add: value: 'T -> Set<'T> + abstract clear: unit -> unit + abstract delete: value: 'T -> bool + abstract entries: unit -> seq<'T * 'T> + abstract forEach: callbackfn: ('T->'T->Set<'T>->unit) * ?thisArg: obj -> unit + abstract has: value: 'T -> bool + abstract keys: unit -> seq<'T> + abstract values: unit -> seq<'T> + + and [] SetConstructor = + [] abstract Create: ?iterable: seq<'T> -> Set<'T> + + and [] WeakSet<'T> = + abstract add: value: 'T -> WeakSet<'T> + abstract clear: unit -> unit + abstract delete: value: 'T -> bool + abstract has: value: 'T -> bool + + and [] WeakSetConstructor = + [] abstract Create: ?iterable: seq<'T> -> WeakSet<'T> + + and [] Promise<'T> = + abstract ``then``: ?onfulfilled: ('T->'TResult) * ?onrejected: (obj->'TResult) -> Promise<'TResult> + abstract catch: ?onrejected: (obj->'T) -> Promise<'T> + + and [] PromiseConstructor = + [] abstract Create: executor: ((obj->unit) -> (obj->unit) -> unit) -> Promise<'T> + abstract all: [] values: obj[] -> Promise + abstract race: values: obj seq -> Promise + abstract reject: reason: obj -> Promise + abstract reject: reason: obj -> Promise<'T> + abstract resolve: value: 'T -> Promise<'T> + abstract resolve: unit -> Promise + + and [] RegExpConstructor = + [] abstract Create: pattern: string * ?flags: string -> Regex + + and [] ArrayBuffer = + abstract byteLength: int + abstract slice: ``begin``: int * ?``end``: int -> ArrayBuffer + + and [] ArrayBufferConstructor = + [] abstract Create: byteLength: int -> ArrayBuffer + abstract isView: arg: obj -> bool + + and [] ArrayBufferView = + abstract buffer: ArrayBuffer + abstract byteLength: int + abstract byteOffset: int + + and ArrayBufferViewConstructor = + [] abstract Create: size: int -> ArrayBufferView + + and [] DataView = + abstract buffer: ArrayBuffer + abstract byteLength: int + abstract byteOffset: int + abstract getFloat32: byteOffset: int * ?littleEndian: bool -> float32 + abstract getFloat64: byteOffset: int * ?littleEndian: bool -> float + abstract getInt8: byteOffset: int -> sbyte + abstract getInt16: byteOffset: int * ?littleEndian: bool -> int16 + abstract getInt32: byteOffset: int * ?littleEndian: bool -> int32 + abstract getUint8: byteOffset: int -> byte + abstract getUint16: byteOffset: int * ?littleEndian: bool -> uint16 + abstract getUint32: byteOffset: int * ?littleEndian: bool -> uint32 + abstract setFloat32: byteOffset: int * value: float32 * ?littleEndian: bool -> unit + abstract setFloat64: byteOffset: int * value: float * ?littleEndian: bool -> unit + abstract setInt8: byteOffset: int * value: sbyte -> unit + abstract setInt16: byteOffset: int * value: int16 * ?littleEndian: bool -> unit + abstract setInt32: byteOffset: int * value: int32 * ?littleEndian: bool -> unit + abstract setUint8: byteOffset: int * value: byte -> unit + abstract setUint16: byteOffset: int * value: uint16 * ?littleEndian: bool -> unit + abstract setUint32: byteOffset: int * value: uint32 * ?littleEndian: bool -> unit + + and [] DataViewConstructor = + [] abstract Create: buffer: ArrayBuffer * ?byteOffset: int * ?byteLength: float -> DataView + + and TypedArray = + abstract buffer: ArrayBuffer + abstract byteLength: int + abstract byteOffset: int + abstract length: int + abstract copyWithin: targetStartIndex:int * start:int * ? ``end``:int -> unit + abstract entries: unit -> obj + abstract keys: unit -> obj + abstract join: separator:string -> string + + and TypedArray<'T> = + inherit TypedArray + [] abstract Item: index: int -> 'T with get, set + abstract fill: value:'T * ?``begin``:int * ?``end``:int -> TypedArray<'T> + abstract filter: ('T -> int -> TypedArray<'T> -> bool) -> TypedArray<'T> + abstract filter: ('T -> int -> bool) -> TypedArray<'T> + abstract filter: ('T -> bool) -> TypedArray<'T> + abstract find: ('T -> int -> TypedArray<'T> -> bool) -> 'T option + abstract find: ('T -> int -> bool) -> 'T option + abstract find: ('T -> bool) -> 'T option + abstract findIndex: ('T -> int -> TypedArray<'T> -> bool) -> int + abstract findIndex: ('T -> int -> bool) -> int + abstract findIndex: ('T -> bool) -> int + abstract forEach: ('T -> int -> TypedArray<'T> -> bool) -> unit + abstract forEach: ('T -> int -> bool) -> unit + abstract forEach: ('T -> bool) -> unit + abstract includes: searchElement:'T * ?fromIndex:int -> bool + abstract indexOf: searchElement:'T * ?fromIndex:int -> int + abstract lastIndexOf: searchElement:'T * ?fromIndex:int -> int + abstract map: ('T -> int -> TypedArray<'T> -> 'U) -> TypedArray<'U> + abstract map: ('T -> int -> 'U) -> TypedArray<'U> + abstract map: ('T -> 'U) -> TypedArray<'U> + abstract reduce: ('State -> 'T -> int -> TypedArray<'T> -> 'State) * state:'State -> 'State + abstract reduce: ('State -> 'T -> int -> 'State) * state:'State -> 'State + abstract reduce: ('State -> 'T -> 'State) * state:'State -> 'State + abstract reduceRight: ('State -> 'T -> int -> TypedArray<'T> -> 'State) * state:'State -> 'State + abstract reduceRight: ('State -> 'T -> int -> 'State) * state:'State -> 'State + abstract reduceRight: ('State -> 'T -> 'State) * state:'State -> 'State + abstract reverse: unit -> TypedArray<'T> + abstract set: source:Array * ?offset:int -> unit + abstract set: source:#TypedArray * ?offset:int -> unit + abstract slice: ?``begin``:int * ?``end``:int -> TypedArray<'T> + abstract some: ('T -> int -> TypedArray<'T> -> bool) -> bool + abstract some: ('T -> int -> bool) -> bool + abstract some: ('T -> bool) -> bool + abstract sort: ?sortFunction:('T -> 'T -> int) -> TypedArray<'T> + abstract subarray: ?``begin``:int * ?``end``:int -> TypedArray<'T> + abstract values: unit -> obj + + + and Int8Array = TypedArray + + and Int8ArrayConstructor = + [] abstract Create: size: int -> Int8Array + [] abstract Create: typedArray: TypedArray -> Int8Array + [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Int8Array + [] abstract Create: data:obj -> Int8Array + + + and Uint8Array = TypedArray + + and Uint8ArrayConstructor = + [] abstract Create: size: int -> Uint8Array + [] abstract Create: typedArray: TypedArray -> Uint8Array + [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Uint8Array + [] abstract Create: data:obj -> Uint8Array + + + and Uint8ClampedArray = TypedArray + + and Uint8ClampedArrayConstructor = + [] abstract Create: size: int -> Uint8ClampedArray + [] abstract Create: typedArray: TypedArray -> Uint8ClampedArray + [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Uint8ClampedArray + [] abstract Create: data:obj -> Uint8ClampedArray + + + and Int16Array = TypedArray + + and Int16ArrayConstructor = + [] abstract Create: size: int -> Int16Array + [] abstract Create: typedArray: TypedArray -> Int16Array + [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Int16Array + [] abstract Create: data:obj -> Int16Array + + + and Uint16Array = TypedArray + + and Uint16ArrayConstructor = + [] abstract Create: size: int -> Uint16Array + [] abstract Create: typedArray: TypedArray -> Uint16Array + [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Uint16Array + [] abstract Create: data:obj -> Uint16Array + + + and Int32Array = TypedArray + + and Int32ArrayConstructor = + [] abstract Create: size: int -> Int32Array + [] abstract Create: typedArray: TypedArray -> Int32Array + [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Int32Array + [] abstract Create: data:obj -> Int32Array + + + and Uint32Array = TypedArray + + and Uint32ArrayConstructor = + [] abstract Create: size: int -> Uint32Array + [] abstract Create: typedArray: TypedArray -> Uint32Array + [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Uint32Array + [] abstract Create: data:obj -> Uint32Array + + + and Float32Array = TypedArray + + and Float32ArrayConstructor = + [] abstract Create: size: int -> Float32Array + [] abstract Create: typedArray: TypedArray -> Float32Array + [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Float32Array + [] abstract Create: data:obj -> Float32Array + + + and Float64Array = TypedArray + + and Float64ArrayConstructor = + [] abstract Create: size: int -> Float64Array + [] abstract Create: typedArray: TypedArray -> Float64Array + [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Float64Array + [] abstract Create: data:obj -> Float64Array + + + and BigInt64Array = TypedArray + + and BigInt64ArrayConstructor = + [] abstract Create: size: int -> BigInt64Array + [] abstract Create: typedArray: TypedArray -> BigInt64Array + [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> BigInt64Array + [] abstract Create: data:obj -> BigInt64Array + + + // no equivalent ? + //and BigUint64Array = TypedArray + + // and BigUint64ArrayConstructor = + // [] abstract Create: size: int -> BigUint64Array + // [] abstract Create: typedArray: TypedArray -> BigUint64Array + // [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> BigUint64Array + // [] abstract Create: data:obj -> BigUint64Array + + + and [] Console = + abstract ``assert``: ?test: bool * ?message: string * [] optionalParams: obj[] -> unit + abstract clear: unit -> unit + abstract count: ?countTitle: string -> unit + abstract debug: ?message: string * [] optionalParams: obj[] -> unit + abstract dir: ?value: obj * [] optionalParams: obj[] -> unit + abstract dirxml: value: obj -> unit + abstract error: ?message: obj * [] optionalParams: obj[] -> unit + abstract group: ?groupTitle: string -> unit + abstract groupCollapsed: ?groupTitle: string -> unit + abstract groupEnd: unit -> unit + abstract info: ?message: obj * [] optionalParams: obj[] -> unit + abstract log: ?message: obj * [] optionalParams: obj[] -> unit + abstract profile: ?reportName: string -> unit + abstract profileEnd: unit -> unit + abstract time: ?timerName: string -> unit + abstract timeEnd: ?timerName: string -> unit + abstract trace: ?message: obj * [] optionalParams: obj[] -> unit + abstract warn: ?message: obj * [] optionalParams: obj[] -> unit + abstract table: ?data: obj -> unit + + let [] NaN: float = pyNative + let [] Infinity: float = pyNative + let [] Math: Math = pyNative + let [] JSON: JSON = pyNative + let [] eval: string -> string = pyNative + let [] isFinite: float -> bool = pyNative + let [] isNaN: float -> bool = pyNative + let [] parseFloat: string -> float = pyNative + let [] parseInt: string -> int -> int = pyNative + let [] decodeURI: string -> string = pyNative + let [] decodeURIComponent: string -> string = pyNative + let [] encodeURI: string -> string = pyNative + let [] encodeURIComponent: string -> string = pyNative + let [] console : Console = pyNative + let [] setTimeout (callback: unit -> unit) (ms: int): int = pyNative + let [] clearTimeout (token: int): unit = pyNative + let [] setInterval(callback: unit -> unit) (ms: int) : int = pyNative + let [] clearInterval (token: int): unit = pyNative + let [] debugger () : unit = pyNative + let [] undefined<'a> : 'a = pyNative + + [] + let private CONSTRUCTORS_WARNING = "JS constructors are now in Fable.Core.JS.Constructors module to prevent conflicts with modules with same name" + + [] + let [] Number: NumberConstructor = pyNative + + [] + let [] Object: ObjectConstructor = pyNative + + [] + let [] Date: DateConstructor = pyNative + + [] + let [] Map: MapConstructor = pyNative + + [] + let [] WeakMap: WeakMapConstructor = pyNative + + [] + let [] Set: SetConstructor = pyNative + + [] + let [] WeakSet: WeakSetConstructor = pyNative + + [] + let [] Promise: PromiseConstructor = pyNative + + [] + let [] RegExp: RegExpConstructor = pyNative + + [] + let [] Array: ArrayConstructor = pyNative + + [] + let [] DataView: DataViewConstructor = pyNative + + [] + let [] ArrayBuffer: ArrayBufferConstructor = pyNative + + [] + let [] ArrayBufferView: ArrayBufferViewConstructor = pyNative + + [] + let [] Int8Array: Int8ArrayConstructor = pyNative + + [] + let [] Uint8Array: Uint8ArrayConstructor = pyNative + + [] + let [] Uint8ClampedArray: Uint8ClampedArrayConstructor = pyNative + + [] + let [] Int16Array: Int16ArrayConstructor = pyNative + + [] + let [] Uint16Array: Uint16ArrayConstructor = pyNative + + [] + let [] Int32Array: Int32ArrayConstructor = pyNative + + [] + let [] Uint32Array: Uint32ArrayConstructor = pyNative + + [] + let [] Float32Array: Float32ArrayConstructor = pyNative + + [] + let [] Float64Array: Float64ArrayConstructor = pyNative + + // [] + // let [] BigInt64Array: BigInt64ArrayConstructor = pyNative + + [] + module Constructors = + + let [] Number: NumberConstructor = pyNative + let [] Object: ObjectConstructor = pyNative + let [] Date: DateConstructor = pyNative + let [] Map: MapConstructor = pyNative + let [] WeakMap: WeakMapConstructor = pyNative + let [] Set: SetConstructor = pyNative + let [] WeakSet: WeakSetConstructor = pyNative + let [] Promise: PromiseConstructor = pyNative + let [] RegExp: RegExpConstructor = pyNative + let [] Array: ArrayConstructor = pyNative + let [] DataView: DataViewConstructor = pyNative + let [] ArrayBuffer: ArrayBufferConstructor = pyNative + let [] ArrayBufferView: ArrayBufferViewConstructor = pyNative + let [] Int8Array: Int8ArrayConstructor = pyNative + let [] Uint8Array: Uint8ArrayConstructor = pyNative + let [] Uint8ClampedArray: Uint8ClampedArrayConstructor = pyNative + let [] Int16Array: Int16ArrayConstructor = pyNative + let [] Uint16Array: Uint16ArrayConstructor = pyNative + let [] Int32Array: Int32ArrayConstructor = pyNative + let [] Uint32Array: Uint32ArrayConstructor = pyNative + let [] Float32Array: Float32ArrayConstructor = pyNative + let [] Float64Array: Float64ArrayConstructor = pyNative + let [] BigInt64Array: BigInt64ArrayConstructor = pyNative + // let [] BigUint64Array: BigUint64ArrayConstructor = pyNative diff --git a/src/Fable.Core/Fable.Core.fsproj b/src/Fable.Core/Fable.Core.fsproj index 80e9e0c566..3e1f2d3649 100644 --- a/src/Fable.Core/Fable.Core.fsproj +++ b/src/Fable.Core/Fable.Core.fsproj @@ -12,6 +12,7 @@ + diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 721384ba8f..693044a272 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -339,6 +339,7 @@ module Reflection = | Fable.LambdaType _ | Fable.DelegateType _ -> jsTypeof "function" expr | Fable.Array _ | Fable.Tuple _ -> let expr, stmts = com.TransformAsExpr(ctx, expr) + printfn "Fable.Array: %A" (expr, stmts) libCall com ctx None "Util" "isArrayLike" [ expr ], stmts | Fable.List _ -> jsInstanceof (libValue com ctx "List" "FSharpList") expr @@ -499,6 +500,7 @@ module Util = | args -> args let getUniqueNameInRootScope (ctx: Context) name = + let name = Helpers.clean name let name = (name, Naming.NoMemberPart) ||> Naming.sanitizeIdent (fun name -> ctx.UsedNames.RootScope.Contains(name) || ctx.UsedNames.DeclarationScopes.Contains(name)) @@ -589,7 +591,7 @@ module Util = | n -> com.GetIdentifierAsExpr(ctx, n), [] let get (com: IPythonCompiler) ctx r left memberName = - // printfn "get: %A" (left, memberName) + printfn "get: %A" (left, memberName) match left with | Expression.Dict(_) -> let expr = Expression.constant(memberName) @@ -599,7 +601,7 @@ module Util = Expression.attribute (value = left, attr = expr, ctx = Load) let getExpr com ctx r (object: Expression) (expr: Expression) = - // printfn "getExpr: %A" (object, expr) + printfn "getExpr: %A" (object, expr) match expr with | Expression.Constant(value=name) when (name :? string) -> let name = name :?> string |> Identifier @@ -612,7 +614,6 @@ module Util = Expression.subscript(value = object, slice = e, ctx = Load), [] let rec getParts com ctx (parts: string list) (expr: Expression) = - printfn "getParts" match parts with | [] -> expr | m::ms -> get com ctx None expr m |> getParts com ctx ms @@ -729,7 +730,7 @@ module Util = | Attached of isStatic: bool let getMemberArgsAndBody (com: IPythonCompiler) ctx kind hasSpread (args: Fable.Ident list) (body: Fable.Expr) = - printfn "getMemberArgsAndBody" + // printfn "getMemberArgsAndBody" let funcName, genTypeParams, args, body = match kind, args with | Attached(isStatic=false), (thisArg::args) -> @@ -886,7 +887,7 @@ module Util = | Some (TransformExpr com ctx (e, stmts)) -> if mustWrapOption t then libCall com ctx r "Option" "some" [ e ], stmts - else e, [] + else e, stmts | None -> undefined r, [] | Fable.EnumConstant(x,_) -> com.TransformAsExpr(ctx, x) @@ -1185,10 +1186,10 @@ module Util = |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) | None -> [], [] - [ Statement.try'(transformBlock com ctx returnStrategy body, - ?handlers=handlers, finalBody=finalizer, ?loc=r) ] + stmts @ [ Statement.try'(transformBlock com ctx returnStrategy body, ?handlers=handlers, finalBody=finalizer, ?loc=r) ] let rec transformIfStatement (com: IPythonCompiler) ctx r ret guardExpr thenStmnt elseStmnt = + printfn "transformIfStatement" let expr, stmts = com.TransformAsExpr(ctx, guardExpr) match expr with | Constant(value=value) when (value :? bool) -> @@ -1203,6 +1204,7 @@ module Util = let block, stmts = com.TransformAsStatements(ctx, ret, elseStmnt) |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) + match block with | [ ] -> Statement.if'(guardExpr, thenStmnt, ?loc=r), stmts | [ elseStmnt ] -> Statement.if'(guardExpr, thenStmnt, [ elseStmnt ], ?loc=r), stmts @@ -1211,11 +1213,37 @@ module Util = let transformGet (com: IPythonCompiler) ctx range typ fableExpr kind = match kind with + | Fable.ExprGet(TransformExpr com ctx (prop, stmts)) -> let expr, stmts' = com.TransformAsExpr(ctx, fableExpr) let expr, stmts'' = getExpr com ctx range expr prop expr, stmts @ stmts' @ stmts'' + | Fable.FieldGet(fieldName="message") -> + let func = Expression.name("str") + let left, stmts = com.TransformAsExpr(ctx, fableExpr) + Expression.call (func, [ left ]), stmts + | Fable.FieldGet(fieldName="push") -> + let attr = Python.Identifier("append") + let value, stmts = com.TransformAsExpr(ctx, fableExpr) + Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | Fable.FieldGet(fieldName="length") -> + let func = Expression.name("len") + let left, stmts = com.TransformAsExpr(ctx, fableExpr) + Expression.call (func, [ left ]), stmts + | Fable.FieldGet(fieldName="toLocaleUpperCase") -> + let attr = Python.Identifier("upper") + let value, stmts = com.TransformAsExpr(ctx, fableExpr) + Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | Fable.FieldGet(fieldName="toLocaleLowerCase") -> + let attr = Python.Identifier("lower") + let value, stmts = com.TransformAsExpr(ctx, fableExpr) + Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | Fable.FieldGet(fieldName="indexOf") -> + let attr = Python.Identifier("find") + let value, stmts = com.TransformAsExpr(ctx, fableExpr) + Expression.attribute (value = value, attr = attr, ctx = Load), stmts + | Fable.FieldGet(fieldName,_) -> let fableExpr = match fableExpr with @@ -1224,6 +1252,7 @@ module Util = | Fable.Value(Fable.BaseValue(_,t), r) -> Fable.Value(Fable.BaseValue(None, t), r) | _ -> fableExpr let expr, stmts = com.TransformAsExpr(ctx, fableExpr) + printfn "Fable.FieldGet: %A" fieldName get com ctx range expr fieldName, stmts | Fable.ListHead -> @@ -1262,18 +1291,19 @@ module Util = expr, stmts @ stmts' @ stmts'' let transformSet (com: IPythonCompiler) ctx range fableExpr typ (value: Fable.Expr) kind = - //printfn "transformSet: %A" (fableExpr, value) + // printfn "transformSet: %A" (fableExpr, value) let expr, stmts = com.TransformAsExpr(ctx, fableExpr) let value, stmts' = com.TransformAsExpr(ctx, value) let ret, stmts'' = match kind with - | Fable.ValueSet -> expr, stmts @ stmts' + | Fable.ValueSet -> + expr, [] | Fable.ExprSet(TransformExpr com ctx (e, stmts'')) -> let expr, stmts''' = getExpr com ctx None expr e - expr, stmts @ stmts' @ stmts'' @ stmts''' + expr, stmts'' @ stmts''' | Fable.FieldSet(fieldName) -> - get com ctx None expr fieldName, stmts @ stmts' - assign range ret value, stmts'' + get com ctx None expr fieldName, [] + assign range ret value, stmts @ stmts' @ stmts'' let transformBindingExprBody (com: IPythonCompiler) (ctx: Context) (var: Fable.Ident) (value: Fable.Expr) = match value with @@ -1662,36 +1692,9 @@ module Util = | Fable.Operation(kind, _, range) -> transformOperation com ctx range kind - | Fable.Get(expr, Fable.FieldGet(fieldName="push"), _, _) -> - let attr = Python.Identifier("append") - let value, stmts = com.TransformAsExpr(ctx, expr) - Expression.attribute (value = value, attr = attr, ctx = Load), stmts - - | Fable.Get(expr, Fable.FieldGet(fieldName="toLocaleUpperCase"), _, _) -> - let attr = Python.Identifier("upper") - let value, stmts = com.TransformAsExpr(ctx, expr) - Expression.attribute (value = value, attr = attr, ctx = Load), stmts - | Fable.Get(expr, Fable.FieldGet(fieldName="toLocaleLowerCase"), _, _) -> - let attr = Python.Identifier("lower") - let value, stmts = com.TransformAsExpr(ctx, expr) - Expression.attribute (value = value, attr = attr, ctx = Load), stmts - | Fable.Get(expr, Fable.FieldGet(fieldName="indexOf"), _, _) -> - let attr = Python.Identifier("find") - let value, stmts = com.TransformAsExpr(ctx, expr) - Expression.attribute (value = value, attr = attr, ctx = Load), stmts - | Fable.Get(Fable.IdentExpr({Name = "String"}), Fable.FieldGet(fieldName="fromCharCode"), _, _) -> let func = Expression.name("chr") func, [] - | Fable.Get(expr, Fable.FieldGet(fieldName="length"), _, _) -> - let func = Expression.name("len") - let left, stmts = com.TransformAsExpr(ctx, expr) - Expression.call (func, [ left ]), stmts - - | Fable.Get(expr, Fable.FieldGet(fieldName="message"), _, _) -> - let func = Expression.name("str") - let left, stmts = com.TransformAsExpr(ctx, expr) - Expression.call (func, [ left ]), stmts | Fable.Get(Fable.IdentExpr({Name=name;Type=Fable.AnonymousRecordType(_)}) as expr, Fable.FieldGet(fieldName=index), _, _) -> let left, stmts = com.TransformAsExpr(ctx, expr) @@ -2275,7 +2278,7 @@ module Util = match name with | "*" | _ -> name - |> Helpers.clean + |> getUniqueNameInRootScope ctx |> Python.Identifier |> Some @@ -2292,6 +2295,7 @@ module Compiler = addWarning com [] range msg member _.GetImportExpr(ctx, moduleName, ?name, ?r) = + // printfn "GetImportExpr: %A" (moduleName, name) let cachedName = moduleName + "::" + defaultArg name "module" match imports.TryGetValue(cachedName) with | true, i -> diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 7fd676c340..6d4d9a9fdb 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -1007,7 +1007,8 @@ let injectArg com (ctx: Context) r moduleName methName (genArgs: (string * Type) match genArg with | Number(numberKind,_) when com.Options.TypedArrays -> args @ [getTypedArrayName com numberKind |> makeIdentExpr] - | _ -> args + | _ -> + args @ [ Expr.Value(ValueKind.NewOption(None, genArg, false), None) ] | Types.adder -> args @ [makeGenericAdder com ctx genArg] | Types.averager -> diff --git a/src/fable-library-py/fable/Native.fs b/src/fable-library-py/fable/Native.fs index 8f02cd1bd4..f9c6e7c72a 100644 --- a/src/fable-library-py/fable/Native.fs +++ b/src/fable-library-py/fable/Native.fs @@ -29,11 +29,11 @@ module Helpers = if callable cons then cons.Allocate(len) else - JS.Constructors.Array.Create(len) + PY.Constructors.Array.Create(len) - let inline isDynamicArrayImpl arr = JS.Constructors.Array.isArray arr + let inline isDynamicArrayImpl arr = PY.Constructors.Array.isArray arr - let inline isTypedArrayImpl arr = JS.Constructors.ArrayBuffer.isView arr + let inline isTypedArrayImpl arr = PY.Constructors.ArrayBuffer.isView arr // let inline typedArraySetImpl (target: obj) (source: obj) (offset: int): unit = // !!target?set(source, offset) From 8ee6a531aead0bc24691b0be207ded604a712b4e Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 14 Jul 2021 08:31:37 +0200 Subject: [PATCH 124/145] Add set tests Fix switch handling --- src/Fable.Transforms/Python/Fable2Python.fs | 96 ++++++++++++++------- src/fable-library-py/fable/Native.fs | 6 +- tests/Python/TestSet.fs | 44 +++++----- 3 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 693044a272..df36c3e489 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -339,7 +339,6 @@ module Reflection = | Fable.LambdaType _ | Fable.DelegateType _ -> jsTypeof "function" expr | Fable.Array _ | Fable.Tuple _ -> let expr, stmts = com.TransformAsExpr(ctx, expr) - printfn "Fable.Array: %A" (expr, stmts) libCall com ctx None "Util" "isArrayLike" [ expr ], stmts | Fable.List _ -> jsInstanceof (libValue com ctx "List" "FSharpList") expr @@ -591,7 +590,7 @@ module Util = | n -> com.GetIdentifierAsExpr(ctx, n), [] let get (com: IPythonCompiler) ctx r left memberName = - printfn "get: %A" (left, memberName) + // printfn "get: %A" (left, memberName) match left with | Expression.Dict(_) -> let expr = Expression.constant(memberName) @@ -601,7 +600,7 @@ module Util = Expression.attribute (value = left, attr = expr, ctx = Load) let getExpr com ctx r (object: Expression) (expr: Expression) = - printfn "getExpr: %A" (object, expr) + // printfn "getExpr: %A" (object, expr) match expr with | Expression.Constant(value=name) when (name :? string) -> let name = name :?> string |> Identifier @@ -750,7 +749,6 @@ module Util = let ctx = { ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams genTypeParams } let args, body = transformFunction com ctx funcName args body - printfn "hasSpread: %A" hasSpread // let args = // let len = args.Args.Length // if not hasSpread || len = 0 then args @@ -1100,6 +1098,9 @@ module Util = let thisArg, stmts = info.ThisArg |> Option.map (fun e -> com.TransformAsExpr(ctx, e)) |> Option.toList |> Helpers.unzipArgs let exprs, stmts' = transformCallArgs com ctx info.HasSpread info.Args + if macro.StartsWith("functools") then + com.GetImportExpr(ctx, "functools") |> ignore + let args = exprs |> List.append thisArg @@ -1354,31 +1355,60 @@ module Util = let actual, stmts = getUnionExprTag com ctx None expr Expression.compare(actual, [Eq], [ expected ], ?loc=range), stmts - let transformSwitch (com: IPythonCompiler) ctx useBlocks returnStrategy evalExpr cases defaultCase: Statement = - // let cases = - // cases |> List.collect (fun (guards, expr) -> - // // Remove empty branches - // match returnStrategy, expr, guards with - // | None, Fable.Value(Fable.UnitConstant,_), _ - // | _, _, [] -> [] - // | _, _, guards -> - // let guards, lastGuard = List.splitLast guards - // let guards = guards |> List.map (fun e -> SwitchCase.switchCase([||], com.TransformAsExpr(ctx, e))) - // let caseBody = com.TransformAsStatements(ctx, returnStrategy, expr) - // let caseBody = - // match returnStrategy with - // | Some Return -> caseBody - // | _ -> Array.append caseBody [|Statement.break'()|] - // guards @ [SwitchCase.switchCase(caseBody, com.TransformAsExpr(ctx, lastGuard))] - // ) - // let cases = - // match defaultCase with - // | Some expr -> - // let defaultCaseBody = com.TransformAsStatements(ctx, returnStrategy, expr) - // cases @ [SwitchCase.switchCase(consequent defaultCaseBody)] - // | None -> cases - // Statement.switchStatement(com.TransformAsExpr(ctx, evalExpr), List.toArray cases) - Statement.expr(Expression.name("Not implemented")) + let transformSwitch (com: IPythonCompiler) ctx useBlocks returnStrategy evalExpr cases defaultCase: Statement list = + let cases = + cases |> List.collect (fun (guards, expr) -> + // Remove empty branches + match returnStrategy, expr, guards with + | None, Fable.Value(Fable.UnitConstant,_), _ + | _, _, [] -> [] + | _, _, guards -> + let guards, lastGuard = List.splitLast guards + let guards = guards |> List.map (fun e -> + let expr, stmts = com.TransformAsExpr(ctx, e) + (stmts, Some expr)) + let caseBody = com.TransformAsStatements(ctx, returnStrategy, expr) + let caseBody = + match returnStrategy with + | Some Return -> caseBody + | _ -> List.append caseBody [ Statement.break'() ] + let expr, stmts = com.TransformAsExpr(ctx, lastGuard) + guards @ [(stmts @ caseBody, Some expr)] + ) + let cases = + match defaultCase with + | Some expr -> + let defaultCaseBody = com.TransformAsStatements(ctx, returnStrategy, expr) + cases @ [(defaultCaseBody, None)] + | None -> cases + + let value, stmts = com.TransformAsExpr(ctx, evalExpr) + + let rec ifThenElse (fallThrough: Python.Expression option) (cases: (Statement list * Expression option) list): Python.Statement list option = + match cases with + | [] -> None + | (body, test) :: cases -> + match test with + | None -> body |> Some + | Some test -> + let expr = Expression.compare (left = value, ops = [ Eq ], comparators = [ test ]) + + let test = + match fallThrough with + | Some ft -> Expression.boolOp (op = Or, values = [ ft; expr ]) + | _ -> expr + + // Check for fallthrough + if body.IsEmpty then + ifThenElse (Some test) cases + else + [ Statement.if' (test = test, body = body, ?orelse = ifThenElse None cases) ] + |> Some + + let result = cases |> ifThenElse None + match result with + | Some ifStmt -> stmts @ ifStmt + | None -> [] let matchTargetIdentAndValues idents values = if List.isEmpty idents then [] @@ -1546,10 +1576,10 @@ module Util = let cases = groupSwitchCases (Fable.Number(Int32, None)) cases (defaultIndex, defaultBoundValues) let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, Fable.Number(Int32, None)) let switch1 = transformSwitch com ctx false (Some targetAssign) evalExpr cases (Some defaultCase) - [ yield! multiVarDecl; switch1; switch2 ] + multiVarDecl @ switch1 @ switch2 | None -> let decisionTree = com.TransformAsStatements(ctx, Some targetAssign, treeExpr) - [ yield! multiVarDecl; yield! decisionTree; yield switch2 ] + multiVarDecl @ decisionTree @ switch2 let transformDecisionTreeAsStatements (com: IPythonCompiler) (ctx: Context) returnStrategy (targets: (Fable.Ident list * Fable.Expr) list) (treeExpr: Fable.Expr): Statement list = @@ -1567,7 +1597,7 @@ module Util = let cases = cases |> List.map (fun (caseExpr, targetIndex, boundValues) -> [caseExpr], Fable.DecisionTreeSuccess(targetIndex, boundValues, t)) let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, t) - [ transformSwitch com ctx true returnStrategy evalExpr cases (Some defaultCase) ] + transformSwitch com ctx true returnStrategy evalExpr cases (Some defaultCase) | None -> com.TransformAsStatements(ctx, returnStrategy, treeExpr) | targetsWithMultiRefs -> @@ -1589,7 +1619,7 @@ module Util = let cases = groupSwitchCases t cases (defaultIndex, defaultBoundValues) let ctx = { ctx with DecisionTargets = targets } let defaultCase = Fable.DecisionTreeSuccess(defaultIndex, defaultBoundValues, t) - [ transformSwitch com ctx true returnStrategy evalExpr cases (Some defaultCase) ] + transformSwitch com ctx true returnStrategy evalExpr cases (Some defaultCase) | None -> transformDecisionTreeWithTwoSwitches com ctx returnStrategy targets treeExpr else diff --git a/src/fable-library-py/fable/Native.fs b/src/fable-library-py/fable/Native.fs index f9c6e7c72a..f73d7e301f 100644 --- a/src/fable-library-py/fable/Native.fs +++ b/src/fable-library-py/fable/Native.fs @@ -43,7 +43,8 @@ module Helpers = let inline fillImpl (array: 'T []) (value: 'T) (start: int) (count: int) : 'T [] = !! array?fill (value, start, start + count) - let inline foldImpl (folder: 'State -> 'T -> 'State) (state: 'State) (array: 'T []) : 'State = + [] + let foldImpl (folder: 'State -> 'T -> 'State) (state: 'State) (array: 'T []) : 'State = !! array?reduce (System.Func<'State, 'T, 'State>(folder), state) let inline foldIndexedImpl (folder: 'State -> 'T -> int -> 'State) (state: 'State) (array: 'T []) : 'State = @@ -88,7 +89,8 @@ module Helpers = let inline filterImpl (predicate: 'T -> bool) (array: 'T []) : 'T [] = !! array?filter (predicate) - let inline reduceImpl (reduction: 'T -> 'T -> 'T) (array: 'T []) : 'T = !! array?reduce (reduction) + [] + let reduceImpl (reduction: 'T -> 'T -> 'T) (array: 'T []) : 'T = pyNative let inline reduceBackImpl (reduction: 'T -> 'T -> 'T) (array: 'T []) : 'T = !! array?reduceRight (reduction) diff --git a/tests/Python/TestSet.fs b/tests/Python/TestSet.fs index 99ee2d39c2..dc02e2643e 100644 --- a/tests/Python/TestSet.fs +++ b/tests/Python/TestSet.fs @@ -2,28 +2,28 @@ module Fable.Tests.Set open Util.Testing -// [] -// let ``test set function works`` () = -// let xs = set [1] -// xs |> Set.isEmpty -// |> equal false +[] +let ``test set function works`` () = + let xs = set [1] + xs |> Set.isEmpty + |> equal false -// [] -// let ``test Set.isEmpty works`` () = -// let xs = set [] -// Set.isEmpty xs |> equal true -// let ys = set [1] -// Set.isEmpty ys |> equal false +[] +let ``test Set.isEmpty works`` () = + let xs = set [] + Set.isEmpty xs |> equal true + let ys = set [1] + Set.isEmpty ys |> equal false -// [] -// let ``test Set.IsEmpty works`` () = -// let xs = Set.empty -// xs.IsEmpty |> equal true -// let ys = set [1; 1] -// ys.IsEmpty |> equal false +[] +let ``test Set.IsEmpty works`` () = + let xs = Set.empty + xs.IsEmpty |> equal true + let ys = set [1; 1] + ys.IsEmpty |> equal false -// [] -// let ``test Set.Count works`` () = -// let xs = Set.empty |> Set.add 1 -// xs.Count -// |> equal 1 +[] +let ``test Set.Count works`` () = + let xs = Set.empty |> Set.add 1 + xs.Count + |> equal 1 From 4e33158abaa003f7babedb92ca59adfd0efd4836 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 14 Jul 2021 11:08:07 +0200 Subject: [PATCH 125/145] Fixes for object expressions --- src/Fable.Transforms/Python/Fable2Python.fs | 34 +++++++++++---------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index df36c3e489..a695c8b7f4 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -943,11 +943,13 @@ module Util = // so use a class expression instead. See https://github.com/fable-compiler/Fable/pull/2165#issuecomment-695835444 m.Info.IsSetter || (m.Info.IsGetter && canHaveSideEffects m.Body)) - let makeMethod prop computed hasSpread args body = + let makeMethod prop hasSpread args body decorators = let args, body = getMemberArgsAndBody com ctx (Attached(isStatic=false)) hasSpread args body - let name = Identifier("test") - FunctionDef.Create(name, args, body) + let name = com.GetIdentifier(ctx, prop) + let self = Arg.arg("self") + let args = { args with Args = self::args.Args } + FunctionDef.Create(name, args, body, decorators) let members = members |> List.collect (fun memb -> @@ -955,25 +957,25 @@ module Util = let prop, computed = memberFromName com ctx memb.Name // If compileAsClass is false, it means getters don't have side effects // and can be compiled as object fields (see condition above) - if info.IsValue || (not compileAsClass && info.IsGetter) then - let expr, stmts = com.TransformAsExpr(ctx, memb.Body) - stmts @ [ Statement.assign([prop], expr) ] - //[ObjectMember.objectProperty(prop, expr, computed_=computed)] - elif info.IsGetter then - let arguments = memb.Args - [ makeMethod prop computed false memb.Args memb.Body ] + // if info.IsValue || (not compileAsClass && info.IsGetter) then + // let expr, stmts = com.TransformAsExpr(ctx, memb.Body) + // stmts @ [ Statement.assign([prop], expr) ] + if info.IsGetter then + let decorators = [ Expression.name("property") ] + [ makeMethod memb.Name false memb.Args memb.Body decorators ] elif info.IsSetter then - [makeMethod prop computed false memb.Args memb.Body] + let decorators = [ Expression.name ("property") ] + [ makeMethod memb.Name false memb.Args memb.Body decorators ] elif info.IsEnumerator then - let method = makeMethod prop computed info.HasSpread memb.Args memb.Body + let method = makeMethod memb.Name info.HasSpread memb.Args memb.Body [] let iterator = let body = enumerator2iterator com ctx let name = com.GetIdentifier(ctx, "__iter__") let args = Arguments.arguments() FunctionDef.Create(name = name, args = args, body = body) - [method; iterator] + [ method; iterator] else - [makeMethod prop computed info.HasSpread memb.Args memb.Body] + [ makeMethod memb.Name info.HasSpread memb.Args memb.Body [] ] ) // let classMembers = @@ -1004,9 +1006,9 @@ module Util = match classMembers with | [] -> [ Pass] | _ -> classMembers - let name = Helpers.getUniqueIdentifier "lifted" + let name = Helpers.getUniqueIdentifier "ObjectExpr" let stmt = Statement.classDef(name, body=classBody, bases=(baseExpr |> Option.toList) ) - Expression.name (name), [ stmt ] + Expression.call(Expression.name (name)), [ stmt ] let transformCallArgs (com: IPythonCompiler) ctx hasSpread args : Expression list * Statement list = match args with From 827f0d9ae3393e32ee2472ce2ea4fb71a9506c97 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 08:23:12 +0200 Subject: [PATCH 126/145] Use subscript access for anonymous records --- src/Fable.Transforms/Python/Fable2Python.fs | 41 ++++++++++++--------- src/fable-library-py/fable/util.py | 29 +++++++++++++-- tests/Python/TestMap.fs | 37 +++++++++++++++++++ tests/Python/TestSet.fs | 12 ++++++ 4 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 tests/Python/TestMap.fs diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index a695c8b7f4..f381ba1abe 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -589,10 +589,10 @@ module Util = | n when Naming.hasIdentForbiddenChars n -> Expression.constant(n), [] | n -> com.GetIdentifierAsExpr(ctx, n), [] - let get (com: IPythonCompiler) ctx r left memberName = + let get (com: IPythonCompiler) ctx r left memberName subscript = // printfn "get: %A" (left, memberName) - match left with - | Expression.Dict(_) -> + match subscript with + | true -> let expr = Expression.constant(memberName) Expression.subscript (value = left, slice = expr, ctx = Load) | _ -> @@ -615,7 +615,7 @@ module Util = let rec getParts com ctx (parts: string list) (expr: Expression) = match parts with | [] -> expr - | m::ms -> get com ctx None expr m |> getParts com ctx ms + | m::ms -> get com ctx None expr m false |> getParts com ctx ms let makeArray (com: IPythonCompiler) ctx exprs = let expr, stmts = exprs |> List.map (fun e -> com.TransformAsExpr(ctx, e)) |> Helpers.unzipArgs @@ -693,7 +693,7 @@ module Util = let callFunctionWithThisContext com ctx r funcExpr (args: Expression list) = let args = thisExpr::args - Expression.call(get com ctx None funcExpr "call", args, ?loc=r) + Expression.call(get com ctx None funcExpr "call" false, args, ?loc=r) let emitExpression range (txt: string) args = let value = @@ -907,7 +907,7 @@ module Util = | _ -> failwith $"transformValue: value {value} not supported!" let enumerator2iterator com ctx = - let enumerator = Expression.call(get com ctx None (Expression.identifier("self")) "GetEnumerator", []) + let enumerator = Expression.call(get com ctx None (Expression.identifier("self")) "GetEnumerator" false, []) [ Statement.return'(libCall com ctx None "Util" "toIterator" [ enumerator ]) ] let extractBaseExprFromBaseCall (com: IPythonCompiler) (ctx: Context) (baseType: Fable.DeclaredType option) baseCall = @@ -1214,9 +1214,12 @@ module Util = | statements -> Statement.if'(guardExpr, thenStmnt, statements, ?loc=r), stmts stmts @ stmts' @ stmts'' @ [ ifStatement ] - let transformGet (com: IPythonCompiler) ctx range typ fableExpr kind = - match kind with + let transformGet (com: IPythonCompiler) ctx range typ (fableExpr: Fable.Expr) kind = + + //printfn "transformGet: %A" fableExpr + printfn "transformGet: %A" (fableExpr.Type) + match kind with | Fable.ExprGet(TransformExpr com ctx (prop, stmts)) -> let expr, stmts' = com.TransformAsExpr(ctx, fableExpr) let expr, stmts'' = getExpr com ctx range expr prop @@ -1255,8 +1258,12 @@ module Util = | Fable.Value(Fable.BaseValue(_,t), r) -> Fable.Value(Fable.BaseValue(None, t), r) | _ -> fableExpr let expr, stmts = com.TransformAsExpr(ctx, fableExpr) + let subscript = + match fableExpr.Type with + | Fable.AnonymousRecordType(_) -> true + | _ -> false printfn "Fable.FieldGet: %A" fieldName - get com ctx range expr fieldName, stmts + get com ctx range expr fieldName subscript, stmts | Fable.ListHead -> // get range (com.TransformAsExpr(ctx, fableExpr)) "head" @@ -1305,7 +1312,7 @@ module Util = let expr, stmts''' = getExpr com ctx None expr e expr, stmts'' @ stmts''' | Fable.FieldSet(fieldName) -> - get com ctx None expr fieldName, [] + get com ctx None expr fieldName false, [] assign range ret value, stmts @ stmts' @ stmts'' let transformBindingExprBody (com: IPythonCompiler) (ctx: Context) (var: Fable.Ident) (value: Fable.Expr) = @@ -1728,10 +1735,10 @@ module Util = let func = Expression.name("chr") func, [] - | Fable.Get(Fable.IdentExpr({Name=name;Type=Fable.AnonymousRecordType(_)}) as expr, Fable.FieldGet(fieldName=index), _, _) -> - let left, stmts = com.TransformAsExpr(ctx, expr) - let index = Expression.constant(index) - Expression.subscript (left, index), stmts + // | Fable.Get(Fable.IdentExpr({Name=name;Type=Fable.AnonymousRecordType(_)}) as expr, Fable.FieldGet(fieldName=index), _, _) -> + // let left, stmts = com.TransformAsExpr(ctx, expr) + // let index = Expression.constant(index) + // Expression.subscript (left, index), stmts | Fable.Get(expr, kind, typ, range) -> transformGet com ctx range typ expr kind @@ -2134,7 +2141,7 @@ module Util = [ yield makeMethod memb.Name arguments body if memb.Info.IsEnumerator then - yield makeMethod "Symbol.iterator" (Arguments.arguments []) (enumerator2iterator com ctx) + yield makeMethod "__iter__" (Arguments.arguments [self]) (enumerator2iterator com ctx) ] let transformUnion (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = @@ -2148,7 +2155,7 @@ module Util = [ yield callSuperAsStatement [] yield! fieldIds |> Array.map (fun id -> - let left = get com ctx None thisExpr id.Name + let left = get com ctx None thisExpr id.Name false let right = match id.Type with | Fable.Number _ -> @@ -2188,7 +2195,7 @@ module Util = yield callSuperAsStatement [] yield! (ent.FSharpFields |> List.collecti (fun i field -> - let left = get com ctx None thisExpr field.Name + let left = get com ctx None thisExpr field.Name false let right = args.[i] assign None left right |> exprAsStatement ctx)) ] diff --git a/src/fable-library-py/fable/util.py b/src/fable-library-py/fable/util.py index e41e161ca4..8498ad5fde 100644 --- a/src/fable-library-py/fable/util.py +++ b/src/fable-library-py/fable/util.py @@ -303,9 +303,32 @@ def isDisposable(x): return x is not None and isinstance(x, IDisposable) -def toIterator(x): - print("toIterator: ", x) - return iter(x) +def toIterator(en): + print("toIterator: ", en) + + class Iterator: + def __iter__(self): + return self + + def __next__(self): + has_next = getattr(en, "System_Collections_IEnumerator_MoveNext")() + if not has_next: + raise StopIteration + return getattr(en, "System_Collections_IEnumerator_get_Current")() + + return Iterator() + + +def stringHash(s): + h = 5381 + for c in s: + h = (h * 33) ^ ord(c) + + return h + + +def numberHash(x): + return x * 2654435761 | 0 def structuralHash(x): diff --git a/tests/Python/TestMap.fs b/tests/Python/TestMap.fs new file mode 100644 index 0000000000..1c515163c5 --- /dev/null +++ b/tests/Python/TestMap.fs @@ -0,0 +1,37 @@ +module Fable.Tests.Maps + +open System.Collections.Generic +open Util.Testing + +type R = { i: int; s: string } +type R2 = { kv: KeyValuePair } + +[] +type R3 = + { Bar: string + Foo: int } + interface System.IComparable with + member this.CompareTo(x) = + match x with + | :? R3 as x -> compare this.Bar x.Bar + | _ -> -1 + override this.GetHashCode() = hash this.Bar + override this.Equals(x) = + match x with + | :? R3 as x -> this.Bar = x.Bar + | _ -> false + +type R4 = + { Bar: string + Baz: int } + +let ``test Map construction from lists works`` () = + let xs = Map [1,1; 2,2] + xs |> Seq.isEmpty + |> equal false + +let ``test Map.isEmpty works`` () = + let xs = Map [] + Map.isEmpty xs |> equal true + let ys = Map [1,1] + Map.isEmpty ys |> equal false diff --git a/tests/Python/TestSet.fs b/tests/Python/TestSet.fs index dc02e2643e..6ed48c9bd4 100644 --- a/tests/Python/TestSet.fs +++ b/tests/Python/TestSet.fs @@ -27,3 +27,15 @@ let ``test Set.Count works`` () = let xs = Set.empty |> Set.add 1 xs.Count |> equal 1 + +[] +let ``test Seq.isEmpty function works on Set`` () = + let xs = set [1] + xs |> Seq.isEmpty + |> equal false + +[] +let ``test Set.add works`` () = + let xs = Set.empty |> Set.add 1 + Set.count xs + |> equal 1 From 5106a41da830bab98c4697e7b70c834910613c37 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 10:24:48 +0200 Subject: [PATCH 127/145] Fixes for object expressions when value --- src/Fable.Transforms/Python/Fable2Python.fs | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index f381ba1abe..8fa88d23ee 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -748,6 +748,7 @@ module Util = let ctx = { ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams genTypeParams } let args, body = transformFunction com ctx funcName args body + // TODO: add self argument in this function // let args = // let len = args.Args.Length @@ -937,6 +938,7 @@ module Util = None let transformObjectExpr (com: IPythonCompiler) ctx (members: Fable.MemberDecl list) baseCall: Expression * Statement list = + printfn "transformObjectExpr" let compileAsClass = Option.isSome baseCall || members |> List.exists (fun m -> // Optimization: Object literals with getters and setters are very slow in V8 @@ -957,10 +959,14 @@ module Util = let prop, computed = memberFromName com ctx memb.Name // If compileAsClass is false, it means getters don't have side effects // and can be compiled as object fields (see condition above) - // if info.IsValue || (not compileAsClass && info.IsGetter) then - // let expr, stmts = com.TransformAsExpr(ctx, memb.Body) - // stmts @ [ Statement.assign([prop], expr) ] - if info.IsGetter then + if info.IsValue || (not compileAsClass && info.IsGetter) then + let expr, stmts = com.TransformAsExpr(ctx, memb.Body) + let stmts = + let decorators = [ Expression.name ("staticmethod") ] + stmts |> List.map (function | FunctionDef(def) -> FunctionDef({ def with DecoratorList = decorators}) | ex -> ex) + stmts @ [ Statement.assign([prop], expr) ] + elif info.IsGetter then + printfn "IsGetter: %A" prop let decorators = [ Expression.name("property") ] [ makeMethod memb.Name false memb.Args memb.Body decorators ] elif info.IsSetter then @@ -975,6 +981,7 @@ module Util = FunctionDef.Create(name = name, args = args, body = body) [ method; iterator] else + printfn "Got here: %A" (memb.Name, info.IsValue) [ makeMethod memb.Name info.HasSpread memb.Args memb.Body [] ] ) @@ -1192,7 +1199,7 @@ module Util = stmts @ [ Statement.try'(transformBlock com ctx returnStrategy body, ?handlers=handlers, finalBody=finalizer, ?loc=r) ] let rec transformIfStatement (com: IPythonCompiler) ctx r ret guardExpr thenStmnt elseStmnt = - printfn "transformIfStatement" + // printfn "transformIfStatement" let expr, stmts = com.TransformAsExpr(ctx, guardExpr) match expr with | Constant(value=value) when (value :? bool) -> @@ -1735,11 +1742,6 @@ module Util = let func = Expression.name("chr") func, [] - // | Fable.Get(Fable.IdentExpr({Name=name;Type=Fable.AnonymousRecordType(_)}) as expr, Fable.FieldGet(fieldName=index), _, _) -> - // let left, stmts = com.TransformAsExpr(ctx, expr) - // let index = Expression.constant(index) - // Expression.subscript (left, index), stmts - | Fable.Get(expr, kind, typ, range) -> transformGet com ctx range typ expr kind From 9ca305433a824f82acd1ef6b020c8ad5651cf9d0 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 11:17:17 +0200 Subject: [PATCH 128/145] Added reflection tests and fixes --- src/Fable.Transforms/Python/Fable2Python.fs | 5 +- .../fable/Fable.Library.fsproj | 4 +- src/fable-library-py/fable/reflection.py | 39 +++++ tests/Python/Fable.Tests.fsproj | 2 + tests/Python/TestReflection.fs | 142 ++++++++++++++++++ 5 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 tests/Python/TestReflection.fs diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 8fa88d23ee..bde0482067 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -966,7 +966,7 @@ module Util = stmts |> List.map (function | FunctionDef(def) -> FunctionDef({ def with DecoratorList = decorators}) | ex -> ex) stmts @ [ Statement.assign([prop], expr) ] elif info.IsGetter then - printfn "IsGetter: %A" prop + // printfn "IsGetter: %A" prop let decorators = [ Expression.name("property") ] [ makeMethod memb.Name false memb.Args memb.Body decorators ] elif info.IsSetter then @@ -981,7 +981,6 @@ module Util = FunctionDef.Create(name = name, args = args, body = body) [ method; iterator] else - printfn "Got here: %A" (memb.Name, info.IsValue) [ makeMethod memb.Name info.HasSpread memb.Args memb.Body [] ] ) @@ -1269,7 +1268,7 @@ module Util = match fableExpr.Type with | Fable.AnonymousRecordType(_) -> true | _ -> false - printfn "Fable.FieldGet: %A" fieldName + // printfn "Fable.FieldGet: %A" fieldName get com ctx range expr fieldName subscript, stmts | Fable.ListHead -> diff --git a/src/fable-library-py/fable/Fable.Library.fsproj b/src/fable-library-py/fable/Fable.Library.fsproj index 32d25e681d..12bbb711b9 100644 --- a/src/fable-library-py/fable/Fable.Library.fsproj +++ b/src/fable-library-py/fable/Fable.Library.fsproj @@ -28,9 +28,9 @@ - + - + diff --git a/src/fable-library-py/fable/reflection.py b/src/fable-library-py/fable/reflection.py index 2fe8347792..29e8dc75dd 100644 --- a/src/fable-library-py/fable/reflection.py +++ b/src/fable-library-py/fable/reflection.py @@ -59,6 +59,10 @@ def fn() -> List[CaseInfo]: return t +def delegate_type(*generics): + return TypeInfo("System.Func` %d" % len(generics), list(generics)) + + def record_type( fullname: str, generics: List[TypeInfo], construct: Constructor, fields: Callable[[], List[FieldInfo]] ) -> TypeInfo: @@ -109,6 +113,41 @@ def equals(t1: TypeInfo, t2: TypeInfo) -> bool: return t1 == t2 +def isGenericType(t): + return t.generics is not None and len(t.generics) + + +def getGenericTypeDefinition(t): + return t if t.generics is None else TypeInfo(t.fullname, list(map(lambda _: obj_type, t.generics))) + + +def name(info): + if isinstance(info, list): + return info[0] + + elif isinstance(info, CaseInfo): + return info.name + + else: + i = info.fullname.rfind("."); + return info.fullname if i == -1 else info.fullname.substr[i + 1:] + + +def fullName(t): + gen = t.generics if t.generics is not None and not isinstance(t, list) else [] + if len(gen): + gen = ",".join([ fullName(x) for x in gen]) + return f"${t.fullname}[{gen}]" + + else: + return t.fullname + + +def namespace(t): + i = t.fullname.rfind(".") + return "" if i == -1 else t.fullname[0: i] + + # if (t1.fullname === "") { // Anonymous records # return t2.fullname === "" # && equalArraysWith(getRecordElements(t1), diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj index 4c9b7e7304..a6e07534f7 100644 --- a/tests/Python/Fable.Tests.fsproj +++ b/tests/Python/Fable.Tests.fsproj @@ -26,11 +26,13 @@ + + diff --git a/tests/Python/TestReflection.fs b/tests/Python/TestReflection.fs new file mode 100644 index 0000000000..862242d6aa --- /dev/null +++ b/tests/Python/TestReflection.fs @@ -0,0 +1,142 @@ +module Fable.Tests.Reflection + +open Util.Testing +open FSharp.Data.UnitSystems.SI.UnitSymbols + +#if !OPTIMIZE_FCS + +type MyDelegate = delegate of int * float -> string +type MyGenDelegate<'a,'b> = delegate of 'b * string -> 'a + +// I can declare a type with delegate fields (both C# and F# style). See #1698 +type WithDelegates = + { Del1: MyDelegate + Del2: MyGenDelegate + Del3: System.Func + Del4: System.Func + Del5: System.Action + Del6: System.Action + Del7: System.Action } + +type TestType = + | Union1 of string + +type TestType2 = + | Union2 of string + +type TestType3 = class end +type TestType4 = class end +type TestType5 = class end + +type GenericRecord<'A,'B> = { a: 'A; b: 'B } + +type MyEnum = + | Foo = 1y + | Bar = 5y + | Baz = 8y + +type MyInterface<'T> = + abstract Value: 'T + +type MyClass() = + class end + +type MyClass2() = + class end + +type MyClass3<'T>(v) = + interface MyInterface<'T> with + member _.Value = v + +type MyClass<'T1, 'T2>(v1: 'T1, v2: 'T2) = + inherit MyClass() + member _.Value1 = v1 + member _.Value2 = v2 + +[] +let ``test typedefof works`` () = + let tdef1 = typedefof + let tdef2 = typedefof + equal tdef1 tdef2 + +[] +let ``test IsGenericType works`` () = + typeof.IsGenericType |> equal true + typeof.IsGenericType |> equal false + let t1 = typeof + let t2 = typeof + let t3 = typeof + t1.IsGenericType |> equal true + t2.IsGenericType |> equal false + t3.IsGenericType |> equal false + +[] +let ``test GetGenericTypeDefinition works`` () = + let tdef1 = typedefof + let tdef2 = typeof.GetGenericTypeDefinition() + let t = typeof + let tdef3 = t.GetGenericTypeDefinition() + equal tdef1 tdef2 + equal tdef1 tdef3 + tdef1 = typeof |> equal false + +[] +let ``test Comparing generic types works`` () = + let t1 = typeof> + let t2 = typeof> + let t3 = typeof> + t1 = t2 |> equal true + t1 = t3 |> equal false + +let inline getName<'t> = function + | "namespace" -> typedefof<'t>.Namespace + | "name" -> typedefof<'t>.Name + | _ -> typedefof<'t>.FullName + +let inline getName2 (d:'t) = function + | "namespace" -> typeof<'t>.Namespace + | "name" -> typeof<'t>.Name + | _ -> typeof<'t>.FullName + +let getName3 (t:System.Type) = function + | "namespace" -> t.Namespace + | "name" -> t.Name + | _ -> t.FullName + +// Fable 2 cannot check types unknown at compile time +// let getName4 (o:obj) = function +// | "namespace" -> o.GetType().Namespace +// | "name" -> o.GetType().Name +// | _ -> o.GetType().FullName + +type Firm = { name: string } + +let normalize (x: string) = + #if FABLE_COMPILER + x + #else + x.Replace("+",".") + #endif +let inline fullname<'T> () = typeof<'T>.FullName |> normalize + +[] +let ``test Type Namespace`` () = + let x = typeof.Namespace + #if FABLE_COMPILER + equal "Fable.Tests.Reflection" x + #else + equal "Fable.Tests" x + #endif + +[] +let ``test Type FullName`` () = + let x = typeof.FullName + x |> normalize |> equal "Fable.Tests.Reflection.TestType" + +[] +let ``test Type Name`` () = + let x = typeof.Name + equal "TestType" x + + +#endif \ No newline at end of file From d3b042006f30a53bbd0e9a75280dceb090bcb6d9 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 11:21:07 +0200 Subject: [PATCH 129/145] Remove unused files --- .ipynb_checkpoints/Untitled-checkpoint.ipynb | 6 - Untitled.ipynb | 147 ------------------- 2 files changed, 153 deletions(-) delete mode 100644 .ipynb_checkpoints/Untitled-checkpoint.ipynb delete mode 100644 Untitled.ipynb diff --git a/.ipynb_checkpoints/Untitled-checkpoint.ipynb b/.ipynb_checkpoints/Untitled-checkpoint.ipynb deleted file mode 100644 index 7fec51502c..0000000000 --- a/.ipynb_checkpoints/Untitled-checkpoint.ipynb +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cells": [], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/Untitled.ipynb b/Untitled.ipynb deleted file mode 100644 index 97d26a0414..0000000000 --- a/Untitled.ipynb +++ /dev/null @@ -1,147 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "class Subject:\n", - " def __init__(self):\n", - " self.grades = []\n", - "\n", - " def __str__(self):\n", - " return f\"Subject({self.grades})\"\n", - " def __repr__(self):\n", - " return str(self)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "ma100 = Subject()\n", - "ma100.grades.append(1)\n", - "ma100.grades.append(2)\n", - "ma100.grades.append(3)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Subject([1, 2, 3])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ma100" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "class Student:\n", - " def __init__(self):\n", - " self._subjects = {}\n", - " def registrer_fag(self, name):\n", - " if name not in self._subjects:\n", - " self._subjects[name] = Subject()\n", - " return self._subjects[name]\n", - " \n", - " def add_grade(self, name, grade):\n", - " subject = self._subjects[name]\n", - " \n", - " grades = subject.grades\n", - " grades.append(grade)\n", - " " - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Subject([1, 2, 3])" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ma100" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[1, 2, 3, 4]" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "student = Student()\n", - "student.registrer_fag(\"ma100\")\n", - "student._subjects[\"ma100\"].grades = [1,2,3,4]\n", - "\n", - "student.registrer_fag(\"ma100\")\n", - "student._subjects[\"ma100\"].grades\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.2" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From db9e196e6f6372a04287d45afdae5de66cface0a Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 11:25:32 +0200 Subject: [PATCH 130/145] Remove Babel2Python --- src/Fable.Cli/Main.fs | 10 +- src/Fable.Transforms/Fable.Transforms.fsproj | 1 - src/Fable.Transforms/Python/Babel2Python.fs | 1056 ------------------ tests/Python/TestReflection.fs | 6 - 4 files changed, 1 insertion(+), 1072 deletions(-) delete mode 100644 src/Fable.Transforms/Python/Babel2Python.fs diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index 5404fa49b4..0ea7a82ceb 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -222,6 +222,7 @@ module private Util = let ctx = PhpPrinter.Output.Writer.create w PhpPrinter.Output.writeFile ctx php w.Flush() + | Python -> let python = fable |> Fable2Python.Compiler.transformFile com let map = { new PythonPrinter.SourceMapGenerator with @@ -229,15 +230,6 @@ module private Util = let writer = new PythonFileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) do! PythonPrinter.run writer map python - // logger("Generating Python from Babel") - // let babel = fable |> Fable2Babel.Compiler.transformFile com - // let python = babel |> Babel2Python.Compiler.transformFile com - // let map = { new PythonPrinter.SourceMapGenerator with - // member _.AddMapping(_,_,_,_,_) = () } - // let outPath = outPath.Replace(".fs", "2.fs") - // let writer = new PythonFileWriter(com.CurrentFile, outPath, cliArgs, dedupTargetDir) - // do! PythonPrinter.run writer map python - "Compiled " + File.getRelativePathFromCwd com.CurrentFile |> Log.verboseOrIf isRecompile diff --git a/src/Fable.Transforms/Fable.Transforms.fsproj b/src/Fable.Transforms/Fable.Transforms.fsproj index 927c808b91..51ca895816 100644 --- a/src/Fable.Transforms/Fable.Transforms.fsproj +++ b/src/Fable.Transforms/Fable.Transforms.fsproj @@ -23,7 +23,6 @@ - diff --git a/src/Fable.Transforms/Python/Babel2Python.fs b/src/Fable.Transforms/Python/Babel2Python.fs deleted file mode 100644 index b3693c0b8e..0000000000 --- a/src/Fable.Transforms/Python/Babel2Python.fs +++ /dev/null @@ -1,1056 +0,0 @@ -module Fable.Transforms.Babel2Python - -open System -open System.Collections.Generic -open System.Text.RegularExpressions - -open Fable -open Fable.AST -open Fable.AST.Python -open Fable.AST.Babel -open Fable.Naming -open Fable.Core - -[] -type ReturnStrategy = - | Return - | NoReturn - | NoBreak // Used in switch statement blocks - -type UsedNames = - { GlobalScope: HashSet - EnclosingScope: HashSet - LocalScope: HashSet } - -type Context = - { ReturnStrategy: ReturnStrategy - UsedNames: UsedNames } - -type IPythonCompiler = - inherit Compiler - abstract GetAllImports: unit -> Python.Statement list - abstract GetIdentifier: ctx: Context * name: string -> Python.Identifier - abstract GetImportExpr: ctx: Context * moduleName: string * ?name: string * ?loc: SourceLocation -> Python.Identifier option - - abstract TransformAsExpr: Context * Babel.Expression -> Python.Expression * Python.Statement list - abstract TransformAsStatements: Context * ReturnStrategy * Babel.Expression -> Python.Statement list - abstract TransformAsStatements: Context * ReturnStrategy * Babel.Statement -> Python.Statement list - abstract TransformAsStatements: Context * ReturnStrategy * Babel.BlockStatement -> Python.Statement list - - abstract TransformAsClassDef: - Context - * Babel.ClassBody - * Babel.Identifier option - * Babel.Expression option - * Babel.ClassImplements array option - * Babel.TypeParameterInstantiation option - * Babel.TypeParameterDeclaration option - * SourceLocation option -> - Python.Statement list - - abstract TransformAsImports: Context * Babel.ImportSpecifier array * Babel.StringLiteral -> Python.Statement list - abstract TransformAsFunction: Context * string * Babel.Pattern array * Babel.BlockStatement -> Python.Statement - - abstract WarnOnlyOnce: string * ?range: SourceLocation -> unit - -module Helpers = - let index = (Seq.initInfinite id).GetEnumerator() - - let getUniqueIdentifier (name: string): Python.Identifier = - do index.MoveNext() |> ignore - let idx = index.Current.ToString() - Python.Identifier($"{name}_{idx}") - - /// Replaces all '$' and `.`with '_' - let clean (name: string) = - //printfn $"clean: {name}" - match name with - | "this" -> "self" - | "async" -> "async_" - | "from" -> "from_" - | "class" -> "class_" - | "for" -> "for_" - | "Math" -> "math" - | "Error" -> "Exception" - | "toString" -> "str" - | "len" -> "len_" - | "Map" -> "dict" - | _ -> - name.Replace('$', '_').Replace('.', '_').Replace('`', '_') - - let rewriteFableImport moduleName = - //printfn "ModuleName: %s" moduleName - let _reFableLib = - Regex(".*(\/fable-library.*)?\/(?[^\/]*)\.(js|fs)", RegexOptions.Compiled) - - let m = _reFableLib.Match(moduleName) - let dashify = applyCaseRule CaseRules.SnakeCase - - if m.Groups.Count > 1 then - let pymodule = - m.Groups.["module"].Value - |> dashify - |> clean - - let moduleName = String.concat "." [ "fable"; pymodule ] - - //printfn "-> Module: %A" moduleName - moduleName - else - // Modules should have short, all-lowercase names. - let moduleName = - let name = - moduleName.Replace("/", "") - |> dashify - string(name.[0]) + name.[1..].Replace(".", "_") - - //printfn "-> Module: %A" moduleName - moduleName - - let unzipArgs (args: (Python.Expression * Python.Statement list) list): Python.Expression list * Python.Statement list = - let stmts = args |> List.map snd |> List.collect id - let args = args |> List.map fst - args, stmts - - /// A few statements in the generated Babel AST do not produce any effect, and will not be printet. But they are - /// left in the AST and we need to skip them since they are not valid for Python (either). - let isProductiveStatement (stmt: Python.Statement) = - let rec hasNoSideEffects (e: Python.Expression) = - //printfn $"hasNoSideEffects: {e}" - - match e with - | Constant _ -> true - | Dict { Keys = keys } -> keys.IsEmpty // Empty object - | Name _ -> true // E.g `void 0` is translated to Name(None) - | _ -> false - - match stmt with - | Expr expr -> - if hasNoSideEffects expr.Value then - None - else - Some stmt - | _ -> Some stmt - -module Util = - let getIdentifier (com: IPythonCompiler) (ctx: Context) (name: string) = - let name = Helpers.clean name - - match name with - | "math" -> com.GetImportExpr(ctx, "math") |> ignore - | _ -> () - - Python.Identifier name - - let rec transformBody (returnStrategy: ReturnStrategy) (body: Python.Statement list) = - let body = body |> List.choose Helpers.isProductiveStatement - - match body, returnStrategy with - | [], ReturnStrategy.Return -> [ Statement.return' () ] - | [], ReturnStrategy.NoBreak - | [], ReturnStrategy.NoReturn -> [ Pass ] - | xs, ReturnStrategy.NoBreak -> - xs - |> List.filter (fun x -> x <> Break) - |> transformBody ReturnStrategy.NoReturn - | _ -> body - - let transformAsImports - (com: IPythonCompiler) - (ctx: Context) - (specifiers: Babel.ImportSpecifier array) - (source: Babel.StringLiteral) - : Python.Statement list = - let (StringLiteral (value = value)) = source - let pymodule = value |> Helpers.rewriteFableImport - - //printfn "Module: %A" pymodule - - let imports: ResizeArray = ResizeArray() - let importFroms = ResizeArray() - - for expr in specifiers do - match expr with - | Babel.ImportMemberSpecifier (local, imported) -> - let asname = - if imported.Name <> local.Name then - com.GetIdentifier(ctx, local.Name) |> Some - else - None - let alias = Alias.alias(com.GetIdentifier(ctx, imported.Name),?asname=asname) - importFroms.Add(alias) - | Babel.ImportDefaultSpecifier (local) -> - let asname = - if local.Name <> pymodule then - com.GetIdentifier(ctx, local.Name) |> Some - else - None - let alias = Alias.alias(com.GetIdentifier(ctx, pymodule), ?asname=asname) - imports.Add(alias) - | Babel.ImportNamespaceSpecifier (Identifier (name = name)) -> - printfn "ImportNamespaceSpecifier: %A" (name, name) - - let asname = - if pymodule <> name then - com.GetIdentifier(ctx, name) |> Some - else - None - let alias = Alias.alias(Python.Identifier(pymodule), ?asname=asname) - importFroms.Add(alias) - - [ if imports.Count > 0 then - Statement.import (imports |> List.ofSeq) - - if importFroms.Count > 0 then - Statement.importFrom (Some(Python.Identifier(pymodule)), importFroms |> List.ofSeq) ] - - let transformAsClassDef - (com: IPythonCompiler) - (ctx: Context) - (body: Babel.ClassBody) - (id: Babel.Identifier option) - (superClass: Babel.Expression option) - (implements: Babel.ClassImplements array option) - (superTypeParameters: Babel.TypeParameterInstantiation option) - (typeParameters: Babel.TypeParameterDeclaration option) - (loc: SourceLocation option) - : Python.Statement list = - //printfn $"transformAsClassDef" - - let bases, stmts = - let entries = - superClass - |> Option.map (fun expr -> com.TransformAsExpr(ctx, expr)) - - match entries with - | Some (expr, stmts) -> [ expr ], stmts - | None -> [], [] - - let body: Python.Statement list = - [ let (ClassBody (body = body)) = body - - for mber in body do - match mber with - | Babel.ClassMember.ClassMethod (kind, key, ``params``, body, computed, ``static``, ``abstract``, returnType, typeParameters, loc) -> - let self = Arg.arg (Python.Identifier("self")) - let parms = ``params`` |> List.ofArray - - let args = - parms - |> List.choose - (function - | Pattern.Identifier (id) -> - Arg.arg (com.GetIdentifier(ctx, id.Name)) |> Some - | _ -> None) - - let varargs = - parms - |> List.choose - (function - | Pattern.RestElement (argument = argument) -> Arg.arg (com.GetIdentifier(ctx, argument.Name)) |> Some - | _ -> None) - |> List.tryHead - let defaults = - if List.length args = 1 then - [ Expression.name "None"] - else - [] - let arguments = Arguments.arguments (args = self :: args, ?vararg = varargs, defaults=defaults) - - match kind, key with - | "method", _ -> - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) - let name = - match key with - | Expression.Identifier(Identifier(name="toString")) -> - com.GetIdentifier(ctx, "__str__") - | Expression.Identifier (id) -> com.GetIdentifier(ctx, id.Name) - // E.g ["System.Collections.Generic.IEnumerator`1.get_Current"]() { ... } - | Expression.Literal(Literal.StringLiteral(StringLiteral(value=name))) -> - com.GetIdentifier(ctx, name) - | MemberExpression(object=Expression.Identifier(Identifier(name="Symbol")); property=Expression.Identifier(Identifier(name="iterator"))) -> - com.GetIdentifier(ctx, "__iter__") - | _ -> - failwith $"transformAsClassDef: Unknown key: {key}" - - FunctionDef.Create(name, arguments, body = body) - | "constructor", _ -> - let name = Python.Identifier("__init__") - let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) - FunctionDef.Create(name, arguments, body = body) - | "get", MemberExpression(object=Expression.Identifier(Identifier(name="Symbol")); property=Expression.Identifier(Identifier(name="toStringTag"))) -> - let name = Python.Identifier("__str__") - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) - FunctionDef.Create(name, arguments, body = body) - | "get", Expression.Identifier(Identifier(name="size")) -> - let name = Python.Identifier("__len__") - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) - FunctionDef.Create(name, arguments, body = body) - | "get", Expression.Identifier(Identifier(name=name)) -> - let name = Python.Identifier(name) - let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) - let decorators = [ Python.Expression.name "property"] - FunctionDef.Create(name, arguments, body = body, decoratorList=decorators) - | _ -> failwith $"transformAsClassDef: Unknown kind: {kind}, key: {key}" - | _ -> failwith $"transformAsClassDef: Unhandled class member {mber}" ] - - let name = com.GetIdentifier(ctx, id.Value.Name) - [ yield! stmts; Statement.classDef(name, body = body, bases = bases) ] - - /// Transform Bable parameters as Python function - let transformAsFunction - (com: IPythonCompiler) - (ctx: Context) - (name: string) - (parms: Babel.Pattern array) - (body: Babel.BlockStatement) - = - let args = - parms - |> List.ofArray - |> List.map - (fun pattern -> - let ident = com.GetIdentifier(ctx, pattern.Name) - Arg.arg (ident)) - - let arguments = Arguments.arguments (args = args) - let enclosingScope = HashSet() - enclosingScope.UnionWith(ctx.UsedNames.EnclosingScope) - enclosingScope.UnionWith(ctx.UsedNames.LocalScope) - let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = enclosingScope }} - let body = com.TransformAsStatements(ctx', ReturnStrategy.Return, body) - - let name = com.GetIdentifier(ctx, name) - FunctionDef.Create(name, arguments, body = body) - - /// Transform Babel expression as Python expression - let rec transformAsExpr (com: IPythonCompiler) (ctx: Context) (expr: Babel.Expression): Python.Expression * list = - //printfn $"transformAsExpr: {expr}" - - match expr with - | AssignmentExpression (left = left; operator = operator; right = right) -> - let left, leftStmts = com.TransformAsExpr(ctx, left) - let right, rightStmts = com.TransformAsExpr(ctx, right) - - match operator with - | "=" -> - Expression.namedExpr (left, right), leftStmts @ rightStmts - | _ -> failwith $"Unsuppored assingment expression: {operator}" - - | BinaryExpression (left = left; operator = "=="; right = Literal(NullLiteral(_))) -> - let left, leftStmts = com.TransformAsExpr(ctx, left) - let right = Expression.name("None") - Expression.compare (left, [ Python.Is ], [ right ]), leftStmts - | BinaryExpression (left = left; operator = "!="; right = Literal(NullLiteral(_))) -> - let left, leftStmts = com.TransformAsExpr(ctx, left) - let right = Expression.name("None") - Expression.compare (left, [ Python.IsNot ], [ right ]), leftStmts - | BinaryExpression (left = left'; operator = operator; right = right') -> - let left, leftStmts = com.TransformAsExpr(ctx, left') - let right, rightStmts = com.TransformAsExpr(ctx, right') - - let toBinOp (op: Operator) = Expression.binOp (left, op, right), leftStmts @ rightStmts - let toCompare op = Expression.compare (left, [ op ], [ right ]), leftStmts @ rightStmts - - let toCall name = - let func = Expression.name (Python.Identifier(name)) - let args = [ left; right ] - Expression.call (func, args), leftStmts @ rightStmts - - match operator with - | "+" -> Add |> toBinOp - | "-" -> Sub |> toBinOp - | "*" -> Mult |> toBinOp - | "/" -> Div |> toBinOp - | "%" -> Mod |> toBinOp - | "**" -> Pow |> toBinOp - | "<<<" | "<<" -> LShift |> toBinOp - | ">>" | ">>>" -> RShift |> toBinOp - | "|" -> BitOr |> toBinOp - | "^" -> BitXor |> toBinOp - | "&" -> BitAnd |> toBinOp - | "===" -> - match right' with - | Expression.Identifier(_) - | Literal(_) -> Eq |> toCompare - | _ -> Is |> toCompare - | "==" -> Eq |> toCompare - | "!==" -> - match right' with - | Expression.Identifier(_) - | Literal(_) -> NotEq |> toCompare - | _ -> IsNot |> toCompare - - | "!=" -> NotEq |> toCompare - | ">" -> Gt |> toCompare - | ">=" -> GtE |> toCompare - | "<" -> Lt |> toCompare - | "<=" -> LtE |> toCompare - | "instanceof" -> toCall "isinstance" - | _ -> failwith $"Unknown operator: {operator}" - - // Transform `~(~(a/b))` to `a // b` - | UnaryExpression (operator = "~"; argument = UnaryExpression(operator="~"; argument=BinaryExpression(left, right, operator, loc))) when operator = "/" -> - let left, leftStmts = com.TransformAsExpr(ctx, left) - let right, rightStmts = com.TransformAsExpr(ctx, right) - Expression.binOp(left, FloorDiv,right), leftStmts @ rightStmts - | UnaryExpression (operator = operator; argument = arg) -> - let op = - match operator with - | "-" -> USub |> Some - | "+" -> UAdd |> Some - | "~" -> Invert |> Some - | "!" -> Not |> Some - | "void" -> None - | _ -> failwith $"Unhandled unary operator: {operator}" - - let operand, stmts = com.TransformAsExpr(ctx, arg) - - match op, arg with - | Some op, _ -> Expression.unaryOp (op, operand), stmts - | None, Literal(NumericLiteral(value=0.)) -> - Expression.name(identifier = Python.Identifier("None")), stmts - | _ -> - operand, stmts - - | ArrowFunctionExpression (``params`` = parms; body = body) -> - let args = - parms - |> List.ofArray - |> List.map (fun pattern -> Arg.arg (com.GetIdentifier(ctx, pattern.Name))) - - let arguments = - let args = - match args with - | [] -> [ Arg.arg (Python.Identifier("_"), Expression.name (Python.Identifier("None"))) ] // Need to receive unit - | _ -> args - - Arguments.arguments (args = args) - - let stmts = body.Body // TODO: Babel AST should be fixed. Body does not have to be a BlockStatement. - - match stmts with - | [| Statement.ReturnStatement (argument = argument) |] -> - let body, stmts = com.TransformAsExpr(ctx, argument) - Expression.lambda (arguments, body), stmts - | _ -> - let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope }} - let body = com.TransformAsStatements(ctx', ReturnStrategy.Return, body) - let name = Helpers.getUniqueIdentifier "lifted" - - let func = FunctionDef.Create(name = name, args = arguments, body = body) - - Expression.name (name), [ func ] - // Transform xs.toString() to str(xs) - | CallExpression (callee = MemberExpression(object=object; property=Expression.Identifier(Identifier(name="toString")))) -> - let object, stmts = com.TransformAsExpr(ctx, object) - let func = Expression.name("str") - Expression.call (func, [ object ]), stmts - // Transform xs.charCodeAt(0) to ord(xs) - | CallExpression (callee = MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "charCodeAt")))) -> - let value, stmts = com.TransformAsExpr(ctx, object) - let func = Expression.name (Python.Identifier "ord") - Expression.call (func, [ value ]), stmts - // Transform String.fromCharCode(97) to chr(xs) - | CallExpression (callee = MemberExpression (computed = false; object = Expression.Identifier (Identifier(name = "String")); property = Expression.Identifier (Identifier(name = "fromCharCode"))); arguments=args) -> - let args, stmts = args |> Array.map (fun obj -> com.TransformAsExpr(ctx, obj)) |> List.ofArray |> Helpers.unzipArgs - let func = Expression.name (Python.Identifier "chr") - Expression.call (func, args), stmts - // Transform "text".split("") to list("text") - | CallExpression (callee = MemberExpression(object=object; property=Expression.Identifier(Identifier(name="split"))); arguments = [| Literal(Literal.StringLiteral(StringLiteral(value=""))) |]) -> - let object, stmts = com.TransformAsExpr(ctx, object) - let func = Expression.name("list") - Expression.call (func, [ object ]), stmts - // Transform xs.join("|") to "|".join(xs) - | CallExpression (callee = MemberExpression(object=object; property=Expression.Identifier(Identifier(name="join"))); arguments = [| Literal(Literal.StringLiteral(StringLiteral(value=value))) |]) -> - let object, stmts = com.TransformAsExpr(ctx, object) - let func = Expression.attribute(value=Expression.constant(value), attr=Python.Identifier("join")) - Expression.call (func, [ object ]), stmts - | CallExpression (callee = callee; arguments = args) -> - let func, stmts = com.TransformAsExpr(ctx, callee) - - let args, stmtArgs = - args - |> List.ofArray - |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) - |> Helpers.unzipArgs - - Expression.call (func, args), stmts @ stmtArgs - | ArrayExpression (elements = elements) -> - let elems, stmts = - elements - |> List.ofArray - |> List.map (fun ex -> com.TransformAsExpr(ctx, ex)) - |> Helpers.unzipArgs - - Expression.list (elems), stmts - | Expression.Literal (Literal.NumericLiteral (value = value)) -> Expression.constant (value = value), [] - | Expression.Literal (Literal.StringLiteral (StringLiteral.StringLiteral (value = value))) -> - Expression.constant (value = value), [] - | Expression.Identifier (Identifier (name = name)) -> - let name = com.GetIdentifier(ctx, name) - Expression.name(identifier = name), [] - | NewExpression (callee = Expression.Identifier(Identifier(name="Int32Array")); arguments = args) -> - match args with - | [| arg |] -> - let expr, stmts = com.TransformAsExpr(ctx, arg) - expr, stmts - | _ -> failwith "Int32Array with multiple arguments not supported." - | NewExpression (callee = callee; arguments = args) -> - let func, stmts = com.TransformAsExpr(ctx, callee) - - let args, stmtArgs = - args - |> List.ofArray - |> List.map (fun arg -> com.TransformAsExpr(ctx, arg)) - |> Helpers.unzipArgs - - Expression.call (func, args), stmts @ stmtArgs - | Expression.Super (se) -> Expression.name (Python.Identifier("super().__init__")), [] - | ObjectExpression (properties = properties) -> - let keys, values, stmts = - [ for prop in properties do - match prop with - | ObjectProperty (key = key; value = value) -> - let key, stmts1 = com.TransformAsExpr(ctx, key) - let value, stmts2 = com.TransformAsExpr(ctx, value) - key, value, stmts1 @ stmts2 - | Babel.ObjectMethod (key = key; ``params`` = parms; body = body) -> - //let body = com.TransformAsStatements(ctx, ReturnStrategy.Return, body) - let key, stmts = com.TransformAsExpr(ctx, key) - - let name = Helpers.getUniqueIdentifier "lifted" - let func = com.TransformAsFunction(ctx, name.Name, parms, body) - key, Expression.name (name), stmts @ [ func ] ] - |> List.unzip3 - - // Transform as namedtuple to allow attribute access of keys. - com.GetImportExpr(ctx, "collections", "namedtuple") |> ignore - let keys = - keys - |> List.map (function - | Expression.Name { Id=Python.Identifier name } -> Expression.constant(Helpers.clean name) - | Expression.Constant(value=value) -> - match value with - | :? string as name -> Expression.constant(Helpers.clean name) - | _ -> Expression.constant(value) - | ex -> ex) - Expression.call( - Expression.call( - Expression.name("namedtuple"), [ Expression.constant("object"); Expression.list(keys)] - ), values - ), stmts |> List.collect id - - | EmitExpression (value = value; args = args) -> - let args, stmts = - args - |> List.ofArray - |> List.map (fun expr -> com.TransformAsExpr(ctx, expr)) - |> Helpers.unzipArgs - - match value with - //| "void $0" -> args.[0], stmts - | "$0.join('')" -> - let value = "''.join($0)" - Expression.emit (value, args), stmts - | "throw $0" -> - let value = "raise $0" - Expression.emit (value, args), stmts - | Naming.StartsWith("void ") value - | Naming.StartsWith("new ") value -> - Expression.emit (value, args), stmts - | _ -> Expression.emit (value, args), stmts - // If computed is true, the node corresponds to a computed (a[b]) member expression and property is an Expression - | MemberExpression (computed = true; object = object; property = property) -> - let value, stmts = com.TransformAsExpr(ctx, object) - match property with - | Expression.Literal (NumericLiteral (value = numeric)) -> - let attr = Expression.constant(numeric) - Expression.subscript(value = value, slice = attr, ctx = Load), stmts - | Expression.Literal (Literal.StringLiteral (StringLiteral (value = str))) -> - let attr = Expression.constant (str) - let func = Expression.name("getattr") - Expression.call(func, args=[value; attr]), stmts - | Expression.Identifier (Identifier(name=name)) -> - let attr = Expression.name (com.GetIdentifier(ctx, name)) - Expression.subscript(value = value, slice = attr, ctx = Load), stmts - | _ -> - let attr, stmts' = com.TransformAsExpr(ctx, property) - let func = Expression.name("getattr") - Expression.call(func=func, args=[value; attr]), stmts @ stmts' - // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier - | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "indexOf"))) -> - let value, stmts = com.TransformAsExpr(ctx, object) - let attr = Python.Identifier "index" - Expression.attribute (value = value, attr = attr, ctx = Load), stmts - // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier - | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "toLocaleUpperCase"))) -> - let value, stmts = com.TransformAsExpr(ctx, object) - let attr = Python.Identifier "upper" - Expression.attribute (value = value, attr = attr, ctx = Load), stmts - | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "toLocaleLowerCase"))) -> - let value, stmts = com.TransformAsExpr(ctx, object) - let attr = Python.Identifier "lower" - Expression.attribute (value = value, attr = attr, ctx = Load), stmts - | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "push"))) -> - let value, stmts = com.TransformAsExpr(ctx, object) - let attr = Python.Identifier "append" - Expression.attribute (value = value, attr = attr, ctx = Load), stmts - // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier - | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "length"))) -> - let value, stmts = com.TransformAsExpr(ctx, object) - let func = Expression.name (Python.Identifier "len") - Expression.call (func, [ value ]), stmts - // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier - | MemberExpression (computed = false; object = object; property = Expression.Identifier (Identifier(name = "message"))) -> - let value, stmts = com.TransformAsExpr(ctx, object) - let func = Expression.name (Python.Identifier "str") - Expression.call (func, [ value ]), stmts - // If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier - | MemberExpression (computed=false; object = object; property = property) -> - let value, stmts = com.TransformAsExpr(ctx, object) - - let attr = - match property with - | Expression.Identifier (Identifier (name = name)) -> com.GetIdentifier(ctx, name) - | _ -> failwith $"transformAsExpr: MemberExpression (false): unknown property {property}" - Expression.attribute (value = value, attr = attr, ctx = Load), stmts - | Expression.Literal (Literal.BooleanLiteral (value = value)) -> Expression.constant (value = value), [] - | FunctionExpression (``params`` = parms; body = body) -> - let args = - parms - |> List.ofArray - |> List.map (fun pattern -> Arg.arg (Python.Identifier pattern.Name)) - - let arguments = Arguments.arguments (args = args) - - match body.Body with - | [| Statement.ExpressionStatement (expr) |] -> - let body, stmts = com.TransformAsExpr(ctx, expr) - Expression.lambda (arguments, body), stmts - | _ -> - let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope }} - - let body = com.TransformAsStatements(ctx', ReturnStrategy.Return, body) - let name = Helpers.getUniqueIdentifier "lifted" - let func = - FunctionDef.Create(name = name, args = arguments, body = body) - - Expression.name (name), [ func ] - | ConditionalExpression (test = test; consequent = consequent; alternate = alternate) -> - let test, stmts1 = com.TransformAsExpr(ctx, test) - let body, stmts2 = com.TransformAsExpr(ctx, consequent) - let orElse, stmts3 = com.TransformAsExpr(ctx, alternate) - - Expression.ifExp (test, body, orElse), stmts1 @ stmts2 @ stmts3 - | Expression.Literal (Literal.NullLiteral (nl)) -> Expression.name (Python.Identifier("None")), [] - | SequenceExpression (expressions = exprs) -> //XXX - // Sequence expressions are tricky. We currently convert them to a function that we call w/zero arguments - let ctx' = { ctx with UsedNames = { ctx.UsedNames with LocalScope = HashSet (); EnclosingScope = ctx.UsedNames.LocalScope }} - - let body = - exprs - |> List.ofArray - |> List.mapi - (fun i ex -> - // Return the last expression - if i = exprs.Length - 1 then - let expr, stmts = com.TransformAsExpr(ctx', ex) - stmts @ [Statement.return' (expr)] - else - com.TransformAsStatements(ctx', ReturnStrategy.Return, ex)) - |> List.collect id - |> transformBody ReturnStrategy.Return - - - let name = Helpers.getUniqueIdentifier ("lifted") - let func = FunctionDef.Create(name = name, args = Arguments.arguments [], body = body) - - let name = Expression.name (name) - Expression.call (name), [ func ] - | ClassExpression(body, id, superClass, implements, superTypeParameters, typeParameters, loc) -> - let name = - match id with - | Some id -> Python.Identifier(id.Name) - | None -> Helpers.getUniqueIdentifier "Lifted" - - let babelId = Identifier.identifier(name=name.Name) |> Some - let stmts = com.TransformAsClassDef(ctx, body, babelId, superClass, implements, superTypeParameters, typeParameters, loc) - Expression.name name, stmts - | ThisExpression (_) -> Expression.name ("self"), [] - | _ -> failwith $"transformAsExpr: Unhandled value: {expr}" - - /// Transform Babel expressions as Python statements. - let rec transformExpressionAsStatements - (com: IPythonCompiler) - (ctx: Context) - (returnStrategy: ReturnStrategy) - (expr: Babel.Expression) - : Python.Statement list = - - //printfn $"transformExpressionAsStatements: {expr}" - - match expr with - // Transform e.g `this["x@22"] = x;` into `setattr(self, "x@22", x)` - | AssignmentExpression (left = MemberExpression (object = object - property = Literal (Literal.StringLiteral (StringLiteral (value = attr)))) - right = right) -> - // object, attr, value - let object, stmts1 = com.TransformAsExpr(ctx, object) - let value, stmts2 = com.TransformAsExpr(ctx, right) - let attr = Expression.constant(attr) - - [ yield! stmts1 - yield! stmts2 - Statement.expr (value = Expression.call (func = Expression.name ("setattr"), args = [ object; attr; value ])) ] - - // Transform e.g `this.x = x;` into `self.x = x` - | AssignmentExpression (left = left; right = right) -> - let value, stmts = com.TransformAsExpr(ctx, right) - let targets, stmts2: Python.Expression list * Python.Statement list = - //printfn "AssignmentExpression: left: %A" left - match left with - | Expression.Identifier (Identifier (name = name)) -> - let target = com.GetIdentifier(ctx, name) - let stmts = - if not (ctx.UsedNames.LocalScope.Contains name) && ctx.UsedNames.EnclosingScope.Contains name then - // printfn "**** Adding non-local: %A" target - [ Statement.nonLocal [target] ] - else - [] - - [ Expression.name(identifier = target, ctx = Store) ], stmts - // a.b = c - | MemberExpression (property = Expression.Identifier (id); object = object; computed=false) -> - let attr = com.GetIdentifier(ctx, id.Name) - let value, stmts = com.TransformAsExpr(ctx, object) - [ Expression.attribute (value = value, attr = attr, ctx = Store) ], stmts - // a.b[c] = d - | MemberExpression (property = Expression.Literal(NumericLiteral(value=value)); object = object; computed=false) -> - let slice = Expression.constant(value) - let expr, stmts = com.TransformAsExpr(ctx, object) - [ Expression.subscript (value = expr, slice = slice, ctx = Store) ], stmts - // object[property] = - | MemberExpression (property = property; object = object; computed=true) -> - let value, stmts = com.TransformAsExpr(ctx, object) - let index, stmts' = com.TransformAsExpr(ctx, property) - [ Expression.subscript (value = value, slice = index, ctx = Store) ], stmts @ stmts' - | _ -> failwith $"AssignmentExpression, unknown expression: {left}" - - [ yield! stmts; yield! stmts2; Statement.assign (targets = targets, value = value) ] - | _ -> - // Wrap the rest in statement expression - let expr, stmts = com.TransformAsExpr(ctx, expr) - [ yield! stmts; Statement.expr expr ] - - /// Transform Babel statement as Python statements. - let rec transformStatementAsStatements - (com: IPythonCompiler) - (ctx: Context) - (returnStrategy: ReturnStrategy) - (stmt: Babel.Statement) - : Python.Statement list = - //printfn $"transformStatementAsStatements: {stmt}, returnStrategy: {returnStrategy}" - - match stmt with - | Statement.BlockStatement (bs) -> - [ yield! com.TransformAsStatements(ctx, returnStrategy, bs) ] - |> transformBody returnStrategy - - | ReturnStatement (argument = arg) -> - let expr, stmts = transformAsExpr com ctx arg - - match returnStrategy with - | ReturnStrategy.NoReturn -> stmts @ [ Statement.expr (expr) ] - | _ -> stmts @ [ Statement.return' (expr) ] - | Statement.Declaration (Declaration.VariableDeclaration (VariableDeclaration (declarations = declarations))) -> - [ for (VariableDeclarator (id = id; init = init)) in declarations do - let targets: Python.Expression list = - let name = com.GetIdentifier(ctx, id.Name) - [ Expression.name(identifier = name, ctx = Store) ] - - match init with - | Some value -> - ctx.UsedNames.LocalScope.Add id.Name |> ignore - let expr, stmts = com.TransformAsExpr(ctx, value) - yield! stmts - Statement.assign (targets, expr) - | None -> () ] - | ExpressionStatement (expr = expression) -> - // Handle Babel expressions that we need to transforme here as Python statements - match expression with - | Expression.AssignmentExpression (_) -> com.TransformAsStatements(ctx, returnStrategy, expression) - | _ -> - [ let expr, stmts = com.TransformAsExpr(ctx, expression) - yield! stmts - Statement.expr (expr) ] - | IfStatement (test = test; consequent = consequent; alternate = alternate) -> - let test, stmts = com.TransformAsExpr(ctx, test) - - let body, nonLocals = - com.TransformAsStatements(ctx, returnStrategy, consequent) - |> transformBody ReturnStrategy.NoReturn - |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) - - let orElse, nonLocals' = - match alternate with - | Some alt -> - com.TransformAsStatements(ctx, returnStrategy, alt) - |> transformBody ReturnStrategy.NoReturn - |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) - - | _ -> [], [] - - [ yield! nonLocals @ nonLocals' @ stmts; Statement.if' (test = test, body = body, orelse = orElse) ] - | WhileStatement (test = test; body = body) -> - let expr, stmts = com.TransformAsExpr(ctx, test) - - let body = - com.TransformAsStatements(ctx, returnStrategy, body) - |> transformBody ReturnStrategy.NoReturn - - [ yield! stmts; Statement.while' (test = expr, body = body, orelse = []) ] - | TryStatement (block = block; handler = handler; finalizer = finalizer) -> - let body = com.TransformAsStatements(ctx, returnStrategy, block) - - let finalBody, nonLocals = - finalizer - |> Option.map (fun f -> com.TransformAsStatements(ctx, returnStrategy, f)) - |> Option.defaultValue [] - |> List.partition (function | Statement.NonLocal (_) -> false | _ -> true ) - - let handlers = - match handler with - | Some (CatchClause (param = parm; body = body)) -> - let body = com.TransformAsStatements(ctx, returnStrategy, body) - - let exn = - Expression.name (Python.Identifier("Exception")) - |> Some - - // Insert a ex.message = str(ex) for all aliased exceptions. - let identifier = Python.Identifier(parm.Name) - [ ExceptHandler.exceptHandler (``type`` = exn, name = identifier, body = body) ] - - | _ -> [] - - [ yield! nonLocals; Statement.try' (body = body, handlers = handlers, finalBody = finalBody) ] - | SwitchStatement (discriminant = discriminant; cases = cases) -> - let value, stmts = com.TransformAsExpr(ctx, discriminant) - - let rec ifThenElse (fallThrough: Python.Expression option) (cases: Babel.SwitchCase list): Python.Statement list option = - match cases with - | [] -> None - | SwitchCase (test = test; consequent = consequent) :: cases -> - let body = - consequent - |> List.ofArray - |> List.collect (fun x -> com.TransformAsStatements(ctx, ReturnStrategy.NoBreak, x)) - - match test with - | None -> body |> Some - | Some test -> - let test, st = com.TransformAsExpr(ctx, test) - - let expr = Expression.compare (left = value, ops = [ Eq ], comparators = [ test ]) - - let test = - match fallThrough with - | Some ft -> Expression.boolOp (op = Or, values = [ ft; expr ]) - | _ -> expr - // Check for fallthrough - if body.IsEmpty then - ifThenElse (Some test) cases - else - [ Statement.if' (test = test, body = body, ?orelse = ifThenElse None cases) ] - |> Some - - let result = cases |> List.ofArray |> ifThenElse None - - match result with - | Some ifStmt -> stmts @ ifStmt - | None -> [] - | Statement.BreakStatement (_) -> [ Break ] - | Statement.Declaration (Declaration.FunctionDeclaration (``params`` = parms; id = id; body = body)) -> - [ com.TransformAsFunction(ctx, id.Name, parms, body) ] - | Statement.Declaration (Declaration.ClassDeclaration (body, id, superClass, implements, superTypeParameters, typeParameters, loc)) -> - transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc - | Statement.ForStatement (init = Some (VariableDeclaration(declarations = [| VariableDeclarator (id = id; init = Some (init)) |])) - test = Some (Expression.BinaryExpression (left = left; right = right; operator = operator)) - update = Some (Expression.UpdateExpression (operator=update)) - body = body) -> - // printfn "For: %A" body - let body = com.TransformAsStatements(ctx, ReturnStrategy.NoReturn, body) - let start, stmts1 = com.TransformAsExpr(ctx, init) - let stop, stmts2 = com.TransformAsExpr(ctx, right) - - let stop, step = - match operator, update with - | "<=", "++" -> - let stop = Expression.binOp (stop, Add, Expression.constant (1)) // Python `range` has exclusive end. - stop, 1 - | ">=", "--" -> stop, -1 - | _ -> failwith $"Unhandled for loop with operator {operator} and update {update}" - - let target = Expression.name (Python.Identifier id.Name) - let step = Expression.constant(step) - - let iter = Expression.call (Expression.name (Python.Identifier "range"), args = [ start; stop; step ]) - - stmts1 @ stmts2 @ [ Statement.for' (target = target, iter = iter, body = body) ] - | LabeledStatement (body = body) -> com.TransformAsStatements(ctx, returnStrategy, body) - | ContinueStatement (_) -> [ Continue ] - | _ -> failwith $"transformStatementAsStatements: Unhandled: {stmt}" - - let transformBlockStatementAsStatements - (com: IPythonCompiler) - (ctx: Context) - (returnStrategy: ReturnStrategy) - (block: Babel.BlockStatement) - : Python.Statement list = - - [ for stmt in block.Body do - yield! transformStatementAsStatements com ctx returnStrategy stmt ] - |> List.sortBy (function | Statement.NonLocal _ -> 0 | _ -> 1) - |> transformBody returnStrategy - - /// Transform Babel program to Python module. - let transformProgram (com: IPythonCompiler) ctx (body: Babel.ModuleDeclaration array): Module = - let returnStrategy = ReturnStrategy.NoReturn - - //printfn "Transform Program: %A" body - let stmt: Python.Statement list = - [ for md in body do - match md with - | Babel.ExportNamedDeclaration (decl) -> - match decl with - | Babel.VariableDeclaration (VariableDeclaration (declarations = declarations)) -> - for (VariableDeclarator (id, init)) in declarations do - let value, stmts = com.TransformAsExpr(ctx, init.Value) - - let targets: Python.Expression list = - let name = com.GetIdentifier(ctx, id.Name) - ctx.UsedNames.GlobalScope.Add id.Name |> ignore - [ Expression.name (identifier = name, ctx = Store) ] - - yield! stmts - yield Statement.assign (targets = targets, value = value) - | Babel.FunctionDeclaration (``params`` = ``params``; body = body; id = id) -> - yield com.TransformAsFunction(ctx, id.Name, ``params``, body) - - | Babel.ClassDeclaration (body, id, superClass, implements, superTypeParameters, typeParameters, loc) -> - yield! transformAsClassDef com ctx body id superClass implements superTypeParameters typeParameters loc - | _ -> failwith $"Unhandled Declaration: {decl}" - - | Babel.ImportDeclaration (specifiers, source) -> - yield! com.TransformAsImports(ctx, specifiers, source) - | Babel.PrivateModuleDeclaration (statement = statement) -> - yield! - com.TransformAsStatements(ctx, ReturnStrategy.Return, statement) - |> transformBody returnStrategy - | _ -> failwith $"Unknown module declaration: {md}" ] - - let imports = com.GetAllImports() - Module.module' (imports @ stmt) - - let getIdentForImport (ctx: Context) (moduleName: string) (name: string option) = - match name with - | None -> - Path.GetFileNameWithoutExtension(moduleName) - |> Python.Identifier - |> Some - | Some name -> - match name with - | "*" - | _ -> name - |> Python.Identifier - |> Some - -module Compiler = - open Util - - type PythonCompiler (com: Compiler) = - let onlyOnceWarnings = HashSet() - let imports = Dictionary() - - interface IPythonCompiler with - member _.WarnOnlyOnce(msg, ?range) = - if onlyOnceWarnings.Add(msg) then - addWarning com [] range msg - - member bcom.GetIdentifier(ctx, name) = getIdentifier bcom ctx name - member _.GetImportExpr(ctx, moduleName, ?name, ?loc) = - let cachedName = moduleName + "::" + defaultArg name "module" - - match imports.TryGetValue(cachedName) with - | (true, ImportFrom { Names = [ { AsName = localIdent } ] }) -> - match localIdent with - | Some localIdent -> localIdent |> Some - | None -> None - | (true, Import _) -> None - | _ -> - let localId = getIdentForImport ctx moduleName name - - match name with - | Some name -> - let nameId = - if name = Naming.placeholder then - "`importMember` must be assigned to a variable" - |> addError com [] loc - name |> Python.Identifier - - let i = Statement.importFrom(Python.Identifier moduleName |> Some, [ Alias.alias (nameId, ?asname=localId) ]) - imports.Add(cachedName, i) - | None -> - let i = Statement.import([ Alias.alias (Python.Identifier moduleName)]) - imports.Add(cachedName, i) - - match localId with - | Some localId -> localId |> Some - | None -> None - - member _.GetAllImports() = - imports.Values - |> List.ofSeq - - member bcom.TransformAsExpr(ctx, e) = transformAsExpr bcom ctx e - member bcom.TransformAsStatements(ctx, ret, e) = transformExpressionAsStatements bcom ctx ret e - member bcom.TransformAsStatements(ctx, ret, e) = transformStatementAsStatements bcom ctx ret e - member bcom.TransformAsStatements(ctx, ret, e) = transformBlockStatementAsStatements bcom ctx ret e - member bcom.TransformAsClassDef(ctx, body, id, superClass, implements, superTypeParameters, typeParameters, loc) = - transformAsClassDef bcom ctx body id superClass implements superTypeParameters typeParameters loc - member bcom.TransformAsFunction(ctx, name, args, body) = transformAsFunction bcom ctx name args body - member bcom.TransformAsImports(ctx, specifiers, source) = transformAsImports bcom ctx specifiers source - - interface Compiler with - member _.Options = com.Options - member _.Plugins = com.Plugins - member _.LibraryDir = com.LibraryDir - member _.CurrentFile = com.CurrentFile - member _.OutputDir = com.OutputDir - member _.ProjectFile = com.ProjectFile - member _.GetEntity(fullName) = com.GetEntity(fullName) - member _.GetImplementationFile(fileName) = com.GetImplementationFile(fileName) - member _.GetRootModule(fileName) = com.GetRootModule(fileName) - member _.GetOrAddInlineExpr(fullName, generate) = com.GetOrAddInlineExpr(fullName, generate) - member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) - - member _.AddLog(msg, severity, ?range, ?fileName: string, ?tag: string) = - com.AddLog(msg, severity, ?range = range, ?fileName = fileName, ?tag = tag) - - let makeCompiler com = PythonCompiler(com) - - let transformFile (com: Compiler) (program: Babel.Program) = - let com = makeCompiler com :> IPythonCompiler - - let ctx = - { ReturnStrategy = ReturnStrategy.NoReturn // Don't return in global scope. - UsedNames = { - GlobalScope = HashSet () - EnclosingScope = HashSet () - LocalScope = HashSet () - } - } - - let (Program body) = program - transformProgram com ctx body diff --git a/tests/Python/TestReflection.fs b/tests/Python/TestReflection.fs index 862242d6aa..efdf635142 100644 --- a/tests/Python/TestReflection.fs +++ b/tests/Python/TestReflection.fs @@ -103,12 +103,6 @@ let getName3 (t:System.Type) = function | "name" -> t.Name | _ -> t.FullName -// Fable 2 cannot check types unknown at compile time -// let getName4 (o:obj) = function -// | "namespace" -> o.GetType().Namespace -// | "name" -> o.GetType().Name -// | _ -> o.GetType().FullName - type Firm = { name: string } let normalize (x: string) = From c1c8dab1375d4be7e43f056691cc691c752252f9 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 11:38:38 +0200 Subject: [PATCH 131/145] Remove obsolete files --- src/Fable.Transforms/Global/Babel.fs | 3 - src/Fable.Transforms/Python/README.md | 2 +- src/fable-library-py/fable/seq.py.old | 60 ------------ .../Untitled-checkpoint.ipynb | 6 -- src/quicktest/QuickTest-Interface.fs | 45 --------- src/quicktest/QuickTest.fs | 97 ++----------------- src/quicktest/QuickTest.fs.ts | 76 --------------- src/quicktest/QuickTest.py | 16 --- src/quicktest/QuickTest2.py | 17 ---- src/quicktest/quick_test.py | 11 --- src/quicktest/quick_test2.py | 11 --- 11 files changed, 10 insertions(+), 334 deletions(-) delete mode 100644 src/fable-library-py/fable/seq.py.old delete mode 100644 src/quicktest/.ipynb_checkpoints/Untitled-checkpoint.ipynb delete mode 100644 src/quicktest/QuickTest-Interface.fs delete mode 100644 src/quicktest/QuickTest.fs.ts delete mode 100644 src/quicktest/QuickTest.py delete mode 100644 src/quicktest/QuickTest2.py delete mode 100644 src/quicktest/quick_test.py delete mode 100644 src/quicktest/quick_test2.py diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index 33200480a4..1916ff2cc4 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -1,9 +1,6 @@ // fsharplint:disable MemberNames InterfaceNames namespace rec Fable.AST.Babel -/// Babel AST. Note that this AST is a bit fragile in the sense that you cannot refer to all the types and union -/// constructors fully qualified. Thus you must fully open the namespace in referring files. - open Fable.AST /// The type field is a string representing the AST variant type. diff --git a/src/Fable.Transforms/Python/README.md b/src/Fable.Transforms/Python/README.md index 0c22338a8a..d891e6c34e 100644 --- a/src/Fable.Transforms/Python/README.md +++ b/src/Fable.Transforms/Python/README.md @@ -1,3 +1,3 @@ # Fable to Python -Experimental support for Python transforming the Babel AST into a Python AST. +Experimental support for Python transforming the Fable AST into a Python AST. diff --git a/src/fable-library-py/fable/seq.py.old b/src/fable-library-py/fable/seq.py.old deleted file mode 100644 index 3064d2b8ac..0000000000 --- a/src/fable-library-py/fable/seq.py.old +++ /dev/null @@ -1,60 +0,0 @@ -from typing import Callable, Iterable, TypeVar -from expression.collections import Seq, seq, frozenlist - -A = TypeVar("A") -B = TypeVar("B") - - -def map(mapper: Callable[[A], B], xs: Seq[A]) -> Seq[B]: - return Seq(xs).map(mapper) - - -def filter(predicate: Callable[[A], bool], xs: Seq[A]) -> Seq[A]: - return Seq(xs).filter(predicate) - - -def length(xs): - return Seq(xs).length() - - -def empty(): - return seq.empty - - -def collect(mapper: Callable[[A], Seq[B]], source: Seq[A]) -> Seq[B]: - return Seq(source).collect(mapper) - - -def skip(count: int, xs: Seq[A]) -> Seq[A]: - return Seq(xs).skip(count) - - -def sum(source: Iterable[A]) -> A: - return Seq(source).sum() - - -def sumBy(projection: Callable[[A], B], source: Iterable[A], _) -> B: - return Seq(source).sum_by(projection) - - -delay = seq.delay -head = seq.head -rangeNumber = seq.range -singleton = seq.singleton -append = seq.concat -ofList = seq.of_list -toList = frozenlist.of_seq -concat = seq.concat -tail = seq.tail - -__all__ = [ - "delay", - "empty", - "head", - "map", - "length", - "rangeNumber", - "singleton", - "skip", - "tail", -] diff --git a/src/quicktest/.ipynb_checkpoints/Untitled-checkpoint.ipynb b/src/quicktest/.ipynb_checkpoints/Untitled-checkpoint.ipynb deleted file mode 100644 index 7fec51502c..0000000000 --- a/src/quicktest/.ipynb_checkpoints/Untitled-checkpoint.ipynb +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cells": [], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/src/quicktest/QuickTest-Interface.fs b/src/quicktest/QuickTest-Interface.fs deleted file mode 100644 index 62850e1350..0000000000 --- a/src/quicktest/QuickTest-Interface.fs +++ /dev/null @@ -1,45 +0,0 @@ -module QuickTest - -open System -open Fable.Core - -[] -type BarInterface = - abstract Bar: string with get, set - abstract DoSomething: f: (float -> float -> float) * v: float -> float - abstract Item: int -> char with get, set - abstract Item: char -> bool with get - abstract Sum: [] items: string[] -> string - -[] -type BarAbstractClass(x: float) = - member _.Value = x - member _.DoSomething(x, y) = x ** y - abstract DoSomething: float -> float - -type BarClass(x) = - inherit BarAbstractClass(10.) - let mutable x = x - override this.DoSomething(x) = - this.DoSomething(x, this.Value) - interface BarInterface with - member _.Bar with get() = x and set(y) = x <- y - member this.DoSomething(f, x) = - let f = f x - let x = f 4.5 - let y = f 7. - this.DoSomething(x - y) - member _.Item with get(i) = x.[i] and set i c = x <- x.ToCharArray() |> Array.mapi (fun i2 c2 -> if i = i2 then c else c2) |> String - member _.Item with get(c) = x.ToCharArray() |> Array.exists ((=) c) - member _.Sum(items) = Array.reduce (fun x y -> x + x + y + y) items - -let test () = - let addPlus2 x y = x + y + 2. - let multiplyTwice x y = x * y * y - let bar2 = BarClass("Bar") :> BarInterface - bar2.[0] <- 'Z' - bar2.Bar <- bar2.Bar + bar2.DoSomething(addPlus2, 3.).ToString("F2") + bar2.[2].ToString() + (sprintf "%b%b" bar2.['B'] bar2.['x']) - bar2.Bar <- bar2.Bar + bar2.Sum("a", "bc", "d") - bar2.Bar |> printfn "%s" // Zar9536.74rfalsefalseaabcbcaabcbcdd - -test() diff --git a/src/quicktest/QuickTest.fs b/src/quicktest/QuickTest.fs index d9ea700bee..eecb47fbd5 100644 --- a/src/quicktest/QuickTest.fs +++ b/src/quicktest/QuickTest.fs @@ -1,92 +1,13 @@ module QuickTest -// Run `dotnet fsi build.fsx quicktest` and then add tests to this file, -// when you save they will be run automatically with latest changes in compiler. -// When everything works, move the tests to the appropriate file in tests/Main. -// Please don't add this file to your commits. +open FSharp.Core -open System -open System.Collections.Generic -open Fable.Core -open Fable.Core.JsInterop -open Fable.Core.Testing +let ``test Seq.collect works with Options`` () = + let xss = [[Some 1; Some 2]; [None; Some 3]] + Seq.collect id xss + |> Seq.sumBy (function + | Some n -> n + | None -> 0 + ) + |> ignore -let log (o: obj) = - printfn "%O" o - -let equal expected actual = - let areEqual = expected = actual - printfn "%A = %A > %b" expected actual areEqual - if not areEqual then - failwithf "[ASSERT ERROR] Expected %A but got %A" expected actual - -let throwsError (expected: string) (f: unit -> 'a): unit = - let success = - try - f () |> ignore - true - with e -> - if not <| String.IsNullOrEmpty(expected) then - equal e.Message expected - false - // TODO better error messages - equal false success - -let testCase (msg: string) f: unit = - try - printfn "%s" msg - f () - with ex -> - printfn "%s" ex.Message - if ex.Message <> null && ex.Message.StartsWith("[ASSERT ERROR]") |> not then - printfn "%s" ex.StackTrace - printfn "" - -let testCaseAsync msg f = - testCase msg (fun () -> - async { - try - do! f () - with ex -> - printfn "%s" ex.Message - if ex.Message <> null && ex.Message.StartsWith("[ASSERT ERROR]") |> not then - printfn "%s" ex.StackTrace - } |> Async.StartImmediate) - -let measureTime (f: unit -> unit) = emitJsStatement () """ - //js - const startTime = process.hrtime(); - f(); - const elapsed = process.hrtime(startTime); - console.log("Ms:", elapsed[0] * 1e3 + elapsed[1] / 1e6); - //!js -""" - -// Write here your unit test, you can later move it -// to Fable.Tests project. For example: -// testCase "Addition works" <| fun () -> -// 2 + 2 |> equal 4 - -let numloops = 10000 - -do - let src: uint8[] = Array.zeroCreate 16384 - let trg: uint8[] = Array.zeroCreate 131072 - - measureTime <| fun () -> - for _ in 1 .. numloops do - let rec loopi i = - if i < trg.Length then - Array.blit src 0 trg i src.Length - loopi (i + src.Length) in loopi 0 - -do - let src: char[] = Array.zeroCreate 16384 - let trg: char[] = Array.zeroCreate 131072 - - measureTime <| fun () -> - for _ in 1 .. numloops do - let rec loopi i = - if i < trg.Length then - Array.blit src 0 trg i src.Length - loopi (i + src.Length) in loopi 0 diff --git a/src/quicktest/QuickTest.fs.ts b/src/quicktest/QuickTest.fs.ts deleted file mode 100644 index 30cb26db2a..0000000000 --- a/src/quicktest/QuickTest.fs.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { isNullOrEmpty, toFail, printf, toConsole } from "./.fable/fable-library.3.1.1/String.js"; -import { equals } from "./.fable/fable-library.3.1.1/Util.js"; -import { startImmediate } from "./.fable/fable-library.3.1.1/Async.js"; -import { singleton } from "./.fable/fable-library.3.1.1/AsyncBuilder.js"; - -export function log(o: any): void { - toConsole(printf("%O"))(o); -} - -export function equal(expected: a$, actual: a$): void { - const areEqual = equals(expected, actual); - toConsole(printf("%A = %A \u003e %b"))(expected)(actual)(areEqual); - if (!areEqual) { - toFail(printf("[ASSERT ERROR] Expected %A but got %A"))(expected)(actual); - } -} - -export function throwsError(expected: string, f: (arg0: void) => a): void { - let success; - try { - const value = f(); - void value; - success = true; - } - catch (e) { - if (!isNullOrEmpty(expected)) { - equal(e.message, expected); - } - success = false; - } - equal(false, success); -} - -export function testCase(msg: string, f: (arg0: void) => void): void { - try { - toConsole(printf("%s"))(msg); - f(); - } - catch (ex) { - const arg10_1 = ex.message; - toConsole(printf("%s"))(arg10_1); - if ((ex.message !== null) ? (!(ex.message.indexOf("[ASSERT ERROR]") === 0)) : false) { - const arg10_2 = ex.stack; - toConsole(printf("%s"))(arg10_2); - } - } - toConsole(printf("")); -} - -export function testCaseAsync(msg: string, f: (arg0: void) => any): void { - testCase(msg, (): void => { - startImmediate(singleton.Delay((): any => singleton.TryWith(singleton.Delay((): any => singleton.Bind(f(), (): any => singleton.Return())), (_arg2: Error): any => { - const ex = _arg2; - const arg10 = ex.message; - toConsole(printf("%s"))(arg10); - if ((ex.message !== null) ? (!(ex.message.indexOf("[ASSERT ERROR]") === 0)) : false) { - const arg10_1 = ex.stack; - toConsole(printf("%s"))(arg10_1); - return singleton.Zero(); - } - else { - return singleton.Zero(); - } - }))); - }); -} - -export function measureTime(f: (arg0: void) => void): a$ { - //js - const startTime = process.hrtime(); - f(); - const elapsed = process.hrtime(startTime); - console.log("Ms:", elapsed[0] * 1e3 + elapsed[1] / 1e6); - //!js -} - diff --git a/src/quicktest/QuickTest.py b/src/quicktest/QuickTest.py deleted file mode 100644 index 8571999e3a..0000000000 --- a/src/quicktest/QuickTest.py +++ /dev/null @@ -1,16 +0,0 @@ -from fable.reflection import class_type - -def lifted_1(b): - return Math$(b) - - -def lifted_3(): - return class_type("QuickTest.Math", None, Math$) - - -Math$ = Lifted_2 - -Math$$reflection = lifted_3 - -Math_$ctor_Z524259A4 = lifted_1 - diff --git a/src/quicktest/QuickTest2.py b/src/quicktest/QuickTest2.py deleted file mode 100644 index 7a27954980..0000000000 --- a/src/quicktest/QuickTest2.py +++ /dev/null @@ -1,17 +0,0 @@ -from collections import namedtuple as namedtuple -from fable.reflection import class_type - -class Math_: - def __init__(self, b=None): - namedtuple("object", [])() - a = 20 - - -def Math__reflection(): - return class_type("QuickTest.Math", None, Math_) - - -def Math__ctor_Z524259A4(b): - return Math_(b) - - diff --git a/src/quicktest/quick_test.py b/src/quicktest/quick_test.py deleted file mode 100644 index e7cc6642aa..0000000000 --- a/src/quicktest/quick_test.py +++ /dev/null @@ -1,11 +0,0 @@ - -def test(): - value = 2 - def lifted_0(): - nonlocal value - value = 42 - - fn = lifted_0 - fn() - - diff --git a/src/quicktest/quick_test2.py b/src/quicktest/quick_test2.py deleted file mode 100644 index a00b5ab6c5..0000000000 --- a/src/quicktest/quick_test2.py +++ /dev/null @@ -1,11 +0,0 @@ - -def test(): - value = 2 - def lifted_0(_=None): - nonlocal value - value = 42 - - fn = lifted_0 - fn() - - From a7988cf6caaa247143d5868f15896d9f637ecb79 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 11:43:32 +0200 Subject: [PATCH 132/145] Revert quicktest --- .flake8 | 3 -- src/quicktest/.vscode/settings.json | 4 -- src/quicktest/QuickTest.fs | 73 +++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 16 deletions(-) delete mode 100644 .flake8 delete mode 100644 src/quicktest/.vscode/settings.json diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 28a7341c5c..0000000000 --- a/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -ignore = E731, T484, T400 # Do not assign a lambda expression, use a def -max-line-length = 121 \ No newline at end of file diff --git a/src/quicktest/.vscode/settings.json b/src/quicktest/.vscode/settings.json deleted file mode 100644 index 9bc2490edf..0000000000 --- a/src/quicktest/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "python.linting.enabled": false, - "python.linting.pylintEnabled": false -} \ No newline at end of file diff --git a/src/quicktest/QuickTest.fs b/src/quicktest/QuickTest.fs index eecb47fbd5..2a5f805975 100644 --- a/src/quicktest/QuickTest.fs +++ b/src/quicktest/QuickTest.fs @@ -1,13 +1,68 @@ module QuickTest -open FSharp.Core +// Run `dotnet fsi build.fsx quicktest` and then add tests to this file, +// when you save they will be run automatically with latest changes in compiler. +// When everything works, move the tests to the appropriate file in tests/Main. +// Please don't add this file to your commits. -let ``test Seq.collect works with Options`` () = - let xss = [[Some 1; Some 2]; [None; Some 3]] - Seq.collect id xss - |> Seq.sumBy (function - | Some n -> n - | None -> 0 - ) - |> ignore +open System +open System.Collections.Generic +open Fable.Core +open Fable.Core.JsInterop +open Fable.Core.Testing +let log (o: obj) = + printfn "%A" o + +let equal expected actual = + let areEqual = expected = actual + printfn "%A = %A > %b" expected actual areEqual + if not areEqual then + failwithf "[ASSERT ERROR] Expected %A but got %A" expected actual + +let throwsError (expected: string) (f: unit -> 'a): unit = + let success = + try + f () |> ignore + true + with e -> + if not <| String.IsNullOrEmpty(expected) then + equal e.Message expected + false + // TODO better error messages + equal false success + +let testCase (msg: string) f: unit = + try + printfn "%s" msg + f () + with ex -> + printfn "%s" ex.Message + if ex.Message <> null && ex.Message.StartsWith("[ASSERT ERROR]") |> not then + printfn "%s" ex.StackTrace + printfn "" + +let testCaseAsync msg f = + testCase msg (fun () -> + async { + try + do! f () + with ex -> + printfn "%s" ex.Message + if ex.Message <> null && ex.Message.StartsWith("[ASSERT ERROR]") |> not then + printfn "%s" ex.StackTrace + } |> Async.StartImmediate) + +let measureTime (f: unit -> unit) = emitJsStatement () """ + //js + const startTime = process.hrtime(); + f(); + const elapsed = process.hrtime(startTime); + console.log("Ms:", elapsed[0] * 1e3 + elapsed[1] / 1e6); + //!js +""" + +// Write here your unit test, you can later move it +// to Fable.Tests project. For example: +// testCase "Addition works" <| fun () -> +// 2 + 2 |> equal 4 \ No newline at end of file From 3f1c23640b63ebe4fceb2a29930f1fe0a9e70bee Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 11:54:41 +0200 Subject: [PATCH 133/145] Revert changes to build.fsx Remove test file Pythagoras.s --- build.fsx | 6 +++--- src/quicktest/{Pythagoras.fs => Pythagoras.fs.python} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename src/quicktest/{Pythagoras.fs => Pythagoras.fs.python} (100%) diff --git a/build.fsx b/build.fsx index 78810d66a4..f81ef641cc 100644 --- a/build.fsx +++ b/build.fsx @@ -572,13 +572,13 @@ match argsLower with | "test-py"::_ -> testPython() | "quicktest"::_ -> buildLibraryIfNotExists() - run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --noCache" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --exclude Fable.Core --noCache --runScript" | "quicktest-py"::_ -> buildLibraryIfNotExists() run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../quicktest --lang Python --exclude Fable.Core --noCache" | "jupyter" :: _ -> buildLibraryIfNotExists () - run "dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src --lang Python --exclude Fable.Core --noCache 2>> /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src/fable.out" + run "dotnet watch -p src/Fable.Cli run -- watch --cwd ../Fable.Jupyter/src --lang Python --exclude Fable.Core --noCache 2>> /Users/dbrattli/Developer/GitHub/Fable.Jupyter/src/fable.out" | "run"::_ -> buildLibraryIfNotExists() @@ -624,4 +624,4 @@ match argsLower with dotnet fsi build.fsx quicktest """ -printfn "Build finished successfully" \ No newline at end of file +printfn "Build finished successfully" diff --git a/src/quicktest/Pythagoras.fs b/src/quicktest/Pythagoras.fs.python similarity index 100% rename from src/quicktest/Pythagoras.fs rename to src/quicktest/Pythagoras.fs.python From c42c106af5cfd719b4a2231488f2fb94149f1bbd Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 11:56:48 +0200 Subject: [PATCH 134/145] Remove unused file --- src/quicktest/Pythagoras.fs.python | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 src/quicktest/Pythagoras.fs.python diff --git a/src/quicktest/Pythagoras.fs.python b/src/quicktest/Pythagoras.fs.python deleted file mode 100644 index ef09709863..0000000000 --- a/src/quicktest/Pythagoras.fs.python +++ /dev/null @@ -1,29 +0,0 @@ -module QuickTest - -let add(a, b, cont) = - cont(a + b) - -let square(x, cont) = - cont(x * x) - -type Math () = - let a = 10 - -let sqrt(x, cont) = - cont(sqrt(x)) - -let pythagoras(a, b, cont) = - square(a, (fun aa -> - printfn "test" - square(b, (fun bb -> - printfn "test" - add(aa, bb, (fun aabb -> - printfn "test" - sqrt(aabb, (fun result -> - printfn "test" - cont(result) - )) - )) - )) - )) - From 5ff46d323899b9b95bc7d55c335dc23d193d166e Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 11:59:10 +0200 Subject: [PATCH 135/145] Remove unused files --- src/quicktest/QuickTest.fs | 2 +- src/quicktest/Untitled.ipynb | 59 --- src/quicktest/__init__.py | 0 yarn.lock | 981 ----------------------------------- 4 files changed, 1 insertion(+), 1041 deletions(-) delete mode 100644 src/quicktest/Untitled.ipynb delete mode 100644 src/quicktest/__init__.py delete mode 100644 yarn.lock diff --git a/src/quicktest/QuickTest.fs b/src/quicktest/QuickTest.fs index 2a5f805975..a61381c5c7 100644 --- a/src/quicktest/QuickTest.fs +++ b/src/quicktest/QuickTest.fs @@ -65,4 +65,4 @@ let measureTime (f: unit -> unit) = emitJsStatement () """ // Write here your unit test, you can later move it // to Fable.Tests project. For example: // testCase "Addition works" <| fun () -> -// 2 + 2 |> equal 4 \ No newline at end of file +// 2 + 2 |> equal 4 diff --git a/src/quicktest/Untitled.ipynb b/src/quicktest/Untitled.ipynb deleted file mode 100644 index 06f379f1f3..0000000000 --- a/src/quicktest/Untitled.ipynb +++ /dev/null @@ -1,59 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "20\n" - ] - } - ], - "source": [ - "def test():\n", - " def inner():\n", - " nonlocal x\n", - " x = x + 10\n", - " print(y)\n", - " \n", - " x = 20\n", - "\n", - " inner()\n", - "\n", - "test()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.2" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/src/quicktest/__init__.py b/src/quicktest/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 8586f5a503..0000000000 --- a/yarn.lock +++ /dev/null @@ -1,981 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@^7.0.0": - "integrity" "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==" - "resolved" "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz" - "version" "7.12.13" - dependencies: - "@babel/highlight" "^7.12.13" - -"@babel/helper-validator-identifier@^7.12.11": - "integrity" "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" - "resolved" "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz" - "version" "7.12.11" - -"@babel/highlight@^7.12.13": - "integrity" "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==" - "resolved" "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz" - "version" "7.13.10" - dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - "chalk" "^2.0.0" - "js-tokens" "^4.0.0" - -"@types/node@^14.14.40": - "integrity" "sha512-2HoZZGylcnz19ZSbvWhgWHqvprw1ZGHanxIrDWYykPD4CauLW4gcyLzCVfUN2kv/1t1F3CurQIdi+s1l9+XgEA==" - "resolved" "https://registry.npmjs.org/@types/node/-/node-14.14.40.tgz" - "version" "14.14.40" - -"@ungap/promise-all-settled@1.1.2": - "integrity" "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" - "resolved" "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz" - "version" "1.1.2" - -"after@~0.8.1": - "integrity" "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" - "resolved" "https://registry.npmjs.org/after/-/after-0.8.2.tgz" - "version" "0.8.2" - -"ansi-colors@4.1.1": - "integrity" "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" - "resolved" "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" - "version" "4.1.1" - -"ansi-regex@^3.0.0": - "integrity" "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz" - "version" "3.0.0" - -"ansi-regex@^5.0.0": - "integrity" "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz" - "version" "5.0.0" - -"ansi-styles@^3.2.1": - "integrity" "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" - "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" - "version" "3.2.1" - dependencies: - "color-convert" "^1.9.0" - -"ansi-styles@^4.0.0", "ansi-styles@^4.1.0": - "integrity" "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==" - "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - "version" "4.3.0" - dependencies: - "color-convert" "^2.0.1" - -"anymatch@~3.1.1": - "integrity" "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==" - "resolved" "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" - "version" "3.1.2" - dependencies: - "normalize-path" "^3.0.0" - "picomatch" "^2.0.4" - -"argparse@^1.0.7": - "integrity" "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==" - "resolved" "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" - "version" "1.0.10" - dependencies: - "sprintf-js" "~1.0.2" - -"argparse@^2.0.1": - "integrity" "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - "resolved" "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" - "version" "2.0.1" - -"balanced-match@^1.0.0": - "integrity" "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - "resolved" "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" - "version" "1.0.2" - -"binary-extensions@^2.0.0": - "integrity" "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" - "resolved" "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" - "version" "2.2.0" - -"bl@~3.0.0": - "integrity" "sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ==" - "resolved" "https://registry.npmjs.org/bl/-/bl-3.0.1.tgz" - "version" "3.0.1" - dependencies: - "readable-stream" "^3.0.1" - -"brace-expansion@^1.1.7": - "integrity" "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" - "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" - "version" "1.1.11" - dependencies: - "balanced-match" "^1.0.0" - "concat-map" "0.0.1" - -"braces@~3.0.2": - "integrity" "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==" - "resolved" "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" - "version" "3.0.2" - dependencies: - "fill-range" "^7.0.1" - -"browser-stdout@1.3.1": - "integrity" "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" - "resolved" "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" - "version" "1.3.1" - -"buffer-from@^0.1.1": - "integrity" "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==" - "resolved" "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz" - "version" "0.1.2" - -"buffer-from@^1.0.0": - "integrity" "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - "resolved" "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz" - "version" "1.1.1" - -"builtin-modules@^1.1.1": - "integrity" "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" - "resolved" "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" - "version" "1.1.1" - -"camelcase@^6.0.0": - "integrity" "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" - "resolved" "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz" - "version" "6.2.0" - -"chalk@^2.0.0": - "integrity" "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" - "resolved" "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - "version" "2.4.2" - dependencies: - "ansi-styles" "^3.2.1" - "escape-string-regexp" "^1.0.5" - "supports-color" "^5.3.0" - -"chalk@^2.3.0": - "integrity" "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" - "resolved" "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - "version" "2.4.2" - dependencies: - "ansi-styles" "^3.2.1" - "escape-string-regexp" "^1.0.5" - "supports-color" "^5.3.0" - -"chalk@^4.0.0": - "integrity" "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==" - "resolved" "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz" - "version" "4.1.0" - dependencies: - "ansi-styles" "^4.1.0" - "supports-color" "^7.1.0" - -"chokidar@3.5.1": - "integrity" "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==" - "resolved" "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz" - "version" "3.5.1" - dependencies: - "anymatch" "~3.1.1" - "braces" "~3.0.2" - "glob-parent" "~5.1.0" - "is-binary-path" "~2.1.0" - "is-glob" "~4.0.1" - "normalize-path" "~3.0.0" - "readdirp" "~3.5.0" - optionalDependencies: - "fsevents" "~2.3.1" - -"cliui@^7.0.2": - "integrity" "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==" - "resolved" "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" - "version" "7.0.4" - dependencies: - "string-width" "^4.2.0" - "strip-ansi" "^6.0.0" - "wrap-ansi" "^7.0.0" - -"color-convert@^1.9.0": - "integrity" "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==" - "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" - "version" "1.9.3" - dependencies: - "color-name" "1.1.3" - -"color-convert@^2.0.1": - "integrity" "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==" - "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" - "version" "2.0.1" - dependencies: - "color-name" "~1.1.4" - -"color-name@~1.1.4": - "integrity" "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - "version" "1.1.4" - -"color-name@1.1.3": - "integrity" "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - "version" "1.1.3" - -"commander@^2.12.1", "commander@^2.20.0": - "integrity" "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - "resolved" "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" - "version" "2.20.3" - -"concat-map@0.0.1": - "integrity" "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - "resolved" "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - "version" "0.0.1" - -"core-util-is@~1.0.0": - "integrity" "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - "resolved" "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - "version" "1.0.2" - -"debug@4.3.1": - "integrity" "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==" - "resolved" "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz" - "version" "4.3.1" - dependencies: - "ms" "2.1.2" - -"decamelize@^4.0.0": - "integrity" "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" - "resolved" "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz" - "version" "4.0.0" - -"diff@^4.0.1": - "integrity" "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" - "resolved" "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" - "version" "4.0.2" - -"diff@5.0.0": - "integrity" "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" - "resolved" "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" - "version" "5.0.0" - -"duplexer2@~0.0.2": - "integrity" "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=" - "resolved" "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz" - "version" "0.0.2" - dependencies: - "readable-stream" "~1.1.9" - -"emoji-regex@^8.0.0": - "integrity" "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - "resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" - "version" "8.0.0" - -"escalade@^3.1.1": - "integrity" "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - "resolved" "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" - "version" "3.1.1" - -"escape-string-regexp@^1.0.5": - "integrity" "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - "version" "1.0.5" - -"escape-string-regexp@4.0.0": - "integrity" "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" - "version" "4.0.0" - -"esm@^3.2.25": - "integrity" "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" - "resolved" "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz" - "version" "3.2.25" - -"esprima@^4.0.0": - "integrity" "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - "resolved" "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" - "version" "4.0.1" - -"fill-range@^7.0.1": - "integrity" "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==" - "resolved" "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" - "version" "7.0.1" - dependencies: - "to-regex-range" "^5.0.1" - -"find-up@5.0.0": - "integrity" "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==" - "resolved" "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" - "version" "5.0.0" - dependencies: - "locate-path" "^6.0.0" - "path-exists" "^4.0.0" - -"flat@^5.0.2": - "integrity" "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" - "resolved" "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" - "version" "5.0.2" - -"fs.realpath@^1.0.0": - "integrity" "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - "resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - "version" "1.0.0" - -"fsevents@~2.3.1": - "integrity" "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==" - "resolved" "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" - "version" "2.3.2" - -"function-bind@^1.1.1": - "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" - "version" "1.1.1" - -"get-caller-file@^2.0.5": - "integrity" "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - "resolved" "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" - "version" "2.0.5" - -"ghreleases@^3.0.2": - "integrity" "sha512-QiR9mIYvRG7hd8JuQYoxeBNOelVuTp2DpdiByRywbCDBSJufK9Vq7VuhD8B+5uviMxZx2AEkCzye61Us9gYgnw==" - "resolved" "https://registry.npmjs.org/ghreleases/-/ghreleases-3.0.2.tgz" - "version" "3.0.2" - dependencies: - "after" "~0.8.1" - "ghrepos" "~2.1.0" - "ghutils" "~3.2.0" - "lodash.uniq" "^4.5.0" - "simple-mime" "~0.1.0" - "url-template" "~2.0.6" - -"ghrepos@~2.1.0": - "integrity" "sha512-6GM0ohSDTAv7xD6GsKfxJiV/CajoofRyUwu0E8l29d1o6lFAUxmmyMP/FH33afA20ZrXzxxcTtN6TsYvudMoAg==" - "resolved" "https://registry.npmjs.org/ghrepos/-/ghrepos-2.1.0.tgz" - "version" "2.1.0" - dependencies: - "ghutils" "~3.2.0" - -"ghutils@~3.2.0": - "integrity" "sha512-WpYHgLQkqU7Cv147wKUEThyj6qKHCdnAG2CL9RRsRQImVdLGdVqblJ3JUnj3ToQwgm1ALPS+FXgR0448AgGPUg==" - "resolved" "https://registry.npmjs.org/ghutils/-/ghutils-3.2.6.tgz" - "version" "3.2.6" - dependencies: - "jsonist" "~2.1.0" - "xtend" "~4.0.1" - -"glob-parent@~5.1.0": - "integrity" "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==" - "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" - "version" "5.1.2" - dependencies: - "is-glob" "^4.0.1" - -"glob@^7.1.1", "glob@7.1.6": - "integrity" "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==" - "resolved" "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" - "version" "7.1.6" - dependencies: - "fs.realpath" "^1.0.0" - "inflight" "^1.0.4" - "inherits" "2" - "minimatch" "^3.0.4" - "once" "^1.3.0" - "path-is-absolute" "^1.0.0" - -"growl@1.10.5": - "integrity" "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" - "resolved" "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz" - "version" "1.10.5" - -"has-flag@^3.0.0": - "integrity" "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" - "version" "3.0.0" - -"has-flag@^4.0.0": - "integrity" "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" - "version" "4.0.0" - -"has@^1.0.3": - "integrity" "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" - "resolved" "https://registry.npmjs.org/has/-/has-1.0.3.tgz" - "version" "1.0.3" - dependencies: - "function-bind" "^1.1.1" - -"he@1.2.0": - "integrity" "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" - "resolved" "https://registry.npmjs.org/he/-/he-1.2.0.tgz" - "version" "1.2.0" - -"hyperquest@~2.1.3": - "integrity" "sha512-fUuDOrB47PqNK/BAMOS13v41UoaqIxqSLHX6CAbOD7OfT+/GCWO1/vPLfTNutOeXrv1ikuaZ3yux+33Z9vh+rw==" - "resolved" "https://registry.npmjs.org/hyperquest/-/hyperquest-2.1.3.tgz" - "version" "2.1.3" - dependencies: - "buffer-from" "^0.1.1" - "duplexer2" "~0.0.2" - "through2" "~0.6.3" - -"inflight@^1.0.4": - "integrity" "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=" - "resolved" "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" - "version" "1.0.6" - dependencies: - "once" "^1.3.0" - "wrappy" "1" - -"inherits@^2.0.3", "inherits@~2.0.1", "inherits@2": - "integrity" "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - "resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - "version" "2.0.4" - -"is-binary-path@~2.1.0": - "integrity" "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==" - "resolved" "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" - "version" "2.1.0" - dependencies: - "binary-extensions" "^2.0.0" - -"is-core-module@^2.2.0": - "integrity" "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==" - "resolved" "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz" - "version" "2.2.0" - dependencies: - "has" "^1.0.3" - -"is-extglob@^2.1.1": - "integrity" "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - "resolved" "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" - "version" "2.1.1" - -"is-fullwidth-code-point@^2.0.0": - "integrity" "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" - "version" "2.0.0" - -"is-fullwidth-code-point@^3.0.0": - "integrity" "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" - "version" "3.0.0" - -"is-glob@^4.0.1", "is-glob@~4.0.1": - "integrity" "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==" - "resolved" "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz" - "version" "4.0.1" - dependencies: - "is-extglob" "^2.1.1" - -"is-number@^7.0.0": - "integrity" "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - "resolved" "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" - "version" "7.0.0" - -"is-plain-obj@^2.1.0": - "integrity" "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" - "resolved" "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" - "version" "2.1.0" - -"isarray@0.0.1": - "integrity" "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - "resolved" "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - "version" "0.0.1" - -"isexe@^2.0.0": - "integrity" "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - "resolved" "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" - "version" "2.0.0" - -"js-tokens@^4.0.0": - "integrity" "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - "resolved" "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" - "version" "4.0.0" - -"js-yaml@^3.13.1": - "integrity" "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==" - "resolved" "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" - "version" "3.14.1" - dependencies: - "argparse" "^1.0.7" - "esprima" "^4.0.0" - -"js-yaml@4.0.0": - "integrity" "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==" - "resolved" "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz" - "version" "4.0.0" - dependencies: - "argparse" "^2.0.1" - -"json-stringify-safe@~5.0.1": - "integrity" "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - "resolved" "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - "version" "5.0.1" - -"jsonist@~2.1.0": - "integrity" "sha512-8yqmWJAC2VaYoSKQAbsfgCpGY5o/1etWzx6ZxaZrC4iGaHrHUZEo+a2MyF8w+2uTavTlHdLWaZUoR19UfBstxQ==" - "resolved" "https://registry.npmjs.org/jsonist/-/jsonist-2.1.2.tgz" - "version" "2.1.2" - dependencies: - "bl" "~3.0.0" - "hyperquest" "~2.1.3" - "json-stringify-safe" "~5.0.1" - "xtend" "~4.0.1" - -"locate-path@^6.0.0": - "integrity" "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==" - "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" - "version" "6.0.0" - dependencies: - "p-locate" "^5.0.0" - -"lodash.uniq@^4.5.0": - "integrity" "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - "resolved" "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" - "version" "4.5.0" - -"log-symbols@4.0.0": - "integrity" "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==" - "resolved" "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz" - "version" "4.0.0" - dependencies: - "chalk" "^4.0.0" - -"minimatch@^3.0.4", "minimatch@3.0.4": - "integrity" "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==" - "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" - "version" "3.0.4" - dependencies: - "brace-expansion" "^1.1.7" - -"minimist@^1.2.5": - "integrity" "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - "resolved" "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz" - "version" "1.2.5" - -"mkdirp@^0.5.3": - "integrity" "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==" - "resolved" "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz" - "version" "0.5.5" - dependencies: - "minimist" "^1.2.5" - -"mocha@^8.3.2": - "integrity" "sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg==" - "resolved" "https://registry.npmjs.org/mocha/-/mocha-8.3.2.tgz" - "version" "8.3.2" - dependencies: - "@ungap/promise-all-settled" "1.1.2" - "ansi-colors" "4.1.1" - "browser-stdout" "1.3.1" - "chokidar" "3.5.1" - "debug" "4.3.1" - "diff" "5.0.0" - "escape-string-regexp" "4.0.0" - "find-up" "5.0.0" - "glob" "7.1.6" - "growl" "1.10.5" - "he" "1.2.0" - "js-yaml" "4.0.0" - "log-symbols" "4.0.0" - "minimatch" "3.0.4" - "ms" "2.1.3" - "nanoid" "3.1.20" - "serialize-javascript" "5.0.1" - "strip-json-comments" "3.1.1" - "supports-color" "8.1.1" - "which" "2.0.2" - "wide-align" "1.1.3" - "workerpool" "6.1.0" - "yargs" "16.2.0" - "yargs-parser" "20.2.4" - "yargs-unparser" "2.0.0" - -"ms@2.1.2": - "integrity" "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" - "version" "2.1.2" - -"ms@2.1.3": - "integrity" "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - "version" "2.1.3" - -"nanoid@3.1.20": - "integrity" "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==" - "resolved" "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz" - "version" "3.1.20" - -"normalize-path@^3.0.0", "normalize-path@~3.0.0": - "integrity" "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - "resolved" "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" - "version" "3.0.0" - -"once@^1.3.0": - "integrity" "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" - "resolved" "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - "version" "1.4.0" - dependencies: - "wrappy" "1" - -"p-limit@^3.0.2": - "integrity" "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==" - "resolved" "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" - "version" "3.1.0" - dependencies: - "yocto-queue" "^0.1.0" - -"p-locate@^5.0.0": - "integrity" "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==" - "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" - "version" "5.0.0" - dependencies: - "p-limit" "^3.0.2" - -"path-exists@^4.0.0": - "integrity" "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" - "version" "4.0.0" - -"path-is-absolute@^1.0.0": - "integrity" "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - "resolved" "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - "version" "1.0.1" - -"path-parse@^1.0.6": - "integrity" "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - "resolved" "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz" - "version" "1.0.6" - -"picomatch@^2.0.4", "picomatch@^2.2.1": - "integrity" "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==" - "resolved" "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz" - "version" "2.2.3" - -"randombytes@^2.1.0": - "integrity" "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==" - "resolved" "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" - "version" "2.1.0" - dependencies: - "safe-buffer" "^5.1.0" - -"readable-stream@^3.0.1": - "integrity" "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==" - "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" - "version" "3.6.0" - dependencies: - "inherits" "^2.0.3" - "string_decoder" "^1.1.1" - "util-deprecate" "^1.0.1" - -"readable-stream@>=1.0.33-1 <1.1.0-0": - "integrity" "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=" - "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - "version" "1.0.34" - dependencies: - "core-util-is" "~1.0.0" - "inherits" "~2.0.1" - "isarray" "0.0.1" - "string_decoder" "~0.10.x" - -"readable-stream@~1.1.9": - "integrity" "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" - "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" - "version" "1.1.14" - dependencies: - "core-util-is" "~1.0.0" - "inherits" "~2.0.1" - "isarray" "0.0.1" - "string_decoder" "~0.10.x" - -"readdirp@~3.5.0": - "integrity" "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==" - "resolved" "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz" - "version" "3.5.0" - dependencies: - "picomatch" "^2.2.1" - -"require-directory@^2.1.1": - "integrity" "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - "resolved" "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - "version" "2.1.1" - -"resolve@^1.3.2": - "integrity" "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==" - "resolved" "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz" - "version" "1.20.0" - dependencies: - "is-core-module" "^2.2.0" - "path-parse" "^1.0.6" - -"rollup@^2.45.2": - "integrity" "sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ==" - "resolved" "https://registry.npmjs.org/rollup/-/rollup-2.45.2.tgz" - "version" "2.45.2" - optionalDependencies: - "fsevents" "~2.3.1" - -"safe-buffer@^5.1.0", "safe-buffer@~5.2.0": - "integrity" "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" - "version" "5.2.1" - -"semver@^5.3.0": - "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" - "version" "5.7.1" - -"serialize-javascript@5.0.1": - "integrity" "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==" - "resolved" "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz" - "version" "5.0.1" - dependencies: - "randombytes" "^2.1.0" - -"simple-mime@~0.1.0": - "integrity" "sha1-lfUXxPRm18/1YacfydqyWW6p7y4=" - "resolved" "https://registry.npmjs.org/simple-mime/-/simple-mime-0.1.0.tgz" - "version" "0.1.0" - -"source-map-support@~0.5.19": - "integrity" "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==" - "resolved" "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz" - "version" "0.5.19" - dependencies: - "buffer-from" "^1.0.0" - "source-map" "^0.6.0" - -"source-map@^0.6.0": - "integrity" "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - "version" "0.6.1" - -"source-map@~0.7.2": - "integrity" "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz" - "version" "0.7.3" - -"sprintf-js@~1.0.2": - "integrity" "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - "resolved" "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" - "version" "1.0.3" - -"string_decoder@^1.1.1": - "integrity" "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==" - "resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - "version" "1.3.0" - dependencies: - "safe-buffer" "~5.2.0" - -"string_decoder@~0.10.x": - "integrity" "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - "resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - "version" "0.10.31" - -"string-width@^1.0.2 || 2": - "integrity" "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==" - "resolved" "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz" - "version" "2.1.1" - dependencies: - "is-fullwidth-code-point" "^2.0.0" - "strip-ansi" "^4.0.0" - -"string-width@^4.1.0": - "integrity" "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==" - "resolved" "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz" - "version" "4.2.2" - dependencies: - "emoji-regex" "^8.0.0" - "is-fullwidth-code-point" "^3.0.0" - "strip-ansi" "^6.0.0" - -"string-width@^4.2.0": - "integrity" "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==" - "resolved" "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz" - "version" "4.2.2" - dependencies: - "emoji-regex" "^8.0.0" - "is-fullwidth-code-point" "^3.0.0" - "strip-ansi" "^6.0.0" - -"strip-ansi@^4.0.0": - "integrity" "sha1-qEeQIusaw2iocTibY1JixQXuNo8=" - "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" - "version" "4.0.0" - dependencies: - "ansi-regex" "^3.0.0" - -"strip-ansi@^6.0.0": - "integrity" "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==" - "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz" - "version" "6.0.0" - dependencies: - "ansi-regex" "^5.0.0" - -"strip-json-comments@3.1.1": - "integrity" "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - "resolved" "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" - "version" "3.1.1" - -"supports-color@^5.3.0": - "integrity" "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" - "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" - "version" "5.5.0" - dependencies: - "has-flag" "^3.0.0" - -"supports-color@^7.1.0": - "integrity" "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==" - "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" - "version" "7.2.0" - dependencies: - "has-flag" "^4.0.0" - -"supports-color@8.1.1": - "integrity" "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==" - "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" - "version" "8.1.1" - dependencies: - "has-flag" "^4.0.0" - -"terser@^5.6.1": - "integrity" "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==" - "resolved" "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz" - "version" "5.6.1" - dependencies: - "commander" "^2.20.0" - "source-map" "~0.7.2" - "source-map-support" "~0.5.19" - -"through2@~0.6.3": - "integrity" "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=" - "resolved" "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz" - "version" "0.6.5" - dependencies: - "readable-stream" ">=1.0.33-1 <1.1.0-0" - "xtend" ">=4.0.0 <4.1.0-0" - -"to-regex-range@^5.0.1": - "integrity" "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==" - "resolved" "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" - "version" "5.0.1" - dependencies: - "is-number" "^7.0.0" - -"tslib@^1.13.0", "tslib@^1.8.1": - "integrity" "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - "resolved" "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" - "version" "1.14.1" - -"tslint@^6.1.3": - "integrity" "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==" - "resolved" "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz" - "version" "6.1.3" - dependencies: - "@babel/code-frame" "^7.0.0" - "builtin-modules" "^1.1.1" - "chalk" "^2.3.0" - "commander" "^2.12.1" - "diff" "^4.0.1" - "glob" "^7.1.1" - "js-yaml" "^3.13.1" - "minimatch" "^3.0.4" - "mkdirp" "^0.5.3" - "resolve" "^1.3.2" - "semver" "^5.3.0" - "tslib" "^1.13.0" - "tsutils" "^2.29.0" - -"tsutils@^2.29.0": - "integrity" "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==" - "resolved" "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz" - "version" "2.29.0" - dependencies: - "tslib" "^1.8.1" - -"typescript@^4.2.4": - "integrity" "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==" - "resolved" "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz" - "version" "4.2.4" - -"url-template@~2.0.6": - "integrity" "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" - "resolved" "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz" - "version" "2.0.8" - -"util-deprecate@^1.0.1": - "integrity" "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - "resolved" "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - "version" "1.0.2" - -"which@2.0.2": - "integrity" "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==" - "resolved" "https://registry.npmjs.org/which/-/which-2.0.2.tgz" - "version" "2.0.2" - dependencies: - "isexe" "^2.0.0" - -"wide-align@1.1.3": - "integrity" "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==" - "resolved" "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz" - "version" "1.1.3" - dependencies: - "string-width" "^1.0.2 || 2" - -"workerpool@6.1.0": - "integrity" "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==" - "resolved" "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz" - "version" "6.1.0" - -"wrap-ansi@^7.0.0": - "integrity" "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==" - "resolved" "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - "version" "7.0.0" - dependencies: - "ansi-styles" "^4.0.0" - "string-width" "^4.1.0" - "strip-ansi" "^6.0.0" - -"wrappy@1": - "integrity" "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - "resolved" "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - "version" "1.0.2" - -"xtend@>=4.0.0 <4.1.0-0", "xtend@~4.0.1": - "integrity" "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - "resolved" "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" - "version" "4.0.2" - -"y18n@^5.0.5": - "integrity" "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - "resolved" "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" - "version" "5.0.8" - -"yargs-parser@^20.2.2", "yargs-parser@20.2.4": - "integrity" "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" - "resolved" "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" - "version" "20.2.4" - -"yargs-unparser@2.0.0": - "integrity" "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==" - "resolved" "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz" - "version" "2.0.0" - dependencies: - "camelcase" "^6.0.0" - "decamelize" "^4.0.0" - "flat" "^5.0.2" - "is-plain-obj" "^2.1.0" - -"yargs@16.2.0": - "integrity" "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==" - "resolved" "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" - "version" "16.2.0" - dependencies: - "cliui" "^7.0.2" - "escalade" "^3.1.1" - "get-caller-file" "^2.0.5" - "require-directory" "^2.1.1" - "string-width" "^4.2.0" - "y18n" "^5.0.5" - "yargs-parser" "^20.2.2" - -"yocto-queue@^0.1.0": - "integrity" "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" - "resolved" "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" - "version" "0.1.0" From 1dc225e5a7f076c2208f8f009e666e319c068a04 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 12:05:29 +0200 Subject: [PATCH 136/145] Use global .gitignore --- .gitignore | 4 + src/fable-library-py/.gitignore | 129 -------------------------------- 2 files changed, 4 insertions(+), 129 deletions(-) delete mode 100644 src/fable-library-py/.gitignore diff --git a/.gitignore b/.gitignore index f5378f55bc..a475d735ea 100644 --- a/.gitignore +++ b/.gitignore @@ -201,3 +201,7 @@ docs/tools/FSharp.Formatting.svclog .ionide.debug *.bak project.lock.json + +# Python +.eggs/ +__pycache__/ \ No newline at end of file diff --git a/src/fable-library-py/.gitignore b/src/fable-library-py/.gitignore deleted file mode 100644 index b6e47617de..0000000000 --- a/src/fable-library-py/.gitignore +++ /dev/null @@ -1,129 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ From dfbe3851b4814062cac204ed65b3eeed25f7ae28 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 12:06:43 +0200 Subject: [PATCH 137/145] Remove vscode settings --- src/fable-library-py/.vscode/settings.json | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/fable-library-py/.vscode/settings.json diff --git a/src/fable-library-py/.vscode/settings.json b/src/fable-library-py/.vscode/settings.json deleted file mode 100644 index 0485978021..0000000000 --- a/src/fable-library-py/.vscode/settings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "python.linting.flake8Enabled": true, - "python.linting.pylintEnabled": false, - "python.linting.mypyEnabled": false, - "python.linting.enabled": true, - "editor.formatOnSave": true, - "python.formatting.provider": "black", - "python.formatting.blackArgs": [ - "--line-length", - "120" - ] -} \ No newline at end of file From a1095dee0c5fb5da384e998acd25cb7e5945dcb9 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 12:19:51 +0200 Subject: [PATCH 138/145] Fix warnings - Add tmp fix to Map.fs --- src/Fable.Transforms/Python/Fable2Python.fs | 8 +++--- src/fable-library/Map.fs | 5 +++- tests/Python/TestReflection.fs | 28 ++++++++++----------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index bde0482067..51415b3a4d 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -938,7 +938,7 @@ module Util = None let transformObjectExpr (com: IPythonCompiler) ctx (members: Fable.MemberDecl list) baseCall: Expression * Statement list = - printfn "transformObjectExpr" + // printfn "transformObjectExpr" let compileAsClass = Option.isSome baseCall || members |> List.exists (fun m -> // Optimization: Object literals with getters and setters are very slow in V8 @@ -1222,8 +1222,8 @@ module Util = let transformGet (com: IPythonCompiler) ctx range typ (fableExpr: Fable.Expr) kind = - //printfn "transformGet: %A" fableExpr - printfn "transformGet: %A" (fableExpr.Type) + // printfn "transformGet: %A" fableExpr + // printfn "transformGet: %A" (fableExpr.Type) match kind with | Fable.ExprGet(TransformExpr com ctx (prop, stmts)) -> @@ -2130,7 +2130,7 @@ module Util = |> List.singleton let transformAttachedMethod (com: IPythonCompiler) ctx (memb: Fable.MemberDecl) = - printfn "transformAttachedMethod" + // printfn "transformAttachedMethod" let isStatic = not memb.Info.IsInstance let makeMethod name args body = let key, computed = memberFromName com ctx name diff --git a/src/fable-library/Map.fs b/src/fable-library/Map.fs index 948460f782..92806f7e4f 100644 --- a/src/fable-library/Map.fs +++ b/src/fable-library/Map.fs @@ -521,6 +521,8 @@ module MapTree = then Some(en.Current, en) else None) +open Fable.Core + [] [] [] @@ -861,7 +863,8 @@ let ofList (elements: ('Key * 'Value) list) = Map<_, _>.Create elements // [] -let ofSeq elements = +let ofSeq elements ([] comparer: IComparer<'T>) = + // FIXME: should use comparer Map<_, _>.Create elements // [] diff --git a/tests/Python/TestReflection.fs b/tests/Python/TestReflection.fs index efdf635142..f90ad07cc9 100644 --- a/tests/Python/TestReflection.fs +++ b/tests/Python/TestReflection.fs @@ -18,15 +18,15 @@ type WithDelegates = Del6: System.Action Del7: System.Action } -type TestType = +type MyType = | Union1 of string -type TestType2 = +type MyType2 = | Union2 of string -type TestType3 = class end -type TestType4 = class end -type TestType5 = class end +type MyType3 = class end +type MyType4 = class end +type MyType5 = class end type GenericRecord<'A,'B> = { a: 'A; b: 'B } @@ -62,9 +62,9 @@ let ``test typedefof works`` () = [] let ``test IsGenericType works`` () = typeof.IsGenericType |> equal true - typeof.IsGenericType |> equal false + typeof.IsGenericType |> equal false let t1 = typeof - let t2 = typeof + let t2 = typeof let t3 = typeof t1.IsGenericType |> equal true t2.IsGenericType |> equal false @@ -82,8 +82,8 @@ let ``test GetGenericTypeDefinition works`` () = [] let ``test Comparing generic types works`` () = - let t1 = typeof> - let t2 = typeof> + let t1 = typeof> + let t2 = typeof> let t3 = typeof> t1 = t2 |> equal true t1 = t3 |> equal false @@ -115,7 +115,7 @@ let inline fullname<'T> () = typeof<'T>.FullName |> normalize [] let ``test Type Namespace`` () = - let x = typeof.Namespace + let x = typeof.Namespace #if FABLE_COMPILER equal "Fable.Tests.Reflection" x #else @@ -124,13 +124,13 @@ let ``test Type Namespace`` () = [] let ``test Type FullName`` () = - let x = typeof.FullName - x |> normalize |> equal "Fable.Tests.Reflection.TestType" + let x = typeof.FullName + x |> normalize |> equal "Fable.Tests.Reflection.MyType" [] let ``test Type Name`` () = - let x = typeof.Name - equal "TestType" x + let x = typeof.Name + equal "MyType" x #endif \ No newline at end of file From b33d270d47ef9d40b999f98b21e5135fa5da5351 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 13:34:40 +0200 Subject: [PATCH 139/145] Fix reflection tests --- src/fable-library-py/fable/reflection.py | 22 +++--- src/fable-library-py/fable/string.py | 2 +- src/fable-library-py/fable/types.py | 18 +++++ tests/Python/TestReflection.fs | 87 +++++++++++++++++++++++- 4 files changed, 118 insertions(+), 11 deletions(-) diff --git a/src/fable-library-py/fable/reflection.py b/src/fable-library-py/fable/reflection.py index 29e8dc75dd..d87bcdd97b 100644 --- a/src/fable-library-py/fable/reflection.py +++ b/src/fable-library-py/fable/reflection.py @@ -59,6 +59,10 @@ def fn() -> List[CaseInfo]: return t +def lambda_type(argType: TypeInfo, returnType: TypeInfo): + return TypeInfo("Microsoft.FSharp.Core.FSharpFunc`2", [argType, returnType]) + + def delegate_type(*generics): return TypeInfo("System.Func` %d" % len(generics), list(generics)) @@ -69,14 +73,6 @@ def record_type( return TypeInfo(fullname, generics, construct, fields=fields) -def full_name(t: TypeInfo) -> str: - gen = t.generics if t.generics is not None else [] - if len(gen): - return t.fullname + "[" + ",".join(map(full_name, gen)) + "]" - - return t.fullname - - def option_type(generic: TypeInfo) -> TypeInfo: return TypeInfo("Microsoft.FSharp.Core.FSharpOption`1", [generic]) @@ -130,7 +126,7 @@ def name(info): else: i = info.fullname.rfind("."); - return info.fullname if i == -1 else info.fullname.substr[i + 1:] + return info.fullname if i == -1 else info.fullname[i + 1:] def fullName(t): @@ -148,6 +144,14 @@ def namespace(t): return "" if i == -1 else t.fullname[0: i] +def isArray(t: TypeInfo) -> bool: + return t.fullname.endswith("[]") + + +def getElementType(t: TypeInfo) -> Optional[TypeInfo]: + return t.generics[0] if isArray(t) else None + + # if (t1.fullname === "") { // Anonymous records # return t2.fullname === "" # && equalArraysWith(getRecordElements(t1), diff --git a/src/fable-library-py/fable/string.py b/src/fable-library-py/fable/string.py index cdabedb9dc..66a14afae0 100644 --- a/src/fable-library-py/fable/string.py +++ b/src/fable-library-py/fable/string.py @@ -443,7 +443,7 @@ def padRight(string: str, len: int, ch: Optional[str] = None) -> str: def replace(string: str, search: str, replace: str): - return re.sub(search, replace, string) + return string.replace(search, replace) def replicate(n: int, x: str) -> str: diff --git a/src/fable-library-py/fable/types.py b/src/fable-library-py/fable/types.py index a407e6864f..56970f5fe4 100644 --- a/src/fable-library-py/fable/types.py +++ b/src/fable-library-py/fable/types.py @@ -183,4 +183,22 @@ def toString(x, callStack=0): str = str Exception = Exception + +class FSharpException(Exception, IComparable): + def toJSON(self): + return recordToJSON(self) + + def toString(self): + return recordToString(self) + + def GetHashCode(self): + recordGetHashCode(self) + + def Equals(self, other: FSharpException): + return recordEquals(self, other) + + def CompareTo(self, other: FSharpException): + return recordCompareTo(self, other) + + __all__ = ["Attribute", "Exception", "FSharpRef", "str", "Union"] diff --git a/tests/Python/TestReflection.fs b/tests/Python/TestReflection.fs index f90ad07cc9..8a759b62e5 100644 --- a/tests/Python/TestReflection.fs +++ b/tests/Python/TestReflection.fs @@ -133,4 +133,89 @@ let ``test Type Name`` () = equal "MyType" x -#endif \ No newline at end of file +[] +let ``test Get fullname of generic types with inline function`` () = + fullname() |> equal "Fable.Tests.Reflection.MyType3" + fullname() |> equal "Fable.Tests.Reflection.MyType4" + + +[] +let ``test Type name is accessible`` () = + let x = { name = "" } + getName "name" |> equal "Firm" + getName2 x "name" |> equal "Firm" + getName3 typedefof "name" |> equal "Firm" + // getName4 x "name" |> equal "Firm" + +[] +let ``test Type namespace is accessible`` () = + let test (x: string) = + x.StartsWith("Fable.Tests") |> equal true + let x = { name = "" } + getName "namespace" |> test + getName2 x "namespace" |> test + getName3 typedefof "namespace" |> test + // getName4 x "namespace" |> test + +[] +let ``test Type full name is accessible`` () = + let test (x: string) = + x.Replace("+", ".") |> equal "Fable.Tests.Reflection.Firm" + let x = { name = "" } + getName "fullname" |> test + getName2 x "fullname" |> test + getName3 typedefof "fullname" |> test + // getName4 x "fullname" |> test + +// FSharpType and FSharpValue reflection tests +open FSharp.Reflection + +exception MyException of String: string * Int: int + +let flip f b a = f a b + +type MyRecord = { + String: string + Int: int +} + +type MyUnion = + | StringCase of SomeString: string * string + | IntCase of SomeInt: int + +type RecordF = { F : int -> string } + +type AsyncRecord = { + asyncProp : Async +} + +type MyList<'T> = +| Nil +| Cons of 'T * MyList<'T> + +let inline create<'T when 'T: (new: unit->'T)> () = new 'T() + +type A() = member __.Value = 5 + +type B() = member __.Value = 10 + +type AnonRec1 = {| name: string; child: {| name: string |} |} +type AnonRec2 = {| numbers: int list |} + +type RecordGetValueType = { + Firstname : string + Age : int +} + +[] +let ``test Reflection: Array`` () = + let arType = typeof + let liType = typeof + equal true arType.IsArray + equal false liType.IsArray + let elType = arType.GetElementType() + typeof = elType |> equal true + typeof = elType |> equal false + liType.GetElementType() |> equal null + +#endif From 9f408502e910b964cb5191b4135da663b727fd3a Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 15:05:34 +0200 Subject: [PATCH 140/145] Fix union field index --- src/Fable.Transforms/Python/Fable2Python.fs | 9 +++---- tests/Python/TestUnionType.fs | 26 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 51415b3a4d..d1c6962012 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -1060,8 +1060,9 @@ module Util = | Fable.Binary(op, TransformExpr com ctx (left, stmts), TransformExpr com ctx (right, stmts')) -> match op with | BinaryEqualStrict -> - match right with - | Expression.Constant(_) -> + match right, left with + | Expression.Constant(_), _ + | _, Expression.Constant(_) -> let op = BinaryEqual Expression.compare(left, op, [right], ?loc=range), stmts @ stmts' | _ -> @@ -1300,10 +1301,10 @@ module Util = let expr, stmts = getUnionExprTag com ctx range fableExpr expr, stmts - | Fable.UnionField(index, _) -> + | Fable.UnionField(_, fieldIndex) -> let expr, stmts = com.TransformAsExpr(ctx, fableExpr) let expr, stmts' = getExpr com ctx None expr (Expression.constant("fields")) - let expr, stmts'' = getExpr com ctx range expr (ofInt index) + let expr, stmts'' = getExpr com ctx range expr (ofInt fieldIndex) expr, stmts @ stmts' @ stmts'' let transformSet (com: IPythonCompiler) ctx range fableExpr typ (value: Fable.Expr) kind = diff --git a/tests/Python/TestUnionType.fs b/tests/Python/TestUnionType.fs index 6d462a81de..d2cac1af3f 100644 --- a/tests/Python/TestUnionType.fs +++ b/tests/Python/TestUnionType.fs @@ -87,3 +87,29 @@ let ``test Union cases matches with no arguments can be generated`` () = | Female -> true | Male -> false |> equal false + +[] +let ``test Union cases matches with one argument can be generated`` () = + let x = Left "abc" + match x with + | Left data -> data + | Right _ -> failwith "unexpected" + |> equal "abc" + +[] +let ``test Union methods can be generated`` () = + let x = Left 5 + x.AsString() + |> equal "5" + +[] +let ``test Nested pattern matching works`` () = + let x = Right(Left 5) + match x with + | Left _ -> failwith "unexpected" + | Right x -> + match x with + | Left x -> x + | Right _ -> failwith "unexpected" + |> equal 5 + From c3f8b5ec85f9f5e590f0e30813d9262ca7d01f3f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 17:27:38 +0200 Subject: [PATCH 141/145] Rename JS to Python - Add more union tests - Add comment for None arg --- src/Fable.Core/Fable.Core.PyInterop.fs | 82 ++++----------------- src/Fable.Transforms/Replacements.fs | 1 + src/fable-library-py/fable/map_util.py | 8 +++ tests/Python/TestUnionType.fs | 99 +++++++++++++++++++++----- 4 files changed, 103 insertions(+), 87 deletions(-) diff --git a/src/Fable.Core/Fable.Core.PyInterop.fs b/src/Fable.Core/Fable.Core.PyInterop.fs index 6bf27ffd9c..5a193085c1 100644 --- a/src/Fable.Core/Fable.Core.PyInterop.fs +++ b/src/Fable.Core/Fable.Core.PyInterop.fs @@ -3,7 +3,7 @@ module Fable.Core.PyInterop open System open Fable.Core -/// Has same effect as `unbox` (dynamic casting erased in compiled JS code). +/// Has same effect as `unbox` (dynamic casting erased in compiled Python code). /// The casted type can be defined on the call site: `!!myObj?bar(5): float` let (!!) x: 'T = pyNative @@ -11,17 +11,17 @@ let (!!) x: 'T = pyNative let inline (!^) (x:^t1) : ^t2 = ((^t1 or ^t2) : (static member op_ErasedCast : ^t1 -> ^t2) x) /// Dynamically access a property of an arbitrary object. -/// `myObj?propA` in JS becomes `myObj.propA` -/// `myObj?(propA)` in JS becomes `myObj[propA]` +/// `myObj?propA` in Python becomes `myObj.propA` +/// `myObj?(propA)` in Python becomes `myObj[propA]` let (?) (o: obj) (prop: obj): 'a = pyNative /// Dynamically assign a value to a property of an arbitrary object. -/// `myObj?propA <- 5` in JS becomes `myObj.propA = 5` -/// `myObj?(propA) <- 5` in JS becomes `myObj[propA] = 5` +/// `myObj?propA <- 5` in Python becomes `myObj.propA = 5` +/// `myObj?(propA) <- 5` in Python becomes `myObj[propA] = 5` let (?<-) (o: obj) (prop: obj) (v: obj): unit = pyNative /// Destructure and apply a tuple to an arbitrary value. -/// E.g. `myFn $ (arg1, arg2)` in JS becomes `myFn(arg1, arg2)` +/// E.g. `myFn $ (arg1, arg2)` in Python becomes `myFn(arg1, arg2)` let ($) (callee: obj) (args: obj): 'a = pyNative /// Upcast the right operand to obj (and uncurry it if it's a function) and create a key-value tuple. @@ -29,17 +29,12 @@ let ($) (callee: obj) (args: obj): 'a = pyNative /// E.g. `createObj [ "a" ==> 5 ]` in Python becomes `{ a: 5 }` let (==>) (key: string) (v: obj): string*obj = pyNative -/// Destructure and apply a tuple to an arbitrary value with `new` keyword. -/// E.g. `createNew myCons (arg1, arg2)` in JS becomes `new myCons(arg1, arg2)` -/// let createNew (o: obj) (args: obj): obj = pyNative +/// Destructure a tuple of arguments and applies to literal Python code as with EmitAttribute. +/// E.g. `emitPyExpr (arg1, arg2) "$0 + $1"` in Python becomes `arg1 + arg2` +let emitPyExpr<'T> (args: obj) (pyCode: string): 'T = pyNative -/// Destructure a tuple of arguments and applies to literal JS code as with EmitAttribute. -/// E.g. `emitJsExpr (arg1, arg2) "$0 + $1"` in Python becomes `arg1 + arg2` -let emitPyExpr<'T> (args: obj) (jsCode: string): 'T = pyNative - -/// Same as emitJsExpr but intended for JS code that must appear in a statement position -/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements -/// E.g. `emitJsExpr aValue "while($0 < 5) doSomething()"` +/// Same as emitPyExpr but intended for Python code that must appear in a statement position +/// E.g. `emitPyExpr aValue "while($0 < 5) doSomething()"` let emitPyStatement<'T> (args: obj) (pyCode: string): 'T = pyNative /// Create a literal Python object from a collection of key-value tuples. @@ -52,15 +47,12 @@ let keyValueList (caseRule: CaseRules) (li: 'T seq): obj = pyNative /// Create a literal Py object from a mutator lambda. Normally used when /// the options interface has too many fields to be represented with a Pojo record. -/// E.g. `jsOptions (fun o -> o.foo <- 5)` in JS becomes `{ foo: 5 }` +/// E.g. `pyOptions (fun o -> o.foo <- 5)` in Python becomes `{ "foo": 5 }` let pyOptions<'T> (f: 'T->unit): 'T = pyNative -/// Create an empty JS object: {} +/// Create an empty Python object: {} let createEmpty<'T> : 'T = pyNative -/// Get the Py function constructor for class types -let pyConstructor<'T> : obj = pyNative - [] let pyTypeof (x: obj): string = pyNative @@ -71,12 +63,6 @@ let pyInstanceof (x: obj) (cons: obj): bool = pyNative [] let callable(x: obj) = pyNative -/// Makes an expression the default export for the JS module. -/// Used to interact with JS tools that require a default export. -/// ATTENTION: This statement must appear on the root level of the file module. -[] -let exportDefault (x: obj): unit = pyNative - /// Works like `ImportAttribute` (same semantics as ES6 imports). /// You can use "*" or "default" selectors. let import<'T> (selector: string) (path: string):'T = pyNative @@ -86,49 +72,9 @@ let import<'T> (selector: string) (path: string):'T = pyNative /// Note the import must be immediately assigned to a value in a let binding let importMember<'T> (path: string):'T = pyNative -/// F#: let defaultMember = importDefaultobj> "myModule" -/// JS: import defaultMember from "myModule" -let importDefault<'T> (path: string):'T = pyNative - /// F#: let myLib = importAll "myLib" -/// JS: import * as myLib from "myLib" +/// Py: from my_lib import * let importAll<'T> (path: string):'T = pyNative /// Imports a file only for its side effects let importSideEffects (path: string): unit = pyNative - -/// Imports a file dynamically at runtime -let importDynamic<'T> (path: string): JS.Promise<'T> = pyNative - -/// Imports a reference from an external file dynamically at runtime -/// ATTENTION: Needs fable-compiler 2.3, pass the reference directly in argument position (avoid pipes) -let importValueDynamic (x: 'T): JS.Promise<'T> = pyNative - -/// Used when you need to send an F# record to a JS library accepting only plain JS objects (POJOs) -let toPlainJsObj(o: 'T): obj = pyNative - -/// Compiles to JS `this` keyword. -/// -/// ## Sample -/// jqueryMethod(fun x y -> jsThis?add(x, y)) -let [] jsThis<'T> : 'T = pyNative - -/// JS `in` operator -let [] isIn (key: string) (target: obj): bool = pyNative - -/// JS non-strict null checking -let [] isNullOrUndefined (target: obj): bool = pyNative - -/// Use it when importing a constructor from a JS library. -type [] JsConstructor = - [] - abstract Create: []args: obj[] -> obj - -/// Use it when importing dynamic functions from JS. If you need a constructor, use `JsConstructor`. -/// -/// ## Sample -/// let f: JsFunc = import "myFunction" "./myLib" -/// f.Invoke(5, "bar") -type [] JsFunc private () = - [] - member __.Invoke([]args:obj[]): obj = pyNative diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 6d4d9a9fdb..0d80a52229 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -1008,6 +1008,7 @@ let injectArg com (ctx: Context) r moduleName methName (genArgs: (string * Type) | Number(numberKind,_) when com.Options.TypedArrays -> args @ [getTypedArrayName com numberKind |> makeIdentExpr] | _ -> + // TODO: Remove None args in tail position in Fable2Babel args @ [ Expr.Value(ValueKind.NewOption(None, genArg, false), None) ] | Types.adder -> args @ [makeGenericAdder com ctx genArg] diff --git a/src/fable-library-py/fable/map_util.py b/src/fable-library-py/fable/map_util.py index 7687e79663..f459684825 100644 --- a/src/fable-library-py/fable/map_util.py +++ b/src/fable-library-py/fable/map_util.py @@ -4,6 +4,14 @@ V = TypeVar("V") +def addToSet(v, set): + if set.has(v): + return False + + set.add(v) + return True + + def addToDict(dict: Dict[K, V], k: K, v: V): if k in dict: raise Exception("An item with the same key has already been added. Key: " + str(k)) diff --git a/tests/Python/TestUnionType.fs b/tests/Python/TestUnionType.fs index d2cac1af3f..38935268c4 100644 --- a/tests/Python/TestUnionType.fs +++ b/tests/Python/TestUnionType.fs @@ -12,31 +12,31 @@ type Either<'TL,'TR> = | Left y -> y.ToString() | Right y -> y.ToString() -// type TestUnion = -// | Case0 -// | Case1 of string -// | Case2 of string * string -// | Case3 of string * string * string +type MyUnion = + | Case0 + | Case1 of string + | Case2 of string * string + | Case3 of string * string * string -// type TestUnion2 = -// | Tag of string -// | NewTag of string +type MyUnion2 = + | Tag of string + | NewTag of string -// let (|Functional|NonFunctional|) (s: string) = -// match s with -// | "fsharp" | "haskell" | "ocaml" -> Functional -// | _ -> NonFunctional +let (|Functional|NonFunctional|) (s: string) = + match s with + | "fsharp" | "haskell" | "ocaml" -> Functional + | _ -> NonFunctional -// let (|Small|Medium|Large|) i = -// if i < 3 then Small 5 -// elif i >= 3 && i < 6 then Medium "foo" -// else Large +let (|Small|Medium|Large|) i = + if i < 3 then Small 5 + elif i >= 3 && i < 6 then Medium "foo" + else Large -// let (|FSharp|_|) (document : string) = -// if document = "fsharp" then Some FSharp else None +let (|FSharp|_|) (document : string) = + if document = "fsharp" then Some FSharp else None -// let (|A|) n = n +let (|A|) n = n // type JsonTypeInner = { // Prop1: string @@ -113,3 +113,64 @@ let ``test Nested pattern matching works`` () = | Right _ -> failwith "unexpected" |> equal 5 +[] +let ``test Union cases matches with many arguments can be generated`` () = + let x = Case3("a", "b", "c") + match x with + | Case3(a, b, c) -> a + b + c + | _ -> failwith "unexpected" + |> equal "abc" + +[] +let ``test Pattern matching with common targets works`` () = + let x = MyUnion.Case2("a", "b") + match x with + | MyUnion.Case0 -> failwith "unexpected" + | MyUnion.Case1 _ + | MyUnion.Case2 _ -> "a" + | MyUnion.Case3(a, b, c) -> a + b + c + |> equal "a" + +[] +let ``test Union cases called Tag still work (bug due to Tag field)`` () = + let x = Tag "abc" + match x with + | Tag x -> x + | _ -> failwith "unexpected" + |> equal "abc" + +[] +let ``test Comprehensive active patterns work`` () = + let isFunctional = function + | Functional -> true + | NonFunctional -> false + isFunctional "fsharp" |> equal true + isFunctional "csharp" |> equal false + isFunctional "haskell" |> equal true + +[] +let ``test Comprehensive active patterns can return values`` () = + let measure = function + | Small i -> string i + | Medium s -> s + | Large -> "bar" + measure 0 |> equal "5" + measure 10 |> equal "bar" + measure 5 |> equal "foo" + +let ``Partial active patterns which don't return values work`` () = // See #478 + let isFunctional = function + | FSharp -> "yes" + | "scala" -> "fifty-fifty" + | _ -> "dunno" + isFunctional "scala" |> equal "fifty-fifty" + isFunctional "smalltalk" |> equal "dunno" + isFunctional "fsharp" |> equal "yes" + +let ``Active patterns can be combined with union case matching`` () = // See #306 + let test = function + | Some(A n, Some(A m)) -> n + m + | _ -> 0 + Some(5, Some 2) |> test |> equal 7 + Some(5, None) |> test |> equal 0 + None |> test |> equal 0 From b81c02b10311291a0dc86a7668826437b845e969 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 17:43:26 +0200 Subject: [PATCH 142/145] Cleanup Fable.Core.PY.fs --- src/Fable.Core/Fable.Core.PY.fs | 544 +-------------------------- src/fable-library-py/fable/Native.fs | 2 - 2 files changed, 1 insertion(+), 545 deletions(-) diff --git a/src/Fable.Core/Fable.Core.PY.fs b/src/Fable.Core/Fable.Core.PY.fs index b4457bbe81..e5191c82db 100644 --- a/src/Fable.Core/Fable.Core.PY.fs +++ b/src/Fable.Core/Fable.Core.PY.fs @@ -6,554 +6,12 @@ open System open System.Text.RegularExpressions module PY = - type [] PropertyDescriptor = - abstract configurable: bool option with get, set - abstract enumerable: bool option with get, set - abstract value: obj option with get, set - abstract writable: bool option with get, set - abstract get: unit -> obj - abstract set: v: obj -> unit - - and [] ArrayConstructor = + type [] ArrayConstructor = [] abstract Create: size: int -> 'T[] abstract isArray: arg: obj -> bool abstract from: arg: obj -> 'T[] - and [] NumberConstructor = - abstract isNaN: float -> bool - - and [] Object = - abstract toString: unit -> string - abstract toLocaleString: unit -> string - abstract valueOf: unit -> obj - abstract hasOwnProperty: v: string -> bool - abstract isPrototypeOf: v: obj -> bool - abstract propertyIsEnumerable: v: string -> bool - abstract hasOwnProperty: v: obj -> bool - abstract propertyIsEnumerable: v: obj -> bool - - and [] ObjectConstructor = - abstract getPrototypeOf: o: obj -> obj - abstract getOwnPropertyDescriptor: o: obj * p: string -> PropertyDescriptor - abstract getOwnPropertyNames: o: obj -> ResizeArray - abstract create: o: obj * ?properties: obj -> obj - abstract defineProperty: o: obj * p: string * attributes: PropertyDescriptor -> obj - abstract defineProperties: o: obj * properties: obj -> obj - abstract seal: o: 'T -> 'T - abstract freeze: o: 'T -> 'T - abstract preventExtensions: o: 'T -> 'T - abstract isSealed: o: obj -> bool - abstract isFrozen: o: obj -> bool - abstract isExtensible: o: obj -> bool - abstract keys: o: obj -> ResizeArray - abstract values: o: obj -> ResizeArray - abstract entries: o: obj -> ResizeArray - abstract assign: target: 'T * source: 'U -> obj - abstract assign: target: 'T * source1: 'U * source2: 'V -> obj - abstract assign: target: 'T * source1: 'U * source2: 'V * source3: 'W -> obj - abstract assign: target: obj * [] sources: obj[] -> obj - // abstract getOwnPropertySymbols: o: obj -> ResizeArray - abstract is: value1: obj * value2: obj -> bool - abstract setPrototypeOf: o: obj * proto: obj -> obj - abstract getOwnPropertyDescriptor: o: obj * propertyKey: obj -> PropertyDescriptor - abstract defineProperty: o: obj * propertyKey: obj * attributes: PropertyDescriptor -> obj - - and [] Math = - abstract E: float - abstract LN10: float - abstract LN2: float - abstract LOG2E: float - abstract LOG10E: float - abstract PI: float - abstract SQRT1_2: float - abstract SQRT2: float - abstract abs: x: float -> float - abstract acos: x: float -> float - abstract asin: x: float -> float - abstract atan: x: float -> float - abstract atan2: y: float * x: float -> float - abstract ceil: x: float -> float - abstract cos: x: float -> float - abstract exp: x: float -> float - abstract floor: x: float -> float - abstract log: x: float -> float - abstract max: [] values: float[] -> float - abstract min: [] values: float[] -> float - abstract pow: x: float * y: float -> float - abstract random: unit -> float - abstract round: x: float -> float - abstract sin: x: float -> float - abstract sqrt: x: float -> float - abstract tan: x: float -> float - abstract clz32: x: float -> float - abstract imul: x: float * y: float -> float - abstract sign: x: float -> float - abstract log10: x: float -> float - abstract log2: x: float -> float - abstract log1p: x: float -> float - abstract expm1: x: float -> float - abstract cosh: x: float -> float - abstract sinh: x: float -> float - abstract tanh: x: float -> float - abstract acosh: x: float -> float - abstract asinh: x: float -> float - abstract atanh: x: float -> float - abstract hypot: [] values: float[] -> float - abstract trunc: x: float -> float - abstract fround: x: float -> float - abstract cbrt: x: float -> float - - and [] Date = - abstract toString: unit -> string - abstract toDateString: unit -> string - abstract toTimeString: unit -> string - abstract toLocaleString: unit -> string - abstract toLocaleDateString: unit -> string - abstract toLocaleTimeString: unit -> string - abstract valueOf: unit -> float - abstract getTime: unit -> float - abstract getFullYear: unit -> float - abstract getUTCFullYear: unit -> float - abstract getMonth: unit -> float - abstract getUTCMonth: unit -> float - abstract getDate: unit -> float - abstract getUTCDate: unit -> float - abstract getDay: unit -> float - abstract getUTCDay: unit -> float - abstract getHours: unit -> float - abstract getUTCHours: unit -> float - abstract getMinutes: unit -> float - abstract getUTCMinutes: unit -> float - abstract getSeconds: unit -> float - abstract getUTCSeconds: unit -> float - abstract getMilliseconds: unit -> float - abstract getUTCMilliseconds: unit -> float - abstract getTimezoneOffset: unit -> float - abstract setTime: time: float -> float - abstract setMilliseconds: ms: float -> float - abstract setUTCMilliseconds: ms: float -> float - abstract setSeconds: sec: float * ?ms: float -> float - abstract setUTCSeconds: sec: float * ?ms: float -> float - abstract setMinutes: min: float * ?sec: float * ?ms: float -> float - abstract setUTCMinutes: min: float * ?sec: float * ?ms: float -> float - abstract setHours: hours: float * ?min: float * ?sec: float * ?ms: float -> float - abstract setUTCHours: hours: float * ?min: float * ?sec: float * ?ms: float -> float - abstract setDate: date: float -> float - abstract setUTCDate: date: float -> float - abstract setMonth: month: float * ?date: float -> float - abstract setUTCMonth: month: float * ?date: float -> float - abstract setFullYear: year: float * ?month: float * ?date: float -> float - abstract setUTCFullYear: year: float * ?month: float * ?date: float -> float - abstract toUTCString: unit -> string - abstract toISOString: unit -> string - abstract toJSON: ?key: obj -> string - - and [] DateConstructor = - [] abstract Create: unit -> DateTime - [] abstract Create: value: float -> DateTime - [] abstract Create: value: string -> DateTime - [] abstract Create: year: float * month: float * ?date: float * ?hours: float * ?minutes: float * ?seconds: float * ?ms: float -> DateTime - [] abstract Invoke: unit -> string - abstract parse: s: string -> float - abstract UTC: year: float * month: float * ?date: float * ?hours: float * ?minutes: float * ?seconds: float * ?ms: float -> float - abstract now: unit -> float - - and [] JSON = - abstract parse: text: string * ?reviver: (obj->obj->obj) -> obj - abstract stringify: value: obj * ?replacer: (string->obj->obj) * ?space: obj -> string - - and [] Map<'K, 'V> = - abstract size: int - abstract clear: unit -> unit - abstract delete: key: 'K -> bool - abstract entries: unit -> seq<'K * 'V> - abstract forEach: callbackfn: ('V->'K->Map<'K, 'V>->unit) * ?thisArg: obj -> unit - abstract get: key: 'K -> 'V - abstract has: key: 'K -> bool - abstract keys: unit -> seq<'K> - abstract set: key: 'K * value: 'V -> Map<'K, 'V> - abstract values: unit -> seq<'V> - - and [] MapConstructor = - [] abstract Create: ?iterable: seq<'K * 'V> -> Map<'K, 'V> - - and [] WeakMap<'K, 'V> = - abstract clear: unit -> unit - abstract delete: key: 'K -> bool - abstract get: key: 'K -> 'V - abstract has: key: 'K -> bool - abstract set: key: 'K * value: 'V -> WeakMap<'K, 'V> - - and [] WeakMapConstructor = - [] abstract Create: ?iterable: seq<'K * 'V> -> WeakMap<'K, 'V> - - and [] Set<'T> = - abstract size: int - abstract add: value: 'T -> Set<'T> - abstract clear: unit -> unit - abstract delete: value: 'T -> bool - abstract entries: unit -> seq<'T * 'T> - abstract forEach: callbackfn: ('T->'T->Set<'T>->unit) * ?thisArg: obj -> unit - abstract has: value: 'T -> bool - abstract keys: unit -> seq<'T> - abstract values: unit -> seq<'T> - - and [] SetConstructor = - [] abstract Create: ?iterable: seq<'T> -> Set<'T> - - and [] WeakSet<'T> = - abstract add: value: 'T -> WeakSet<'T> - abstract clear: unit -> unit - abstract delete: value: 'T -> bool - abstract has: value: 'T -> bool - - and [] WeakSetConstructor = - [] abstract Create: ?iterable: seq<'T> -> WeakSet<'T> - - and [] Promise<'T> = - abstract ``then``: ?onfulfilled: ('T->'TResult) * ?onrejected: (obj->'TResult) -> Promise<'TResult> - abstract catch: ?onrejected: (obj->'T) -> Promise<'T> - - and [] PromiseConstructor = - [] abstract Create: executor: ((obj->unit) -> (obj->unit) -> unit) -> Promise<'T> - abstract all: [] values: obj[] -> Promise - abstract race: values: obj seq -> Promise - abstract reject: reason: obj -> Promise - abstract reject: reason: obj -> Promise<'T> - abstract resolve: value: 'T -> Promise<'T> - abstract resolve: unit -> Promise - - and [] RegExpConstructor = - [] abstract Create: pattern: string * ?flags: string -> Regex - - and [] ArrayBuffer = - abstract byteLength: int - abstract slice: ``begin``: int * ?``end``: int -> ArrayBuffer - - and [] ArrayBufferConstructor = - [] abstract Create: byteLength: int -> ArrayBuffer - abstract isView: arg: obj -> bool - - and [] ArrayBufferView = - abstract buffer: ArrayBuffer - abstract byteLength: int - abstract byteOffset: int - - and ArrayBufferViewConstructor = - [] abstract Create: size: int -> ArrayBufferView - - and [] DataView = - abstract buffer: ArrayBuffer - abstract byteLength: int - abstract byteOffset: int - abstract getFloat32: byteOffset: int * ?littleEndian: bool -> float32 - abstract getFloat64: byteOffset: int * ?littleEndian: bool -> float - abstract getInt8: byteOffset: int -> sbyte - abstract getInt16: byteOffset: int * ?littleEndian: bool -> int16 - abstract getInt32: byteOffset: int * ?littleEndian: bool -> int32 - abstract getUint8: byteOffset: int -> byte - abstract getUint16: byteOffset: int * ?littleEndian: bool -> uint16 - abstract getUint32: byteOffset: int * ?littleEndian: bool -> uint32 - abstract setFloat32: byteOffset: int * value: float32 * ?littleEndian: bool -> unit - abstract setFloat64: byteOffset: int * value: float * ?littleEndian: bool -> unit - abstract setInt8: byteOffset: int * value: sbyte -> unit - abstract setInt16: byteOffset: int * value: int16 * ?littleEndian: bool -> unit - abstract setInt32: byteOffset: int * value: int32 * ?littleEndian: bool -> unit - abstract setUint8: byteOffset: int * value: byte -> unit - abstract setUint16: byteOffset: int * value: uint16 * ?littleEndian: bool -> unit - abstract setUint32: byteOffset: int * value: uint32 * ?littleEndian: bool -> unit - - and [] DataViewConstructor = - [] abstract Create: buffer: ArrayBuffer * ?byteOffset: int * ?byteLength: float -> DataView - - and TypedArray = - abstract buffer: ArrayBuffer - abstract byteLength: int - abstract byteOffset: int - abstract length: int - abstract copyWithin: targetStartIndex:int * start:int * ? ``end``:int -> unit - abstract entries: unit -> obj - abstract keys: unit -> obj - abstract join: separator:string -> string - - and TypedArray<'T> = - inherit TypedArray - [] abstract Item: index: int -> 'T with get, set - abstract fill: value:'T * ?``begin``:int * ?``end``:int -> TypedArray<'T> - abstract filter: ('T -> int -> TypedArray<'T> -> bool) -> TypedArray<'T> - abstract filter: ('T -> int -> bool) -> TypedArray<'T> - abstract filter: ('T -> bool) -> TypedArray<'T> - abstract find: ('T -> int -> TypedArray<'T> -> bool) -> 'T option - abstract find: ('T -> int -> bool) -> 'T option - abstract find: ('T -> bool) -> 'T option - abstract findIndex: ('T -> int -> TypedArray<'T> -> bool) -> int - abstract findIndex: ('T -> int -> bool) -> int - abstract findIndex: ('T -> bool) -> int - abstract forEach: ('T -> int -> TypedArray<'T> -> bool) -> unit - abstract forEach: ('T -> int -> bool) -> unit - abstract forEach: ('T -> bool) -> unit - abstract includes: searchElement:'T * ?fromIndex:int -> bool - abstract indexOf: searchElement:'T * ?fromIndex:int -> int - abstract lastIndexOf: searchElement:'T * ?fromIndex:int -> int - abstract map: ('T -> int -> TypedArray<'T> -> 'U) -> TypedArray<'U> - abstract map: ('T -> int -> 'U) -> TypedArray<'U> - abstract map: ('T -> 'U) -> TypedArray<'U> - abstract reduce: ('State -> 'T -> int -> TypedArray<'T> -> 'State) * state:'State -> 'State - abstract reduce: ('State -> 'T -> int -> 'State) * state:'State -> 'State - abstract reduce: ('State -> 'T -> 'State) * state:'State -> 'State - abstract reduceRight: ('State -> 'T -> int -> TypedArray<'T> -> 'State) * state:'State -> 'State - abstract reduceRight: ('State -> 'T -> int -> 'State) * state:'State -> 'State - abstract reduceRight: ('State -> 'T -> 'State) * state:'State -> 'State - abstract reverse: unit -> TypedArray<'T> - abstract set: source:Array * ?offset:int -> unit - abstract set: source:#TypedArray * ?offset:int -> unit - abstract slice: ?``begin``:int * ?``end``:int -> TypedArray<'T> - abstract some: ('T -> int -> TypedArray<'T> -> bool) -> bool - abstract some: ('T -> int -> bool) -> bool - abstract some: ('T -> bool) -> bool - abstract sort: ?sortFunction:('T -> 'T -> int) -> TypedArray<'T> - abstract subarray: ?``begin``:int * ?``end``:int -> TypedArray<'T> - abstract values: unit -> obj - - - and Int8Array = TypedArray - - and Int8ArrayConstructor = - [] abstract Create: size: int -> Int8Array - [] abstract Create: typedArray: TypedArray -> Int8Array - [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Int8Array - [] abstract Create: data:obj -> Int8Array - - - and Uint8Array = TypedArray - - and Uint8ArrayConstructor = - [] abstract Create: size: int -> Uint8Array - [] abstract Create: typedArray: TypedArray -> Uint8Array - [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Uint8Array - [] abstract Create: data:obj -> Uint8Array - - - and Uint8ClampedArray = TypedArray - - and Uint8ClampedArrayConstructor = - [] abstract Create: size: int -> Uint8ClampedArray - [] abstract Create: typedArray: TypedArray -> Uint8ClampedArray - [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Uint8ClampedArray - [] abstract Create: data:obj -> Uint8ClampedArray - - - and Int16Array = TypedArray - - and Int16ArrayConstructor = - [] abstract Create: size: int -> Int16Array - [] abstract Create: typedArray: TypedArray -> Int16Array - [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Int16Array - [] abstract Create: data:obj -> Int16Array - - - and Uint16Array = TypedArray - - and Uint16ArrayConstructor = - [] abstract Create: size: int -> Uint16Array - [] abstract Create: typedArray: TypedArray -> Uint16Array - [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Uint16Array - [] abstract Create: data:obj -> Uint16Array - - - and Int32Array = TypedArray - - and Int32ArrayConstructor = - [] abstract Create: size: int -> Int32Array - [] abstract Create: typedArray: TypedArray -> Int32Array - [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Int32Array - [] abstract Create: data:obj -> Int32Array - - - and Uint32Array = TypedArray - - and Uint32ArrayConstructor = - [] abstract Create: size: int -> Uint32Array - [] abstract Create: typedArray: TypedArray -> Uint32Array - [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Uint32Array - [] abstract Create: data:obj -> Uint32Array - - - and Float32Array = TypedArray - - and Float32ArrayConstructor = - [] abstract Create: size: int -> Float32Array - [] abstract Create: typedArray: TypedArray -> Float32Array - [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Float32Array - [] abstract Create: data:obj -> Float32Array - - - and Float64Array = TypedArray - - and Float64ArrayConstructor = - [] abstract Create: size: int -> Float64Array - [] abstract Create: typedArray: TypedArray -> Float64Array - [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> Float64Array - [] abstract Create: data:obj -> Float64Array - - - and BigInt64Array = TypedArray - - and BigInt64ArrayConstructor = - [] abstract Create: size: int -> BigInt64Array - [] abstract Create: typedArray: TypedArray -> BigInt64Array - [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> BigInt64Array - [] abstract Create: data:obj -> BigInt64Array - - - // no equivalent ? - //and BigUint64Array = TypedArray - - // and BigUint64ArrayConstructor = - // [] abstract Create: size: int -> BigUint64Array - // [] abstract Create: typedArray: TypedArray -> BigUint64Array - // [] abstract Create: buffer: ArrayBuffer * ?offset:int * ?length:int -> BigUint64Array - // [] abstract Create: data:obj -> BigUint64Array - - - and [] Console = - abstract ``assert``: ?test: bool * ?message: string * [] optionalParams: obj[] -> unit - abstract clear: unit -> unit - abstract count: ?countTitle: string -> unit - abstract debug: ?message: string * [] optionalParams: obj[] -> unit - abstract dir: ?value: obj * [] optionalParams: obj[] -> unit - abstract dirxml: value: obj -> unit - abstract error: ?message: obj * [] optionalParams: obj[] -> unit - abstract group: ?groupTitle: string -> unit - abstract groupCollapsed: ?groupTitle: string -> unit - abstract groupEnd: unit -> unit - abstract info: ?message: obj * [] optionalParams: obj[] -> unit - abstract log: ?message: obj * [] optionalParams: obj[] -> unit - abstract profile: ?reportName: string -> unit - abstract profileEnd: unit -> unit - abstract time: ?timerName: string -> unit - abstract timeEnd: ?timerName: string -> unit - abstract trace: ?message: obj * [] optionalParams: obj[] -> unit - abstract warn: ?message: obj * [] optionalParams: obj[] -> unit - abstract table: ?data: obj -> unit - - let [] NaN: float = pyNative - let [] Infinity: float = pyNative - let [] Math: Math = pyNative - let [] JSON: JSON = pyNative - let [] eval: string -> string = pyNative - let [] isFinite: float -> bool = pyNative - let [] isNaN: float -> bool = pyNative - let [] parseFloat: string -> float = pyNative - let [] parseInt: string -> int -> int = pyNative - let [] decodeURI: string -> string = pyNative - let [] decodeURIComponent: string -> string = pyNative - let [] encodeURI: string -> string = pyNative - let [] encodeURIComponent: string -> string = pyNative - let [] console : Console = pyNative - let [] setTimeout (callback: unit -> unit) (ms: int): int = pyNative - let [] clearTimeout (token: int): unit = pyNative - let [] setInterval(callback: unit -> unit) (ms: int) : int = pyNative - let [] clearInterval (token: int): unit = pyNative - let [] debugger () : unit = pyNative - let [] undefined<'a> : 'a = pyNative - - [] - let private CONSTRUCTORS_WARNING = "JS constructors are now in Fable.Core.JS.Constructors module to prevent conflicts with modules with same name" - - [] - let [] Number: NumberConstructor = pyNative - - [] - let [] Object: ObjectConstructor = pyNative - - [] - let [] Date: DateConstructor = pyNative - - [] - let [] Map: MapConstructor = pyNative - - [] - let [] WeakMap: WeakMapConstructor = pyNative - - [] - let [] Set: SetConstructor = pyNative - - [] - let [] WeakSet: WeakSetConstructor = pyNative - - [] - let [] Promise: PromiseConstructor = pyNative - - [] - let [] RegExp: RegExpConstructor = pyNative - - [] - let [] Array: ArrayConstructor = pyNative - - [] - let [] DataView: DataViewConstructor = pyNative - - [] - let [] ArrayBuffer: ArrayBufferConstructor = pyNative - - [] - let [] ArrayBufferView: ArrayBufferViewConstructor = pyNative - - [] - let [] Int8Array: Int8ArrayConstructor = pyNative - - [] - let [] Uint8Array: Uint8ArrayConstructor = pyNative - - [] - let [] Uint8ClampedArray: Uint8ClampedArrayConstructor = pyNative - - [] - let [] Int16Array: Int16ArrayConstructor = pyNative - - [] - let [] Uint16Array: Uint16ArrayConstructor = pyNative - - [] - let [] Int32Array: Int32ArrayConstructor = pyNative - - [] - let [] Uint32Array: Uint32ArrayConstructor = pyNative - - [] - let [] Float32Array: Float32ArrayConstructor = pyNative - - [] - let [] Float64Array: Float64ArrayConstructor = pyNative - - // [] - // let [] BigInt64Array: BigInt64ArrayConstructor = pyNative - [] module Constructors = - let [] Number: NumberConstructor = pyNative - let [] Object: ObjectConstructor = pyNative - let [] Date: DateConstructor = pyNative - let [] Map: MapConstructor = pyNative - let [] WeakMap: WeakMapConstructor = pyNative - let [] Set: SetConstructor = pyNative - let [] WeakSet: WeakSetConstructor = pyNative - let [] Promise: PromiseConstructor = pyNative - let [] RegExp: RegExpConstructor = pyNative let [] Array: ArrayConstructor = pyNative - let [] DataView: DataViewConstructor = pyNative - let [] ArrayBuffer: ArrayBufferConstructor = pyNative - let [] ArrayBufferView: ArrayBufferViewConstructor = pyNative - let [] Int8Array: Int8ArrayConstructor = pyNative - let [] Uint8Array: Uint8ArrayConstructor = pyNative - let [] Uint8ClampedArray: Uint8ClampedArrayConstructor = pyNative - let [] Int16Array: Int16ArrayConstructor = pyNative - let [] Uint16Array: Uint16ArrayConstructor = pyNative - let [] Int32Array: Int32ArrayConstructor = pyNative - let [] Uint32Array: Uint32ArrayConstructor = pyNative - let [] Float32Array: Float32ArrayConstructor = pyNative - let [] Float64Array: Float64ArrayConstructor = pyNative - let [] BigInt64Array: BigInt64ArrayConstructor = pyNative - // let [] BigUint64Array: BigUint64ArrayConstructor = pyNative diff --git a/src/fable-library-py/fable/Native.fs b/src/fable-library-py/fable/Native.fs index f73d7e301f..b848868fb1 100644 --- a/src/fable-library-py/fable/Native.fs +++ b/src/fable-library-py/fable/Native.fs @@ -33,8 +33,6 @@ module Helpers = let inline isDynamicArrayImpl arr = PY.Constructors.Array.isArray arr - let inline isTypedArrayImpl arr = PY.Constructors.ArrayBuffer.isView arr - // let inline typedArraySetImpl (target: obj) (source: obj) (offset: int): unit = // !!target?set(source, offset) From 38161ac2b2a16b9661122ca1fb5503b4f9905278 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 18:20:43 +0200 Subject: [PATCH 143/145] Remove `callable` Will boxing work with a null check? --- src/Fable.Core/Fable.Core.JsInterop.fs | 4 ---- src/Fable.Core/Fable.Core.PyInterop.fs | 4 ---- src/fable-library/Array.fs | 7 ++++--- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Fable.Core/Fable.Core.JsInterop.fs b/src/Fable.Core/Fable.Core.JsInterop.fs index a60d3ad98f..09410530b6 100644 --- a/src/Fable.Core/Fable.Core.JsInterop.fs +++ b/src/Fable.Core/Fable.Core.JsInterop.fs @@ -70,10 +70,6 @@ let jsTypeof (x: obj): string = jsNative [] let jsInstanceof (x: obj) (cons: obj): bool = jsNative -/// Check if object is callable, i.e a function. -[] -let inline callable(x: obj) = jsNative - [] let jsThis<'T> : 'T = jsNative diff --git a/src/Fable.Core/Fable.Core.PyInterop.fs b/src/Fable.Core/Fable.Core.PyInterop.fs index 5a193085c1..c09bcd5d91 100644 --- a/src/Fable.Core/Fable.Core.PyInterop.fs +++ b/src/Fable.Core/Fable.Core.PyInterop.fs @@ -59,10 +59,6 @@ let pyTypeof (x: obj): string = pyNative [] let pyInstanceof (x: obj) (cons: obj): bool = pyNative -/// Check if object is callable, i.e a function. -[] -let callable(x: obj) = pyNative - /// Works like `ImportAttribute` (same semantics as ES6 imports). /// You can use "*" or "default" selectors. let import<'T> (selector: string) (path: string):'T = pyNative diff --git a/src/fable-library/Array.fs b/src/fable-library/Array.fs index 1f34b6c422..5cb7c92631 100644 --- a/src/fable-library/Array.fs +++ b/src/fable-library/Array.fs @@ -431,9 +431,10 @@ let choose (chooser: 'T->'U option) (array: 'T[]) ([] cons: Cons<'U>) = match chooser array.[i] with | None -> () | Some y -> pushImpl res y |> ignore - if callable cons - then map id res cons - else res // avoid extra copy + + match box cons with + | null -> res // avoid extra copy + | _ -> map id res cons let foldIndexed folder (state: 'State) (array: 'T[]) = // if isTypedArrayImpl array then From 2eb5a5299c54c0591adbdddc8683b3e282821be8 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 18:31:23 +0200 Subject: [PATCH 144/145] Remove callable from Native.fs --- src/fable-library-py/fable/Native.fs | 10 +++------- src/fable-library/Native.fs | 3 --- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/fable-library-py/fable/Native.fs b/src/fable-library-py/fable/Native.fs index b848868fb1..9d5a08678e 100644 --- a/src/fable-library-py/fable/Native.fs +++ b/src/fable-library-py/fable/Native.fs @@ -13,9 +13,6 @@ type Cons<'T> = abstract Allocate : len: int -> 'T [] module Helpers = - /// Use PyInterop.callable - let inline callable(x: obj) = callable x - [] let arrayFrom (xs: 'T seq) : 'T [] = pyNative @@ -26,11 +23,10 @@ module Helpers = let allocateArrayFrom (xs: 'T []) (len: int) : 'T [] = pyNative let allocateArrayFromCons (cons: Cons<'T>) (len: int) : 'T [] = - if callable cons then - cons.Allocate(len) - else + if isNull (box cons) then PY.Constructors.Array.Create(len) - + else + cons.Allocate(len) let inline isDynamicArrayImpl arr = PY.Constructors.Array.isArray arr // let inline typedArraySetImpl (target: obj) (source: obj) (offset: int): unit = diff --git a/src/fable-library/Native.fs b/src/fable-library/Native.fs index 1d88dd4d98..b3df5784c5 100644 --- a/src/fable-library/Native.fs +++ b/src/fable-library/Native.fs @@ -13,9 +13,6 @@ type Cons<'T> = abstract Allocate: len: int -> 'T[] module Helpers = - /// Use PyInterop.callable - let inline callable(x: obj) = callable x - [] let arrayFrom (xs: 'T seq): 'T[] = jsNative From 72c972bc82e2ed25aed43edb2843e9d610607bcb Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Jul 2021 19:11:54 +0200 Subject: [PATCH 145/145] Enable disabled tests --- tests/Python/Fable.Tests.fsproj | 1 + tests/Python/TestSudoku.fs | 2 +- tests/Python/TestUnionType.fs | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Python/Fable.Tests.fsproj b/tests/Python/Fable.Tests.fsproj index a6e07534f7..f33d46612e 100644 --- a/tests/Python/Fable.Tests.fsproj +++ b/tests/Python/Fable.Tests.fsproj @@ -33,6 +33,7 @@ + diff --git a/tests/Python/TestSudoku.fs b/tests/Python/TestSudoku.fs index aeb4cccc1c..81322933d1 100644 --- a/tests/Python/TestSudoku.fs +++ b/tests/Python/TestSudoku.fs @@ -65,7 +65,7 @@ let rec substitute row col (x:Sudoku) = let getFirstSolution = substitute 0 0 >> Seq.head -//[] +[] let testsSudoku () = let solution = [[0; 0; 8; 3; 0; 0; 6; 0; 0] diff --git a/tests/Python/TestUnionType.fs b/tests/Python/TestUnionType.fs index 38935268c4..8b90d32298 100644 --- a/tests/Python/TestUnionType.fs +++ b/tests/Python/TestUnionType.fs @@ -158,7 +158,8 @@ let ``test Comprehensive active patterns can return values`` () = measure 10 |> equal "bar" measure 5 |> equal "foo" -let ``Partial active patterns which don't return values work`` () = // See #478 +[] +let ``test Partial active patterns which don't return values work`` () = // See #478 let isFunctional = function | FSharp -> "yes" | "scala" -> "fifty-fifty" @@ -167,7 +168,8 @@ let ``Partial active patterns which don't return values work`` () = // See #478 isFunctional "smalltalk" |> equal "dunno" isFunctional "fsharp" |> equal "yes" -let ``Active patterns can be combined with union case matching`` () = // See #306 +[] +let ``test Active patterns can be combined with union case matching`` () = // See #306 let test = function | Some(A n, Some(A m)) -> n + m | _ -> 0