diff --git a/README.md b/README.md index 36120e9..21f390f 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ If you're already a user of CPython, you can skip this step. Otherwise, go to [this official download](https://www.python.org/downloads/) page, - download and install any valid distribution(`>=3.5`). + download and install any valid distribution(`>=3.5`, **<=3.8**). -1. Install [nodejs](https://nodejs.org/en/), which is distributed with a command `npm`, and use `npm` to install `purescript` and its package manager `spago`: +1. Install [nodejs](https://nodejs.org/en/), which is distributed with a command `npm`, and use `npm` to install `purescript` and its package manager `spago`. The latest version of the purescript compiler that is supported is **0.13.8**: ```bash npm install -g purescript npm install -g spago @@ -64,7 +64,7 @@ A major motivation for my working on PureScript is its lightweighted but awesome For VSCode users, installing the plugin `PureScript IDE` and `File -> Preferences -> Settings -> (search purescript) -> Turn on "Add Spago sources"` will be sufficient. **No need to install some GitHub repo and build for 4 hours! And this IDE feels swift!** -## Troubleshot `pspy-blueprint` +## Troubleshoot `pspy-blueprint` If `pspy-blueprint` provided by the Python package `purescripto` didn't work(e.g., users of MacOSX < 10.15), you should manually install it from this repository, and currently there're 2 options: @@ -72,3 +72,14 @@ If `pspy-blueprint` provided by the Python package `purescripto` didn't work(e.g 2. Install from source(Need Haskell [stack](https://docs.haskellstack.org/en/stable/README)): clone this repo, and use command `stack install .`, which will install `pspy-blueprint` to your `.local` PATH. For Linux users, you might also need to use `chmod u+x ` to allow the permission to execute. + +## Troubleshoot: Execution Not Sync to Latest Code + +This seems to be a recent issue produced by the upstream compiler, and you can resolve this by removing the current `output` directory: + +```bash +rm -rf $YOUR_PROJECT_ROOT/output && spago build && pspy --run +``` + +This will produce the result of your latest code. + diff --git a/ci-passing.sh b/ci-passing.sh index 8c2b4c5..26e4cae 100644 --- a/ci-passing.sh +++ b/ci-passing.sh @@ -3,8 +3,6 @@ cd passing nvm install 12.16.1 nvm use 12.16.1 -npm install -g purescript -npm install -g spago # this allows failure pyenv global 3.7 diff --git a/package.yaml b/package.yaml index 31819a1..8f1f2d1 100644 --- a/package.yaml +++ b/package.yaml @@ -4,7 +4,7 @@ github: "purescript-python/purescript-python" license: MIT author: "Taine Zhao" maintainer: "twshere@outlook.com" -copyright: "2020 Taine Zhao" +copyright: "2021 Taine Zhao" extra-source-files: - README.md @@ -20,29 +20,29 @@ extra-source-files: description: Please see the README on GitHub at dependencies: - - aeson >=1.0 && <1.5 - - aeson-better-errors >=0.8 - - aeson-pretty - - base >=4.11 && <4.13 - - base-compat >=0.6.0 - - bytestring - - containers - - directory >=1.2.3 - - filepath - - monad-control >=1.0.0.0 && <1.1 - - monad-logger >=0.3 && <0.4 - - mtl >=2.1.0 && <2.3.0 - - protolude >=0.1.6 && <0.2.4 - - semigroups >=0.16.2 && <0.19 - - text - - transformers >=0.3.0 && <0.6 - - transformers-base >=0.4.0 && <0.5 - - transformers-compat >=0.3.0 - - utf8-string >=1 && <2 + - aeson >=1.5.6.0 && <1.6 + - aeson-better-errors >=0.9.1.0 && <0.10 + - aeson-pretty >=0.8.8 && <0.9 + - base >=4.14.1.0 && <4.15 + - base-compat >=0.11.2 && <0.12 + - bytestring >=0.10.12.0 && <0.11 + - containers >=0.6.2.1 && <0.7 + - directory >=1.3.6.0 && <1.4 + - filepath >=1.4.2.1 && <1.5 + - monad-control >=1.0.2.3 && <1.1 + - monad-logger >=0.3.36 && <0.4 + - mtl >=2.2.2 && <2.3 + - protolude >=0.3.0 && <0.4 + - semigroups >=0.19.1 && <0.20 + - text >=1.2.4.1 && <1.3 + - transformers >=0.5.6.2 && <0.6 + - transformers-base >=0.4.5.2 && <0.5 + - transformers-compat >=0.6.6 && <0.7 + - utf8-string >=1.0.2 && <1.1 - purescript + - purescript-cst - prettyprinter - bytestring-conversion - - utf8-string - zip default-extensions: @@ -77,12 +77,27 @@ default-extensions: when: - condition: os(darwin) then: - ghc-options: -Wall -O2 + ghc-options: + - -O2 + - -O2 -static + - -fno-warn-unused-imports + - -fno-warn-name-shadowing + - -fno-warn-missing-signatures + - -fno-warn-missing-pattern-synonym-signatures + - -fno-warn-unused-matches + - -fno-warn-orphans ld-options: - -pthread - -Wall else: - ghc-options: -Wall -O2 -static + ghc-options: + - -O2 -static + - -fno-warn-unused-imports + - -fno-warn-name-shadowing + - -fno-warn-missing-signatures + - -fno-warn-missing-pattern-synonym-signatures + - -fno-warn-unused-matches + - -fno-warn-orphans cc-options: -static ld-options: - -static diff --git a/passing/packages.dhall b/passing/packages.dhall index 3b82887..97129b4 100644 --- a/passing/packages.dhall +++ b/passing/packages.dhall @@ -119,7 +119,7 @@ let additions = let upstream = - https://github.com/purescript/package-sets/releases/download/psc-0.13.6-20200226/packages.dhall sha256:3a52562e05b31a7b51d12d5b228ccbe567c527781a88e9028ab42374ab55c0f1 + https://github.com/purescript/package-sets/releases/download/psc-0.14.4-20211030/packages.dhall sha256:5cd7c5696feea3d3f84505d311348b9e90a76c4ce3684930a0ff29606d2d816c let overrides = {=} diff --git a/passing/python-ffi/Global.py b/passing/python-ffi/Global.py new file mode 100644 index 0000000..82d6b6c --- /dev/null +++ b/passing/python-ffi/Global.py @@ -0,0 +1,97 @@ +import math +import urllib.parse + + +nan = math.nan +isNaN = math.isnan + + +infinity = math.inf +isFinite = math.isfinite + + +def readInt(radix): + def ap(n): + try: + return float(int(n, radix)) + except ValueError: + try: + return float(int(str(int(float(n))), radix)) + except ValueError: + return math.nan + return math.nan + + return ap + + +readFloat = float + + +def unsafeToFixed(digits): + def n_(n): + f = r"{:0." + str(digits) + r"f}" + return f.format(n) + + return n_ + + +def unsafeToExponential(digits): + def n_(n): + f = r"{:." + str(digits) + r"e}" + return f.format(n).replace("e-0", "e-").replace("e+0", "e+") + + return n_ + + +def unsafeToPrecision(digits): + def n_(n): + raise NotImplementedError() + + return n_ + + +def formatNumber(fmt): + def ap(fail, succ, digits, n): + try: + return succ(fmt(digits)(n)) + except Exception as e: + return fail(str(e)) + + return ap + + +_toFixed = formatNumber(unsafeToFixed) +_toExponential = formatNumber(unsafeToExponential) +_toPrecision = formatNumber(unsafeToPrecision) + + +def encdecURI(encdec): + def ap(fail, succ, s): + try: + return succ(encdec(s)) + except Exception as e: + return fail(str(e)) + + return ap + + +def decodeURI(s): + return urllib.parse.unquote(s, errors="strict") + + +def encodeURI(s): + return urllib.parse.quote(s, safe="~@#$&()*!+=:;,.?/'") + + +def decodeURIComponent(s): + return urllib.parse.unquote(s, errors="strict") + + +def encodeURIComponent(s): + return urllib.parse.quote(s, safe="~()*!.'") + + +_decodeURI = encdecURI(decodeURI) +_encodeURI = encdecURI(encodeURI) +_decodeURIComponent = encdecURI(decodeURIComponent) +_encodeURIComponent = encdecURI(encodeURIComponent) diff --git a/passing/python-ffi/Global/Unsafe.py b/passing/python-ffi/Global/Unsafe.py new file mode 100644 index 0000000..b924418 --- /dev/null +++ b/passing/python-ffi/Global/Unsafe.py @@ -0,0 +1,45 @@ +import json +import urllib.parse +import re + + +def unsafeStringify(x): + return json.dumps(x) + + +def unsafeToFixed(digits): + def n_(n): + f = r"{:0." + str(digits) + r"f}" + return f.format(n) + + return n_ + + +def unsafeToExponential(digits): + def n_(n): + f = r"{:." + str(digits) + r"e}" + return f.format(n).replace("e-0", "e-").replace("e+0", "e+") + + return n_ + + +def unsafeToPrecision(digits): + def n_(n): + raise NotImplementedError() + + return n_ + + +def unsafeDecodeURI(s): + return urllib.parse.unquote(s, errors="strict") + + +def unsafeEncodeURI(s): + return urllib.parse.quote(s, safe="~@#$&()*!+=:;,.?/'") + +def unsafeDecodeURIComponent(s): + return urllib.parse.unquote(s, errors="strict") + + +def unsafeEncodeURIComponent(s): + return urllib.parse.quote(s, safe="~()*!.'") diff --git a/passing/spago.dhall b/passing/spago.dhall index 242c12a..b56a44f 100644 --- a/passing/spago.dhall +++ b/passing/spago.dhall @@ -15,7 +15,7 @@ You can edit this file as you like. , "exceptions" , "foldable-traversable" , "foreign" - , "globals" + , "record" , "math" , "maybe" , "ordered-collections" diff --git a/passing/test/Global.Unsafe.purs b/passing/test/Global.Unsafe.purs new file mode 100644 index 0000000..1c8552e --- /dev/null +++ b/passing/test/Global.Unsafe.purs @@ -0,0 +1,41 @@ +module Global.Unsafe where + +-- | Uses the global JSON object to turn anything into a string. Careful! Trying +-- | to serialize functions returns undefined +foreign import unsafeStringify :: forall a. a -> String + +-- | Formats Number as a String with limited number of digits after the dot. +-- | +-- | May throw RangeError if the number of digits is not within the allowed range +-- | (standard precision range is 0 to 20, but implementations may change it) +foreign import unsafeToFixed :: Int -> Number -> String + +-- | Formats Number as String in exponential notation limiting number of digits +-- | after the decimal dot. +-- | +-- | May throw RangeError if the number of digits is not within the allowed range +-- | (standard precision range is 0 to 20, but implementations may change it) +foreign import unsafeToExponential :: Int -> Number -> String + +-- | Formats Number as String in fixed-point or exponential notation rounded +-- | to specified number of significant digits. +-- | +-- | May throw RangeError if the number of digits is not within the allowed range +-- | (standard precision range is 0 to 100, but implementations may change it) +foreign import unsafeToPrecision :: Int -> Number -> String + +-- | URI decoding. May throw a `URIError` if given a value with undecodeable +-- | escape sequences. +foreign import unsafeDecodeURI :: String -> String + +-- | URI encoding. May throw a `URIError` if given a value with unencodeable +-- | characters. +foreign import unsafeEncodeURI :: String -> String + +-- | URI component decoding. May throw a `URIError` if given a value with +-- | undecodeable escape sequences. +foreign import unsafeDecodeURIComponent :: String -> String + +-- | URI component encoding. May throw a `URIError` if given a value with +-- | unencodeable characters. +foreign import unsafeEncodeURIComponent :: String -> String \ No newline at end of file diff --git a/passing/test/Global.purs b/passing/test/Global.purs new file mode 100644 index 0000000..5019e55 --- /dev/null +++ b/passing/test/Global.purs @@ -0,0 +1,93 @@ +-- | This module defines types for some global Javascript functions +-- | and values. +module Global + ( nan + , isNaN + , infinity + , isFinite + , readInt + , readFloat + , toFixed + , toExponential + , toPrecision + , decodeURI + , encodeURI + , decodeURIComponent + , encodeURIComponent + ) where + +import Prelude +import Data.Function.Uncurried (Fn3, Fn4, runFn3, runFn4) +import Data.Maybe (Maybe(..)) + +-- | Not a number (NaN) +foreign import nan :: Number + +-- | Test whether a number is NaN +foreign import isNaN :: Number -> Boolean + +-- | Positive infinity +foreign import infinity :: Number + +-- | Test whether a number is finite +foreign import isFinite :: Number -> Boolean + +-- | Parse an integer from a `String` in the specified base +foreign import readInt :: Int -> String -> Number + +-- | Parse a floating point value from a `String` +foreign import readFloat :: String -> Number + +foreign import _toFixed :: forall a. Fn4 (String -> a) (String -> a) Int Number a + +foreign import _toExponential :: forall a. Fn4 (String -> a) (String -> a) Int Number a + +foreign import _toPrecision :: forall a. Fn4 (String -> a) (String -> a) Int Number a + +-- | Formats Number as a String with limited number of digits after the dot. +-- | May return `Nothing` when specified number of digits is less than 0 or +-- | greater than 20. See ECMA-262 for more information. +toFixed :: Int -> Number -> Maybe String +toFixed digits n = runFn4 _toFixed (const Nothing) Just digits n + +-- | Formats Number as String in exponential notation limiting number of digits +-- | after the decimal dot. May return `Nothing` when specified number of +-- | digits is less than 0 or greater than 20 depending on the implementation. +-- | See ECMA-262 for more information. +toExponential :: Int -> Number -> Maybe String +toExponential digits n = runFn4 _toExponential (const Nothing) Just digits n + +-- | Formats Number as String in fixed-point or exponential notation rounded +-- | to specified number of significant digits. May return `Nothing` when +-- | precision is less than 1 or greater than 21 depending on the +-- | implementation. See ECMA-262 for more information. +toPrecision :: Int -> Number -> Maybe String +toPrecision digits n = runFn4 _toPrecision (const Nothing) Just digits n + +foreign import _decodeURI :: forall a. Fn3 (String -> a) (String -> a) String a + +foreign import _encodeURI :: forall a. Fn3 (String -> a) (String -> a) String a + +foreign import _decodeURIComponent :: forall a. Fn3 (String -> a) (String -> a) String a + +foreign import _encodeURIComponent :: forall a. Fn3 (String -> a) (String -> a) String a + +-- | URI decoding. Returns `Nothing` when given a value with undecodeable +-- | escape sequences. +decodeURI :: String -> Maybe String +decodeURI s = runFn3 _decodeURI (const Nothing) Just s + +-- | URI encoding. Returns `Nothing` when given a value with unencodeable +-- | characters. +encodeURI :: String -> Maybe String +encodeURI s = runFn3 _encodeURI (const Nothing) Just s + +-- | URI component decoding. Returns `Nothing` when given a value with +-- | undecodeable escape sequences. +decodeURIComponent :: String -> Maybe String +decodeURIComponent s = runFn3 _decodeURIComponent (const Nothing) Just s + +-- | URI component encoding. Returns `Nothing` when given a value with +-- | unencodeable characters. +encodeURIComponent :: String -> Maybe String +encodeURIComponent s = runFn3 _encodeURIComponent (const Nothing) Just s \ No newline at end of file diff --git a/passing/test/Record.purs b/passing/test/Record.purs index 71f0c44..cb08f37 100644 --- a/passing/test/Record.purs +++ b/passing/test/Record.purs @@ -2,11 +2,11 @@ module Test.Record (testRecord) where import Prelude import Effect (Effect) -import Record (delete, equal, get, insert, merge, modify, rename, set) -import Record.Builder as Builder +import Data.Record (delete, equal, get, insert, merge, modify, rename, set) +import Data.Record.Builder as Builder import Control.Monad.ST (run) as ST -import Record.ST (poke, thaw, freeze, modify) as ST -import Record.Unsafe (unsafeHas) +import Data.Record.ST (poke, thaw, freeze, modify) as ST +import Data.Record.Unsafe (unsafeHas) import Data.Symbol (SProxy(..)) import Test.Assert (assert') diff --git a/src/Language/PureScript/CodeGen/Py.hs b/src/Language/PureScript/CodeGen/Py.hs index 21c5bbe..68a8b31 100644 --- a/src/Language/PureScript/CodeGen/Py.hs +++ b/src/Language/PureScript/CodeGen/Py.hs @@ -54,7 +54,7 @@ import Language.PureScript.Names hiding (runModuleName) import Language.PureScript.Options import Language.PureScript.PSString (PSString, mkString, decodeStringWithReplacement) import Language.PureScript.Traversals (sndM) -import qualified Language.PureScript.Constants as C +import qualified Language.PureScript.Constants.Prim as C import Language.PureScript.CodeGen.Py.Common (unmangle) import Language.PureScript.CodeGen.Py.Naming (identToPy) @@ -75,7 +75,7 @@ moduleToJS => Module Ann -> Text -> m (Bool, AST) -moduleToJS (Module _ coms mn _ imps exps foreigns decls) package = +moduleToJS (Module _ coms mn _ imps exps reExps foreigns decls) package = rethrow (addHint (ErrorInModule mn)) $ do let usedNames = concatMap getNames decls let mnLookup = renameImports usedNames imps @@ -84,6 +84,7 @@ moduleToJS (Module _ coms mn _ imps exps foreigns decls) package = optimized <- traverse (traverse optimize) jsDecls let mnReverseLookup = M.fromList $ map (\(origName, (_, safeName)) -> (moduleNameToJs safeName, origName)) $ M.toList mnLookup let usedModuleNames = foldMap (foldMap (findModules mnReverseLookup)) optimized + `S.union` M.keysSet reExps jsImports <- traverse (importToJs mnLookup) . filter (`S.member` usedModuleNames) . (\\ (mn : C.primModules)) $ ordNub $ map snd imps @@ -100,11 +101,12 @@ moduleToJS (Module _ coms mn _ imps exps foreigns decls) package = let hasForeign = not $ null foreigns let foreign' = [foreignImport | hasForeign] let moduleBody = header : foreign' ++ jsImports ++ concat optimized - let foreignExps = exps `intersect` foreigns let standardExps = exps \\ foreignExps + let reExps' = M.toList (M.withoutKeys reExps (S.fromList C.primModules)) let exps' = AST.ObjectLiteral Nothing $ map (mkString . runIdent &&& AST.Var Nothing . identToPy) standardExps - ++ map (mkString . runIdent &&& foreignIdent) foreignExps + ++ map (mkString . runIdent &&& foreignIdent) foreignExps + ++ concatMap (reExportPairs mnLookup) reExps' let exportObj = [AST.Assignment Nothing (AST.Var Nothing $ unmangle "exports") exps'] return (hasForeign, AST.Block Nothing $ moduleBody ++ exportObj) @@ -127,6 +129,21 @@ moduleToJS (Module _ coms mn _ imps exps foreigns decls) package = getNames (NonRec _ ident _) = [ident] getNames (Rec vals) = map (snd . fst) vals + -- | Generate code in the JavaScript IR for re-exported declarations, prepending + -- the module name from whence it was imported. + reExportPairs :: M.Map ModuleName (Ann, ModuleName) -> (ModuleName, [Ident]) -> [(PSString, AST)] + reExportPairs mnLookup (mn', idents) = + let toExportedMember :: Ident -> AST + toExportedMember = + maybe + (AST.Var Nothing . identToJs) + (flip accessor . AST.Var Nothing . moduleNameToJs . snd) + (M.lookup mn' mnLookup) + in + map + (mkString . runIdent &&& toExportedMember) + idents + -- | Creates alternative names for each module to ensure they don't collide -- with declaration names. renameImports :: [Ident] -> [(Ann, ModuleName)] -> M.Map ModuleName (Ann, ModuleName) @@ -134,7 +151,7 @@ moduleToJS (Module _ coms mn _ imps exps foreigns decls) package = where go :: M.Map ModuleName (Ann, ModuleName) -> [Ident] -> [(Ann, ModuleName)] -> M.Map ModuleName (Ann, ModuleName) go acc used ((ann, mn') : mns') = - let mni = Ident $ runModuleName mn' + let mni = Ident $ moduleNameToJs mn' in if mn' /= mn && mni `elem` used then let newName = freshModuleName 1 mn' used in go (M.insert mn' (ann, newName) acc) (Ident (runModuleName newName) : used) mns' @@ -189,6 +206,7 @@ moduleToJS (Module _ coms mn _ imps exps foreigns decls) package = -- Generate code in the simplified JavaScript intermediate representation for a declaration -- bindToJs :: Bind Ann -> m [AST] + bindToJs (NonRec (_, _, _, Just IsTypeClassConstructor) _ _) = pure [] bindToJs (NonRec ann ident val) = return <$> nonRecToJS ann ident val bindToJs (Rec vals) = forM vals (uncurry . uncurry $ nonRecToJS) @@ -255,16 +273,6 @@ moduleToJS (Module _ coms mn _ imps exps foreigns decls) package = obj <- valueToJs o sts <- mapM (sndM valueToJs) ps extendObj obj sts - valueToJs' e@(Abs (_, _, _, Just IsTypeClassConstructor) _ _) = - let args = unAbs e - in return $ AST.Function Nothing Nothing (map identToPy args ++ [thisName]) (AST.Block Nothing $ map assign args ++ [this]) - where - unAbs :: Expr Ann -> [Ident] - unAbs (Abs _ arg val) = arg : unAbs val - unAbs _ = [] - assign :: Ident -> AST - assign name = AST.Assignment Nothing (indexerString (mkString $ runIdent name) this) - (var name) valueToJs' (Abs _ arg val) = do ret <- valueToJs val let jsArg = case arg of @@ -278,8 +286,6 @@ moduleToJS (Module _ coms mn _ imps exps foreigns decls) package = Var (_, _, _, Just IsNewtype) _ -> return (head args') Var (_, _, _, Just (IsConstructor _ fields)) name | length args == length fields -> return $ AST.Unary Nothing AST.New $ AST.App Nothing (qualifiedToJS id name) args' - Var (_, _, _, Just IsTypeClassConstructor) name -> - return $ AST.Unary Nothing AST.New $ AST.App Nothing (qualifiedToJS id name) args' _ -> flip (foldl (\fn a -> AST.App Nothing fn [a])) args' <$> valueToJs f where unApp :: Expr Ann -> [Expr Ann] -> (Expr Ann, [Expr Ann]) diff --git a/src/Language/PureScript/CodeGen/Py/Common.hs b/src/Language/PureScript/CodeGen/Py/Common.hs index a68df12..d4c5110 100644 --- a/src/Language/PureScript/CodeGen/Py/Common.hs +++ b/src/Language/PureScript/CodeGen/Py/Common.hs @@ -6,9 +6,10 @@ module Language.PureScript.CodeGen.Py.Common where import Data.String import Data.Text (Text) import qualified Data.Text as T -import qualified Data.Char as C -import Text.Printf (printf) +-- import qualified Data.Char as C +-- import Text.Printf (printf) +unmanglePrefix :: Text unmanglePrefix = "special@" -- | unmangle or specialize names @@ -16,6 +17,8 @@ unmanglePrefix = "special@" -- - __all__ -- - import -- - this + +unmangle :: Text -> Text unmangle = T.append unmanglePrefix data SourceLoc @@ -51,4 +54,6 @@ unbox = \case Mangled n -> Just $ "ps_" ++ T.unpack n _ -> Nothing + +pattern Unbox :: String -> BoxedName pattern Unbox a <- (unbox -> Just a) diff --git a/stack.yaml b/stack.yaml index eec0437..be26e11 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,17 +1,28 @@ -resolver: lts-13.26 -pvp-bounds: upper +resolver: lts-17.6 +pvp-bounds: both packages: - '.' +ghc-options: + # Build with advanced optimizations enabled by default + "$locals": -O2 -Werror extra-deps: -- purescript-0.13.8@sha256:c2855514c6f7da4b5f5e3b1020597111d2982b69f460d1c33b7e9f6c9ea8159c,57030 -- happy-1.19.9 - language-javascript-0.7.0.0 -- network-3.0.1.1 -- these-1.0.1 -- semialign-1 +- purescript-0.14.5@sha256:511f50e7f267b65e1f656cdff9f9665073496efdf4375a3a86aa68496dae7281,18623 +- purescript-cst-0.4.0.0@sha256:bfe7be3962e83b645a4a8cd1805f31de17db3d3456962e1a2d17016fe5d7f96d,3861 + +nix: + enable: false + packages: + - zlib + # Test dependencies + - nodejs + - nodePackages.npm + - nodePackages.bower flags: aeson-pretty: lib-only: true these: assoc: false - quickcheck: false + haskeline: + # Avoids a libtinfo dynamic library dependency + terminfo: false