diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index e9ab379..60d828d 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -9,13 +9,13 @@ ] }, "fable": { - "version": "4.0.0-theta-003", + "version": "4.0.0-theta-006", "commands": [ "fable" ] }, "fantomas": { - "version": "5.0.0-beta-009", + "version": "5.0.0", "commands": [ "fantomas" ] diff --git a/README.md b/README.md index dccee94..1880dba 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ and many more. Some bindings have already been added: - Python Standard Libray - Jupyter - Flask -- MicroBit - CogniteSdk ## Version @@ -30,8 +29,8 @@ how to deal with Python version handling. Prerequisite for compiling F# to Python using Fable: ```sh -> dotnet tool install --global fable --version 4.0.0-theta-003 -> dotnet add package Fable.Core --version 4.0.0-theta-001 +> dotnet tool install --global fable --prerelease +> dotnet add package Fable.Core --prerelease ``` To use the `Fable.Python` library in your Fable project: @@ -62,7 +61,6 @@ It contains example code for using Fable Python with: - [Flask](https://github.com/dbrattli/Fable.Python/tree/main/examples/flask). References [Feliz.ViewEngine](https://github.com/dbrattli/Feliz.ViewEngine) as a nuget package. -- [MicroBit](https://github.com/dbrattli/Fable.Python/tree/main/examples/microbit) - [Timeflies](https://github.com/dbrattli/Fable.Python/tree/main/examples/timeflies), Cool demo using Tkinter and references [FSharp.Control.AsyncRx](https://github.com/dbrattli/AsyncRx) as a nuget @@ -87,12 +85,9 @@ It contains example code for using Fable Python with: ## Poetry Fable.Python uses [Poetry](https://python-poetry.org/) for package and -dependency management. This means that packages generated within -`fable_modules` must be referenced as [path -dependencies](https://python-poetry.org/docs/dependency-specification/#path-dependencies). -It is also possible to reference -[fable-library](https://pypi.org/project/fable-library/) from PyPI to -avoid bundling the code. +dependency management. To handle dependencies when adding Fable Python +compatible NuGet packages, you should use +[Femto](https://github.com/Zaid-Ajaj/Femto). ## Contributing diff --git a/examples/microbit/.paket/Paket.Restore.targets b/examples/microbit/.paket/Paket.Restore.targets deleted file mode 100644 index e230bb2..0000000 --- a/examples/microbit/.paket/Paket.Restore.targets +++ /dev/null @@ -1,557 +0,0 @@ - - - - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - $(MSBuildVersion) - 15.0.0 - false - true - - true - $(MSBuildThisFileDirectory) - $(MSBuildThisFileDirectory)..\ - $(PaketRootPath)paket-files\paket.restore.cached - $(PaketRootPath)paket.lock - classic - proj - assembly - native - /Library/Frameworks/Mono.framework/Commands/mono - mono - - - $(PaketRootPath)paket.bootstrapper.exe - $(PaketToolsPath)paket.bootstrapper.exe - $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\ - - "$(PaketBootStrapperExePath)" - $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" - - - - - true - true - - - True - - - False - - $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/')) - - - - - - - - - $(PaketRootPath)paket - $(PaketToolsPath)paket - - - - - - $(PaketRootPath)paket.exe - $(PaketToolsPath)paket.exe - - - - - - <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json")) - <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"')) - <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false - - - - - - - - - - - <_PaketCommand>dotnet paket - - - - - - $(PaketToolsPath)paket - $(PaketBootStrapperExeDir)paket - - - paket - - - - - <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) - <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)" - <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" - <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)" - - - - - - - - - - - - - - - - - - - - - true - $(NoWarn);NU1603;NU1604;NU1605;NU1608 - false - true - - - - - - - - - $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) - - - - - - - $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``)) - $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``)) - - - - - %(PaketRestoreCachedKeyValue.Value) - %(PaketRestoreCachedKeyValue.Value) - - - - - true - false - true - - - - - true - - - - - - - - - - - - - - - - - - - $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached - - $(MSBuildProjectFullPath).paket.references - - $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references - - $(MSBuildProjectDirectory)\paket.references - - false - true - true - references-file-or-cache-not-found - - - - - $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) - $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) - references-file - false - - - - - false - - - - - true - target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths) - - - - - - - - - - - false - true - - - - - - - - - - - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7]) - - - %(PaketReferencesFileLinesInfo.PackageVersion) - All - runtime - $(ExcludeAssets);contentFiles - $(ExcludeAssets);build;buildMultitargeting;buildTransitive - true - true - - - - - $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools - - - - - - - - - $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) - $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) - - - %(PaketCliToolFileLinesInfo.PackageVersion) - - - - - - - - - - false - - - - - - <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/> - - - - - - $(MSBuildProjectDirectory)/$(MSBuildProjectFile) - true - false - true - false - true - false - true - false - true - false - true - $(PaketIntermediateOutputPath)\$(Configuration) - $(PaketIntermediateOutputPath) - - - - <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/> - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/microbit/Build.fs b/examples/microbit/Build.fs deleted file mode 100644 index bc68bb5..0000000 --- a/examples/microbit/Build.fs +++ /dev/null @@ -1,46 +0,0 @@ -open System.IO -open Fake.Core -open Fake.IO - -open Helpers - -initializeContext() - -let srcPath = Path.getFullName "src" -let appName = "app.py" - -Target.create "Clean" (fun _ -> - run dotnet "fable-py clean --yes" srcPath // Delete *.py files created by Fable -) - -Target.create "Build" (fun _ -> - run dotnet $"fable-py -c Release" srcPath - - // Rewrite imports to flat file system. - let python = File.ReadAllText($"{srcPath}/{appName}") - let python = python.Replace("fable_modules.fable_library.", "") - File.WriteAllText($"{srcPath}/{appName}", python) -) - -Target.create "Flash" (fun _ -> - run flash appName srcPath -) - -Target.create "FableLibrary" (fun _ -> - run ufs $"put util.py" srcPath -) - -open Fake.Core.TargetOperators - -let dependencies = [ - "Clean" - ==> "Build" - "Clean" - ==> "Build" - ==> "Flash" - "Clean" - ==> "FableLibrary" -] - -[] -let main args = runOrDefault args \ No newline at end of file diff --git a/examples/microbit/Helpers.fs b/examples/microbit/Helpers.fs deleted file mode 100644 index 819fad3..0000000 --- a/examples/microbit/Helpers.fs +++ /dev/null @@ -1,107 +0,0 @@ -module Helpers - -open Fake.Core - -let initializeContext () = - let execContext = Context.FakeExecutionContext.Create false "build.fsx" [ ] - Context.setExecutionContext (Context.RuntimeContext.Fake execContext) - -module Proc = - module Parallel = - open System - - let locker = obj() - - let colors = - [| ConsoleColor.Blue - ConsoleColor.Yellow - ConsoleColor.Magenta - ConsoleColor.Cyan - ConsoleColor.DarkBlue - ConsoleColor.DarkYellow - ConsoleColor.DarkMagenta - ConsoleColor.DarkCyan |] - - let print color (colored: string) (line: string) = - lock locker - (fun () -> - let currentColor = Console.ForegroundColor - Console.ForegroundColor <- color - Console.Write colored - Console.ForegroundColor <- currentColor - Console.WriteLine line) - - let onStdout index name (line: string) = - let color = colors.[index % colors.Length] - if isNull line then - print color $"{name}: --- END ---" "" - else if String.isNotNullOrEmpty line then - print color $"{name}: " line - - let onStderr name (line: string) = - let color = ConsoleColor.Red - if isNull line |> not then - print color $"{name}: " line - - let redirect (index, (name, createProcess)) = - createProcess - |> CreateProcess.redirectOutputIfNotRedirected - |> CreateProcess.withOutputEvents (onStdout index name) (onStderr name) - - let printStarting indexed = - for (index, (name, c: CreateProcess<_>)) in indexed do - let color = colors.[index % colors.Length] - let wd = - c.WorkingDirectory - |> Option.defaultValue "" - let exe = c.Command.Executable - let args = c.Command.Arguments.ToStartInfo - print color $"{name}: {wd}> {exe} {args}" "" - - let run cs = - cs - |> Seq.toArray - |> Array.indexed - |> fun x -> printStarting x; x - |> Array.map redirect - |> Array.Parallel.map Proc.run - -let createProcess exe arg dir = - CreateProcess.fromRawCommandLine exe arg - |> CreateProcess.withWorkingDirectory dir - |> CreateProcess.ensureExitCode - -let dotnet = createProcess "dotnet" -let flash = createProcess "uflash" -let ufs = createProcess "ufs" - -let npm = - let npmPath = - match ProcessUtils.tryFindFileOnPath "npm" with - | Some path -> path - | None -> - "npm was not found in path. Please install it and make sure it's available from your path. " + - "See https://safe-stack.github.io/docs/quickstart/#install-pre-requisites for more info" - |> failwith - - createProcess npmPath - -let run proc arg dir = - proc arg dir - |> Proc.run - |> ignore - -let runParallel processes = - processes - |> Proc.Parallel.run - |> ignore - -let runOrDefault args = - try - match args with - | [| target |] -> Target.runOrDefault target - | _ -> Target.runOrDefault "Run" - 0 - with e -> - printfn "%A" e - 1 diff --git a/examples/microbit/README.md b/examples/microbit/README.md deleted file mode 100644 index 20ea4ee..0000000 --- a/examples/microbit/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# Fable Python on BBC micro:bit - -Write your F# program in `src/App.fs`. - -Note that the Fable Library is not supported on the micro:bit so it's -very limited what we can do. Parts of Fable Library needs to -be ported to work with MicroPython see `util.fs` as an example. - -The micro:bit have a flat file-system so all files needs to be in the -same top-level directory. For more information see -https://microbit-micropython.readthedocs.io/en/latest/tutorials/storage.html - -## Install Dependecies - -To flash the microbit we use -[`uFlash`](https://uflash.readthedocs.io/en/latest/) to flash the app -and [`MicroFS`](https://microfs.readthedocs.io/en/latest/) to transfer -any Fable Libary (modified) files. - -These tools are needed by the Build script: - -```sh -pip install uflash -pip install microfs - -dotnet tool restore -dotnet restore -``` - -## Build - -```sh -dotnet run Build -``` - -## Flash to MicroBit - -```sh -dotnet run Flash -``` - -## Copy Fable Library - -Flashing will not copy the Fable Library (just `util.py` for now), so it -needs to be transferred separately: - -```sh -dotnet run FableLibrary -``` - diff --git a/examples/microbit/paket.dependencies b/examples/microbit/paket.dependencies deleted file mode 100644 index 75611ca..0000000 --- a/examples/microbit/paket.dependencies +++ /dev/null @@ -1,9 +0,0 @@ -source https://api.nuget.org/v3/index.json -framework: net5.0 -storage: none - -nuget Fable.Core.Experimental >= 4.0.0-alpha-022 -nuget Fable.Python - -nuget Fake.Core.Target -nuget Fake.IO.FileSystem diff --git a/examples/microbit/paket.lock b/examples/microbit/paket.lock deleted file mode 100644 index 23dcfae..0000000 --- a/examples/microbit/paket.lock +++ /dev/null @@ -1,54 +0,0 @@ -STORAGE: NONE -RESTRICTION: == net5.0 -NUGET - remote: https://api.nuget.org/v3/index.json - Fable.Core.Experimental (4.0.0-alpha-002) - FSharp.Core (>= 4.7.2) - Fable.Python (0.9) - Fable.Core.Experimental (>= 4.0.0-alpha-002) - FSharp.Core (>= 6.0) - Fake.Core.CommandLineParsing (5.20.4) - FParsec (>= 1.1.1) - FSharp.Core (>= 4.7.2) - Fake.Core.Context (5.20.4) - FSharp.Core (>= 4.7.2) - Fake.Core.Environment (5.20.4) - FSharp.Core (>= 4.7.2) - Fake.Core.FakeVar (5.20.4) - Fake.Core.Context (>= 5.20.4) - FSharp.Core (>= 4.7.2) - Fake.Core.Process (5.20.4) - Fake.Core.Environment (>= 5.20.4) - Fake.Core.FakeVar (>= 5.20.4) - Fake.Core.String (>= 5.20.4) - Fake.Core.Trace (>= 5.20.4) - Fake.IO.FileSystem (>= 5.20.4) - FSharp.Core (>= 4.7.2) - System.Collections.Immutable (>= 1.7.1) - Fake.Core.String (5.20.4) - FSharp.Core (>= 4.7.2) - Fake.Core.Target (5.20.4) - Fake.Core.CommandLineParsing (>= 5.20.4) - Fake.Core.Context (>= 5.20.4) - Fake.Core.Environment (>= 5.20.4) - Fake.Core.FakeVar (>= 5.20.4) - Fake.Core.Process (>= 5.20.4) - Fake.Core.String (>= 5.20.4) - Fake.Core.Trace (>= 5.20.4) - FSharp.Control.Reactive (>= 4.4.2) - FSharp.Core (>= 4.7.2) - Fake.Core.Trace (5.20.4) - Fake.Core.Environment (>= 5.20.4) - Fake.Core.FakeVar (>= 5.20.4) - FSharp.Core (>= 4.7.2) - Fake.IO.FileSystem (5.20.4) - Fake.Core.String (>= 5.20.4) - FSharp.Core (>= 4.7.2) - FParsec (1.1.1) - FSharp.Core (>= 4.3.4) - FSharp.Control.Reactive (5.0.2) - FSharp.Core (>= 4.7.2) - System.Reactive (>= 5.0) - FSharp.Core (6.0) - System.Collections.Immutable (5.0) - System.Reactive (5.0) diff --git a/examples/microbit/paket.references b/examples/microbit/paket.references deleted file mode 100644 index 40aad72..0000000 --- a/examples/microbit/paket.references +++ /dev/null @@ -1,2 +0,0 @@ -Fake.Core.Target -Fake.IO.FileSystem diff --git a/examples/microbit/src/App.fs b/examples/microbit/src/App.fs deleted file mode 100644 index e5ec232..0000000 --- a/examples/microbit/src/App.fs +++ /dev/null @@ -1,32 +0,0 @@ -module App - -open Fable.Python.MicroBit - -//display.scroll ("Fable Python!") |> ignore - -let mutable time = 0 -let mutable start = 0 -let mutable running = false - -while true do - if running then - display.show (Image.HEART) - sleep (300) - display.show (Image.HEART_SMALL) - sleep (300) - else - display.show (Image.ASLEEP) - - if button_a.was_pressed () then - running <- true - start <- running_time () - - if button_b.was_pressed () then - if running then - time <- time + running_time () - start - - running <- false - - if pin_logo.is_touched () then - if not running then - display.scroll (int (time / 1000)) diff --git a/examples/microbit/src/MicroBit.fsproj b/examples/microbit/src/MicroBit.fsproj deleted file mode 100644 index 974f566..0000000 --- a/examples/microbit/src/MicroBit.fsproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net5.0 - false - Dag Brattli - Dag Brattli - Exe - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/microbit/src/app.py b/examples/microbit/src/app.py deleted file mode 100644 index ab3b19c..0000000 --- a/examples/microbit/src/app.py +++ /dev/null @@ -1,4 +0,0 @@ -from microbit import display - -value = display.scroll("Fable Python!") - diff --git a/examples/microbit/src/paket.references b/examples/microbit/src/paket.references deleted file mode 100644 index 1d37423..0000000 --- a/examples/microbit/src/paket.references +++ /dev/null @@ -1 +0,0 @@ -Fable.Core \ No newline at end of file diff --git a/examples/microbit/src/types.py b/examples/microbit/src/types.py deleted file mode 100755 index 9ce7184..0000000 --- a/examples/microbit/src/types.py +++ /dev/null @@ -1,249 +0,0 @@ -from __future__ import annotations - -from .util import equals -from .util import IComparable - - -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) -> T: - return self.getter() - - @contents.setter - def contents(self, v) -> None: - self.setter(v) - - -class Union(IComparable): - def __init__(self): - self.tag: int - self.fields = () - - def cases(): - ... - - @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) -> 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) -> 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 - - a = self.__dict__ if hasattr(self, "__dict__") else self - b = other.__dict__ if hasattr(other, "__dict__") else other - - return a == b - - -def recordCompareTo(self, other): - if self is other: - return 0 - - else: - for name in self.__dict__.keys(): - if self.__dict__[name] < other.__dict__.get(name): - return -1 - elif self.__dict__[name] > other.__dict__.get(name): - return 1 - - return 0 - - -def recordToString(self): - return "{ " + "\n ".join(map(lambda kv: kv[0] + " = " + str(kv[1]), self.__dict__.items())) + " }" - - -def recordGetHashCode(self): - return hash(*self.values()) - - -class Record(IComparable): - def toJSON(self) -> str: - return record_to_JSON(self) - - def __str__(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) -> bool: - return True if self.CompareTo(other) == -1 else False - - def __eq__(self, other) -> bool: - return self.Equals(other) - - def __hash__(self) -> int: - return recordGetHashCode(self) - - -class Attribute: - pass - - -def seq_to_string(self): - str = "[" - - for count, x in enumerate(self): - if count == 0: - str += to_string(x) - - elif count == 100: - str += "; ..." - break - - else: - str += "; " + to_string(x) - - return str + "]" - - -def to_string(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 seq_to_string(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) - - -class Exception(Exception): - def __init__(self, msg=None): - self.msg = msg - - def __eq__(self, other): - if self is other: - return True - - if other is None: - return False - - return self.msg == other.msg - - -class FSharpException(Exception, IComparable): - def __init__(self): - self.Data0 = None - - def toJSON(self): - return record_to_JSON(self) - - def __str__(self): - return recordToString(self) - - def __eq__(self, other): - if self is other: - return True - - if other is None: - return False - - return self.Data0 == other.Data0 - - def __lt__(self, other: Any) -> bool: - if not isinstance(other, FSharpException): - return False - - if self.Data0: - if other.Data0: - return self.Data0 < other.Data0 - else: - return False - - elif not self.Data0: - if other.Data0: - return False - else: - return True - - return super().__lt__(other) - - def __hash__(self) -> int: - return hash(self.Data0) - - 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", "to_string", "Union"] diff --git a/examples/microbit/src/util.py b/examples/microbit/src/util.py deleted file mode 100755 index 874bc8b..0000000 --- a/examples/microbit/src/util.py +++ /dev/null @@ -1,412 +0,0 @@ -import builtins -import math - - -class ObjectDisposedException(Exception): - def __init__(self): - super().__init__("Cannot access a disposed object") - - -class IDisposable: - def dispose(self): - ... - - 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 - - def dispose(self): - """Performs the task of cleaning up resources.""" - - dispose = False - 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: - def GetHashCode(self): - return hash(self) - - def __eq__(self, other): - return NotImplemented - - def __hash__(self): - raise NotImplementedError - - -class IComparable(IEquatable): - def CompareTo(self, other): - if self < other: - return -1 - elif self == other: - return 0 - return 1 - - def __lt__(self, other): - raise NotImplementedError - - -def equals(a, b): - return a == b - - -def is_comparable(x): - return hasattr(x, "CompareTo") and callable(x.CompareTo) - - -def compare(a, b): - if a is b: - return 0 - - if a is None: - return -1 if b else 0 - - if b is None: - return 1 if a else 0 - - if is_comparable(a): - return a.CompareTo(b) - - if hasattr(a, "__eq__") and callable(a.__eq__) and a == b: - return 0 - - if hasattr(a, "__lt__") and callable(a.__lt__) and a < b: - return -1 - - return 1 - - -def compare_arrays(a, b): - return compare(a, b) - - -def equal_arrays_with(x, y, eq): - if x is None: - return y is None - if y is None: - return False - - if len(x) != len(y): - return False - - return eq(x, y) - - -def equal_arrays(x, y): - return equal_arrays_with(x, y, equals) - - -def compare_primitives(x, y): - 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 clamp(comparer, value, min, max): - # return (comparer(value, min) < 0) ? min : (comparer(value, max) > 0) ? max : value; - return min if (comparer(value, min) < 0) else max if comparer(value, max) > 0 else value - - -def create_atom(value=None): - atom = value - - def _(value=None, isSetter=None): - nonlocal atom - - if not isSetter: - return atom - else: - atom = value - return None - - return _ - - -def create_obj(fields): - # TODO: return dict(filelds) ? - obj = {} - - for k, v in fields: - obj[k] = v - - return obj - - -def int16to_string(i, radix=10): - 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 int32to_string(i: int, radix: int = 10): - if radix == 10: - return "{:d}".format(i) - if radix == 16: - return "{:x}".format(i) - if radix == 2: - return "{:b}".format(i) - return str(i) - - -class IEnumerator(IDisposable): - def Current(self): - ... - - def MoveNext(self): - ... - - def Reset(self): - ... - - def Dispose(self): - ... - - def __getattr__(self, name): - return { - "System_Collections_Generic_IEnumerator_00601_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: - def GetEnumerator(self): - ... - - -class Enumerator: - def __init__(self, iter): - self.iter = iter - self.current = None - - 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 get_enumerator(o): - attr = getattr(o, "GetEnumerator", None) - if attr: - return attr() - else: - return Enumerator(iter(o)) - - -CURRIED_KEY = "__CURRIED__" - - -def uncurry(arity: int, f): - # 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("Uncurrying to more than 8-arity is not supported:", arity) - - setattr(f, CURRIED_KEY, f) - return uncurriedFn - - -def curry(arity: int, f): - 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 is_array_like(x): - return hasattr(x, "__len__") and callable(x.__len__) - - -def is_disposable(x): - return x is not None and isinstance(x, IDisposable) - - -def is_hashable(x): - return hasattr(x, "GetHashCode") - - -def is_hashable_py(x): - return hasattr(x, "__hash__") and callable(x.__hash__) - - -def to_iterator(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() - - -class ObjectRef: - id_map = dict() - count = 0 - - @staticmethod - def id(o): - _id = id(o) - if not _id in ObjectRef.id_map: - count = ObjectRef.count + 1 - ObjectRef.id_map[_id] = count - - return ObjectRef.id_map[_id] - - -def safe_hash(x): - return 0 if x is None else x.GetHashCode() if is_hashable(x) else number_hash(ObjectRef.id(x)) - - -def string_hash(s): - h = 5381 - for c in s: - h = (h * 33) ^ ord(c) - - return h - - -def number_hash(x): - return x * 2654435761 | 0 - - -def identity_hash(x): - if x is None: - return 0 - - if is_hashable(x): - return x.GetHashCode() - - if is_hashable_py(x): - return hash(x) - - return physical_hash(x) - - -def combine_hash_codes(hashes): - if not hashes: - return 0 - - # return functools.reduce(lambda h1, h2: ((h1 << 5) + h1) ^ h2, hashes) - return 0 - - -def structural_hash(x): - print("structural_hash: ", x) - return hash(x) - - -def array_hash(xs): - hashes = [] - for i, x in enumerate(xs): - hashes.append(structural_hash(x)) - - return combine_hash_codes(hashes) - - -def physical_hash(x): - if hasattr(x, "__hash__") and callable(x.__hash__): - return hash(x) - - return number_hash(ObjectRef.id(x)) - - -def round(value, digits=0): - m = pow(10, digits) - n = +(value * m if digits else value) - i = math.floor(n) - f = n - i - e = 1e-8 - r = (i if (i % 2 == 0) else i + 1) if (f > 0.5 - e and f < 0.5 + e) else builtins.round(n) - return r / m if digits else r diff --git a/examples/timeflies/Program.fs b/examples/timeflies/Program.fs index 28ad828..3265525 100644 --- a/examples/timeflies/Program.fs +++ b/examples/timeflies/Program.fs @@ -15,7 +15,8 @@ let root = Tk() root.title ("Fable Python Rocks on Tkinter!") let queue = Queue() -let source, mouseMoves: IAsyncObserver * IAsyncObservable = AsyncRx.subject () +let source, mouseMoves: IAsyncObserver * IAsyncObservable = + AsyncRx.subject () let workerAsync (mb: MailboxProcessor) = let rec messageLoop () = @@ -38,11 +39,10 @@ let stream = Seq.toList "TIME FLIES LIKE AN ARROW" |> Seq.mapi (fun i c -> i, Label(frame, text = (string c), fg = "black", bg = "white")) |> AsyncRx.ofSeq - |> AsyncRx.flatMap - (fun (i, label) -> - mouseMoves - |> AsyncRx.delay (100 * i) - |> AsyncRx.map (fun (x, y) -> label, x + i * 12 + 15, y)) + |> AsyncRx.flatMap (fun (i, label) -> + mouseMoves + |> AsyncRx.delay (100 * i) + |> AsyncRx.map (fun (x, y) -> label, x + i * 12 + 15, y)) let sink (ev: Notification