├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── set-issue-expectations.yml │ └── set-pull-expectations.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── builder └── src │ ├── BackgroundWriter.hs │ ├── Build.hs │ ├── Deps │ ├── Bump.hs │ ├── Diff.hs │ ├── Registry.hs │ ├── Solver.hs │ └── Website.hs │ ├── Elm │ ├── Details.hs │ └── Outline.hs │ ├── File.hs │ ├── Generate.hs │ ├── Http.hs │ ├── Reporting.hs │ ├── Reporting │ ├── Exit.hs │ ├── Exit │ │ └── Help.hs │ └── Task.hs │ └── Stuff.hs ├── cabal.config ├── compiler └── src │ ├── AST │ ├── Canonical.hs │ ├── Optimized.hs │ ├── Source.hs │ └── Utils │ │ ├── Binop.hs │ │ ├── Shader.hs │ │ └── Type.hs │ ├── Canonicalize │ ├── Effects.hs │ ├── Environment.hs │ ├── Environment │ │ ├── Dups.hs │ │ ├── Foreign.hs │ │ └── Local.hs │ ├── Expression.hs │ ├── Module.hs │ ├── Pattern.hs │ └── Type.hs │ ├── Compile.hs │ ├── Data │ ├── Bag.hs │ ├── Index.hs │ ├── Map │ │ └── Utils.hs │ ├── Name.hs │ ├── NonEmptyList.hs │ ├── OneOrMore.hs │ └── Utf8.hs │ ├── Elm │ ├── Compiler │ │ ├── Imports.hs │ │ ├── Type.hs │ │ └── Type │ │ │ └── Extract.hs │ ├── Constraint.hs │ ├── Docs.hs │ ├── Float.hs │ ├── Interface.hs │ ├── Kernel.hs │ ├── Licenses.hs │ ├── Magnitude.hs │ ├── ModuleName.hs │ ├── Package.hs │ ├── String.hs │ └── Version.hs │ ├── Generate │ ├── Html.hs │ ├── JavaScript.hs │ ├── JavaScript │ │ ├── Builder.hs │ │ ├── Expression.hs │ │ ├── Functions.hs │ │ └── Name.hs │ └── Mode.hs │ ├── Json │ ├── Decode.hs │ ├── Encode.hs │ └── String.hs │ ├── Nitpick │ ├── Debug.hs │ └── PatternMatches.hs │ ├── Optimize │ ├── Case.hs │ ├── DecisionTree.hs │ ├── Expression.hs │ ├── Module.hs │ ├── Names.hs │ └── Port.hs │ ├── Parse │ ├── Declaration.hs │ ├── Expression.hs │ ├── Keyword.hs │ ├── Module.hs │ ├── Number.hs │ ├── Pattern.hs │ ├── Primitives.hs │ ├── Shader.hs │ ├── Space.hs │ ├── String.hs │ ├── Symbol.hs │ ├── Type.hs │ └── Variable.hs │ ├── Reporting │ ├── Annotation.hs │ ├── Doc.hs │ ├── Error.hs │ ├── Error │ │ ├── Canonicalize.hs │ │ ├── Docs.hs │ │ ├── Import.hs │ │ ├── Json.hs │ │ ├── Main.hs │ │ ├── Pattern.hs │ │ ├── Syntax.hs │ │ └── Type.hs │ ├── Render │ │ ├── Code.hs │ │ ├── Type.hs │ │ └── Type │ │ │ └── Localizer.hs │ ├── Report.hs │ ├── Result.hs │ ├── Suggest.hs │ └── Warning.hs │ └── Type │ ├── Constrain │ ├── Expression.hs │ ├── Module.hs │ └── Pattern.hs │ ├── Error.hs │ ├── Instantiate.hs │ ├── Occurs.hs │ ├── Solve.hs │ ├── Type.hs │ ├── Unify.hs │ └── UnionFind.hs ├── docs ├── elm.json │ ├── application.md │ └── package.md └── upgrade-instructions │ ├── 0.16.md │ ├── 0.17.md │ ├── 0.18.md │ ├── 0.19.0.md │ ├── 0.19.1.md │ └── earlier.md ├── elm.cabal ├── hints ├── bad-recursion.md ├── comparing-custom-types.md ├── comparing-records.md ├── implicit-casts.md ├── import-cycles.md ├── imports.md ├── infinite-type.md ├── init.md ├── missing-patterns.md ├── optimize.md ├── port-modules.md ├── recursive-alias.md ├── repl.md ├── shadowing.md ├── tuples.md └── type-annotations.md ├── installers ├── README.md ├── linux │ ├── Dockerfile │ └── README.md ├── mac │ ├── Distribution.xml │ ├── README.md │ ├── Resources │ │ └── en.lproj │ │ │ ├── conclusion.rtf │ │ │ └── welcome.rtf │ ├── helper-scripts │ │ ├── elm-startup.sh │ │ └── uninstall.sh │ ├── make-installer.sh │ ├── postinstall │ └── preinstall ├── npm │ ├── .gitignore │ ├── .npmignore │ ├── PUBLISHING.md │ ├── README.md │ ├── bin │ │ └── elm │ ├── binary.js │ ├── install.js │ ├── package.json │ ├── packages │ │ ├── darwin_arm64 │ │ │ ├── README.md │ │ │ └── package.json │ │ ├── darwin_x64 │ │ │ ├── README.md │ │ │ └── package.json │ │ ├── linux_arm64 │ │ │ ├── README.md │ │ │ └── package.json │ │ ├── linux_x64 │ │ │ ├── README.md │ │ │ └── package.json │ │ └── win32_x64 │ │ │ ├── README.md │ │ │ └── package.json │ └── troubleshooting.md └── win │ ├── CreateInternetShortcut.nsh │ ├── Nsisfile.nsi │ ├── README.md │ ├── inst.dat │ ├── logo.ico │ ├── make_installer.cmd │ ├── removefrompath.vbs │ ├── uninst.dat │ ├── updatepath.vbs │ └── welcome.bmp ├── reactor ├── assets │ ├── favicon.ico │ ├── source-code-pro.ttf │ ├── source-sans-pro.ttf │ └── styles.css ├── check.py ├── elm.json └── src │ ├── Deps.elm │ ├── Errors.elm │ ├── Index.elm │ ├── Index │ ├── Icon.elm │ ├── Navigator.elm │ └── Skeleton.elm │ ├── NotFound.elm │ └── mock.txt ├── roadmap.md ├── terminal ├── impl │ ├── Terminal.hs │ └── Terminal │ │ ├── Chomp.hs │ │ ├── Error.hs │ │ ├── Helpers.hs │ │ └── Internal.hs └── src │ ├── Bump.hs │ ├── Develop.hs │ ├── Develop │ ├── Generate │ │ ├── Help.hs │ │ └── Index.hs │ ├── Socket.hs │ ├── StaticFiles.hs │ └── StaticFiles │ │ └── Build.hs │ ├── Diff.hs │ ├── Init.hs │ ├── Install.hs │ ├── Main.hs │ ├── Make.hs │ ├── Publish.hs │ └── Repl.hs └── worker ├── elm.cabal ├── elm.json ├── nginx.conf ├── outlines ├── compile │ └── elm.json └── repl │ └── elm.json └── src ├── Artifacts.hs ├── Cors.hs ├── Endpoint ├── Compile.hs ├── Quotes.hs ├── Repl.hs └── Slack.hs ├── Errors.elm └── Main.hs /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Elm 2 | 3 | Thanks helping with the development of Elm! This document describes the basic 4 | standards for opening pull requests and making the review process as smooth as 5 | possible. 6 | 7 | 8 | ## Expectations 9 | 10 | - Pull requests are reviewed in [batches](https://github.com/elm/expectations/blob/master/batching.md), so it can take some time to get a response. 11 | - Smaller pull requests are easier to review. To fix nine typos, nine specific issues will always go faster than one big one. Learn why [here](https://github.com/elm/expectations/blob/master/small-pull-requests.md). 12 | - Reviewers may not know as much as you about certain situations, so add links to supporting evidence for important claims, especially regarding standards for CSS, HTTP, URI, etc. 13 | 14 | Finally, please be patient with the core team. They are trying their best with limited resources! 15 | 16 | 17 | ## Style Guide 18 | 19 | * Haskell — conform to [these guidelines][haskell] 20 | * JavaScript — use [Google's JS style guide][js] 21 | 22 | [haskell]: https://gist.github.com/evancz/0a1f3717c92fe71702be 23 | [js]: https://google.github.io/styleguide/javascriptguide.xml 24 | 25 | 26 | ## Branches 27 | 28 | [The master branch][master] is the home of the next release of the compiler 29 | so new features and improvements get merged there. Most pull requests 30 | should target this branch! 31 | 32 | [master]: http://github.com/elm-lang/elm/tree/master 33 | 34 | 35 | ## Licensing 36 | 37 | Nothing extra to do for this. The default on GitHub described [here](https://docs.github.com/en/github/site-policy/github-terms-of-service#6-contributions-under-repository-license) is that: 38 | 39 | > Whenever you add Content to a repository containing notice of a license, you license that Content under the same terms, and you agree that you have the right to license that Content under those terms. If you have a separate agreement to license that Content under different terms, such as a contributor license agreement, that agreement will supersede. 40 | 41 | 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | **Quick Summary:** ??? 3 | 4 | 5 | ## SSCCE 6 | 7 | ```elm 8 | 9 | ``` 10 | 11 | - **Elm:** ??? 12 | - **Browser:** ??? 13 | - **Operating System:** ??? 14 | 15 | 16 | ## Additional Details 17 | 18 | ??? -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | **Quick Summary:** ??? 3 | 4 | 5 | ## SSCCE 6 | 7 | ```elm 8 | 9 | ``` 10 | 11 | - **Elm:** ??? 12 | - **Browser:** ??? 13 | - **Operating System:** ??? 14 | 15 | 16 | ## Additional Details 17 | 18 | ??? 19 | -------------------------------------------------------------------------------- /.github/workflows/set-issue-expectations.yml: -------------------------------------------------------------------------------- 1 | name: Set Issue Expectations 2 | on: 3 | issues: 4 | types: [opened] 5 | jobs: 6 | comment-on-issue: 7 | name: Comment On Issue 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/github@v1.0.0 11 | env: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | with: 14 | args: | 15 | comment "Thanks for reporting this! To set expectations: 16 | 17 | - Issues are reviewed in [batches](https://github.com/elm/expectations/blob/master/batching.md), so it can take some time to get a response. 18 | - Ask questions in a [community forum](https://elm-lang.org/community). You will get an answer quicker that way! 19 | - If you experience something similar, open a new issue. [We like duplicates](https://github.com/elm/expectations/blob/master/duplicates.md). 20 | 21 | Finally, please be patient with the core team. They are trying their best with limited resources." 22 | -------------------------------------------------------------------------------- /.github/workflows/set-pull-expectations.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request_target: 3 | types: [opened] 4 | 5 | jobs: 6 | comment-on-pull: 7 | name: Comment On Pull 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/github-script@v3 11 | with: 12 | github-token: ${{secrets.GITHUB_TOKEN}} 13 | script: | 14 | github.issues.createComment({ 15 | issue_number: context.issue.number, 16 | owner: context.repo.owner, 17 | repo: context.repo.repo, 18 | body: "Thanks for suggesting these code changes. To set expectations:\n\n- Pull requests are reviewed in [batches](https://github.com/elm/expectations/blob/master/batching.md), so it can take some time to get a response.\n- Smaller pull requests are easier to review. To fix nine typos, nine specific issues will always go faster than one big one. Learn why [here](https://github.com/elm/expectations/blob/master/small-pull-requests.md).\n- Reviewers may not know as much as you about certain situations, so add links to supporting evidence for important claims, especially regarding standards for CSS, HTTP, URI, etc.\n\nFinally, please be patient with the core team. They are trying their best with limited resources." 19 | }) 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | dist 3 | cabal-dev 4 | .cabal-sandbox/ 5 | cabal.sandbox.config 6 | .DS_Store 7 | *~ 8 | travis.log 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: minimal 2 | services: docker 3 | 4 | env: 5 | global: 6 | - LINUX_ARCHIVE=binary-for-linux-64-bit.gz 7 | 8 | before_install: 9 | - docker build -t elm -f installers/linux/Dockerfile . 10 | - docker cp $(docker create elm):/usr/local/bin/elm . 11 | - gzip -9 -c elm > $LINUX_ARCHIVE 12 | 13 | deploy: 14 | provider: releases 15 | api_key: 16 | secure: Yz2Lo4u9rZQ7Ee7ohAsrZpkqsYDUerCSMdSQIH8ryrf7phHhiloPEkTKsM+NupHqU/LEAVsunxbau4QrCEjA2vPavAPVk8cKomRUWK/YjbXHKa24hPkal2c+A2bnMQ6w3qYk/PjL9rW+Goq++/SNLcYZwHBV0Chl2blivMwWCSA= 17 | file: $LINUX_ARCHIVE 18 | skip_cleanup: true 19 | on: 20 | branch: master 21 | tags: true 22 | 23 | notifications: 24 | email: 25 | recipients: 26 | - rlefevre@dmy.fr 27 | on_success: change 28 | on_failure: change 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012-present Evan Czaplicki 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elm 2 | 3 | A delightful language for reliable webapps. 4 | 5 | Check out the [Home Page](http://elm-lang.org/), [Try Online](http://elm-lang.org/try), or [The Official Guide](http://guide.elm-lang.org/) 6 | 7 | 8 |
9 | 10 | ## Install 11 | 12 | ✨ [Install](https://guide.elm-lang.org/install/elm.html) ✨ 13 | 14 | For multiple versions, previous versions, and uninstallation, see the instructions [here](https://github.com/elm/compiler/blob/master/installers/README.md). 15 | 16 |
17 | 18 | ## Help 19 | 20 | If you are stuck, ask around on [the Elm slack channel][slack]. Folks are friendly and happy to help with questions! 21 | 22 | [slack]: http://elmlang.herokuapp.com/ 23 | -------------------------------------------------------------------------------- /builder/src/BackgroundWriter.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE BangPatterns #-} 2 | module BackgroundWriter 3 | ( Scope 4 | , withScope 5 | , writeBinary 6 | ) 7 | where 8 | 9 | 10 | import Control.Concurrent (forkIO) 11 | import Control.Concurrent.MVar (MVar, newEmptyMVar, newMVar, putMVar, takeMVar) 12 | import qualified Data.Binary as Binary 13 | import Data.Foldable (traverse_) 14 | 15 | import qualified File 16 | 17 | 18 | 19 | -- BACKGROUND WRITER 20 | 21 | 22 | newtype Scope = 23 | Scope (MVar [MVar ()]) 24 | 25 | 26 | withScope :: (Scope -> IO a) -> IO a 27 | withScope callback = 28 | do workList <- newMVar [] 29 | result <- callback (Scope workList) 30 | mvars <- takeMVar workList 31 | traverse_ takeMVar mvars 32 | return result 33 | 34 | 35 | writeBinary :: (Binary.Binary a) => Scope -> FilePath -> a -> IO () 36 | writeBinary (Scope workList) path value = 37 | do mvar <- newEmptyMVar 38 | _ <- forkIO (File.writeBinary path value >> putMVar mvar ()) 39 | oldWork <- takeMVar workList 40 | let !newWork = mvar:oldWork 41 | putMVar workList newWork 42 | 43 | -------------------------------------------------------------------------------- /builder/src/Deps/Bump.hs: -------------------------------------------------------------------------------- 1 | module Deps.Bump 2 | ( getPossibilities 3 | ) 4 | where 5 | 6 | 7 | import qualified Data.List as List 8 | 9 | import qualified Deps.Registry as Registry 10 | import qualified Elm.Magnitude as M 11 | import qualified Elm.Version as V 12 | 13 | 14 | 15 | -- GET POSSIBILITIES 16 | 17 | 18 | getPossibilities :: Registry.KnownVersions -> [(V.Version, V.Version, M.Magnitude)] 19 | getPossibilities (Registry.KnownVersions latest previous) = 20 | let 21 | allVersions = reverse (latest:previous) 22 | minorPoints = map last (List.groupBy sameMajor allVersions) 23 | patchPoints = map last (List.groupBy sameMinor allVersions) 24 | in 25 | (latest, V.bumpMajor latest, M.MAJOR) 26 | : map (\v -> (v, V.bumpMinor v, M.MINOR)) minorPoints 27 | ++ map (\v -> (v, V.bumpPatch v, M.PATCH)) patchPoints 28 | 29 | 30 | sameMajor :: V.Version -> V.Version -> Bool 31 | sameMajor (V.Version major1 _ _) (V.Version major2 _ _) = 32 | major1 == major2 33 | 34 | 35 | sameMinor :: V.Version -> V.Version -> Bool 36 | sameMinor (V.Version major1 minor1 _) (V.Version major2 minor2 _) = 37 | major1 == major2 && minor1 == minor2 38 | -------------------------------------------------------------------------------- /builder/src/Deps/Website.hs: -------------------------------------------------------------------------------- 1 | module Deps.Website 2 | ( domain 3 | , route 4 | , metadata 5 | ) 6 | where 7 | 8 | 9 | import qualified Elm.Package as Pkg 10 | import qualified Elm.Version as V 11 | import qualified Http 12 | 13 | 14 | domain :: String 15 | domain = 16 | "https://package.elm-lang.org" 17 | 18 | 19 | route :: String -> [(String,String)] -> String 20 | route path params = 21 | Http.toUrl (domain ++ path) params 22 | 23 | 24 | metadata :: Pkg.Name -> V.Version -> String -> String 25 | metadata name version file = 26 | domain ++ "/packages/" ++ Pkg.toUrl name ++ "/" ++ V.toChars version ++ "/" ++ file 27 | -------------------------------------------------------------------------------- /builder/src/Reporting/Exit/Help.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Reporting.Exit.Help 3 | ( Report 4 | , report 5 | , docReport 6 | , jsonReport 7 | , compilerReport 8 | , reportToDoc 9 | , reportToJson 10 | , toString 11 | , toStdout 12 | , toStderr 13 | ) 14 | where 15 | 16 | 17 | import GHC.IO.Handle (hIsTerminalDevice) 18 | import System.IO (Handle, hPutStr, stderr, stdout) 19 | 20 | import qualified Json.Encode as E 21 | import Json.Encode ((==>)) 22 | import Reporting.Doc ((<+>)) 23 | import qualified Reporting.Doc as D 24 | import qualified Reporting.Error as Error 25 | 26 | 27 | 28 | -- REPORT 29 | 30 | 31 | data Report 32 | = CompilerReport FilePath Error.Module [Error.Module] 33 | | Report 34 | { _title :: String 35 | , _path :: Maybe FilePath 36 | , _message :: D.Doc 37 | } 38 | 39 | 40 | report :: String -> Maybe FilePath -> String -> [D.Doc] -> Report 41 | report title path startString others = 42 | Report title path $ D.stack (D.reflow startString:others) 43 | 44 | 45 | docReport :: String -> Maybe FilePath -> D.Doc -> [D.Doc] -> Report 46 | docReport title path startDoc others = 47 | Report title path $ D.stack (startDoc:others) 48 | 49 | 50 | jsonReport :: String -> Maybe FilePath -> D.Doc -> Report 51 | jsonReport = 52 | Report 53 | 54 | 55 | compilerReport :: FilePath -> Error.Module -> [Error.Module] -> Report 56 | compilerReport = 57 | CompilerReport 58 | 59 | 60 | 61 | -- TO DOC 62 | 63 | 64 | reportToDoc :: Report -> D.Doc 65 | reportToDoc report_ = 66 | case report_ of 67 | CompilerReport root e es -> 68 | Error.toDoc root e es 69 | 70 | Report title maybePath message -> 71 | let 72 | makeDashes n = 73 | replicate (max 1 (80 - n)) '-' 74 | 75 | errorBarEnd = 76 | case maybePath of 77 | Nothing -> 78 | makeDashes (4 + length title) 79 | 80 | Just path -> 81 | makeDashes (5 + length title + length path) ++ " " ++ path 82 | 83 | errorBar = 84 | D.dullcyan $ 85 | "--" <+> D.fromChars title <+> D.fromChars errorBarEnd 86 | in 87 | D.stack [errorBar, message, ""] 88 | 89 | 90 | 91 | -- TO JSON 92 | 93 | 94 | reportToJson :: Report -> E.Value 95 | reportToJson report_ = 96 | case report_ of 97 | CompilerReport _ e es -> 98 | E.object 99 | [ "type" ==> E.chars "compile-errors" 100 | , "errors" ==> E.list Error.toJson (e:es) 101 | ] 102 | 103 | Report title maybePath message -> 104 | E.object 105 | [ "type" ==> E.chars "error" 106 | , "path" ==> maybe E.null E.chars maybePath 107 | , "title" ==> E.chars title 108 | , "message" ==> D.encode message 109 | ] 110 | 111 | 112 | 113 | -- OUTPUT 114 | 115 | 116 | toString :: D.Doc -> String 117 | toString = 118 | D.toString 119 | 120 | 121 | toStdout :: D.Doc -> IO () 122 | toStdout doc = 123 | toHandle stdout doc 124 | 125 | 126 | toStderr :: D.Doc -> IO () 127 | toStderr doc = 128 | toHandle stderr doc 129 | 130 | 131 | toHandle :: Handle -> D.Doc -> IO () 132 | toHandle handle doc = 133 | do isTerminal <- hIsTerminalDevice handle 134 | if isTerminal 135 | then D.toAnsi handle doc 136 | else hPutStr handle (toString doc) 137 | -------------------------------------------------------------------------------- /builder/src/Reporting/Task.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE Rank2Types #-} 2 | module Reporting.Task 3 | ( Task 4 | , run 5 | , throw 6 | , mapError 7 | -- 8 | , io 9 | , mio 10 | , eio 11 | ) 12 | where 13 | 14 | 15 | 16 | -- TASKS 17 | 18 | 19 | newtype Task x a = 20 | Task 21 | ( 22 | forall result. (a -> IO result) -> (x -> IO result) -> IO result 23 | ) 24 | 25 | 26 | run :: Task x a -> IO (Either x a) 27 | run (Task task) = 28 | task (return . Right) (return . Left) 29 | 30 | 31 | throw :: x -> Task x a 32 | throw x = 33 | Task $ \_ err -> err x 34 | 35 | 36 | mapError :: (x -> y) -> Task x a -> Task y a 37 | mapError func (Task task) = 38 | Task $ \ok err -> 39 | task ok (err . func) 40 | 41 | 42 | 43 | -- IO 44 | 45 | 46 | {-# INLINE io #-} 47 | io :: IO a -> Task x a 48 | io work = 49 | Task $ \ok _ -> work >>= ok 50 | 51 | 52 | mio :: x -> IO (Maybe a) -> Task x a 53 | mio x work = 54 | Task $ \ok err -> 55 | do result <- work 56 | case result of 57 | Just a -> ok a 58 | Nothing -> err x 59 | 60 | 61 | eio :: (x -> y) -> IO (Either x a) -> Task y a 62 | eio func work = 63 | Task $ \ok err -> 64 | do result <- work 65 | case result of 66 | Right a -> ok a 67 | Left x -> err (func x) 68 | 69 | 70 | 71 | -- INSTANCES 72 | 73 | 74 | instance Functor (Task x) where 75 | {-# INLINE fmap #-} 76 | fmap func (Task taskA) = 77 | Task $ \ok err -> 78 | let 79 | okA arg = ok (func arg) 80 | in 81 | taskA okA err 82 | 83 | 84 | instance Applicative (Task x) where 85 | {-# INLINE pure #-} 86 | pure a = 87 | Task $ \ok _ -> ok a 88 | 89 | {-# INLINE (<*>) #-} 90 | (<*>) (Task taskFunc) (Task taskArg) = 91 | Task $ \ok err -> 92 | let 93 | okFunc func = 94 | let 95 | okArg arg = ok (func arg) 96 | in 97 | taskArg okArg err 98 | in 99 | taskFunc okFunc err 100 | 101 | 102 | instance Monad (Task x) where 103 | {-# INLINE return #-} 104 | return = pure 105 | 106 | {-# INLINE (>>=) #-} 107 | (>>=) (Task taskA) callback = 108 | Task $ \ok err -> 109 | let 110 | okA a = 111 | case callback a of 112 | Task taskB -> taskB ok err 113 | in 114 | taskA okA err 115 | -------------------------------------------------------------------------------- /builder/src/Stuff.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | module Stuff 3 | ( details 4 | , interfaces 5 | , objects 6 | , prepublishDir 7 | , elmi 8 | , elmo 9 | , temp 10 | , findRoot 11 | , withRootLock 12 | , withRegistryLock 13 | , PackageCache 14 | , getPackageCache 15 | , registry 16 | , package 17 | , getReplCache 18 | , getElmHome 19 | ) 20 | where 21 | 22 | 23 | import qualified System.Directory as Dir 24 | import qualified System.Environment as Env 25 | import qualified System.FileLock as Lock 26 | import qualified System.FilePath as FP 27 | import System.FilePath ((), (<.>)) 28 | 29 | import qualified Elm.ModuleName as ModuleName 30 | import qualified Elm.Package as Pkg 31 | import qualified Elm.Version as V 32 | 33 | 34 | 35 | -- PATHS 36 | 37 | 38 | stuff :: FilePath -> FilePath 39 | stuff root = 40 | root "elm-stuff" compilerVersion 41 | 42 | 43 | details :: FilePath -> FilePath 44 | details root = 45 | stuff root "d.dat" 46 | 47 | 48 | interfaces :: FilePath -> FilePath 49 | interfaces root = 50 | stuff root "i.dat" 51 | 52 | 53 | objects :: FilePath -> FilePath 54 | objects root = 55 | stuff root "o.dat" 56 | 57 | 58 | prepublishDir :: FilePath -> FilePath 59 | prepublishDir root = 60 | stuff root "prepublish" 61 | 62 | 63 | compilerVersion :: FilePath 64 | compilerVersion = 65 | V.toChars V.compiler 66 | 67 | 68 | 69 | -- ELMI and ELMO 70 | 71 | 72 | elmi :: FilePath -> ModuleName.Raw -> FilePath 73 | elmi root name = 74 | toArtifactPath root name "elmi" 75 | 76 | 77 | elmo :: FilePath -> ModuleName.Raw -> FilePath 78 | elmo root name = 79 | toArtifactPath root name "elmo" 80 | 81 | 82 | toArtifactPath :: FilePath -> ModuleName.Raw -> String -> FilePath 83 | toArtifactPath root name ext = 84 | stuff root ModuleName.toHyphenPath name <.> ext 85 | 86 | 87 | 88 | -- TEMP 89 | 90 | 91 | temp :: FilePath -> String -> FilePath 92 | temp root ext = 93 | stuff root "temp" <.> ext 94 | 95 | 96 | 97 | -- ROOT 98 | 99 | 100 | findRoot :: IO (Maybe FilePath) 101 | findRoot = 102 | do dir <- Dir.getCurrentDirectory 103 | findRootHelp (FP.splitDirectories dir) 104 | 105 | 106 | findRootHelp :: [String] -> IO (Maybe FilePath) 107 | findRootHelp dirs = 108 | case dirs of 109 | [] -> 110 | return Nothing 111 | 112 | _:_ -> 113 | do exists <- Dir.doesFileExist (FP.joinPath dirs "elm.json") 114 | if exists 115 | then return (Just (FP.joinPath dirs)) 116 | else findRootHelp (init dirs) 117 | 118 | 119 | 120 | -- LOCKS 121 | 122 | 123 | withRootLock :: FilePath -> IO a -> IO a 124 | withRootLock root work = 125 | do let dir = stuff root 126 | Dir.createDirectoryIfMissing True dir 127 | Lock.withFileLock (dir "lock") Lock.Exclusive (\_ -> work) 128 | 129 | 130 | withRegistryLock :: PackageCache -> IO a -> IO a 131 | withRegistryLock (PackageCache dir) work = 132 | Lock.withFileLock (dir "lock") Lock.Exclusive (\_ -> work) 133 | 134 | 135 | 136 | -- PACKAGE CACHES 137 | 138 | 139 | newtype PackageCache = PackageCache FilePath 140 | 141 | 142 | getPackageCache :: IO PackageCache 143 | getPackageCache = 144 | PackageCache <$> getCacheDir "packages" 145 | 146 | 147 | registry :: PackageCache -> FilePath 148 | registry (PackageCache dir) = 149 | dir "registry.dat" 150 | 151 | 152 | package :: PackageCache -> Pkg.Name -> V.Version -> FilePath 153 | package (PackageCache dir) name version = 154 | dir Pkg.toFilePath name V.toChars version 155 | 156 | 157 | 158 | -- CACHE 159 | 160 | 161 | getReplCache :: IO FilePath 162 | getReplCache = 163 | getCacheDir "repl" 164 | 165 | 166 | getCacheDir :: FilePath -> IO FilePath 167 | getCacheDir projectName = 168 | do home <- getElmHome 169 | let root = home compilerVersion projectName 170 | Dir.createDirectoryIfMissing True root 171 | return root 172 | 173 | 174 | getElmHome :: IO FilePath 175 | getElmHome = 176 | do maybeCustomHome <- Env.lookupEnv "ELM_HOME" 177 | case maybeCustomHome of 178 | Just customHome -> return customHome 179 | Nothing -> Dir.getAppUserDataDirectory "elm" 180 | -------------------------------------------------------------------------------- /cabal.config: -------------------------------------------------------------------------------- 1 | profiling: False 2 | library-profiling: True 3 | -------------------------------------------------------------------------------- /compiler/src/AST/Source.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | module AST.Source 3 | ( Expr, Expr_(..), VarType(..) 4 | , Def(..) 5 | , Pattern, Pattern_(..) 6 | , Type, Type_(..) 7 | , Module(..) 8 | , getName 9 | , getImportName 10 | , Import(..) 11 | , Value(..) 12 | , Union(..) 13 | , Alias(..) 14 | , Infix(..) 15 | , Port(..) 16 | , Effects(..) 17 | , Manager(..) 18 | , Docs(..) 19 | , Comment(..) 20 | , Exposing(..) 21 | , Exposed(..) 22 | , Privacy(..) 23 | ) 24 | where 25 | 26 | 27 | import Data.Name (Name) 28 | import qualified Data.Name as Name 29 | 30 | import qualified AST.Utils.Binop as Binop 31 | import qualified AST.Utils.Shader as Shader 32 | import qualified Elm.Float as EF 33 | import qualified Elm.String as ES 34 | import qualified Parse.Primitives as P 35 | import qualified Reporting.Annotation as A 36 | 37 | 38 | 39 | -- EXPRESSIONS 40 | 41 | 42 | type Expr = A.Located Expr_ 43 | 44 | 45 | data Expr_ 46 | = Chr ES.String 47 | | Str ES.String 48 | | Int Int 49 | | Float EF.Float 50 | | Var VarType Name 51 | | VarQual VarType Name Name 52 | | List [Expr] 53 | | Op Name 54 | | Negate Expr 55 | | Binops [(Expr, A.Located Name)] Expr 56 | | Lambda [Pattern] Expr 57 | | Call Expr [Expr] 58 | | If [(Expr, Expr)] Expr 59 | | Let [A.Located Def] Expr 60 | | Case Expr [(Pattern, Expr)] 61 | | Accessor Name 62 | | Access Expr (A.Located Name) 63 | | Update (A.Located Name) [(A.Located Name, Expr)] 64 | | Record [(A.Located Name, Expr)] 65 | | Unit 66 | | Tuple Expr Expr [Expr] 67 | | Shader Shader.Source Shader.Types 68 | 69 | 70 | data VarType = LowVar | CapVar 71 | 72 | 73 | 74 | -- DEFINITIONS 75 | 76 | 77 | data Def 78 | = Define (A.Located Name) [Pattern] Expr (Maybe Type) 79 | | Destruct Pattern Expr 80 | 81 | 82 | 83 | -- PATTERN 84 | 85 | 86 | type Pattern = A.Located Pattern_ 87 | 88 | 89 | data Pattern_ 90 | = PAnything 91 | | PVar Name 92 | | PRecord [A.Located Name] 93 | | PAlias Pattern (A.Located Name) 94 | | PUnit 95 | | PTuple Pattern Pattern [Pattern] 96 | | PCtor A.Region Name [Pattern] 97 | | PCtorQual A.Region Name Name [Pattern] 98 | | PList [Pattern] 99 | | PCons Pattern Pattern 100 | | PChr ES.String 101 | | PStr ES.String 102 | | PInt Int 103 | 104 | 105 | 106 | -- TYPE 107 | 108 | 109 | type Type = 110 | A.Located Type_ 111 | 112 | 113 | data Type_ 114 | = TLambda Type Type 115 | | TVar Name 116 | | TType A.Region Name [Type] 117 | | TTypeQual A.Region Name Name [Type] 118 | | TRecord [(A.Located Name, Type)] (Maybe (A.Located Name)) 119 | | TUnit 120 | | TTuple Type Type [Type] 121 | 122 | 123 | 124 | -- MODULE 125 | 126 | 127 | data Module = 128 | Module 129 | { _name :: Maybe (A.Located Name) 130 | , _exports :: A.Located Exposing 131 | , _docs :: Docs 132 | , _imports :: [Import] 133 | , _values :: [A.Located Value] 134 | , _unions :: [A.Located Union] 135 | , _aliases :: [A.Located Alias] 136 | , _binops :: [A.Located Infix] 137 | , _effects :: Effects 138 | } 139 | 140 | 141 | getName :: Module -> Name 142 | getName (Module maybeName _ _ _ _ _ _ _ _) = 143 | case maybeName of 144 | Just (A.At _ name) -> 145 | name 146 | 147 | Nothing -> 148 | Name._Main 149 | 150 | 151 | getImportName :: Import -> Name 152 | getImportName (Import (A.At _ name) _ _) = 153 | name 154 | 155 | 156 | data Import = 157 | Import 158 | { _import :: A.Located Name 159 | , _alias :: Maybe Name 160 | , _exposing :: Exposing 161 | } 162 | 163 | 164 | data Value = Value (A.Located Name) [Pattern] Expr (Maybe Type) 165 | data Union = Union (A.Located Name) [A.Located Name] [(A.Located Name, [Type])] 166 | data Alias = Alias (A.Located Name) [A.Located Name] Type 167 | data Infix = Infix Name Binop.Associativity Binop.Precedence Name 168 | data Port = Port (A.Located Name) Type 169 | 170 | 171 | data Effects 172 | = NoEffects 173 | | Ports [Port] 174 | | Manager A.Region Manager 175 | 176 | 177 | data Manager 178 | = Cmd (A.Located Name) 179 | | Sub (A.Located Name) 180 | | Fx (A.Located Name) (A.Located Name) 181 | 182 | 183 | data Docs 184 | = NoDocs A.Region 185 | | YesDocs Comment [(Name, Comment)] 186 | 187 | 188 | newtype Comment = 189 | Comment P.Snippet 190 | 191 | 192 | 193 | -- EXPOSING 194 | 195 | 196 | data Exposing 197 | = Open 198 | | Explicit [Exposed] 199 | 200 | 201 | data Exposed 202 | = Lower (A.Located Name) 203 | | Upper (A.Located Name) Privacy 204 | | Operator A.Region Name 205 | 206 | 207 | data Privacy 208 | = Public A.Region 209 | | Private 210 | -------------------------------------------------------------------------------- /compiler/src/AST/Utils/Binop.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | module AST.Utils.Binop 3 | ( Precedence(..) 4 | , Associativity(..) 5 | ) 6 | where 7 | 8 | 9 | import Prelude hiding (Either(..)) 10 | import Control.Monad (liftM) 11 | import Data.Binary 12 | 13 | 14 | 15 | -- BINOP STUFF 16 | 17 | 18 | newtype Precedence = Precedence Int 19 | deriving (Eq, Ord) 20 | 21 | 22 | data Associativity 23 | = Left 24 | | Non 25 | | Right 26 | deriving (Eq) 27 | 28 | 29 | 30 | -- BINARY 31 | 32 | 33 | instance Binary Precedence where 34 | get = 35 | liftM Precedence get 36 | 37 | put (Precedence n) = 38 | put n 39 | 40 | 41 | instance Binary Associativity where 42 | get = 43 | do n <- getWord8 44 | case n of 45 | 0 -> return Left 46 | 1 -> return Non 47 | 2 -> return Right 48 | _ -> fail "Error reading valid associativity from serialized string" 49 | 50 | put assoc = 51 | putWord8 $ 52 | case assoc of 53 | Left -> 0 54 | Non -> 1 55 | Right -> 2 56 | -------------------------------------------------------------------------------- /compiler/src/AST/Utils/Shader.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | {-# LANGUAGE EmptyDataDecls #-} 3 | module AST.Utils.Shader 4 | ( Source 5 | , Types(..) 6 | , Type(..) 7 | , fromChars 8 | , toJsStringBuilder 9 | ) 10 | where 11 | 12 | 13 | import Control.Monad (liftM) 14 | import Data.Binary (Binary, get, put) 15 | import qualified Data.ByteString as BS 16 | import qualified Data.ByteString.Builder as B 17 | import qualified Data.ByteString.UTF8 as BS_UTF8 18 | import qualified Data.Map as Map 19 | import qualified Data.Name as Name 20 | 21 | 22 | 23 | -- SOURCE 24 | 25 | 26 | newtype Source = 27 | Source BS.ByteString 28 | 29 | 30 | 31 | -- TYPES 32 | 33 | 34 | data Types = 35 | Types 36 | { _attribute :: Map.Map Name.Name Type 37 | , _uniform :: Map.Map Name.Name Type 38 | , _varying :: Map.Map Name.Name Type 39 | } 40 | 41 | 42 | data Type 43 | = Int 44 | | Float 45 | | V2 46 | | V3 47 | | V4 48 | | M4 49 | | Texture 50 | 51 | 52 | 53 | -- TO BUILDER 54 | 55 | 56 | toJsStringBuilder :: Source -> B.Builder 57 | toJsStringBuilder (Source src) = 58 | B.byteString src 59 | 60 | 61 | 62 | -- FROM CHARS 63 | 64 | 65 | fromChars :: [Char] -> Source 66 | fromChars chars = 67 | Source (BS_UTF8.fromString (escape chars)) 68 | 69 | 70 | escape :: [Char] -> [Char] 71 | escape chars = 72 | case chars of 73 | [] -> 74 | [] 75 | 76 | c:cs 77 | | c == '\r' -> escape cs 78 | | c == '\n' -> '\\' : 'n' : escape cs 79 | | c == '\"' -> '\\' : '"' : escape cs 80 | | c == '\'' -> '\\' : '\'' : escape cs 81 | | c == '\\' -> '\\' : '\\' : escape cs 82 | | otherwise -> c : escape cs 83 | 84 | 85 | 86 | -- BINARY 87 | 88 | 89 | instance Binary Source where 90 | get = liftM Source get 91 | put (Source a) = put a 92 | -------------------------------------------------------------------------------- /compiler/src/AST/Utils/Type.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | module AST.Utils.Type 3 | ( delambda 4 | , dealias 5 | , deepDealias 6 | , iteratedDealias 7 | ) 8 | where 9 | 10 | 11 | import qualified Data.Map as Map 12 | import qualified Data.Name as Name 13 | 14 | import AST.Canonical (Type(..), AliasType(..), FieldType(..)) 15 | 16 | 17 | 18 | -- DELAMBDA 19 | 20 | 21 | delambda :: Type -> [Type] 22 | delambda tipe = 23 | case tipe of 24 | TLambda arg result -> 25 | arg : delambda result 26 | 27 | _ -> 28 | [tipe] 29 | 30 | 31 | 32 | -- DEALIAS 33 | 34 | 35 | dealias :: [(Name.Name, Type)] -> AliasType -> Type 36 | dealias args aliasType = 37 | case aliasType of 38 | Holey tipe -> 39 | dealiasHelp (Map.fromList args) tipe 40 | 41 | Filled tipe -> 42 | tipe 43 | 44 | 45 | dealiasHelp :: Map.Map Name.Name Type -> Type -> Type 46 | dealiasHelp typeTable tipe = 47 | case tipe of 48 | TLambda a b -> 49 | TLambda 50 | (dealiasHelp typeTable a) 51 | (dealiasHelp typeTable b) 52 | 53 | TVar x -> 54 | Map.findWithDefault tipe x typeTable 55 | 56 | TRecord fields ext -> 57 | TRecord (Map.map (dealiasField typeTable) fields) ext 58 | 59 | TAlias home name args t' -> 60 | TAlias home name (map (fmap (dealiasHelp typeTable)) args) t' 61 | 62 | TType home name args -> 63 | TType home name (map (dealiasHelp typeTable) args) 64 | 65 | TUnit -> 66 | TUnit 67 | 68 | TTuple a b maybeC -> 69 | TTuple 70 | (dealiasHelp typeTable a) 71 | (dealiasHelp typeTable b) 72 | (fmap (dealiasHelp typeTable) maybeC) 73 | 74 | 75 | dealiasField :: Map.Map Name.Name Type -> FieldType -> FieldType 76 | dealiasField typeTable (FieldType index tipe) = 77 | FieldType index (dealiasHelp typeTable tipe) 78 | 79 | 80 | 81 | -- DEEP DEALIAS 82 | 83 | 84 | deepDealias :: Type -> Type 85 | deepDealias tipe = 86 | case tipe of 87 | TLambda a b -> 88 | TLambda (deepDealias a) (deepDealias b) 89 | 90 | TVar _ -> 91 | tipe 92 | 93 | TRecord fields ext -> 94 | TRecord (Map.map deepDealiasField fields) ext 95 | 96 | TAlias _ _ args tipe' -> 97 | deepDealias (dealias args tipe') 98 | 99 | TType home name args -> 100 | TType home name (map deepDealias args) 101 | 102 | TUnit -> 103 | TUnit 104 | 105 | TTuple a b c -> 106 | TTuple (deepDealias a) (deepDealias b) (fmap deepDealias c) 107 | 108 | 109 | deepDealiasField :: FieldType -> FieldType 110 | deepDealiasField (FieldType index tipe) = 111 | FieldType index (deepDealias tipe) 112 | 113 | 114 | 115 | -- ITERATED DEALIAS 116 | 117 | 118 | iteratedDealias :: Type -> Type 119 | iteratedDealias tipe = 120 | case tipe of 121 | TAlias _ _ args realType -> 122 | iteratedDealias (dealias args realType) 123 | 124 | _ -> 125 | tipe 126 | -------------------------------------------------------------------------------- /compiler/src/Canonicalize/Environment/Dups.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Canonicalize.Environment.Dups 4 | ( detect 5 | , checkFields 6 | , checkFields' 7 | , Dict 8 | , none 9 | , one 10 | , insert 11 | , union 12 | , unions 13 | ) 14 | where 15 | 16 | 17 | import qualified Data.Map as Map 18 | import qualified Data.Name as Name 19 | 20 | import qualified Data.OneOrMore as OneOrMore 21 | import qualified Reporting.Annotation as A 22 | import qualified Reporting.Error.Canonicalize as Error 23 | import qualified Reporting.Result as Result 24 | 25 | 26 | 27 | -- DUPLICATE TRACKER 28 | 29 | 30 | type Dict value = 31 | Map.Map Name.Name (OneOrMore.OneOrMore (Info value)) 32 | 33 | 34 | data Info value = 35 | Info 36 | { _region :: A.Region 37 | , _value :: value 38 | } 39 | 40 | 41 | 42 | -- DETECT 43 | 44 | 45 | type ToError = 46 | Name.Name -> A.Region -> A.Region -> Error.Error 47 | 48 | 49 | detect :: ToError -> Dict a -> Result.Result i w Error.Error (Map.Map Name.Name a) 50 | detect toError dict = 51 | Map.traverseWithKey (detectHelp toError) dict 52 | 53 | 54 | detectHelp :: ToError -> Name.Name -> OneOrMore.OneOrMore (Info a) -> Result.Result i w Error.Error a 55 | detectHelp toError name values = 56 | case values of 57 | OneOrMore.One (Info _ value) -> 58 | return value 59 | 60 | OneOrMore.More left right -> 61 | let 62 | (Info r1 _, Info r2 _) = 63 | OneOrMore.getFirstTwo left right 64 | in 65 | Result.throw (toError name r1 r2) 66 | 67 | 68 | 69 | -- CHECK FIELDS 70 | 71 | 72 | checkFields :: [(A.Located Name.Name, a)] -> Result.Result i w Error.Error (Map.Map Name.Name a) 73 | checkFields fields = 74 | detect Error.DuplicateField (foldr addField none fields) 75 | 76 | 77 | addField :: (A.Located Name.Name, a) -> Dict a -> Dict a 78 | addField (A.At region name, value) dups = 79 | Map.insertWith OneOrMore.more name (OneOrMore.one (Info region value)) dups 80 | 81 | 82 | checkFields' :: (A.Region -> a -> b) -> [(A.Located Name.Name, a)] -> Result.Result i w Error.Error (Map.Map Name.Name b) 83 | checkFields' toValue fields = 84 | detect Error.DuplicateField (foldr (addField' toValue) none fields) 85 | 86 | 87 | addField' :: (A.Region -> a -> b) -> (A.Located Name.Name, a) -> Dict b -> Dict b 88 | addField' toValue (A.At region name, value) dups = 89 | Map.insertWith OneOrMore.more name (OneOrMore.one (Info region (toValue region value))) dups 90 | 91 | 92 | 93 | -- BUILDING DICTIONARIES 94 | 95 | 96 | none :: Dict a 97 | none = 98 | Map.empty 99 | 100 | 101 | one :: Name.Name -> A.Region -> value -> Dict value 102 | one name region value = 103 | Map.singleton name (OneOrMore.one (Info region value)) 104 | 105 | 106 | insert :: Name.Name -> A.Region -> a -> Dict a -> Dict a 107 | insert name region value dict = 108 | Map.insertWith (\new old -> OneOrMore.more old new) name (OneOrMore.one (Info region value)) dict 109 | 110 | 111 | union :: Dict a -> Dict a -> Dict a 112 | union a b = 113 | Map.unionWith OneOrMore.more a b 114 | 115 | 116 | unions :: [Dict a] -> Dict a 117 | unions dicts = 118 | Map.unionsWith OneOrMore.more dicts 119 | -------------------------------------------------------------------------------- /compiler/src/Compile.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall -fno-warn-unused-do-bind #-} 2 | module Compile 3 | ( Artifacts(..) 4 | , compile 5 | ) 6 | where 7 | 8 | 9 | import qualified Data.Map as Map 10 | import qualified Data.Name as Name 11 | 12 | import qualified AST.Source as Src 13 | import qualified AST.Canonical as Can 14 | import qualified AST.Optimized as Opt 15 | import qualified Canonicalize.Module as Canonicalize 16 | import qualified Elm.Interface as I 17 | import qualified Elm.ModuleName as ModuleName 18 | import qualified Elm.Package as Pkg 19 | import qualified Nitpick.PatternMatches as PatternMatches 20 | import qualified Optimize.Module as Optimize 21 | import qualified Reporting.Error as E 22 | import qualified Reporting.Result as R 23 | import qualified Reporting.Render.Type.Localizer as Localizer 24 | import qualified Type.Constrain.Module as Type 25 | import qualified Type.Solve as Type 26 | 27 | import System.IO.Unsafe (unsafePerformIO) 28 | 29 | 30 | 31 | -- COMPILE 32 | 33 | 34 | data Artifacts = 35 | Artifacts 36 | { _modul :: Can.Module 37 | , _types :: Map.Map Name.Name Can.Annotation 38 | , _graph :: Opt.LocalGraph 39 | } 40 | 41 | 42 | compile :: Pkg.Name -> Map.Map ModuleName.Raw I.Interface -> Src.Module -> Either E.Error Artifacts 43 | compile pkg ifaces modul = 44 | do canonical <- canonicalize pkg ifaces modul 45 | annotations <- typeCheck modul canonical 46 | () <- nitpick canonical 47 | objects <- optimize modul annotations canonical 48 | return (Artifacts canonical annotations objects) 49 | 50 | 51 | 52 | -- PHASES 53 | 54 | 55 | canonicalize :: Pkg.Name -> Map.Map ModuleName.Raw I.Interface -> Src.Module -> Either E.Error Can.Module 56 | canonicalize pkg ifaces modul = 57 | case snd $ R.run $ Canonicalize.canonicalize pkg ifaces modul of 58 | Right canonical -> 59 | Right canonical 60 | 61 | Left errors -> 62 | Left $ E.BadNames errors 63 | 64 | 65 | typeCheck :: Src.Module -> Can.Module -> Either E.Error (Map.Map Name.Name Can.Annotation) 66 | typeCheck modul canonical = 67 | case unsafePerformIO (Type.run =<< Type.constrain canonical) of 68 | Right annotations -> 69 | Right annotations 70 | 71 | Left errors -> 72 | Left (E.BadTypes (Localizer.fromModule modul) errors) 73 | 74 | 75 | nitpick :: Can.Module -> Either E.Error () 76 | nitpick canonical = 77 | case PatternMatches.check canonical of 78 | Right () -> 79 | Right () 80 | 81 | Left errors -> 82 | Left (E.BadPatterns errors) 83 | 84 | 85 | optimize :: Src.Module -> Map.Map Name.Name Can.Annotation -> Can.Module -> Either E.Error Opt.LocalGraph 86 | optimize modul annotations canonical = 87 | case snd $ R.run $ Optimize.optimize annotations canonical of 88 | Right localGraph -> 89 | Right localGraph 90 | 91 | Left errors -> 92 | Left (E.BadMains (Localizer.fromModule modul) errors) 93 | -------------------------------------------------------------------------------- /compiler/src/Data/Bag.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | module Data.Bag 3 | ( Bag(..) 4 | , empty 5 | , one 6 | , append 7 | , map 8 | , toList 9 | , fromList 10 | ) 11 | where 12 | 13 | 14 | import Prelude hiding (map) 15 | import qualified Data.List as List 16 | 17 | 18 | 19 | -- BAGS 20 | 21 | 22 | data Bag a 23 | = Empty 24 | | One a 25 | | Two (Bag a) (Bag a) 26 | 27 | 28 | 29 | -- HELPERS 30 | 31 | 32 | empty :: Bag a 33 | empty = 34 | Empty 35 | 36 | 37 | one :: a -> Bag a 38 | one = 39 | One 40 | 41 | 42 | append :: Bag a -> Bag a -> Bag a 43 | append left right = 44 | case (left, right) of 45 | (other, Empty) -> 46 | other 47 | 48 | (Empty, other) -> 49 | other 50 | 51 | (_, _) -> 52 | Two left right 53 | 54 | 55 | 56 | -- MAP 57 | 58 | 59 | map :: (a -> b) -> Bag a -> Bag b 60 | map func bag = 61 | case bag of 62 | Empty -> 63 | Empty 64 | 65 | One a -> 66 | One (func a) 67 | 68 | Two left right -> 69 | Two (map func left) (map func right) 70 | 71 | 72 | 73 | -- TO LIST 74 | 75 | 76 | toList :: Bag a -> [a] 77 | toList bag = 78 | toListHelp bag [] 79 | 80 | 81 | toListHelp :: Bag a -> [a] -> [a] 82 | toListHelp bag list = 83 | case bag of 84 | Empty -> 85 | list 86 | 87 | One x -> 88 | x : list 89 | 90 | Two a b -> 91 | toListHelp a (toListHelp b list) 92 | 93 | 94 | 95 | -- FROM LIST 96 | 97 | 98 | fromList :: (a -> b) -> [a] -> Bag b 99 | fromList func list = 100 | case list of 101 | [] -> 102 | Empty 103 | 104 | first : rest -> 105 | List.foldl' (add func) (One (func first)) rest 106 | 107 | 108 | add :: (a -> b) -> Bag b -> a -> Bag b 109 | add func bag value = 110 | Two (One (func value)) bag 111 | -------------------------------------------------------------------------------- /compiler/src/Data/Index.hs: -------------------------------------------------------------------------------- 1 | module Data.Index 2 | ( ZeroBased 3 | , first 4 | , second 5 | , third 6 | , next 7 | , toMachine 8 | , toHuman 9 | , indexedMap 10 | , indexedTraverse 11 | , indexedForA 12 | , VerifiedList(..) 13 | , indexedZipWith 14 | , indexedZipWithA 15 | ) 16 | where 17 | 18 | 19 | import Control.Monad (liftM) 20 | import Data.Binary 21 | 22 | 23 | 24 | -- ZERO BASED 25 | 26 | 27 | newtype ZeroBased = ZeroBased Int 28 | deriving (Eq, Ord) 29 | 30 | 31 | first :: ZeroBased 32 | first = 33 | ZeroBased 0 34 | 35 | 36 | second :: ZeroBased 37 | second = 38 | ZeroBased 1 39 | 40 | 41 | third :: ZeroBased 42 | third = 43 | ZeroBased 2 44 | 45 | 46 | {-# INLINE next #-} 47 | next :: ZeroBased -> ZeroBased 48 | next (ZeroBased i) = 49 | ZeroBased (i + 1) 50 | 51 | 52 | 53 | -- DESTRUCT 54 | 55 | 56 | toMachine :: ZeroBased -> Int 57 | toMachine (ZeroBased index) = 58 | index 59 | 60 | 61 | toHuman :: ZeroBased -> Int 62 | toHuman (ZeroBased index) = 63 | index + 1 64 | 65 | 66 | 67 | -- INDEXED MAP 68 | 69 | 70 | {-# INLINE indexedMap #-} 71 | indexedMap :: (ZeroBased -> a -> b) -> [a] -> [b] 72 | indexedMap func xs = 73 | zipWith func (map ZeroBased [0 .. length xs]) xs 74 | 75 | 76 | {-# INLINE indexedTraverse #-} 77 | indexedTraverse :: (Applicative f) => (ZeroBased -> a -> f b) -> [a] -> f [b] 78 | indexedTraverse func xs = 79 | sequenceA (indexedMap func xs) 80 | 81 | 82 | {-# INLINE indexedForA #-} 83 | indexedForA :: (Applicative f) => [a] -> (ZeroBased -> a -> f b) -> f [b] 84 | indexedForA xs func = 85 | sequenceA (indexedMap func xs) 86 | 87 | 88 | 89 | -- VERIFIED/INDEXED ZIP 90 | 91 | 92 | data VerifiedList a 93 | = LengthMatch [a] 94 | | LengthMismatch Int Int 95 | 96 | 97 | indexedZipWith :: (ZeroBased -> a -> b -> c) -> [a] -> [b] -> VerifiedList c 98 | indexedZipWith func listX listY = 99 | indexedZipWithHelp func 0 listX listY [] 100 | 101 | 102 | indexedZipWithHelp :: (ZeroBased -> a -> b -> c) -> Int -> [a] -> [b] -> [c] -> VerifiedList c 103 | indexedZipWithHelp func index listX listY revListZ = 104 | case (listX, listY) of 105 | ([], []) -> 106 | LengthMatch (reverse revListZ) 107 | 108 | (x:xs, y:ys) -> 109 | indexedZipWithHelp func (index + 1) xs ys $ 110 | func (ZeroBased index) x y : revListZ 111 | 112 | (_, _) -> 113 | LengthMismatch (index + length listX) (index + length listY) 114 | 115 | 116 | indexedZipWithA :: (Applicative f) => (ZeroBased -> a -> b -> f c) -> [a] -> [b] -> f (VerifiedList c) 117 | indexedZipWithA func listX listY = 118 | case indexedZipWith func listX listY of 119 | LengthMatch xs -> 120 | LengthMatch <$> sequenceA xs 121 | 122 | LengthMismatch x y -> 123 | pure (LengthMismatch x y) 124 | 125 | 126 | 127 | -- BINARY 128 | 129 | 130 | instance Binary ZeroBased where 131 | get = liftM ZeroBased get 132 | put (ZeroBased n) = put n 133 | -------------------------------------------------------------------------------- /compiler/src/Data/Map/Utils.hs: -------------------------------------------------------------------------------- 1 | module Data.Map.Utils 2 | ( fromKeys 3 | , fromKeysA 4 | , fromValues 5 | , any 6 | ) 7 | where 8 | 9 | 10 | import Prelude hiding (any) 11 | import qualified Data.Map as Map 12 | import Data.Map.Internal (Map(..)) 13 | 14 | 15 | 16 | -- FROM KEYS 17 | 18 | 19 | fromKeys :: (Ord k) => (k -> v) -> [k] -> Map.Map k v 20 | fromKeys toValue keys = 21 | Map.fromList $ map (\k -> (k, toValue k)) keys 22 | 23 | 24 | fromKeysA :: (Applicative f, Ord k) => (k -> f v) -> [k] -> f (Map.Map k v) 25 | fromKeysA toValue keys = 26 | Map.fromList <$> traverse (\k -> (,) k <$> toValue k) keys 27 | 28 | 29 | fromValues :: (Ord k) => (v -> k) -> [v] -> Map.Map k v 30 | fromValues toKey values = 31 | Map.fromList $ map (\v -> (toKey v, v)) values 32 | 33 | 34 | 35 | -- ANY 36 | 37 | 38 | {-# INLINE any #-} 39 | any :: (v -> Bool) -> Map.Map k v -> Bool 40 | any isGood = go 41 | where 42 | go Tip = False 43 | go (Bin _ _ v l r) = isGood v || go l || go r 44 | -------------------------------------------------------------------------------- /compiler/src/Data/NonEmptyList.hs: -------------------------------------------------------------------------------- 1 | module Data.NonEmptyList 2 | ( List(..) 3 | , singleton 4 | , toList 5 | , sortBy 6 | ) 7 | where 8 | 9 | 10 | import Control.Monad (liftM2) 11 | import Data.Binary (Binary, get, put) 12 | import qualified Data.List as List 13 | 14 | 15 | 16 | -- LIST 17 | 18 | 19 | data List a = 20 | List a [a] 21 | 22 | 23 | singleton :: a -> List a 24 | singleton a = 25 | List a [] 26 | 27 | 28 | toList :: List a -> [a] 29 | toList (List x xs) = 30 | x:xs 31 | 32 | 33 | 34 | -- INSTANCES 35 | 36 | 37 | instance Functor List where 38 | fmap func (List x xs) = List (func x) (map func xs) 39 | 40 | 41 | instance Traversable List where 42 | traverse func (List x xs) = List <$> func x <*> traverse func xs 43 | 44 | 45 | instance Foldable List where 46 | foldr step state (List x xs) = step x (foldr step state xs) 47 | foldl step state (List x xs) = foldl step (step state x) xs 48 | foldl1 step (List x xs) = foldl step x xs 49 | 50 | 51 | 52 | -- SORT BY 53 | 54 | 55 | sortBy :: (Ord b) => (a -> b) -> List a -> List a 56 | sortBy toRank (List x xs) = 57 | let 58 | comparison a b = 59 | compare (toRank a) (toRank b) 60 | in 61 | case List.sortBy comparison xs of 62 | [] -> 63 | List x [] 64 | 65 | y:ys -> 66 | case comparison x y of 67 | LT -> List x (y:ys) 68 | EQ -> List x (y:ys) 69 | GT -> List y (List.insertBy comparison x ys) 70 | 71 | 72 | 73 | -- BINARY 74 | 75 | 76 | instance (Binary a) => Binary (List a) where 77 | put (List x xs) = put x >> put xs 78 | get = liftM2 List get get 79 | -------------------------------------------------------------------------------- /compiler/src/Data/OneOrMore.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | module Data.OneOrMore 3 | ( OneOrMore(..) 4 | , one 5 | , more 6 | , map 7 | , destruct 8 | , getFirstTwo 9 | ) 10 | where 11 | 12 | 13 | import Prelude hiding (map) 14 | 15 | 16 | 17 | -- ONE OR MORE 18 | 19 | 20 | data OneOrMore a 21 | = One a 22 | | More (OneOrMore a) (OneOrMore a) 23 | 24 | 25 | one :: a -> OneOrMore a 26 | one = 27 | One 28 | 29 | 30 | more :: OneOrMore a -> OneOrMore a -> OneOrMore a 31 | more = 32 | More 33 | 34 | 35 | 36 | -- MAP 37 | 38 | 39 | map :: (a -> b) -> OneOrMore a -> OneOrMore b 40 | map func oneOrMore = 41 | case oneOrMore of 42 | One value -> 43 | One (func value) 44 | 45 | More left right -> 46 | More (map func left) (map func right) 47 | 48 | 49 | 50 | -- DESTRUCT 51 | 52 | 53 | destruct :: (a -> [a] -> b) -> OneOrMore a -> b 54 | destruct func oneOrMore = 55 | destructLeft func oneOrMore [] 56 | 57 | 58 | destructLeft :: (a -> [a] -> b) -> OneOrMore a -> [a] -> b 59 | destructLeft func oneOrMore xs = 60 | case oneOrMore of 61 | One x -> 62 | func x xs 63 | 64 | More a b -> 65 | destructLeft func a (destructRight b xs) 66 | 67 | 68 | destructRight :: OneOrMore a -> [a] -> [a] 69 | destructRight oneOrMore xs = 70 | case oneOrMore of 71 | One x -> 72 | x : xs 73 | 74 | More a b -> 75 | destructRight a (destructRight b xs) 76 | 77 | 78 | 79 | -- GET FIRST TWO 80 | 81 | 82 | getFirstTwo :: OneOrMore a -> OneOrMore a -> (a,a) 83 | getFirstTwo left right = 84 | case left of 85 | One x -> 86 | (x, getFirstOne right) 87 | 88 | More lleft lright -> 89 | getFirstTwo lleft lright 90 | 91 | 92 | getFirstOne :: OneOrMore a -> a 93 | getFirstOne oneOrMore = 94 | case oneOrMore of 95 | One x -> 96 | x 97 | 98 | More left _ -> 99 | getFirstOne left 100 | -------------------------------------------------------------------------------- /compiler/src/Elm/Compiler/Imports.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Elm.Compiler.Imports 4 | ( defaults 5 | ) 6 | where 7 | 8 | 9 | import qualified Data.Name as Name 10 | 11 | import qualified AST.Source as Src 12 | import qualified Elm.ModuleName as ModuleName 13 | import qualified Reporting.Annotation as A 14 | 15 | 16 | 17 | -- DEFAULTS 18 | 19 | 20 | defaults :: [Src.Import] 21 | defaults = 22 | [ import_ ModuleName.basics Nothing Src.Open 23 | , import_ ModuleName.debug Nothing closed 24 | , import_ ModuleName.list Nothing (operator "::") 25 | , import_ ModuleName.maybe Nothing (typeOpen Name.maybe) 26 | , import_ ModuleName.result Nothing (typeOpen Name.result) 27 | , import_ ModuleName.string Nothing (typeClosed Name.string) 28 | , import_ ModuleName.char Nothing (typeClosed Name.char) 29 | , import_ ModuleName.tuple Nothing closed 30 | , import_ ModuleName.platform Nothing (typeClosed Name.program) 31 | , import_ ModuleName.cmd (Just Name.cmd) (typeClosed Name.cmd) 32 | , import_ ModuleName.sub (Just Name.sub) (typeClosed Name.sub) 33 | ] 34 | 35 | 36 | import_ :: ModuleName.Canonical -> Maybe Name.Name -> Src.Exposing -> Src.Import 37 | import_ (ModuleName.Canonical _ name) maybeAlias exposing = 38 | Src.Import (A.At A.zero name) maybeAlias exposing 39 | 40 | 41 | 42 | -- EXPOSING 43 | 44 | 45 | closed :: Src.Exposing 46 | closed = 47 | Src.Explicit [] 48 | 49 | 50 | typeOpen :: Name.Name -> Src.Exposing 51 | typeOpen name = 52 | Src.Explicit [ Src.Upper (A.At A.zero name) (Src.Public A.zero) ] 53 | 54 | 55 | typeClosed :: Name.Name -> Src.Exposing 56 | typeClosed name = 57 | Src.Explicit [ Src.Upper (A.At A.zero name) Src.Private ] 58 | 59 | 60 | operator :: Name.Name -> Src.Exposing 61 | operator op = 62 | Src.Explicit [ Src.Operator A.zero op ] 63 | -------------------------------------------------------------------------------- /compiler/src/Elm/Float.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | {-# LANGUAGE EmptyDataDecls, FlexibleInstances #-} 3 | module Elm.Float 4 | ( Float 5 | , fromPtr 6 | , toBuilder 7 | ) 8 | where 9 | 10 | 11 | import Prelude hiding (Float) 12 | import Data.Binary (Binary, get, put) 13 | import qualified Data.ByteString.Builder as B 14 | import qualified Data.Utf8 as Utf8 15 | import Data.Word (Word8) 16 | import Foreign.Ptr (Ptr) 17 | 18 | 19 | 20 | -- FLOATS 21 | 22 | 23 | type Float = 24 | Utf8.Utf8 ELM_FLOAT 25 | 26 | 27 | data ELM_FLOAT 28 | 29 | 30 | 31 | -- HELPERS 32 | 33 | 34 | fromPtr :: Ptr Word8 -> Ptr Word8 -> Float 35 | fromPtr = 36 | Utf8.fromPtr 37 | 38 | 39 | {-# INLINE toBuilder #-} 40 | toBuilder :: Float -> B.Builder 41 | toBuilder = 42 | Utf8.toBuilder 43 | 44 | 45 | 46 | -- BINARY 47 | 48 | 49 | instance Binary (Utf8.Utf8 ELM_FLOAT) where 50 | get = Utf8.getUnder256 51 | put = Utf8.putUnder256 52 | -------------------------------------------------------------------------------- /compiler/src/Elm/Magnitude.hs: -------------------------------------------------------------------------------- 1 | module Elm.Magnitude 2 | ( Magnitude(..) 3 | , toChars 4 | ) 5 | where 6 | 7 | 8 | 9 | -- MAGNITUDE 10 | 11 | 12 | data Magnitude 13 | = PATCH 14 | | MINOR 15 | | MAJOR 16 | deriving (Eq, Ord) 17 | 18 | 19 | toChars :: Magnitude -> String 20 | toChars magnitude = 21 | case magnitude of 22 | PATCH -> "PATCH" 23 | MINOR -> "MINOR" 24 | MAJOR -> "MAJOR" 25 | -------------------------------------------------------------------------------- /compiler/src/Elm/String.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall -fno-warn-name-shadowing #-} 2 | {-# LANGUAGE BangPatterns, EmptyDataDecls, FlexibleInstances #-} 3 | module Elm.String 4 | ( String 5 | , toChars 6 | , toBuilder 7 | , Chunk(..) 8 | , fromChunks 9 | ) 10 | where 11 | 12 | 13 | import Prelude hiding (String) 14 | import Data.Binary (Binary, get, put) 15 | import Data.Bits ((.&.), shiftR) 16 | import qualified Data.ByteString.Builder as B 17 | import qualified Data.Utf8 as Utf8 18 | import Data.Utf8 (MBA, newByteArray, copyFromPtr, freeze, writeWord8) 19 | import GHC.Exts (RealWorld, Ptr) 20 | import GHC.IO (stToIO, unsafeDupablePerformIO) 21 | import GHC.ST (ST) 22 | import GHC.Word (Word8) 23 | 24 | 25 | 26 | -- STRINGS 27 | 28 | 29 | type String = 30 | Utf8.Utf8 ELM_STRING 31 | 32 | 33 | data ELM_STRING 34 | 35 | 36 | 37 | -- HELPERS 38 | 39 | 40 | toChars :: String -> [Char] 41 | toChars = 42 | Utf8.toChars 43 | 44 | 45 | {-# INLINE toBuilder #-} 46 | toBuilder :: String -> B.Builder 47 | toBuilder = 48 | Utf8.toBuilder 49 | 50 | 51 | 52 | -- FROM CHUNKS 53 | 54 | 55 | data Chunk 56 | = Slice (Ptr Word8) Int 57 | | Escape Word8 58 | | CodePoint Int 59 | 60 | 61 | fromChunks :: [Chunk] -> String 62 | fromChunks chunks = 63 | unsafeDupablePerformIO (stToIO ( 64 | do let !len = sum (map chunkToWidth chunks) 65 | mba <- newByteArray len 66 | writeChunks mba 0 chunks 67 | freeze mba 68 | )) 69 | 70 | 71 | chunkToWidth :: Chunk -> Int 72 | chunkToWidth chunk = 73 | case chunk of 74 | Slice _ len -> len 75 | Escape _ -> 2 76 | CodePoint c -> if c < 0xFFFF then 6 else 12 77 | 78 | 79 | writeChunks :: MBA RealWorld -> Int -> [Chunk] -> ST RealWorld () 80 | writeChunks mba offset chunks = 81 | case chunks of 82 | [] -> 83 | return () 84 | 85 | chunk : chunks -> 86 | case chunk of 87 | Slice ptr len -> 88 | do copyFromPtr ptr mba offset len 89 | let !newOffset = offset + len 90 | writeChunks mba newOffset chunks 91 | 92 | Escape word -> 93 | do writeWord8 mba offset 0x5C {- \ -} 94 | writeWord8 mba (offset + 1) word 95 | let !newOffset = offset + 2 96 | writeChunks mba newOffset chunks 97 | 98 | CodePoint code -> 99 | if code < 0xFFFF then 100 | do writeCode mba offset code 101 | let !newOffset = offset + 6 102 | writeChunks mba newOffset chunks 103 | else 104 | do let (hi,lo) = divMod (code - 0x10000) 0x400 105 | writeCode mba (offset ) (hi + 0xD800) 106 | writeCode mba (offset + 6) (lo + 0xDC00) 107 | let !newOffset = offset + 12 108 | writeChunks mba newOffset chunks 109 | 110 | 111 | writeCode :: MBA RealWorld -> Int -> Int -> ST RealWorld () 112 | writeCode mba offset code = 113 | do writeWord8 mba offset 0x5C {- \ -} 114 | writeWord8 mba (offset + 1) 0x75 {- u -} 115 | writeHex mba (offset + 2) (shiftR code 12) 116 | writeHex mba (offset + 3) (shiftR code 8) 117 | writeHex mba (offset + 4) (shiftR code 4) 118 | writeHex mba (offset + 5) code 119 | 120 | 121 | writeHex :: MBA RealWorld -> Int -> Int -> ST RealWorld () 122 | writeHex mba !offset !bits = 123 | do let !n = fromIntegral bits .&. 0x0F 124 | writeWord8 mba offset (if n < 10 then 0x30 + n else 0x37 + n) 125 | 126 | 127 | 128 | -- BINARY 129 | 130 | 131 | instance Binary (Utf8.Utf8 ELM_STRING) where 132 | get = Utf8.getVeryLong 133 | put = Utf8.putVeryLong 134 | -------------------------------------------------------------------------------- /compiler/src/Generate/Html.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE QuasiQuotes #-} 3 | module Generate.Html 4 | ( sandwich 5 | ) 6 | where 7 | 8 | 9 | import qualified Data.ByteString.Builder as B 10 | import Data.Monoid ((<>)) 11 | import qualified Data.Name as Name 12 | import Text.RawString.QQ (r) 13 | 14 | 15 | 16 | -- SANDWICH 17 | 18 | 19 | sandwich :: Name.Name -> B.Builder -> B.Builder 20 | sandwich moduleName javascript = 21 | let name = Name.toBuilder moduleName in 22 | [r| 23 | 24 | 25 | 26 | |] <> name <> [r| 27 | 28 | 29 | 30 | 31 | 32 |

33 | 
34 | 
52 | 
53 | 
54 | |]
55 | 


--------------------------------------------------------------------------------
/compiler/src/Generate/JavaScript/Functions.hs:
--------------------------------------------------------------------------------
 1 | {-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
 2 | module Generate.JavaScript.Functions
 3 |   ( functions
 4 |   )
 5 |   where
 6 | 
 7 | 
 8 | import qualified Data.ByteString.Builder as B
 9 | import Text.RawString.QQ (r)
10 | 
11 | 
12 | 
13 | -- FUNCTIONS
14 | 
15 | 
16 | functions :: B.Builder
17 | functions = [r|
18 | 
19 | function F(arity, fun, wrapper) {
20 |   wrapper.a = arity;
21 |   wrapper.f = fun;
22 |   return wrapper;
23 | }
24 | 
25 | function F2(fun) {
26 |   return F(2, fun, function(a) { return function(b) { return fun(a,b); }; })
27 | }
28 | function F3(fun) {
29 |   return F(3, fun, function(a) {
30 |     return function(b) { return function(c) { return fun(a, b, c); }; };
31 |   });
32 | }
33 | function F4(fun) {
34 |   return F(4, fun, function(a) { return function(b) { return function(c) {
35 |     return function(d) { return fun(a, b, c, d); }; }; };
36 |   });
37 | }
38 | function F5(fun) {
39 |   return F(5, fun, function(a) { return function(b) { return function(c) {
40 |     return function(d) { return function(e) { return fun(a, b, c, d, e); }; }; }; };
41 |   });
42 | }
43 | function F6(fun) {
44 |   return F(6, fun, function(a) { return function(b) { return function(c) {
45 |     return function(d) { return function(e) { return function(f) {
46 |     return fun(a, b, c, d, e, f); }; }; }; }; };
47 |   });
48 | }
49 | function F7(fun) {
50 |   return F(7, fun, function(a) { return function(b) { return function(c) {
51 |     return function(d) { return function(e) { return function(f) {
52 |     return function(g) { return fun(a, b, c, d, e, f, g); }; }; }; }; }; };
53 |   });
54 | }
55 | function F8(fun) {
56 |   return F(8, fun, function(a) { return function(b) { return function(c) {
57 |     return function(d) { return function(e) { return function(f) {
58 |     return function(g) { return function(h) {
59 |     return fun(a, b, c, d, e, f, g, h); }; }; }; }; }; }; };
60 |   });
61 | }
62 | function F9(fun) {
63 |   return F(9, fun, function(a) { return function(b) { return function(c) {
64 |     return function(d) { return function(e) { return function(f) {
65 |     return function(g) { return function(h) { return function(i) {
66 |     return fun(a, b, c, d, e, f, g, h, i); }; }; }; }; }; }; }; };
67 |   });
68 | }
69 | 
70 | function A2(fun, a, b) {
71 |   return fun.a === 2 ? fun.f(a, b) : fun(a)(b);
72 | }
73 | function A3(fun, a, b, c) {
74 |   return fun.a === 3 ? fun.f(a, b, c) : fun(a)(b)(c);
75 | }
76 | function A4(fun, a, b, c, d) {
77 |   return fun.a === 4 ? fun.f(a, b, c, d) : fun(a)(b)(c)(d);
78 | }
79 | function A5(fun, a, b, c, d, e) {
80 |   return fun.a === 5 ? fun.f(a, b, c, d, e) : fun(a)(b)(c)(d)(e);
81 | }
82 | function A6(fun, a, b, c, d, e, f) {
83 |   return fun.a === 6 ? fun.f(a, b, c, d, e, f) : fun(a)(b)(c)(d)(e)(f);
84 | }
85 | function A7(fun, a, b, c, d, e, f, g) {
86 |   return fun.a === 7 ? fun.f(a, b, c, d, e, f, g) : fun(a)(b)(c)(d)(e)(f)(g);
87 | }
88 | function A8(fun, a, b, c, d, e, f, g, h) {
89 |   return fun.a === 8 ? fun.f(a, b, c, d, e, f, g, h) : fun(a)(b)(c)(d)(e)(f)(g)(h);
90 | }
91 | function A9(fun, a, b, c, d, e, f, g, h, i) {
92 |   return fun.a === 9 ? fun.f(a, b, c, d, e, f, g, h, i) : fun(a)(b)(c)(d)(e)(f)(g)(h)(i);
93 | }
94 | 
95 | |]
96 | 


--------------------------------------------------------------------------------
/compiler/src/Generate/Mode.hs:
--------------------------------------------------------------------------------
 1 | module Generate.Mode
 2 |   ( Mode(..)
 3 |   , isDebug
 4 |   , ShortFieldNames
 5 |   , shortenFieldNames
 6 |   )
 7 |   where
 8 | 
 9 | 
10 | import qualified Data.List as List
11 | import qualified Data.Map as Map
12 | import qualified Data.Maybe as Maybe
13 | import qualified Data.Name as Name
14 | 
15 | import qualified AST.Optimized as Opt
16 | import qualified Elm.Compiler.Type.Extract as Extract
17 | import qualified Generate.JavaScript.Name as JsName
18 | 
19 | 
20 | 
21 | -- MODE
22 | 
23 | 
24 | data Mode
25 |   = Dev (Maybe Extract.Types)
26 |   | Prod ShortFieldNames
27 | 
28 | 
29 | isDebug :: Mode -> Bool
30 | isDebug mode =
31 |   case mode of
32 |     Dev mi -> Maybe.isJust mi
33 |     Prod _ -> False
34 | 
35 | 
36 | 
37 | -- SHORTEN FIELD NAMES
38 | 
39 | 
40 | type ShortFieldNames =
41 |   Map.Map Name.Name JsName.Name
42 | 
43 | 
44 | shortenFieldNames :: Opt.GlobalGraph -> ShortFieldNames
45 | shortenFieldNames (Opt.GlobalGraph _ frequencies) =
46 |   Map.foldr addToShortNames Map.empty $
47 |     Map.foldrWithKey addToBuckets Map.empty frequencies
48 | 
49 | 
50 | addToBuckets :: Name.Name -> Int -> Map.Map Int [Name.Name] -> Map.Map Int [Name.Name]
51 | addToBuckets field frequency buckets =
52 |   Map.insertWith (++) frequency [field] buckets
53 | 
54 | 
55 | addToShortNames :: [Name.Name] -> ShortFieldNames -> ShortFieldNames
56 | addToShortNames fields shortNames =
57 |   List.foldl' addField shortNames fields
58 | 
59 | 
60 | addField :: ShortFieldNames -> Name.Name -> ShortFieldNames
61 | addField shortNames field =
62 |   let rename = JsName.fromInt (Map.size shortNames) in
63 |   Map.insert field rename shortNames
64 | 


--------------------------------------------------------------------------------
/compiler/src/Nitpick/Debug.hs:
--------------------------------------------------------------------------------
 1 | module Nitpick.Debug
 2 |   ( hasDebugUses
 3 |   )
 4 |   where
 5 | 
 6 | 
 7 | import qualified Data.Map.Utils as Map
 8 | 
 9 | import qualified AST.Optimized as Opt
10 | 
11 | 
12 | 
13 | -- HAS DEBUG USES
14 | 
15 | 
16 | hasDebugUses :: Opt.LocalGraph -> Bool
17 | hasDebugUses (Opt.LocalGraph _ graph _) =
18 |   Map.any nodeHasDebug graph
19 | 
20 | 
21 | nodeHasDebug :: Opt.Node -> Bool
22 | nodeHasDebug node =
23 |   case node of
24 |     Opt.Define expr _           -> hasDebug expr
25 |     Opt.DefineTailFunc _ expr _ -> hasDebug expr
26 |     Opt.Ctor _ _                -> False
27 |     Opt.Enum _                  -> False
28 |     Opt.Box                     -> False
29 |     Opt.Link _                  -> False
30 |     Opt.Cycle _ vs fs _         -> any (hasDebug . snd) vs || any defHasDebug fs
31 |     Opt.Manager _               -> False
32 |     Opt.Kernel _ _              -> False
33 |     Opt.PortIncoming expr _     -> hasDebug expr
34 |     Opt.PortOutgoing expr _     -> hasDebug expr
35 | 
36 | 
37 | hasDebug :: Opt.Expr -> Bool
38 | hasDebug expression =
39 |   case expression of
40 |     Opt.Bool _           -> False
41 |     Opt.Chr _            -> False
42 |     Opt.Str _            -> False
43 |     Opt.Int _            -> False
44 |     Opt.Float _          -> False
45 |     Opt.VarLocal _       -> False
46 |     Opt.VarGlobal _      -> False
47 |     Opt.VarEnum _ _      -> False
48 |     Opt.VarBox _         -> False
49 |     Opt.VarCycle _ _     -> False
50 |     Opt.VarDebug _ _ _ _ -> True
51 |     Opt.VarKernel _ _    -> False
52 |     Opt.List exprs       -> any hasDebug exprs
53 |     Opt.Function _ expr  -> hasDebug expr
54 |     Opt.Call e es        -> hasDebug e || any hasDebug es
55 |     Opt.TailCall _ args  -> any (hasDebug . snd) args
56 |     Opt.If conds finally -> any (\(c,e) -> hasDebug c || hasDebug e) conds || hasDebug finally
57 |     Opt.Let def body     -> defHasDebug def || hasDebug body
58 |     Opt.Destruct _ expr  -> hasDebug expr
59 |     Opt.Case _ _ d jumps -> deciderHasDebug d || any (hasDebug . snd) jumps
60 |     Opt.Accessor _       -> False
61 |     Opt.Access r _       -> hasDebug r
62 |     Opt.Update r fs      -> hasDebug r || any hasDebug fs
63 |     Opt.Record fs        -> any hasDebug fs
64 |     Opt.Unit             -> False
65 |     Opt.Tuple a b c      -> hasDebug a || hasDebug b || maybe False hasDebug c
66 |     Opt.Shader _ _ _     -> False
67 | 
68 | 
69 | defHasDebug :: Opt.Def -> Bool
70 | defHasDebug def =
71 |   case def of
72 |     Opt.Def _ expr       -> hasDebug expr
73 |     Opt.TailDef _ _ expr -> hasDebug expr
74 | 
75 | 
76 | deciderHasDebug :: Opt.Decider Opt.Choice -> Bool
77 | deciderHasDebug decider =
78 |   case decider of
79 |     Opt.Leaf (Opt.Inline expr)  -> hasDebug expr
80 |     Opt.Leaf (Opt.Jump _)       -> False
81 |     Opt.Chain _ success failure -> deciderHasDebug success || deciderHasDebug failure
82 |     Opt.FanOut _ tests fallback -> any (deciderHasDebug . snd) tests || deciderHasDebug fallback
83 | 
84 | 
85 | 
86 | -- TODO: FIND GLOBALLY UNUSED DEFINITIONS?
87 | -- TODO: FIND PACKAGE USAGE STATS? (e.g. elm/core = 142, author/project = 2, etc.)
88 | 


--------------------------------------------------------------------------------
/compiler/src/Parse/Symbol.hs:
--------------------------------------------------------------------------------
 1 | {-# OPTIONS_GHC -Wall #-}
 2 | {-# LANGUAGE BangPatterns, OverloadedStrings #-}
 3 | module Parse.Symbol
 4 |   ( operator
 5 |   , BadOperator(..)
 6 |   , binopCharSet
 7 |   )
 8 |   where
 9 | 
10 | 
11 | import qualified Data.Char as Char
12 | import qualified Data.IntSet as IntSet
13 | import qualified Data.Name as Name
14 | import qualified Data.Vector as Vector
15 | import Foreign.Ptr (Ptr, plusPtr, minusPtr)
16 | import GHC.Word (Word8)
17 | 
18 | import Parse.Primitives (Parser, Row, Col)
19 | import qualified Parse.Primitives as P
20 | 
21 | 
22 | 
23 | -- OPERATOR
24 | 
25 | 
26 | data BadOperator
27 |   = BadDot
28 |   | BadPipe
29 |   | BadArrow
30 |   | BadEquals
31 |   | BadHasType
32 | 
33 | 
34 | operator :: (Row -> Col -> x) -> (BadOperator -> Row -> Col -> x) -> Parser x Name.Name
35 | operator toExpectation toError =
36 |   P.Parser $ \(P.State src pos end indent row col) cok _ cerr eerr ->
37 |     let !newPos = chompOps pos end in
38 |     if pos == newPos then
39 |       eerr row col toExpectation
40 | 
41 |     else
42 |       case Name.fromPtr pos newPos of
43 |         "."  -> eerr row col (toError BadDot)
44 |         "|"  -> cerr row col (toError BadPipe)
45 |         "->" -> cerr row col (toError BadArrow)
46 |         "="  -> cerr row col (toError BadEquals)
47 |         ":"  -> cerr row col (toError BadHasType)
48 |         op   ->
49 |           let
50 |             !newCol = col + fromIntegral (minusPtr newPos pos)
51 |             !newState = P.State src newPos end indent row newCol
52 |           in
53 |           cok op newState
54 | 
55 | 
56 | chompOps :: Ptr Word8 -> Ptr Word8 -> Ptr Word8
57 | chompOps pos end =
58 |   if pos < end && isBinopCharHelp (P.unsafeIndex pos) then
59 |     chompOps (plusPtr pos 1) end
60 |   else
61 |     pos
62 | 
63 | 
64 | {-# INLINE isBinopCharHelp #-}
65 | isBinopCharHelp :: Word8 -> Bool
66 | isBinopCharHelp word =
67 |   word < 128 && Vector.unsafeIndex binopCharVector (fromIntegral word)
68 | 
69 | 
70 | {-# NOINLINE binopCharVector #-}
71 | binopCharVector :: Vector.Vector Bool
72 | binopCharVector =
73 |   Vector.generate 128 (\i -> IntSet.member i binopCharSet)
74 | 
75 | 
76 | {-# NOINLINE binopCharSet #-}
77 | binopCharSet :: IntSet.IntSet
78 | binopCharSet =
79 |   IntSet.fromList (map Char.ord "+-/*=.<>:&|^?%!")
80 | 


--------------------------------------------------------------------------------
/compiler/src/Reporting/Annotation.hs:
--------------------------------------------------------------------------------
  1 | {-# OPTIONS_GHC -Wall #-}
  2 | module Reporting.Annotation
  3 |   ( Located(..)
  4 |   , Position(..)
  5 |   , Region(..)
  6 |   , traverse
  7 |   , toValue
  8 |   , merge
  9 |   , at
 10 |   , toRegion
 11 |   , mergeRegions
 12 |   , zero
 13 |   , one
 14 |   )
 15 |   where
 16 | 
 17 | 
 18 | import Prelude hiding (traverse)
 19 | import Control.Monad (liftM2)
 20 | import Data.Binary (Binary, get, put)
 21 | import Data.Word (Word16)
 22 | 
 23 | 
 24 | 
 25 | -- LOCATED
 26 | 
 27 | 
 28 | data Located a =
 29 |   At Region a  -- PERF see if unpacking region is helpful
 30 | 
 31 | 
 32 | instance Functor Located where
 33 |   fmap f (At region a) =
 34 |     At region (f a)
 35 | 
 36 | 
 37 | traverse :: (Functor f) => (a -> f b) -> Located a -> f (Located b)
 38 | traverse func (At region value) =
 39 |   At region <$> func value
 40 | 
 41 | 
 42 | toValue :: Located a -> a
 43 | toValue (At _ value) =
 44 |   value
 45 | 
 46 | 
 47 | merge :: Located a -> Located b -> value -> Located value
 48 | merge (At r1 _) (At r2 _) value =
 49 |   At (mergeRegions r1 r2) value
 50 | 
 51 | 
 52 | 
 53 | -- POSITION
 54 | 
 55 | 
 56 | data Position =
 57 |   Position
 58 |     {-# UNPACK #-} !Word16
 59 |     {-# UNPACK #-} !Word16
 60 |   deriving (Eq)
 61 | 
 62 | 
 63 | at :: Position -> Position -> a -> Located a
 64 | at start end a =
 65 |   At (Region start end) a
 66 | 
 67 | 
 68 | 
 69 | -- REGION
 70 | 
 71 | 
 72 | data Region = Region Position Position
 73 |   deriving (Eq)
 74 | 
 75 | 
 76 | toRegion :: Located a -> Region
 77 | toRegion (At region _) =
 78 |   region
 79 | 
 80 | 
 81 | mergeRegions :: Region -> Region -> Region
 82 | mergeRegions (Region start _) (Region _ end) =
 83 |   Region start end
 84 | 
 85 | 
 86 | zero :: Region
 87 | zero =
 88 |   Region (Position 0 0) (Position 0 0)
 89 | 
 90 | 
 91 | one :: Region
 92 | one =
 93 |   Region (Position 1 1) (Position 1 1)
 94 | 
 95 | 
 96 | instance Binary Region where
 97 |   put (Region a b) = put a >> put b
 98 |   get = liftM2 Region get get
 99 | 
100 | 
101 | instance Binary Position where
102 |   put (Position a b) = put a >> put b
103 |   get = liftM2 Position get get
104 | 


--------------------------------------------------------------------------------
/compiler/src/Reporting/Error/Main.hs:
--------------------------------------------------------------------------------
  1 | {-# OPTIONS_GHC -Wall #-}
  2 | {-# LANGUAGE OverloadedStrings #-}
  3 | module Reporting.Error.Main
  4 |   ( Error(..)
  5 |   , toReport
  6 |   )
  7 |   where
  8 | 
  9 | 
 10 | import qualified Data.Name as Name
 11 | 
 12 | import qualified AST.Canonical as Can
 13 | import qualified Reporting.Annotation as A
 14 | import qualified Reporting.Doc as D
 15 | import qualified Reporting.Error.Canonicalize as E
 16 | import qualified Reporting.Render.Code as Code
 17 | import qualified Reporting.Render.Type as RT
 18 | import qualified Reporting.Render.Type.Localizer as L
 19 | import qualified Reporting.Report as Report
 20 | 
 21 | 
 22 | 
 23 | -- ERROR
 24 | 
 25 | 
 26 | data Error
 27 |   = BadType A.Region Can.Type
 28 |   | BadCycle A.Region Name.Name [Name.Name]
 29 |   | BadFlags A.Region Can.Type E.InvalidPayload
 30 | 
 31 | 
 32 | 
 33 | -- TO REPORT
 34 | 
 35 | 
 36 | toReport :: L.Localizer -> Code.Source -> Error -> Report.Report
 37 | toReport localizer source err =
 38 |   case err of
 39 |     BadType region tipe ->
 40 |       Report.Report "BAD MAIN TYPE" region [] $
 41 |         Code.toSnippet source region Nothing
 42 |           (
 43 |             "I cannot handle this type of `main` value:"
 44 |           ,
 45 |             D.stack
 46 |               [ "The type of `main` value I am seeing is:"
 47 |               , D.indent 4 $ D.dullyellow $ RT.canToDoc localizer RT.None tipe
 48 |               , D.reflow $
 49 |                   "I only know how to handle Html, Svg, and Programs\
 50 |                   \ though. Modify `main` to be one of those types of values!"
 51 |               ]
 52 |           )
 53 | 
 54 |     BadCycle region name names ->
 55 |       Report.Report "BAD MAIN" region [] $
 56 |         Code.toSnippet source region Nothing
 57 |           (
 58 |             "A `main` definition cannot be defined in terms of itself."
 59 |           ,
 60 |             D.stack
 61 |               [ D.reflow $
 62 |                   "It should be a boring value with no recursion. But\
 63 |                   \ instead it is involved in this cycle of definitions:"
 64 |               , D.cycle 4 name names
 65 |               ]
 66 |           )
 67 | 
 68 |     BadFlags region _badType invalidPayload ->
 69 |       let
 70 |         formatDetails (aBadKindOfThing, butThatIsNoGood) =
 71 |           Report.Report "BAD FLAGS" region [] $
 72 |             Code.toSnippet source region Nothing
 73 |               (
 74 |                 D.reflow $
 75 |                   "Your `main` program wants " ++ aBadKindOfThing ++ " from JavaScript."
 76 |               ,
 77 |                 butThatIsNoGood
 78 |               )
 79 |       in
 80 |       formatDetails $
 81 |         case invalidPayload of
 82 |           E.ExtendedRecord ->
 83 |             (
 84 |               "an extended record"
 85 |             ,
 86 |               D.reflow $
 87 |                 "But the exact shape of the record must be known at compile time. No type variables!"
 88 |             )
 89 | 
 90 |           E.Function ->
 91 |             (
 92 |               "a function"
 93 |             ,
 94 |               D.reflow $
 95 |                 "But if I allowed functions from JS, it would be possible to sneak\
 96 |                 \ side-effects and runtime exceptions into Elm!"
 97 |             )
 98 | 
 99 |           E.TypeVariable name ->
100 |             (
101 |               "an unspecified type"
102 |             ,
103 |               D.reflow $
104 |                 "But type variables like `" ++ Name.toChars name ++ "` cannot be given as flags.\
105 |                 \ I need to know exactly what type of data I am getting, so I can guarantee that\
106 |                 \ unexpected data cannot sneak in and crash the Elm program."
107 |             )
108 | 
109 |           E.UnsupportedType name ->
110 |             (
111 |               "a `" ++ Name.toChars name ++ "` value"
112 |             ,
113 |               D.stack
114 |                 [ D.reflow $ "I cannot handle that. The types that CAN be in flags include:"
115 |                 , D.indent 4 $
116 |                     D.reflow $
117 |                       "Ints, Floats, Bools, Strings, Maybes, Lists, Arrays,\
118 |                       \ tuples, records, and JSON values."
119 |                 , D.reflow $
120 |                     "Since JSON values can flow through, you can use JSON encoders and decoders\
121 |                     \ to allow other types through as well. More advanced users often just do\
122 |                     \ everything with encoders and decoders for more control and better errors."
123 |                 ]
124 |             )
125 | 


--------------------------------------------------------------------------------
/compiler/src/Reporting/Render/Type/Localizer.hs:
--------------------------------------------------------------------------------
  1 | {-# OPTIONS_GHC -Wall #-}
  2 | {-# LANGUAGE OverloadedStrings #-}
  3 | module Reporting.Render.Type.Localizer
  4 |   ( Localizer
  5 |   , toDoc
  6 |   , toChars
  7 |   , empty
  8 |   , fromNames
  9 |   , fromModule
 10 |   )
 11 |   where
 12 | 
 13 | 
 14 | import qualified Data.Map as Map
 15 | import qualified Data.Name as Name
 16 | import qualified Data.Set as Set
 17 | 
 18 | import qualified AST.Source as Src
 19 | import qualified Elm.ModuleName as ModuleName
 20 | import Reporting.Doc ((<>))
 21 | import qualified Reporting.Doc as D
 22 | import qualified Reporting.Annotation as A
 23 | 
 24 | 
 25 | 
 26 | -- LOCALIZER
 27 | 
 28 | 
 29 | newtype Localizer =
 30 |   Localizer (Map.Map Name.Name Import)
 31 | 
 32 | 
 33 | data Import =
 34 |   Import
 35 |     { _alias :: Maybe Name.Name
 36 |     , _exposing :: Exposing
 37 |     }
 38 | 
 39 | 
 40 | data Exposing
 41 |   = All
 42 |   | Only (Set.Set Name.Name)
 43 | 
 44 | 
 45 | empty :: Localizer
 46 | empty =
 47 |   Localizer Map.empty
 48 | 
 49 | 
 50 | 
 51 | -- LOCALIZE
 52 | 
 53 | 
 54 | toDoc :: Localizer -> ModuleName.Canonical -> Name.Name -> D.Doc
 55 | toDoc localizer home name =
 56 |   D.fromChars (toChars localizer home name)
 57 | 
 58 | 
 59 | toChars :: Localizer -> ModuleName.Canonical -> Name.Name -> String
 60 | toChars (Localizer localizer) moduleName@(ModuleName.Canonical _ home) name =
 61 |   case Map.lookup home localizer of
 62 |     Nothing ->
 63 |       Name.toChars home <> "." <> Name.toChars name
 64 | 
 65 |     Just (Import alias exposing) ->
 66 |       case exposing of
 67 |         All ->
 68 |           Name.toChars name
 69 | 
 70 |         Only set ->
 71 |           if Set.member name set then
 72 |             Name.toChars name
 73 |           else if name == Name.list && moduleName == ModuleName.list then
 74 |             "List"
 75 |           else
 76 |             Name.toChars (maybe home id alias) <> "." <> Name.toChars name
 77 | 
 78 | 
 79 | 
 80 | -- FROM NAMES
 81 | 
 82 | 
 83 | fromNames :: Map.Map Name.Name a -> Localizer
 84 | fromNames names =
 85 |   Localizer $ Map.map (\_ -> Import Nothing All) names
 86 | 
 87 | 
 88 | 
 89 | -- FROM MODULE
 90 | 
 91 | 
 92 | fromModule :: Src.Module -> Localizer
 93 | fromModule modul@(Src.Module _ _ _ imports _ _ _ _ _) =
 94 |   Localizer $ Map.fromList $
 95 |     (Src.getName modul, Import Nothing All) : map toPair imports
 96 | 
 97 | 
 98 | toPair :: Src.Import -> (Name.Name, Import)
 99 | toPair (Src.Import (A.At _ name) alias exposing) =
100 |   ( name
101 |   , Import alias (toExposing exposing)
102 |   )
103 | 
104 | 
105 | toExposing :: Src.Exposing -> Exposing
106 | toExposing exposing =
107 |   case exposing of
108 |     Src.Open ->
109 |       All
110 | 
111 |     Src.Explicit exposedList ->
112 |       Only (foldr addType Set.empty exposedList)
113 | 
114 | 
115 | addType :: Src.Exposed -> Set.Set Name.Name -> Set.Set Name.Name
116 | addType exposed types =
117 |   case exposed of
118 |     Src.Lower _               -> types
119 |     Src.Upper (A.At _ name) _ -> Set.insert name types
120 |     Src.Operator _ _          -> types
121 | 


--------------------------------------------------------------------------------
/compiler/src/Reporting/Report.hs:
--------------------------------------------------------------------------------
 1 | {-# LANGUAGE OverloadedStrings #-}
 2 | module Reporting.Report
 3 |     ( Report(..)
 4 |     )
 5 |     where
 6 | 
 7 | 
 8 | import qualified Reporting.Annotation as A
 9 | import qualified Reporting.Doc as D
10 | 
11 | 
12 | 
13 | -- BUILD REPORTS
14 | 
15 | 
16 | data Report =
17 |   Report
18 |     { _title :: String
19 |     , _region :: A.Region
20 |     , _sgstns :: [String]
21 |     , _message :: D.Doc
22 |     }
23 | 


--------------------------------------------------------------------------------
/compiler/src/Reporting/Result.hs:
--------------------------------------------------------------------------------
  1 | {-# OPTIONS_GHC -Wall #-}
  2 | {-# LANGUAGE Rank2Types #-}
  3 | module Reporting.Result
  4 |   ( Result(..)
  5 |   , run
  6 |   , ok
  7 |   , warn
  8 |   , throw
  9 |   , mapError
 10 |   )
 11 |   where
 12 | 
 13 | 
 14 | import qualified Data.OneOrMore as OneOrMore
 15 | import qualified Reporting.Warning as Warning
 16 | 
 17 | 
 18 | 
 19 | -- RESULT
 20 | 
 21 | 
 22 | newtype Result info warnings error a =
 23 |   Result (
 24 |     forall result.
 25 |       info
 26 |       -> warnings
 27 |       -> (info -> warnings -> OneOrMore.OneOrMore error -> result)
 28 |       -> (info -> warnings -> a -> result)
 29 |       -> result
 30 |   )
 31 | 
 32 | 
 33 | run :: Result () [w] e a -> ([w], Either (OneOrMore.OneOrMore e) a)
 34 | run (Result k) =
 35 |   k () []
 36 |     (\() w e -> (reverse w, Left e))
 37 |     (\() w a -> (reverse w, Right a))
 38 | 
 39 | 
 40 | 
 41 | -- HELPERS
 42 | 
 43 | 
 44 | ok :: a -> Result i w e a
 45 | ok a =
 46 |   Result $ \i w _ good ->
 47 |     good i w a
 48 | 
 49 | 
 50 | warn :: Warning.Warning -> Result i [Warning.Warning] e ()
 51 | warn warning =
 52 |   Result $ \i warnings _ good ->
 53 |     good i (warning:warnings) ()
 54 | 
 55 | 
 56 | throw :: e -> Result i w e a
 57 | throw e =
 58 |   Result $ \i w bad _ ->
 59 |     bad i w (OneOrMore.one e)
 60 | 
 61 | 
 62 | mapError :: (e -> e') -> Result i w e a -> Result i w e' a
 63 | mapError func (Result k) =
 64 |   Result $ \i w bad good ->
 65 |     let
 66 |       bad1 i1 w1 e1 =
 67 |         bad i1 w1 (OneOrMore.map func e1)
 68 |     in
 69 |     k i w bad1 good
 70 | 
 71 | 
 72 | 
 73 | -- FANCY INSTANCE STUFF
 74 | 
 75 | 
 76 | instance Functor (Result i w e) where
 77 |   fmap func (Result k) =
 78 |     Result $ \i w bad good ->
 79 |       let
 80 |         good1 i1 w1 value =
 81 |           good i1 w1 (func value)
 82 |       in
 83 |       k i w bad good1
 84 | 
 85 | 
 86 | instance Applicative (Result i w e) where
 87 |   pure = ok
 88 | 
 89 |   (<*>) (Result kf) (Result kv) =
 90 |     Result $ \i w bad good ->
 91 |       let
 92 |         bad1 i1 w1 e1 =
 93 |           let
 94 |             bad2 i2 w2 e2 = bad i2 w2 (OneOrMore.more e1 e2)
 95 |             good2 i2 w2 _value = bad i2 w2 e1
 96 |           in
 97 |           kv i1 w1 bad2 good2
 98 | 
 99 |         good1 i1 w1 func =
100 |           let
101 |             bad2 i2 w2 e2 = bad i2 w2 e2
102 |             good2 i2 w2 value = good i2 w2 (func value)
103 |           in
104 |           kv i1 w1 bad2 good2
105 |       in
106 |       kf i w bad1 good1
107 | 
108 | 
109 | instance Monad (Result i w e) where
110 |   return = ok
111 | 
112 |   (>>=) (Result ka) callback =
113 |     Result $ \i w bad good ->
114 |       let
115 |         good1 i1 w1 a =
116 |           case callback a of
117 |             Result kb -> kb i1 w1 bad good
118 |       in
119 |       ka i w bad good1
120 | 
121 |   (>>) (Result ka) (Result kb) =
122 |     Result $ \i w bad good ->
123 |       let
124 |         good1 i1 w1 _ =
125 |           kb i1 w1 bad good
126 |       in
127 |       ka i w bad good1
128 | 
129 |   -- PERF add INLINE to these?
130 | 


--------------------------------------------------------------------------------
/compiler/src/Reporting/Suggest.hs:
--------------------------------------------------------------------------------
 1 | {-# OPTIONS_GHC -Wall #-}
 2 | {-# LANGUAGE OverloadedStrings #-}
 3 | module Reporting.Suggest
 4 |   ( distance
 5 |   , sort
 6 |   , rank
 7 |   )
 8 |   where
 9 | 
10 | 
11 | import qualified Data.Char as Char
12 | import qualified Data.List as List
13 | import qualified Text.EditDistance as Dist
14 | 
15 | 
16 | 
17 | -- DISTANCE
18 | 
19 | 
20 | distance :: String -> String -> Int
21 | distance x y =
22 |   Dist.restrictedDamerauLevenshteinDistance Dist.defaultEditCosts x y
23 | 
24 | 
25 | 
26 | -- SORT
27 | 
28 | 
29 | sort :: String -> (a -> String) -> [a] -> [a]
30 | sort target toString values =
31 |   List.sortOn (distance (toLower target) . toLower . toString) values
32 | 
33 | 
34 | toLower :: String -> String
35 | toLower string =
36 |   map Char.toLower string
37 | 
38 | 
39 | 
40 | -- RANK
41 | 
42 | 
43 | rank :: String -> (a -> String) -> [a] -> [(Int,a)]
44 | rank target toString values =
45 |   let
46 |     toRank v =
47 |       distance (toLower target) (toLower (toString v))
48 | 
49 |     addRank v =
50 |       (toRank v, v)
51 |   in
52 |   List.sortOn fst (map addRank values)
53 | 


--------------------------------------------------------------------------------
/compiler/src/Reporting/Warning.hs:
--------------------------------------------------------------------------------
  1 | {-# OPTIONS_GHC -Wall #-}
  2 | {-# LANGUAGE OverloadedStrings #-}
  3 | module Reporting.Warning
  4 |   ( Warning(..)
  5 |   , Context(..)
  6 |   , toReport
  7 |   )
  8 |   where
  9 | 
 10 | 
 11 | import Data.Monoid ((<>))
 12 | import qualified Data.Name as Name
 13 | 
 14 | import qualified AST.Canonical as Can
 15 | import qualified AST.Utils.Type as Type
 16 | import qualified Reporting.Annotation as A
 17 | import qualified Reporting.Doc as D
 18 | import qualified Reporting.Report as Report
 19 | import qualified Reporting.Render.Code as Code
 20 | import qualified Reporting.Render.Type as RT
 21 | import qualified Reporting.Render.Type.Localizer as L
 22 | 
 23 | 
 24 | 
 25 | -- ALL POSSIBLE WARNINGS
 26 | 
 27 | 
 28 | data Warning
 29 |   = UnusedImport A.Region Name.Name
 30 |   | UnusedVariable A.Region Context Name.Name
 31 |   | MissingTypeAnnotation A.Region Name.Name Can.Type
 32 | 
 33 | 
 34 | data Context = Def | Pattern
 35 | 
 36 | 
 37 | 
 38 | -- TO REPORT
 39 | 
 40 | 
 41 | toReport :: L.Localizer -> Code.Source -> Warning -> Report.Report
 42 | toReport localizer source warning =
 43 |   case warning of
 44 |     UnusedImport region moduleName ->
 45 |       Report.Report "unused import" region [] $
 46 |         Code.toSnippet source region Nothing
 47 |           (
 48 |             D.reflow $
 49 |               "Nothing from the `" <> Name.toChars moduleName <> "` module is used in this file."
 50 |           ,
 51 |             "I recommend removing unused imports."
 52 |           )
 53 | 
 54 |     UnusedVariable region context name ->
 55 |       let title = defOrPat context "unused definition" "unused variable" in
 56 |       Report.Report title region [] $
 57 |         Code.toSnippet source region Nothing
 58 |           (
 59 |             D.reflow $
 60 |               "You are not using `" <> Name.toChars name <> "` anywhere."
 61 |           ,
 62 |             D.stack
 63 |               [ D.reflow $
 64 |                   "Is there a typo? Maybe you intended to use `" <> Name.toChars name
 65 |                   <> "` somewhere but typed another name instead?"
 66 |               , D.reflow $
 67 |                   defOrPat context
 68 |                     ( "If you are sure there is no typo, remove the definition.\
 69 |                       \ This way future readers will not have to wonder why it is there!"
 70 |                     )
 71 |                     ( "If you are sure there is no typo, replace `" <> Name.toChars name
 72 |                       <> "` with _ so future readers will not have to wonder why it is there!"
 73 |                     )
 74 |               ]
 75 |           )
 76 | 
 77 |     MissingTypeAnnotation region name inferredType ->
 78 |         Report.Report "missing type annotation" region [] $
 79 |           Code.toSnippet source region Nothing
 80 |             (
 81 |               D.reflow $
 82 |                 case Type.deepDealias inferredType of
 83 |                   Can.TLambda _ _ ->
 84 |                     "The `" <> Name.toChars name <> "` function has no type annotation."
 85 | 
 86 |                   _ ->
 87 |                     "The `" <> Name.toChars name <> "` definition has no type annotation."
 88 |             ,
 89 |               D.stack
 90 |                 [ "I inferred the type annotation myself though! You can copy it into your code:"
 91 |                 , D.green $ D.hang 4 $ D.sep $
 92 |                     [ D.fromName name <> " :"
 93 |                     , RT.canToDoc localizer RT.None inferredType
 94 |                     ]
 95 |                 ]
 96 |             )
 97 | 
 98 | 
 99 | defOrPat :: Context -> a -> a -> a
100 | defOrPat context def pat =
101 |   case context of
102 |     Def -> def
103 |     Pattern -> pat
104 | 
105 | 


--------------------------------------------------------------------------------
/compiler/src/Type/Instantiate.hs:
--------------------------------------------------------------------------------
 1 | {-# OPTIONS_GHC -Wall #-}
 2 | {-# LANGUAGE OverloadedStrings #-}
 3 | module Type.Instantiate
 4 |   ( FreeVars
 5 |   , fromSrcType
 6 |   )
 7 |   where
 8 | 
 9 | 
10 | import qualified Data.Map.Strict as Map
11 | import Data.Map.Strict ((!))
12 | import qualified Data.Name as Name
13 | 
14 | import qualified AST.Canonical as Can
15 | import Type.Type
16 | 
17 | 
18 | 
19 | -- FREE VARS
20 | 
21 | 
22 | type FreeVars =
23 |   Map.Map Name.Name Type
24 | 
25 | 
26 | 
27 | -- FROM SOURCE TYPE
28 | 
29 | 
30 | fromSrcType :: Map.Map Name.Name Type -> Can.Type -> IO Type
31 | fromSrcType freeVars sourceType =
32 |   case sourceType of
33 |     Can.TLambda arg result ->
34 |       FunN
35 |         <$> fromSrcType freeVars arg
36 |         <*> fromSrcType freeVars result
37 | 
38 |     Can.TVar name ->
39 |       return (freeVars ! name)
40 | 
41 |     Can.TType home name args ->
42 |       AppN home name <$> traverse (fromSrcType freeVars) args
43 | 
44 |     Can.TAlias home name args aliasedType ->
45 |       do  targs <- traverse (traverse (fromSrcType freeVars)) args
46 |           AliasN home name targs <$>
47 |             case aliasedType of
48 |               Can.Filled realType ->
49 |                 fromSrcType freeVars realType
50 | 
51 |               Can.Holey realType ->
52 |                 fromSrcType (Map.fromList targs) realType
53 | 
54 |     Can.TTuple a b maybeC ->
55 |       TupleN
56 |         <$> fromSrcType freeVars a
57 |         <*> fromSrcType freeVars b
58 |         <*> traverse (fromSrcType freeVars) maybeC
59 | 
60 |     Can.TUnit ->
61 |       return UnitN
62 | 
63 |     Can.TRecord fields maybeExt ->
64 |       RecordN
65 |         <$> traverse (fromSrcFieldType freeVars) fields
66 |         <*>
67 |           case maybeExt of
68 |             Nothing ->
69 |               return EmptyRecordN
70 | 
71 |             Just ext ->
72 |               return (freeVars ! ext)
73 | 
74 | 
75 | fromSrcFieldType :: Map.Map Name.Name Type -> Can.FieldType -> IO Type
76 | fromSrcFieldType freeVars (Can.FieldType _ tipe) =
77 |   fromSrcType freeVars tipe
78 | 


--------------------------------------------------------------------------------
/compiler/src/Type/Occurs.hs:
--------------------------------------------------------------------------------
 1 | {-# OPTIONS_GHC -Wall #-}
 2 | {-# LANGUAGE OverloadedStrings #-}
 3 | module Type.Occurs
 4 |   ( occurs
 5 |   )
 6 |   where
 7 | 
 8 | 
 9 | import Data.Foldable (foldrM)
10 | import qualified Data.Map.Strict as Map
11 | 
12 | import Type.Type as Type
13 | import qualified Type.UnionFind as UF
14 | 
15 | 
16 | 
17 | -- OCCURS
18 | 
19 | 
20 | occurs :: Type.Variable -> IO Bool
21 | occurs var =
22 |   occursHelp [] var False
23 | 
24 | 
25 | occursHelp :: [Type.Variable] -> Type.Variable -> Bool -> IO Bool
26 | occursHelp seen var foundCycle =
27 |   if elem var seen then
28 |     return True
29 | 
30 |   else
31 |     do  (Descriptor content _ _ _) <- UF.get var
32 |         case content of
33 |           FlexVar _ ->
34 |               return foundCycle
35 | 
36 |           FlexSuper _ _ ->
37 |               return foundCycle
38 | 
39 |           RigidVar _ ->
40 |               return foundCycle
41 | 
42 |           RigidSuper _ _ ->
43 |               return foundCycle
44 | 
45 |           Structure term ->
46 |               let newSeen = var : seen in
47 |               case term of
48 |                 App1 _ _ args ->
49 |                     foldrM (occursHelp newSeen) foundCycle args
50 | 
51 |                 Fun1 a b ->
52 |                     occursHelp newSeen a =<<
53 |                       occursHelp newSeen b foundCycle
54 | 
55 |                 EmptyRecord1 ->
56 |                     return foundCycle
57 | 
58 |                 Record1 fields ext ->
59 |                     occursHelp newSeen ext =<<
60 |                       foldrM (occursHelp newSeen) foundCycle (Map.elems fields)
61 | 
62 |                 Unit1 ->
63 |                     return foundCycle
64 | 
65 |                 Tuple1 a b maybeC ->
66 |                     case maybeC of
67 |                       Nothing ->
68 |                         occursHelp newSeen a =<<
69 |                           occursHelp newSeen b foundCycle
70 | 
71 |                       Just c ->
72 |                         occursHelp newSeen a =<<
73 |                           occursHelp newSeen b =<<
74 |                             occursHelp newSeen c foundCycle
75 | 
76 |           Alias _ _ args _ ->
77 |               foldrM (occursHelp (var:seen)) foundCycle (map snd args)
78 | 
79 |           Error ->
80 |               return foundCycle
81 | 


--------------------------------------------------------------------------------
/docs/elm.json/application.md:
--------------------------------------------------------------------------------
 1 | # `elm.json` for applications
 2 | 
 3 | This is a decent baseline for pretty much any applications made with Elm. You will need these dependencies or more.
 4 | 
 5 | ```json
 6 | {
 7 |     "type": "application",
 8 |     "source-directories": [
 9 |         "src"
10 |     ],
11 |     "elm-version": "0.19.1",
12 |     "dependencies": {
13 |         "direct": {
14 |             "elm/browser": "1.0.0",
15 |             "elm/core": "1.0.0",
16 |             "elm/html": "1.0.0",
17 |             "elm/json": "1.0.0"
18 |         },
19 |         "indirect": {
20 |             "elm/time": "1.0.0",
21 |             "elm/url": "1.0.0",
22 |             "elm/virtual-dom": "1.0.0"
23 |         }
24 |     },
25 |     "test-dependencies": {
26 |         "direct": {},
27 |         "indirect": {}
28 |     }
29 | }
30 | ```
31 | 
32 | 
33 | 34 | 35 | ## `"type"` 36 | 37 | Either `"application"` or `"package"`. All the other fields are based on this choice! 38 | 39 |
40 | 41 | 42 | ## `"source-directories"` 43 | 44 | A list of directories where Elm code lives. Most projects just use `"src"` for everything. 45 | 46 |
47 | 48 | 49 | ## `"elm-version"` 50 | 51 | The exact version of Elm this builds with. Should be `"0.19.1"` for most people! 52 | 53 |
54 | 55 | 56 | ## `"dependencies"` 57 | 58 | All the packages you depend upon. We use exact versions, so your `elm.json` file doubles as a "lock file" that ensures reliable builds. 59 | 60 | You can use modules from any `"direct"` dependency in your code. Some `"direct"` dependencies have their own dependencies that folks typically do not care about. These are the `"indirect"` dependencies. They are listed explicitly so that (1) builds are reproducible and (2) you can easily review the quantity and quality of dependencies. 61 | 62 | **Note:** We plan to eventually have a screen in `reactor` that helps add, remove, and upgrade packages. It can sometimes be tricky to keep all of the constraints happy, so we think having a UI will help a lot. If you get into trouble in the meantime, adding things back one-by-one often helps, and I hope you do not get into trouble! 63 | 64 |
65 | 66 | 67 | ## `"test-dependencies"` 68 | 69 | All the packages that you use in `tests/` with `elm-test` but not in the application you actually want to ship. This also uses exact versions to make tests more reliable. 70 | -------------------------------------------------------------------------------- /docs/elm.json/package.md: -------------------------------------------------------------------------------- 1 | # `elm.json` for packages 2 | 3 | This is roughly `elm.json` for the `elm/json` package: 4 | 5 | ```json 6 | { 7 | "type": "package", 8 | "name": "elm/json", 9 | "summary": "Encode and decode JSON values", 10 | "license": "BSD-3-Clause", 11 | "version": "1.0.0", 12 | "exposed-modules": [ 13 | "Json.Decode", 14 | "Json.Encode" 15 | ], 16 | "elm-version": "0.19.0 <= v < 0.20.0", 17 | "dependencies": { 18 | "elm/core": "1.0.0 <= v < 2.0.0" 19 | }, 20 | "test-dependencies": {} 21 | } 22 | ``` 23 | 24 |
25 | 26 | 27 | ## `"type"` 28 | 29 | Either `"application"` or `"package"`. All the other fields are based on this choice. 30 | 31 |
32 | 33 | 34 | ## `"name"` 35 | 36 | The name of a GitHub repo like `"elm-lang/core"` or `"rtfeldman/elm-css"`. 37 | 38 | > **Note:** We currently only support GitHub repos to ensure that there are no author name collisions. This seems like a pretty tricky problem to solve in a pleasant way. For example, do we have to keep an author name registry and give them out as we see them? But if someone is the same person on two platforms? And how to make this all happen in a way this is really nice for typical Elm users? Etc. So adding other hosting endpoints is harder than it sounds. 39 | 40 |
41 | 42 | 43 | ## `"summary"` 44 | 45 | A short summary that will appear on [`package.elm-lang.org`](https://package.elm-lang.org/) that describes what the package is for. Must be under 80 characters. 46 | 47 |
48 | 49 | 50 | ## `"license"` 51 | 52 | An OSI approved SPDX code like `"BSD-3-Clause"` or `"MIT"`. These are the two most common licenses in the Elm ecosystem, and BSD-3-Clause is a good default. But you can see the full list of options [here](https://spdx.org/licenses/). 53 | 54 |
55 | 56 | 57 | ## `"version"` 58 | 59 | All packages start at `"1.0.0"` and from there, Elm automatically enforces semantic versioning by comparing API changes. 60 | 61 | So if you make a PATCH change and call `elm bump` it will update you to `"1.0.1"`. And if you then decide to remove a function (a MAJOR change) and call `elm bump` it will update you to `"2.0.0"`. Etc. 62 | 63 |
64 | 65 | 66 | ## `"exposed-modules"` 67 | 68 | A list of modules that will be exposed to people using your package. The order you list them will be the order they appear on [`package.elm-lang.org`](https://package.elm-lang.org/). 69 | 70 | **Note:** If you have five or more modules, you can use a labelled list like [this](https://github.com/elm-lang/core/blob/master/elm.json). We show the labels on the package website to help people sort through larger packages with distinct categories. Labels must be under 20 characters. 71 | 72 |
73 | 74 | 75 | ## `"elm-version"` 76 | 77 | The range of Elm compilers that work with your package. Right now `"0.19.0 <= v < 0.20.0"` is always what you want for this. 78 | 79 |
80 | 81 | 82 | ## `"dependencies"` 83 | 84 | A list of packages that you depend upon. In each application, there can only be one version of each package, so wide ranges are great. Fewer dependencies is even better though! 85 | 86 | > **Note:** Dependency ranges should only express _tested_ ranges. It is not nice to use optimistic ranges and end up causing build failures for your users down the line. Eventually we would like to have an automated system that tries to build and test packages as new packages come out. If it all works, we could send a PR to the author widening the range. 87 | 88 |
89 | 90 | 91 | ## `"test-dependencies"` 92 | 93 | Dependencies that are only used in the `tests/` directory by `elm test`. Values from these packages will not appear in any final build artifacts. 94 | -------------------------------------------------------------------------------- /docs/upgrade-instructions/0.19.1.md: -------------------------------------------------------------------------------- 1 | # Upgrading to 0.19.1 2 | 3 | **There are no language changes**, so once you swap to `"elm-version": "0.19.1"` in your `elm.json`, most users should be able to proceed without any further code changes. **You may run into a handful of bugfixes though!** These cases are outlined below! 4 | 5 | 6 |
7 | 8 | ## Improvements 9 | 10 | - Parse error message quality (like [this](https://github.com/elm/error-message-catalog/issues/255) and [this](https://github.com/elm/error-message-catalog/issues/225)) 11 | - Faster compilation, especially for incremental compiles 12 | - Uses filelocks so that cached files are not corrupted when plugins run `elm make` multiple times on the same project at the same time. (Still worth avoiding that though!) 13 | - More intuitive multiline declarations in REPL 14 | - Various bug fixes (e.g. `--debug`, `x /= 0`, `type Height = Height Float` in `--optimize`) 15 | 16 | 17 |
18 | 19 | ## Detectable Bug Fixes 20 | 21 | There are three known cases where code that compiled with 0.19.0 will not compile with 0.19.1 due to bug fixes: 22 | 23 | 24 | ### 1. Ambiguous Imports 25 | 26 | Say you have an import like this: 27 | 28 | ```elm 29 | import Html exposing (min) 30 | import Regex exposing (never) 31 | 32 | x = min 33 | y = never 34 | ``` 35 | 36 | These should be reported as ambiguous usages since the names are also exposed by `Basics`, but there was a regression in 0.19.0 described [here](https://github.com/elm/compiler/issues/1945) such that they weren't caught in specific circumstances. 37 | 38 | The fix is to use a qualified name like `Html.min` or `Regex.never` to make it unambiguous. 39 | 40 | We found a couple instances of this in packages and have submitted PRs to the relevant authors in August 2019. You may run into this in your own code as well. 41 | 42 | For more details on why this is considered a regression, check out the details [here](https://github.com/elm/compiler/issues/1945#issuecomment-507871919) or try it in 0.18.0 to see how it worked before. 43 | 44 | 45 | ### 2. Tabs in Comments 46 | 47 | The 0.19.0 binaries did not catch tab characters in comments. The new parser is better at checking for tabs, so it will object when it finds these. 48 | 49 | Again, we found this in some packages and reached out to the relevant authors with PRs so patches would be published before the 0.19.1 release. 50 | 51 | 52 | ### 3. Port Module with no Ports 53 | 54 | If you have any files that start with: 55 | 56 | ```elm 57 | port module Main exposing (..) 58 | ``` 59 | 60 | But they do not actually have any `port` declarations, the 0.19.1 binary will ask you to switch to a normal module declaration like `module Main exposing (..)` 61 | -------------------------------------------------------------------------------- /hints/comparing-custom-types.md: -------------------------------------------------------------------------------- 1 | # Comparing Custom Types 2 | 3 | The built-in comparison operators work on a fixed set of types, like `Int` and `String`. That covers a lot of cases, but what happens when you want to compare custom types? 4 | 5 | This page aims to catalog these scenarios and offer alternative paths that can get you unstuck. 6 | 7 | 8 | ## Wrapped Types 9 | 10 | It is common to try to get some extra type safety by creating really simple custom types: 11 | 12 | ```elm 13 | type Id = Id Int 14 | type Age = Age Int 15 | 16 | type Comment = Comment String 17 | type Description = Description String 18 | ``` 19 | 20 | By wrapping the primitive values like this, the type system can now help you make sure that you never mix up a `Id` and an `Age`. Those are different types! This trick is extra cool because it has no runtime cost in `--optimize` mode. The compiler can just use an `Int` or `String` directly when you use that flag! 21 | 22 | The problem arises when you want to use a `Id` as a key in a dictionary. This is a totally reasonable thing to do, but the current version of Elm cannot handle this scenario. 23 | 24 | Instead of creating a `Dict Id Info` type, one thing you can do is create a custom data structure like this: 25 | 26 | ```elm 27 | module User exposing (Id, Table, empty, get, add) 28 | 29 | import Dict exposing (Dict) 30 | 31 | 32 | -- USER 33 | 34 | type Id = Id Int 35 | 36 | 37 | -- TABLE 38 | 39 | type Table info = 40 | Table Int (Dict Int info) 41 | 42 | empty : Table info 43 | empty = 44 | Table 0 Dict.empty 45 | 46 | get : Id -> Table info -> Maybe info 47 | get (Id id) (Table _ dict) = 48 | Dict.get id dict 49 | 50 | add : info -> Table info -> (Table info, Id) 51 | add info (Table nextId dict) = 52 | ( Table (nextId + 1) (Dict.insert nextId info dict) 53 | , Id nextId 54 | ) 55 | ``` 56 | 57 | There are a couple nice things about this approach: 58 | 59 | 1. The only way to get a new `User.Id` is to `add` information to a `User.Table`. 60 | 2. All the operations on a `User.Table` are explicit. Does it make sense to remove users? To merge two tables together? Are there any special details to consider in those cases? This will always be captured explicitly in the interface of the `User` module. 61 | 3. If you ever want to switch the internal representation from `Dict` to `Array` or something else, it is no problem. All the changes will be within the `User` module. 62 | 63 | So while this approach is not as convenient as using a `Dict` directly, it has some benefits of its own that can be helpful in some cases. 64 | 65 | 66 | ## Enumerations to Ints 67 | 68 | Say you need to define a `trafficLightToInt` function: 69 | 70 | ```elm 71 | type TrafficLight = Green | Yellow | Red 72 | 73 | trafficLightToInt : TrafficLight -> Int 74 | trafficLightToInt trafficLight = 75 | ??? 76 | ``` 77 | 78 | We have heard that some people would prefer to use a dictionary for this sort of thing. That way you do not need to write the numbers yourself, they can be generated such that you never have a typo. 79 | 80 | I would recommend using a `case` expression though: 81 | 82 | ```elm 83 | type TrafficLight = Green | Yellow | Red 84 | 85 | trafficLightToInt : TrafficLight -> Int 86 | trafficLightToInt trafficLight = 87 | case trafficLight of 88 | Green -> 1 89 | Yellow -> 2 90 | Red -> 3 91 | ``` 92 | 93 | This is really straight-forward while avoiding questions like “is `Green` less than or greater than `Red`?” 94 | 95 | 96 | ## Something else? 97 | 98 | If you have some other situation, please tell us about it [here](https://github.com/elm/error-message-catalog/issues). That is a log of error messages that can be improved, and we can use the particulars of your scenario to add more advice on this page! 99 | -------------------------------------------------------------------------------- /hints/comparing-records.md: -------------------------------------------------------------------------------- 1 | # Comparing Records 2 | 3 | The built-in comparison operators work on a fixed set of types, like `Int` and `String`. That covers a lot of cases, but what happens when you want to compare records? 4 | 5 | This page aims to catalog these scenarios and offer alternative paths that can get you unstuck. 6 | 7 | 8 | ## Sorting Records 9 | 10 | Say we want a `view` function that can show a list of students sorted by different characteristics. 11 | 12 | We could create something like this: 13 | 14 | ```elm 15 | import Html exposing (..) 16 | 17 | type alias Student = 18 | { name : String 19 | , age : Int 20 | , gpa : Float 21 | } 22 | 23 | type Order = Name | Age | GPA 24 | 25 | viewStudents : Order -> List Student -> Html msg 26 | viewStudents order students = 27 | let 28 | orderlyStudents = 29 | case order of 30 | Name -> List.sortBy .name students 31 | Age -> List.sortBy .age students 32 | GPA -> List.sortBy .gpa students 33 | in 34 | ul [] (List.map viewStudent orderlyStudents) 35 | 36 | viewStudent : Student -> Html msg 37 | viewStudent student = 38 | li [] [ text student.name ] 39 | ``` 40 | 41 | If you are worried about the performance of changing the order or updating information about particular students, you can start using the [`Html.Lazy`](https://package.elm-lang.org/packages/elm/html/latest/Html-Lazy) and [`Html.Keyed`](https://package.elm-lang.org/packages/elm/html/latest/Html-Keyed) modules. The updated code would look something like this: 42 | 43 | ```elm 44 | import Html exposing (..) 45 | import Html.Lazy exposing (lazy) 46 | import Html.Keyed as Keyed 47 | 48 | type Order = Name | Age | GPA 49 | 50 | type alias Student = 51 | { name : String 52 | , age : Int 53 | , gpa : Float 54 | } 55 | 56 | viewStudents : Order -> List Student -> Html msg 57 | viewStudents order students = 58 | let 59 | orderlyStudents = 60 | case order of 61 | Name -> List.sortBy .name students 62 | Age -> List.sortBy .age students 63 | GPA -> List.sortBy .gpa students 64 | in 65 | Keyed.ul [] (List.map viewKeyedStudent orderlyStudents) 66 | 67 | viewKeyedStudent : Student -> (String, Html msg) 68 | viewKeyedStudent student = 69 | ( student.name, lazy viewStudent student ) 70 | 71 | viewStudent : Student -> Html msg 72 | viewStudent student = 73 | li [] [ text student.name ] 74 | ``` 75 | 76 | By using `Keyed.ul` we help the renderer move the DOM nodes around based on their key. This makes it much cheaper to reorder a bunch of students. And by using `lazy` we help the renderer skip a bunch of work. If the `Student` is the same as last time, the render can skip over it. 77 | 78 | > **Note:** Some people are skeptical of having logic like this in `view` functions, but I think the alternative (maintaining sort order in your `Model`) has some serious downsides. Say a colleague is adding a message to `Add` students, but they do not know about the sort order rules needed for presentation. Bug! So in this alternate design, you must diligently test your `update` function to make sure that no message disturbs the sort order. This is bound to lead to bugs over time! 79 | > 80 | > With all the optimizations possible with `Html.Lazy` and `Html.Keyed`, I would always be inclined to work on optimizing my `view` functions rather than making my `update` functions more complicated and error prone. 81 | 82 | 83 | ## Something else? 84 | 85 | If you have some other situation, please tell us about it [here](https://github.com/elm/error-message-catalog/issues). That is a log of error messages that can be improved, and we can use the particulars of your scenario to add more advice on this page! 86 | -------------------------------------------------------------------------------- /hints/implicit-casts.md: -------------------------------------------------------------------------------- 1 | 2 | # Implicit Casts 3 | 4 | Many languages automatically convert from `Int` to `Float` when they think it is necessary. This conversion is often called an [implicit cast](https://en.wikipedia.org/wiki/Type_conversion). 5 | 6 | Languages that will add in implicit casts for addition include: 7 | 8 | - JavaScript 9 | - Python 10 | - Ruby 11 | - C 12 | - C++ 13 | - C# 14 | - Java 15 | - Scala 16 | 17 | These languages generally agree that an `Int` may be implicitly cast to a `Float` when necessary. So everyone is doing it, why not Elm?! 18 | 19 | ## Type Inference + Implicit Casts 20 | 21 | Elm comes from the ML-family of languages. Languages in the ML-family that **never** do implicit casts include: 22 | 23 | - Standard ML 24 | - OCaml 25 | - Elm 26 | - F# 27 | - Haskell 28 | 29 | Why would so many languages from this lineage require explicit conversions though? 30 | 31 | Well, we have to go back to the 1970s for some background. J. Roger Hindley and Robin Milner independently discovered an algorithm that could _efficiently_ figure out the type of everything in your program without any type annotations. Type Inference! Every ML-family language has some variation of this algorithm at the center of its design. 32 | 33 | For decades, the problem was that nobody could figure out how to combine type inference with implicit casts AND make the resulting algorithm efficient enough for daily use. As far as I know, Scala was the first widely known language to figure out how to combine these two things! Its creator, Martin Odersky did a lot of work on combining type inference and subtyping to make this possible. 34 | 35 | So for any ML-family language designed before Scala, it is safe to assume that implicit conversions just was not an option. Okay, but what about Elm?! It comes after Scala, so why not do it like them?! 36 | 37 | 1. You pay performance cost to mix type inference and implicit conversions. At least as far as anyone knows, it defeats an optimization that is crucial to getting _reliably_ good performance. It is fine in most cases, but it can be a real issue in very large code bases. 38 | 39 | 2. Based on experience reports from Scala users, it seemed like the convenience was not worth the hidden cost. Yes, you can convert `n` in `(n + 1.5)` and everything is nice, but when you are in larger programs that are sparsely annotated, it can be quite difficult to figure out what is going on. 40 | 41 | This user data may be confounded by the fact that Scala allows quite extensive conversions, not just from `Int` to `Float`, but I think it is worth taking seriously nonetheless. So it is _possible_, but it has tradeoffs. 42 | 43 | 44 | ## Conclusion 45 | 46 | First, based on the landscape of design possibilities, it seems like requiring _explicit_ conversions is a pretty nice balance. We can have type inference, it can produce friendly error messages, the algorithm is snappy, and an unintended implicit cast will not flow hundreds of lines before manifesting to the user. 47 | 48 | Second, Elm very much favors explicit code, so this also fits in with the overall spirit of the language and libraries. 49 | 50 | I hope that clarifies why you have to add those `toFloat` and `round` functions! It definitely can take some getting used to, but there are tons of folks who get past that acclimation period and really love the tradeoffs! 51 | -------------------------------------------------------------------------------- /hints/infinite-type.md: -------------------------------------------------------------------------------- 1 | 2 | # Hints for Infinite Types 3 | 4 | Infinite types are probably the trickiest kind of bugs to track down. **Writing down type annotations is usually the fastest way to figure them out.** Let's work through an example to get a feel for how these errors usually work though! 5 | 6 | 7 | ## Example 8 | 9 | A common way to get an infinite type error is very small typos. For example, do you see the problem in the following code? 10 | 11 | ```elm 12 | incrementNumbers list = 13 | List.map incrementNumbers list 14 | 15 | incrementNumber n = 16 | n + 1 17 | ``` 18 | 19 | The issue is that `incrementNumbers` calls itself, not the `incrementNumber` function defined below. So there is an extra `s` in this program! Let's focus on that: 20 | 21 | ```elm 22 | incrementNumbers list = 23 | List.map incrementNumbers list -- BUG extra `s` makes this self-recursive 24 | ``` 25 | 26 | Now the compiler does not know that anything is wrong yet. It just tries to figure out the types like normal. It knows that `incrementNumbers` is a function. The definition uses `List.map` so we can deduce that `list : List t1` and the result of this function call should be some other `List t2`. This also means that `incrementNumbers : List t1 -> List t2`. 27 | 28 | The issue is that `List.map` uses `incrementNumbers` on `list`! That means that each element of `list` (which has type `t1`) must be fed into `incrementNumbers` (which takes `List t1`) 29 | 30 | That means that `t1 = List t1`, which is an infinite type! If we start expanding this, we get `List (List (List (List (List ...))))` out to infinity! 31 | 32 | The point is mainly that we are in a confusing situation. The types are confusing. This explanation is confusing. The compiler is confused. It is a bad time. But luckily, the more type annotations you add, the better chance there is that you and the compiler can figure things out! So say we change our definition to: 33 | 34 | ```elm 35 | incrementNumbers : List Int -> List Int 36 | incrementNumbers list = 37 | List.map incrementNumbers list -- STILL HAS BUG 38 | ``` 39 | 40 | Now we are going to get a pretty normal type error. Hey, you said that each element in the `list` is an `Int` but I cannot feed that into a `List Int -> List Int` function! Something like that. 41 | 42 | In summary, the root issue is often some small typo, and the best way out is to start adding type annotations on everything! 43 | -------------------------------------------------------------------------------- /hints/init.md: -------------------------------------------------------------------------------- 1 | 2 | # Creating an Elm project 3 | 4 | The main goal of `elm init` is to get you to this page! 5 | 6 | It just creates an `elm.json` file and a `src/` directory for your code. 7 | 8 | 9 | ## What is `elm.json`? 10 | 11 | This file describes your project. It lists all of the packages you depend upon, so it will say the particular version of [`elm/core`](https://package.elm-lang.org/packages/elm/core/latest/) and [`elm/html`](https://package.elm-lang.org/packages/elm/html/latest/) that you are using. It makes builds reproducible! You can read a bit more about it [here](https://github.com/elm/compiler/blob/master/docs/elm.json/application.md). 12 | 13 | You should generally not edit it by hand. It is better to add new dependencies with commands like `elm install elm/http` or `elm install elm/json`. 14 | 15 | 16 | ## What goes in `src/`? 17 | 18 | This is where all of your Elm files live. It is best to start with a file called `src/Main.elm`. As you work through [the official guide](https://guide.elm-lang.org/), you can put the code examples in that `src/Main.elm` file. 19 | 20 | 21 | ## How do I compile it? 22 | 23 | Run `elm reactor` in your project. Now you can go to [`http://localhost:8000`](http://localhost:8000) and browse through all the files in your project. If you navigate to `.elm` files, it will compile them for you! 24 | 25 | If you want to do things more manually, you can run `elm make src/Main.elm` and it will produce an `index.html` file that you can look at in your browser. 26 | 27 | 28 | ## How do I structure my directories? 29 | 30 | Many folks get anxious about their project structure. “If I get it wrong, I am doomed!” This anxiety makes sense in languages where refactoring is risky, but Elm is not one of those languages! 31 | 32 | So we recommend that newcomers staying in one file until you get into the 600 to 1000 range. Push out of your comfort zone. Having the experience of being fine in large files will help you understand the boundaries in Elm, rather than just defaulting to the boundaries you learned in another language. 33 | 34 | The talk [The Life of a File](https://youtu.be/XpDsk374LDE) gets into this a lot more. The advice about building modules around a specific [custom type](https://guide.elm-lang.org/types/custom_types.html) is particularly important! You will see that emphasized a lot as you work through the official guide. 35 | 36 | 37 | ## How do I write tests? 38 | 39 | Elm will catch a bunch of errors statically, and I think it is worth skipping tests at first to get a feeling for when tests will actually help you _in Elm_. 40 | 41 | From there, we have a great testing package called [`elm-explorations/test`](https://github.com/elm-explorations/test) that can help you out! It is particularly helpful for teams working on a large codebase. When you are editing code you have never seen before, tests can capture additional details and constraints that are not otherwise apparent! 42 | 43 | 44 | ## How do I start fancier projects? 45 | 46 | I wanted `elm init` to generate as little code as possible. It is mainly meant to get you to this page! If you would like a more elaborate starting point, I recommend starting projects with commands like these: 47 | 48 | ```bash 49 | git clone https://github.com/evancz/elm-todomvc.git 50 | git clone https://github.com/rtfeldman/elm-spa-example.git 51 | ``` 52 | 53 | The idea is that Elm projects should be so simple that nobody needs a tool to generate a bunch of stuff. This also captures the fact that project structure _should_ evolve organically as your application develops, never ending up exactly the same as other projects. 54 | 55 | But if you have something particular you want, I recommend creating your own starter recipe and using `git clone` when you start new projects. That way (1) you can get exactly what you want and (2) we do not end up with a complex `elm init` that ends up being confusing for beginners! 56 | -------------------------------------------------------------------------------- /hints/missing-patterns.md: -------------------------------------------------------------------------------- 1 | 2 | # Hints for Missing Patterns 3 | 4 | Elm checks to make sure that all possible inputs to a function or `case` are handled. This gives us the guarantee that no Elm code is ever going to crash because data had an unexpected shape. 5 | 6 | There are a couple techniques for making this work for you in every scenario. 7 | 8 | 9 | ## The danger of wildcard patterns 10 | 11 | A common scenario is that you want to add a tag to a custom type that is used in a bunch of places. For example, maybe you are working different variations of users in a chat room: 12 | 13 | ```elm 14 | type User 15 | = Regular String Int 16 | | Anonymous 17 | 18 | toName : User -> String 19 | toName user = 20 | case user of 21 | Regular name _ -> 22 | name 23 | 24 | _ -> 25 | "anonymous" 26 | ``` 27 | 28 | Notice the wildcard pattern in `toName`. This will hurt us! Say we add a `Visitor String` variant to `User` at some point. Now we have a bug that visitor names are reported as `"anonymous"`, and the compiler cannot help us! 29 | 30 | So instead, it is better to explicitly list all possible variants, like this: 31 | 32 | ```elm 33 | type User 34 | = Regular String Int 35 | | Visitor String 36 | | Anonymous 37 | 38 | toName : User -> String 39 | toName user = 40 | case user of 41 | Regular name _ -> 42 | name 43 | 44 | Anonymous -> 45 | "anonymous" 46 | ``` 47 | 48 | Now the compiler will say "hey, what should `toName` do when it sees a `Visitor`?" This is a tiny bit of extra work, but it is very worth it! 49 | 50 | 51 | ## I want to go fast! 52 | 53 | Imagine that the `User` type appears in 20 or 30 functions across your project. When we add a `Visitor` variant, the compiler points out all the places that need to be updated. That is very convenient, but in a big project, maybe you want to get through it extra quickly. 54 | 55 | In that case, it can be helpful to use [`Debug.todo`](https://package.elm-lang.org/packages/elm-lang/core/latest/Debug#todo) to leave some code incomplete: 56 | 57 | ```elm 58 | type User 59 | = Regular String Int 60 | | Visitor String 61 | | Anonymous 62 | 63 | toName : User -> String 64 | toName user = 65 | case user of 66 | Regular name _ -> 67 | name 68 | 69 | Visitor _ -> 70 | Debug.todo "give the visitor name" 71 | 72 | Anonymous -> 73 | "anonymous" 74 | 75 | -- and maybe a bunch of other things 76 | ``` 77 | 78 | In this case it is easier to just write the implementation, but the point is that on more complex functions, you can put things off a bit. 79 | 80 | The Elm compiler is actually aware of `Debug.todo` so when it sees it in a `case` like this, it will crash with a bunch of helpful information. It will tell you: 81 | 82 | 1. The name of the module that contains the code. 83 | 2. The line numbers of the `case` containing the TODO. 84 | 3. The particular value that led to this TODO. 85 | 86 | From that information you have a pretty good idea of what went wrong and can go fix it. 87 | 88 | I tend to use `Debug.todo` as the message when my goal is to go quick because it makes it easy to go and find all remaining todos in my code before a release. 89 | 90 | 91 | ## A list that definitely is not empty 92 | 93 | This can come up from time to time, but Elm **will not** let you write code like this: 94 | 95 | ```elm 96 | last : List a -> a 97 | last list = 98 | case list of 99 | [x] -> 100 | x 101 | 102 | _ :: rest -> 103 | last rest 104 | ``` 105 | 106 | This is no good. It does not handle the empty list. There are two ways to handle this. One is to make the function return a `Maybe` like this: 107 | 108 | ```elm 109 | last : List a -> Maybe a 110 | last list = 111 | case list of 112 | [] -> 113 | Nothing 114 | 115 | [x] -> 116 | Just x 117 | 118 | _ :: rest -> 119 | last rest 120 | ``` 121 | 122 | This is nice because it lets users know that there might be a failure, so they can recover from it however they want. 123 | 124 | The other option is to “unroll the list” one level to ensure that no one can ever provide an empty list in the first place: 125 | 126 | ```elm 127 | last : a -> List a -> a 128 | last first rest = 129 | case rest of 130 | [] -> 131 | first 132 | 133 | newFirst :: newRest -> 134 | last newFirst newRest 135 | ``` 136 | 137 | By demanding the first element of the list as an argument, it becomes impossible to call this function if you have an empty list! 138 | 139 | This “unroll the list” trick is quite useful. I recommend using it directly, not through some external library. It is nothing special. Just a useful idea! 140 | -------------------------------------------------------------------------------- /hints/optimize.md: -------------------------------------------------------------------------------- 1 | 2 | # How to optimize Elm code 3 | 4 | When you are serving a website, there are two kinds of optimizations you want to do: 5 | 6 | 1. **Asset Size** — How can we send as few bits as possible? 7 | 2. **Performance** — How can those bits run as quickly as possible? 8 | 9 | It turns out that Elm does really well on both! We have [very small assets](https://elm-lang.org/news/small-assets-without-the-headache) and [very fast code](https://elm-lang.org/news/blazing-fast-html-round-two) when compared to the popular alternatives. 10 | 11 | Okay, but how do we get those numbers? 12 | 13 | 14 | ## Instructions 15 | 16 | Step one is to compile with the `--optimize` flag. This does things like shortening record field names and unboxing values. 17 | 18 | Step two is to call `uglifyjs` with a bunch of special flags. The flags unlock optimizations that are unreliable in normal JS code, but because Elm does not have side-effects, they work fine for us! 19 | 20 | Putting those together, here is how I would optimize `src/Main.elm` with two terminal commands: 21 | 22 | ```bash 23 | elm make src/Main.elm --optimize --output=elm.js 24 | uglifyjs elm.js --compress "pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe" | uglifyjs --mangle --output elm.min.js 25 | ``` 26 | 27 | After this you will have an `elm.js` and a significantly smaller `elm.min.js` file! 28 | 29 | **Note 1:** `uglifyjs` is called twice there. First to `--compress` and second to `--mangle`. This is necessary! Otherwise `uglifyjs` will ignore our `pure_funcs` flag. 30 | 31 | **Note 2:** If the `uglifyjs` command is not available in your terminal, you can run the command `npm install uglify-js --global` to download it. You probably already have `npm` from getting `elm repl` working, but if not, it is bundled with [nodejs](https://nodejs.org/). 32 | 33 | ## Scripts 34 | 35 | It is hard to remember all that, so it is probably a good idea to write a script that does it. 36 | 37 | I would maybe want to run `./optimize.sh src/Main.elm` and get out `elm.js` and `elm.min.js`, so on Mac or Linux, I would make a script called `optimize.sh` like this: 38 | 39 | ```bash 40 | #!/bin/sh 41 | 42 | set -e 43 | 44 | js="elm.js" 45 | min="elm.min.js" 46 | 47 | elm make --optimize --output=$js $@ 48 | 49 | uglifyjs $js --compress "pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe" | uglifyjs --mangle --output $min 50 | 51 | echo "Initial size: $(cat $js | wc -c) bytes ($js)" 52 | echo "Minified size:$(cat $min | wc -c) bytes ($min)" 53 | echo "Gzipped size: $(cat $min | gzip -c | wc -c) bytes" 54 | ``` 55 | 56 | It also prints out all the asset sizes for you! Your server should be configured to gzip the assets it sends, so the last line is telling you how many bytes would _actually_ get sent to the user. 57 | 58 | Again, the important commands are `elm` and `uglifyjs` which work on any platform, so it should not be too tough to do something similar on Windows. 59 | 60 | -------------------------------------------------------------------------------- /hints/port-modules.md: -------------------------------------------------------------------------------- 1 | 2 | # No Ports in Packages 3 | 4 | The package ecosystem is one of the most important parts of Elm. Right now, our ecosystem has some compelling benefits: 5 | 6 | - There are many obvious default packages that work well. 7 | - Adding dependencies cannot introduce runtime exceptions. 8 | - Patch changes cannot lead to surprise build failures. 9 | 10 | These are really important factors if you want to *quickly* create *reliable* applications. The Elm community thinks this is valuable. 11 | 12 | Other communities think that the *number* of packages is a better measure of ecosystem health. That is a fine metric to use, but it is not the one we use for Elm. We would rather have 50 great packages than 100k packages of wildly varying quality. 13 | 14 | 15 | ## So what about ports? 16 | 17 | Imagine you install a new package that claims to support `localStorage`. You get it set up, working through any compile errors. You run it, but it does not seem to work! After trying to figure it out for hours, you realize there is some poorly documented `port` to hook up... 18 | 19 | Okay, now you need to hook up some JavaScript code. Is that JS file in the Elm package? Or is it on `npm`? Wait, what version on `npm` though? And is this patch version going to work as well? Also, how does this file fit into my build process? And assuming we get through all that, maybe the `port` has the same name as one of the ports in your project. Or it clashes with a `port` name in another package. 20 | 21 | **Suddenly adding dependencies is much more complicated and risky!** An experienced developer would always check for ports up front, spending a bunch of time manually classifying unacceptable packages. Most people would not know to do that and learn all the pitfalls through personal experience, ultimately spending even *more* time than the person who defensively checks to avoid these issues. 22 | 23 | So “ports in packages” would impose an enormous cost on application developers, and in the end, we would have a less reliable package ecosystem overall. 24 | 25 | 26 | ## Conclusion 27 | 28 | Our wager with the Elm package ecosystem is that it is better to get a package *right* than to get it *right now*. So while we could use “ports in packages” as a way to get twenty `localStorage` packages of varying quality *right now*, we are choosing not to go that route. Instead we ask that developers use ports directly in their application code, getting the same result a different way. 29 | 30 | Now this may not be the right choice for your particular project, and that is okay! We will be expanding our core libraries over time, as explained [here](https://github.com/elm-lang/projects/blob/master/roadmap.md#where-is-the-localstorage-package), and we hope you will circle back later to see if Elm has grown into a better fit! 31 | 32 | If you have more questions about this choice or what it means for your application, please come ask in [the Elm slack](http://elmlang.herokuapp.com/). Folks are friendly and happy to help out! Chances are that a `port` in your application will work great for your case once you learn more about how they are meant to be used. 33 | -------------------------------------------------------------------------------- /hints/repl.md: -------------------------------------------------------------------------------- 1 | 2 | # REPL 3 | 4 | The REPL lets you interact with Elm values and functions in your terminal. 5 | 6 | 7 | ## Use 8 | 9 | You can type in expressions, definitions, custom types, and module imports using normal Elm syntax. 10 | 11 | ```elm 12 | > 1 + 1 13 | 2 : number 14 | 15 | > "hello" ++ "world" 16 | "helloworld" : String 17 | ``` 18 | 19 | The same can be done with definitions and custom types: 20 | 21 | ```elm 22 | > fortyTwo = 42 23 | 42 : number 24 | 25 | > increment n = n + 1 26 | : number -> number 27 | 28 | > increment 41 29 | 42 : number 30 | 31 | > factorial n = 32 | | if n < 1 then 33 | | 1 34 | | else 35 | | n * factorial (n-1) 36 | | 37 | : number -> number 38 | 39 | > factorial 5 40 | 120 : number 41 | 42 | > type User 43 | | = Regular String 44 | | | Visitor String 45 | | 46 | 47 | > case Regular "Tom" of 48 | | Regular name -> "Hey again!" 49 | | Visitor name -> "Nice to meet you!" 50 | | 51 | "Hey again!" : String 52 | ``` 53 | 54 | When you run `elm repl` in a project with an [`elm.json`](https://github.com/elm/compiler/blob/master/docs/elm.json/application.md) file, you can import any module available in the project. So if your project has an `elm/html` dependency, you could say: 55 | 56 | ```elm 57 | > import Html exposing (Html) 58 | 59 | > Html.text "hello" 60 | : Html msg 61 | 62 | > Html.text 63 | : String -> Html msg 64 | ``` 65 | 66 | If you create a module in your project named `MyThing` in your project, you can say `import MyThing` in the REPL as well. Any module that is accessible in your project should be accessible in the REPL. 67 | 68 | 69 | ## Exit 70 | 71 | To exit the REPL, you can type `:exit`. 72 | 73 | You can also press `ctrl-d` or `ctrl-c` on some platforms. 74 | -------------------------------------------------------------------------------- /hints/shadowing.md: -------------------------------------------------------------------------------- 1 | 2 | # Variable Shadowing 3 | 4 | Variable shadowing is when you define the same variable name twice in an ambiguous way. Here is a pretty reasonable use of shadowing: 5 | 6 | ```elm 7 | viewName : Maybe String -> Html msg 8 | viewName name = 9 | case name of 10 | Nothing -> 11 | ... 12 | 13 | Just name -> 14 | ... 15 | ``` 16 | 17 | I define a `name` with type `Maybe String` and then in that second branch, I define a `name` that is a `String`. Now that there are two `name` values, it is not 100% obvious which one you want in that second branch. 18 | 19 | Most linters produce warnings on variable shadowing, so Elm makes “best practices” the default. Just rename the first one to `maybeName` and move on. 20 | 21 | This choice is relatively uncommon in programming languages though, so I want to provide the reasoning behind it. 22 | 23 | 24 | ## The Cost of Shadowing 25 | 26 | The code snippet from above is the best case scenario for variable shadowing. It is pretty clear really. But that is because it is a fake example. It does not even compile. 27 | 28 | In a large module that is evolving over time, this is going to cause bugs in a very predictable way. You will have two definitions, separated by hundreds of lines. For example: 29 | 30 | ```elm 31 | name : String 32 | name = 33 | "Tom" 34 | 35 | -- hundreds of lines 36 | 37 | viewName : String -> Html msg 38 | viewName name = 39 | ... name ... name ... name ... 40 | ``` 41 | 42 | Okay, so the `viewName` function has an argument `name` and it uses it three times. Maybe the `viewName` function is 50 lines long in total, so those uses are not totally easy to see. This is fine so far, but say your colleague comes along five months later and wants to support first and last names. They refactor the code like this: 43 | 44 | ```elm 45 | viewName : String -> String -> Html msg 46 | viewName firstName lastName = 47 | ... name ... name ... name ... 48 | ``` 49 | 50 | The code compiles, but it does not work as intended. They forgot to change all the uses of `name`, and because it shadows the top-level `name` value, it always shows up as `"Tom"`. It is a simple mistake, but it is always the last thing I think of. 51 | 52 | > Is the data being fetched properly? Let me log all of the JSON requests. Maybe the JSON decoders are messed up? Hmm. Maybe someone is transforming the name in a bad way at some point? Let me check my `update` code. 53 | 54 | Basically, a bunch of time gets wasted on something that could easily be detected by the compiler. But this bug is rare, right? 55 | 56 | 57 | ## Aggregate Cost 58 | 59 | Thinking of a unique and helpful name takes some extra time. Maybe 30 seconds. But it means that: 60 | 61 | 1. Your code is easier to read and understand later on. So you spend 30 seconds once `O(1)` rather than spending 10 seconds each time someone reads that code in the future `O(n)`. 62 | 63 | 2. The tricky shadowing bug described above is impossible. Say there is a 5% chance that any given edit produces a shadowing bug, and that resolving that shadowing bug takes one hour. That means the expected time for each edit increases by three minutes. 64 | 65 | If you are still skeptical, I encourage you can play around with the number of edits, time costs, and probabilities here. When shadowing is not allowed, the resulting overhead for the entire lifetime of the code is the 30 seconds it takes to pick a better name, so that is what you need to beat! 66 | 67 | 68 | ## Summary 69 | 70 | Without shadowing, the code easier to read and folks spend less time on pointless debugging. The net outcome is that folks have more time to make something wonderful with Elm! 71 | -------------------------------------------------------------------------------- /hints/tuples.md: -------------------------------------------------------------------------------- 1 | 2 | # From Tuples to Records 3 | 4 | The largest tuple possible in Elm has three entries. Once you get to four, it is best to make a record with named entries. 5 | 6 | For example, it is _conceivable_ to represent a rectangle as four numbers like `(10,10,100,100)` but it would be more self-documenting to use a record like this: 7 | 8 | ```elm 9 | type alias Rectangle = 10 | { x : Float 11 | , y : Float 12 | , width : Float 13 | , height : Float 14 | } 15 | ``` 16 | 17 | Now it is clear that the dimensions should be `Float` values. It is also clear that we are not using the convention of specifying the top-left and bottom-right corners. It could be clearer about whether the `x` and `y` is the point in the top-left or in the middle though! 18 | 19 | Anyway, using records like this also gives you access to syntax like `rect.x`, `.x`, and `{ rect | x = 40 }`. It is not clear how to design features like that for arbitrarily sized tuples, so we did not. We already have a way, and it is more self-documenting! 20 | -------------------------------------------------------------------------------- /hints/type-annotations.md: -------------------------------------------------------------------------------- 1 | 2 | # Hints for Type Annotation Problems 3 | 4 | At the root of this kind of issue is always the fact that a type annotation in your code does not match the corresponding definition. Now that may mean that the type annotation is "wrong" or it may mean that the definition is "wrong". The compiler cannot figure out your intent, only that there is some mismatch. 5 | 6 | This document is going to outline the various things that can go wrong and show some examples. 7 | 8 | 9 | ## Annotation vs. Definition 10 | 11 | The most common issue is with user-defined type variables that are too general. So let's say you have defined a function like this: 12 | 13 | ```elm 14 | addPair : (a, a) -> a 15 | addPair (x, y) = 16 | x + y 17 | ``` 18 | 19 | The issue is that the type annotation is saying "I will accept a tuple containing literally *anything*" but the definition is using `(+)` which requires things to be numbers. So the compiler is going to infer that the true type of the definition is this: 20 | 21 | ```elm 22 | addPair : (number, number) -> number 23 | ``` 24 | 25 | So you will probably see an error saying "I cannot match `a` with `number`" which is essentially saying, you are trying to provide a type annotation that is **too general**. You are saying `addPair` accepts anything, but in fact, it can only handle numbers. 26 | 27 | In cases like this, you want to go with whatever the compiler inferred. It is good at figuring this kind of stuff out ;) 28 | 29 | 30 | ## Annotation vs. Itself 31 | 32 | It is also possible to have a type annotation that clashes with itself. This is probably more rare, but someone will run into it eventually. Let's use another version of `addPair` with problems: 33 | 34 | ```elm 35 | addPair : (Int, Int) -> number 36 | addPair (x, y) = 37 | x + y 38 | ``` 39 | 40 | In this case the annotation says we should get a `number` out, but because we were specific about the inputs being `Int`, the output should also be an `Int`. 41 | 42 | 43 | ## Annotation vs. Internal Annotation 44 | 45 | A quite tricky case is when an outer type annotation clashes with an inner type annotation. Here is an example of this: 46 | 47 | ```elm 48 | filter : (a -> Bool) -> List a -> List a 49 | filter isOkay list = 50 | let 51 | keepIfOkay : a -> Maybe a 52 | keepIfOkay x = 53 | if isOkay x then Just x else Nothing 54 | in 55 | List.filterMap keepIfOkay list 56 | ``` 57 | 58 | This case is very unfortunate because all the type annotations are correct, but there is a detail of how type inference works right now that **user-defined type variables are not shared between annotations**. This can lead to probably the worst type error messages we have because the problem here is that `a` in the outer annotation does not equal `a` in the inner annotation. 59 | 60 | For now the best route is to leave off the inner annotation. It is unfortunate, and hopefully we will be able to do a nicer thing in future releases. 61 | -------------------------------------------------------------------------------- /installers/README.md: -------------------------------------------------------------------------------- 1 | # Installing Elm 2 | 3 | The normal path is to work through [the guide](https://guide.elm-lang.org/) until you need to install, but you can skip to installation directly by going [here](https://guide.elm-lang.org/install/terminal.html). 4 | 5 | 6 |
7 | 8 | ## Installing Multiple Versions 9 | 10 | The secret is that Elm is just a single executable file. If you are developing a project in `~/Desktop/project/` you can download this file into that directory and run commands like `~/Desktop/project/elm make src/Main.elm` or `./elm make src/Main.elm`. You just run the local copy of the executable file! 11 | 12 | The instructions for [Mac][mac] and [Linux][lin] explain how to do this in more detail. You can follow the same steps on Windows, but you need to do each step by hand. (E.g. download the file through your browser rather than with a terminal command.) 13 | 14 | [mac]: https://github.com/elm/compiler/blob/master/installers/mac/README.md 15 | [lin]: https://github.com/elm/compiler/blob/master/installers/linux/README.md 16 | 17 |
18 | 19 | ## Installing Previous Versions 20 | 21 | The past binaries for Mac, Linux, and Windows are hosted [here](https://github.com/elm/compiler/releases). 22 | 23 | You can download the executable files directly and use them locally. 24 | 25 | 26 |
27 | 28 | ## Uninstall 29 | 30 | - [Mac](https://github.com/elm/compiler/blob/master/installers/mac/README.md#uninstall) 31 | - [Linux](https://github.com/elm/compiler/blob/master/installers/linux/README.md#uninstall) 32 | - [Windows](https://github.com/elm/compiler/blob/master/installers/win/README.md#uninstall) 33 | -------------------------------------------------------------------------------- /installers/linux/Dockerfile: -------------------------------------------------------------------------------- 1 | # Based initially on https://gist.github.com/rlefevre/1523f47e75310e28eee243c9c5651ac9 2 | # 3 | # Build Linux x64 binary from elm compiler top-level directory: 4 | # $ docker build -t elm -f installers/linux/Dockerfile . 5 | # 6 | # Retrieve elm Linux binary: 7 | # $ docker cp $(docker create elm):/usr/local/bin/elm DESTINATION_DIRECTORY 8 | # 9 | # Delete docker elm image: 10 | # $ docker rmi elm 11 | # 12 | # Display all images: 13 | # $ docker images -a 14 | # 15 | # Delete all unused docker images: 16 | # $ docker system prune -a 17 | 18 | # Use Alpine 3.11 with GHC 8.6.5 19 | FROM alpine:3.11 20 | 21 | # Install packages required to build elm 22 | RUN apk add --no-cache ghc cabal wget musl-dev zlib-dev zlib-static ncurses-dev ncurses-static 23 | 24 | WORKDIR /elm 25 | 26 | # Import source code 27 | COPY builder builder 28 | COPY compiler compiler 29 | COPY reactor reactor 30 | COPY terminal terminal 31 | COPY cabal.config elm.cabal LICENSE ./ 32 | 33 | # Build statically linked elm binary 34 | RUN cabal new-update 35 | RUN cabal new-build --ghc-option=-optl=-static --ghc-option=-split-sections 36 | RUN cp ./dist-newstyle/build/x86_64-linux/ghc-*/elm-*/x/elm/build/elm/elm /usr/local/bin/elm 37 | 38 | # Remove debug symbols to optimize the binary size 39 | RUN strip -s /usr/local/bin/elm 40 | -------------------------------------------------------------------------------- /installers/linux/README.md: -------------------------------------------------------------------------------- 1 | # Install Instructions 2 | 3 | The pre-compiled binary for Linux works on a very wide range of distributions. 4 | 5 | It should be possible to install it by running the following commands in your terminal: 6 | 7 | ```bash 8 | # Move to your Desktop so you can see what is going on easier. 9 | # 10 | cd ~/Desktop/ 11 | 12 | # Download the 0.19.1 binary for Linux. 13 | # 14 | # +-----------+----------------------+ 15 | # | FLAG | MEANING | 16 | # +-----------+----------------------+ 17 | # | -L | follow redirects | 18 | # | -o elm.gz | name the file elm.gz | 19 | # +-----------+----------------------+ 20 | # 21 | curl -L -o elm.gz https://github.com/elm/compiler/releases/download/0.19.1/binary-for-linux-64-bit.gz 22 | 23 | # There should now be a file named `elm.gz` on your Desktop. 24 | # 25 | # The downloaded file is compressed to make it faster to download. 26 | # This next command decompresses it, replacing `elm.gz` with `elm`. 27 | # 28 | gunzip elm.gz 29 | 30 | # There should now be a file named `elm` on your Desktop! 31 | # 32 | # Every file has "permissions" about whether it can be read, written, or executed. 33 | # So before we use this file, we need to mark this file as executable: 34 | # 35 | chmod +x elm 36 | 37 | # The `elm` file is now executable. That means running `~/Desktop/elm --help` 38 | # should work. Saying `./elm --help` works the same. 39 | # 40 | # But we want to be able to say `elm --help` without specifying the full file 41 | # path every time. We can do this by moving the `elm` binary to one of the 42 | # directories listed in your `PATH` environment variable: 43 | # 44 | sudo mv elm /usr/local/bin/ 45 | 46 | # Now it should be possible to run the `elm` binary just by saying its name! 47 | # 48 | elm --help 49 | ``` 50 | 51 |
52 | 53 | ## Wait, what is the `PATH` variable? 54 | 55 | When you run a command like `elm make src/Main.elm`, your computer starts by trying to find an executable file called `elm`. 56 | 57 | The `PATH` is the list of directories that get searched. You can see these directories by running: 58 | 59 | ```bash 60 | echo $PATH 61 | ``` 62 | 63 | This prints `/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin` on my computer. The directories are separated by a `:` so there are five possibilities listed here. 64 | 65 | When I run `elm make src/Main.elm`, my terminal starts by searching these five directories for an executable file named `elm`. It finds `/usr/local/bin/elm` and then runs `/usr/local/bin/elm make src/Main.elm` with whatever arguments I gave. 66 | 67 | So the `PATH` environment variable is a convention that allows you to refer to a specific executable file without knowing exactly where it lives on your computer. This is actually how all "terminal commands" work! Commands like `ls` are really executable files that live in directories listed in your `PATH` variable. 68 | 69 | So the point of running `sudo mv elm /usr/local/bin/` is to turn the `elm` binary into a terminal command, allowing us to call it just like `ls` and `cd`. 70 | 71 | **Note:** Why do we need to use `sudo` for that one command? Imagine if some program was able to add executables named `ls` or `cd` to `/usr/local/bin` that did something tricky and unexpected. That would be a security problem! Many distributions make this scenario less likely by requiring special permissions to modify the `/usr/local/bin/` directory. 72 | 73 | 74 |
75 | 76 | ## Uninstall 77 | 78 | The following commands should remove everything: 79 | 80 | ```bash 81 | # Remove the `elm` executable. 82 | # 83 | sudo rm /usr/local/bin/elm 84 | 85 | # Remove any cached files. The files here reduce compile times when 86 | # starting new projects and make it possible to work offline in more 87 | # cases. No need to keep it around if you are uninstalling though! 88 | # 89 | rm -r ~/.elm/ 90 | ``` 91 | 92 | If you have any Elm projects still on your computer, you can remove their `elm-stuff/` directories as well. 93 | 94 | -------------------------------------------------------------------------------- /installers/mac/Distribution.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Elm 4 | 5 | 6 | 7 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | binaries.pkg 33 | 34 | -------------------------------------------------------------------------------- /installers/mac/README.md: -------------------------------------------------------------------------------- 1 | # Install Instructions 2 | 3 | The easiest way to install is to to use [the Mac installer](https://github.com/elm/compiler/releases/download/0.19.1/installer-for-mac.pkg)! 4 | 5 | But it is also possible to install by running the following commands in your terminal: 6 | 7 | ```bash 8 | # Move to your Desktop so you can see what is going on easier. 9 | # 10 | cd ~/Desktop/ 11 | 12 | # Download the 0.19.1 binary for Linux. 13 | # 14 | # +-----------+----------------------+ 15 | # | FLAG | MEANING | 16 | # +-----------+----------------------+ 17 | # | -L | follow redirects | 18 | # | -o elm.gz | name the file elm.gz | 19 | # +-----------+----------------------+ 20 | # 21 | curl -L -o elm.gz https://github.com/elm/compiler/releases/download/0.19.1/binary-for-mac-64-bit.gz 22 | 23 | # There should now be a file named `elm.gz` on your Desktop. 24 | # 25 | # The downloaded file is compressed to make it faster to download. 26 | # This next command decompresses it, replacing `elm.gz` with `elm`. 27 | # 28 | gunzip elm.gz 29 | 30 | # There should now be a file named `elm` on your Desktop! 31 | # 32 | # Every file has "permissions" about whether it can be read, written, or executed. 33 | # So before we use this file, we need to mark this file as executable: 34 | # 35 | chmod +x elm 36 | 37 | # The `elm` file is now executable. That means running `~/Desktop/elm --help` 38 | # should work. Saying `./elm --help` works the same. 39 | # 40 | # But we want to be able to say `elm --help` without specifying the full file 41 | # path every time. We can do this by moving the `elm` binary to one of the 42 | # directories listed in your `PATH` environment variable: 43 | # 44 | sudo mv elm /usr/local/bin/ 45 | 46 | # Now it should be possible to run the `elm` binary just by saying its name! 47 | # 48 | elm --help 49 | ``` 50 | 51 |
52 | 53 | ## What is the `PATH` variable? 54 | 55 | When you run a command like `elm make src/Main.elm`, your computer starts by trying to find an executable file called `elm`. 56 | 57 | The `PATH` is the list of directories that get searched. You can see these directories by running: 58 | 59 | ```bash 60 | echo $PATH 61 | ``` 62 | 63 | This prints `/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin` on my computer. The directories are separated by a `:` so there are five possibilities listed here. 64 | 65 | When I run `elm make src/Main.elm`, my terminal starts by searching these five directories for an executable file named `elm`. It finds `/usr/local/bin/elm` and then runs `/usr/local/bin/elm make src/Main.elm` with whatever arguments I gave. 66 | 67 | So the `PATH` environment variable is a convention that allows you to refer to a specific executable file without knowing exactly where it lives on your computer. This is actually how all "terminal commands" work! Commands like `ls` are really executable files that live in directories listed in your `PATH` variable. 68 | 69 | So the point of running `sudo mv elm /usr/local/bin/` is to turn the `elm` binary into a terminal command, allowing us to call it just like `ls` and `cd`. 70 | 71 | **Note:** Why do we need to use `sudo` for that one command? Imagine if some program was able to add executables named `ls` or `cd` to `/usr/local/bin` that did something tricky and unexpected. That would be a security problem! Many distributions make this scenario less likely by requiring special permissions to modify the `/usr/local/bin/` directory. 72 | 73 | 74 |
75 | 76 | ## Uninstall 77 | 78 | The following commands should remove everything: 79 | 80 | ```bash 81 | # Remove the `elm` executable. 82 | # 83 | sudo rm /usr/local/bin/elm 84 | 85 | # Remove any cached files. The files here reduce compile times when 86 | # starting new projects and make it possible to work offline in more 87 | # cases. No need to keep it around if you are uninstalling though! 88 | # 89 | rm -r ~/.elm/ 90 | ``` 91 | 92 | If you have any Elm projects still on your computer, you can remove their `elm-stuff/` directories as well. 93 | 94 | -------------------------------------------------------------------------------- /installers/mac/Resources/en.lproj/conclusion.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf2509 2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fmodern\fcharset0 CourierNewPSMT;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\expandedcolortbl;;} 5 | \paperw11900\paperh16840\margl1440\margr1440\vieww11180\viewh8400\viewkind0 6 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 7 | 8 | \f0\fs28 \cf0 Try opening the terminal and running commands like:\ 9 | \ 10 | 11 | \f1 elm init\ 12 | elm make src/Main.elm --optimize\ 13 | elm repl 14 | \f0 \ 15 | \ 16 | Check out {\field{\*\fldinst{HYPERLINK "https://guide.elm-lang.org/"}}{\fldrslt this tutorial}} for more advice!} -------------------------------------------------------------------------------- /installers/mac/Resources/en.lproj/welcome.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf2509 2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fmodern\fcharset0 CourierNewPSMT;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\expandedcolortbl;;} 5 | \paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 6 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 7 | 8 | \f0\fs28 \cf0 Thank you for trying out Elm!\ 9 | \ 10 | This installer makes 11 | \f1 elm 12 | \f0 available in your terminal.} -------------------------------------------------------------------------------- /installers/mac/helper-scripts/elm-startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | open 'http://guide.elm-lang.org' 4 | -------------------------------------------------------------------------------- /installers/mac/helper-scripts/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "Warning: You are about to remove all Elm executables!" 6 | 7 | installdir=/usr/local/bin 8 | 9 | for bin in elm elm-compiler elm-get elm-reactor elm-repl elm-doc elm-server elm-package elm-make 10 | do 11 | if [ -f $installdir/$bin ]; then 12 | sudo rm -f $installdir/$bin 13 | fi 14 | if [ -f $installdir/$bin-unwrapped ]; then 15 | sudo rm -f $installdir/$bin-unwrapped 16 | fi 17 | 18 | done 19 | 20 | sharedir=/usr/local/share/elm 21 | sudo rm -rf $sharedir 22 | -------------------------------------------------------------------------------- /installers/mac/make-installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run the following command to create an installer: 3 | # 4 | # bash make-installer.sh 5 | # 6 | 7 | 8 | 9 | #### SETUP #### 10 | 11 | set -e 12 | 13 | # Create directory structure for new pkgs 14 | pkg_root=$(mktemp -d -t package-artifacts) 15 | pkg_binaries=$pkg_root 16 | pkg_scripts=$pkg_root/Scripts 17 | 18 | mkdir -p $pkg_binaries 19 | mkdir -p $pkg_scripts 20 | 21 | usr_binaries=/usr/local/bin 22 | 23 | 24 | #### BUILD ASSETS #### 25 | 26 | cp ../../dist/build/elm/elm $pkg_binaries/elm 27 | 28 | cp $(pwd)/preinstall $pkg_scripts 29 | cp $(pwd)/postinstall $pkg_scripts 30 | 31 | pkgbuild \ 32 | --sign "Developer ID Installer: " \ 33 | --identifier org.elm-lang.binary \ 34 | --install-location $usr_binaries \ 35 | --scripts $pkg_scripts \ 36 | --filter 'Scripts.*' \ 37 | --root $pkg_root \ 38 | binaries.pkg 39 | 40 | 41 | #### BUNDLE ASSETS #### 42 | 43 | rm -f installer-for-mac.pkg 44 | 45 | productbuild \ 46 | --sign "Developer ID Installer: " \ 47 | --identifier org.elm-lang.installer \ 48 | --distribution Distribution.xml \ 49 | --package-path . \ 50 | --resources Resources \ 51 | installer-for-mac.pkg 52 | 53 | 54 | #### CLEAN UP #### 55 | 56 | rm binaries.pkg 57 | rm -rf $pkg_root 58 | 59 | 60 | #### BEGIN NOTARIZATION #### 61 | 62 | xcrun altool \ 63 | --notarize-app \ 64 | --primary-bundle-id "org.elm-lang.installer" \ 65 | --username "" \ 66 | --password "@keychain:Developer-altool" \ 67 | --file "installer-for-mac.pkg" 68 | 69 | # From https://scriptingosx.com/2019/09/notarize-a-command-line-tool/ 70 | # 71 | #### Check on notarization: 72 | # 73 | # xcrun altool \ 74 | # --notarization-info "" \ 75 | # --username "" \ 76 | # --password "@keychain:Developer-altool" 77 | # 78 | # 79 | #### Staple Notarization: 80 | # 81 | # xcrun stapler staple installer-for-mac.pkg 82 | -------------------------------------------------------------------------------- /installers/mac/postinstall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | echo "$(date)" > /tmp/elm-installer.log 6 | -------------------------------------------------------------------------------- /installers/mac/preinstall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | installdir=/usr/local/bin 6 | 7 | for bin in elm elm-compiler elm-package elm-reactor elm-repl 8 | do 9 | if [ -f $installdir/$bin ]; then 10 | sudo rm -f $installdir/$bin 11 | fi 12 | if [ -f $installdir/$bin-unwrapped ]; then 13 | sudo rm -f $installdir/$bin-unwrapped 14 | fi 15 | done 16 | 17 | sharedir=/usr/local/share/elm 18 | sudo rm -rf $sharedir 19 | -------------------------------------------------------------------------------- /installers/npm/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages/*/elm 3 | packages/*/elm.exe 4 | -------------------------------------------------------------------------------- /installers/npm/.npmignore: -------------------------------------------------------------------------------- 1 | README.md 2 | .gitignore 3 | .git 4 | -------------------------------------------------------------------------------- /installers/npm/README.md: -------------------------------------------------------------------------------- 1 | # Elm Installer 2 | 3 | [Elm](https://elm-lang.org) is a functional programming language that compiles to JavaScript. 4 | 5 | Head over to [The Official Guide](https://guide.elm-lang.org/) to start learning Elm! 6 | 7 | 8 |
9 | 10 | ## What is this package for? 11 | 12 | For normal installs, I reccomend using the instructions [here](https://guide.elm-lang.org/install/elm.html) instead. This package is only for people who enjoy using `npm` even when it is not necessary, or for people who want to use `npm` for certain scenarios such as: 13 | 14 | **Multiple versions** 15 | 16 | People using Elm at work may use different versions of Elm in different projects. They can run `npm install elm@latest-0.19.1` in each project and use the binary at `./node_modules/.bin/elm` for compilation. 17 | 18 | **Continuous integration** 19 | 20 | The `npm` installer works for this, but there are faster and more reliable options: 21 | 22 | 1. You can download `elm` directly from GitHub with [this script](https://github.com/elm/compiler/blob/master/installers/linux/README.md). This allows you to skip `npm` entirely. 23 | 2. Many continuous integration have ways to cache files ([example](https://docs.travis-ci.com/user/caching/)) to make builds faster and more reliable. This is the ideal setup. 24 | 25 | That said, it works to use the `npm` installer on CI if you prefer that option. 26 | 27 | 28 |
29 | 30 | ## Install Locally 31 | 32 | The following command should download the latest Elm 0.19.1 binary: 33 | 34 | ``` 35 | npm install elm@latest-0.19.1 36 | ``` 37 | 38 | You should be able to run `./node_modules/bin/elm --version` within your project and see `0.19.1`. Now you can compile with `./node_modules/bin/elm make src/Main.elm` and not disrupt other packages. 39 | 40 | Use `npm install elm@latest-0.19.0` or `npm install elm@latest-0.18.0` for earlier versions. 41 | 42 | **Note:** The `latest-X.Y.Z` convention is used in case we need to publish patches for the `npm` installer within a given Elm release. For example, say `npm` decides that some transitive dependency is not secure. Nothing is changing about Elm or the binaries, but we need to publish a new `npm` installer that fixes this issue. 43 | 44 | -------------------------------------------------------------------------------- /installers/npm/bin/elm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var child_process = require('child_process'); 4 | 5 | 6 | // Some npm users enable --ignore-scripts (a good security measure) so 7 | // they do not run the post-install hook and install.js does not run. 8 | // Instead they will run this script. 9 | // 10 | // On Mac and Linux, we hard link the elm executable into the exact same 11 | // location as this file. Since npm uses symlinks on these platforms, 12 | // that means that the first run will invoke this file and subsequent 13 | // runs will call the elm binary directly. 14 | // 15 | // On Windows, our binary file must be named elm.exe for it to run properly. 16 | // Instead of symlinks, npm creates two files: 17 | // 18 | // - node_modules/.bin/elm (a bash file) 19 | // - node_modules/.bin/elm.cmd (a batch file) 20 | // 21 | // Both files specifically invoke `node` to run the file listed at package.bin, 22 | // so there is no way around instantiating node for no reason on Windows. 23 | 24 | 25 | var binaryPath = require('../binary.js')(); 26 | child_process 27 | .spawn(binaryPath, process.argv.slice(2), { stdio: 'inherit' }) 28 | .on('exit', process.exit); 29 | -------------------------------------------------------------------------------- /installers/npm/binary.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var package = require('./package.json'); 3 | var path = require('path'); 4 | 5 | 6 | 7 | // MAIN 8 | // 9 | // This function is used by install.js and by the bin/elm backup that gets 10 | // called when --ignore-scripts is enabled. 11 | 12 | 13 | module.exports = function() 14 | { 15 | // figure out package of binary 16 | var version = package.version.replace(/^(\d+\.\d+\.\d+).*$/, '$1'); // turn '1.2.3-alpha' into '1.2.3' 17 | var subPackageName = '@elm_binaries/' + process.platform + '_' + process.arch; 18 | 19 | verifyPlatform(version, subPackageName); 20 | 21 | var fileName = process.platform === 'win32' ? 'elm.exe' : 'elm'; 22 | 23 | try 24 | { 25 | var subBinaryPath = require.resolve(subPackageName + '/' + fileName); 26 | } 27 | catch (error) 28 | { 29 | if (error && error.code === 'MODULE_NOT_FOUND') 30 | { 31 | exitFailure(version, missingSubPackageHelp(subPackageName)); 32 | } 33 | else 34 | { 35 | exitFailure(version, 'I had trouble requiring the binary package for your platform (' + subPackageName + '):\n\n' + error); 36 | } 37 | } 38 | 39 | // Yarn 2 and later ("Berry") always invokes `node` (regardless of configuration) 40 | // so we cannot do any optimizations there 41 | var isYarnBerry = /\byarn\/(?!1\.)/.test(process.env.npm_config_user_agent || ""); 42 | 43 | // as mentioned in bin/elm we cannot do any optimizations on Windows 44 | if (process.platform === 'win32' || isYarnBerry) 45 | { 46 | return subBinaryPath; 47 | } 48 | 49 | // figure out where to put the binary 50 | var binaryPath = path.resolve(__dirname, package.bin.elm); 51 | var tmpPath = binaryPath + '.tmp'; 52 | 53 | // optimize by replacing the JS bin/elm with the native binary directly 54 | try 55 | { 56 | // atomically replace the file with a hard link to the binary 57 | fs.linkSync(subBinaryPath, tmpPath); 58 | fs.renameSync(tmpPath, binaryPath); 59 | } 60 | catch (error) 61 | { 62 | exitFailure(version, 'I had some trouble writing file to disk. It is saying:\n\n' + error); 63 | } 64 | 65 | return binaryPath; 66 | } 67 | 68 | 69 | 70 | // VERIFY PLATFORM 71 | 72 | 73 | function verifyPlatform(version, subPackageName) 74 | { 75 | if (subPackageName in package.optionalDependencies) return; 76 | 77 | var situation = process.platform + '_' + process.arch; 78 | console.error( 79 | '-- ERROR -----------------------------------------------------------------------\n\n' 80 | + 'I am detecting that your computer (' + situation + ') may not be compatible with any\n' 81 | + 'of the official pre-built binaries.\n\n' 82 | + 'I recommend against using the npm installer for your situation. Check out the\n' 83 | + 'alternative installers at https://github.com/elm/compiler/releases/tag/' + version + '\n' 84 | + 'to see if there is something that will work better for you.\n\n' 85 | + 'From there I recommend asking for guidance on Slack or Discourse to find someone\n' 86 | + 'who can help with your specific situation.\n\n' 87 | + '--------------------------------------------------------------------------------\n' 88 | ); 89 | process.exit(1); 90 | } 91 | 92 | 93 | 94 | // EXIT FAILURE 95 | 96 | 97 | function exitFailure(version, message) 98 | { 99 | console.error( 100 | '-- ERROR -----------------------------------------------------------------------\n\n' 101 | + message 102 | + '\n\nNOTE: You can avoid npm entirely by downloading directly from:\n' 103 | + 'https://github.com/elm/compiler/releases/tag/' + version + '\n' 104 | + 'All this package does is distribute a file from there.\n\n' 105 | + '--------------------------------------------------------------------------------\n' 106 | ); 107 | process.exit(1); 108 | } 109 | 110 | 111 | 112 | // MISSING SUB PACKAGE HELP 113 | 114 | 115 | function missingSubPackageHelp(subPackageName) 116 | { 117 | return ( 118 | 'I tried to get `elm` from ' + subPackageName + ', but something went wrong.\n' 119 | + 'This can happen if you use the "--omit=optional" or "--no-optional" npm flag, or\n' 120 | + 'if your "node_modules" folder was copied over from a different computer (or VM).\n' 121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /installers/npm/install.js: -------------------------------------------------------------------------------- 1 | require('./binary.js')(); 2 | -------------------------------------------------------------------------------- /installers/npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elm", 3 | "version": "0.19.1-6", 4 | "description": "Installer for Elm: just downloads the binary into node_modules", 5 | "license": "BSD-3-Clause", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/elm/compiler.git" 9 | }, 10 | "homepage": "https://github.com/elm/compiler/tree/master/installers/npm", 11 | "bugs": "https://github.com/elm/compiler/issues", 12 | "author": { 13 | "name": "Evan Czaplicki", 14 | "email": "evan@elm-lang.org", 15 | "url": "https://github.com/evancz" 16 | }, 17 | "engines": { 18 | "node": ">=7.0.0" 19 | }, 20 | "scripts": { 21 | "install": "node install.js" 22 | }, 23 | "files": [ 24 | "install.js", 25 | "binary.js", 26 | "bin", 27 | "bin/elm" 28 | ], 29 | "keywords": [ 30 | "bin", 31 | "binary", 32 | "binaries", 33 | "elm", 34 | "install", 35 | "installer" 36 | ], 37 | "bin": { "elm": "bin/elm" }, 38 | "optionalDependencies": { 39 | "@elm_binaries/darwin_arm64": "0.19.1-0", 40 | "@elm_binaries/darwin_x64": "0.19.1-0", 41 | "@elm_binaries/linux_x64": "0.19.1-0", 42 | "@elm_binaries/win32_x64": "0.19.1-0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /installers/npm/packages/darwin_arm64/README.md: -------------------------------------------------------------------------------- 1 | # Elm Binary for macOS (arm64) 2 | 3 | Some people install [Elm](https://elm-lang.org/) with `npm`. This package helps make [`npm install elm`](https://www.npmjs.com/package/elm) a bit faster and a bit more reliable. It is not intended for direct use! 4 | 5 | If you do not need to use `npm`, the official binaries are published via [GitHub releases](https://github.com/elm/compiler/releases) with installation instructions. 6 | -------------------------------------------------------------------------------- /installers/npm/packages/darwin_arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@elm_binaries/darwin_arm64", 3 | "version": "0.19.1-0", 4 | "description": "Download the Elm binary for macOS (arm64)", 5 | "repository": "https://github.com/elm/compiler", 6 | "license": "BSD-3-Clause", 7 | "os": [ "darwin" ], 8 | "cpu": [ "arm64" ] 9 | } 10 | -------------------------------------------------------------------------------- /installers/npm/packages/darwin_x64/README.md: -------------------------------------------------------------------------------- 1 | # Elm Binary for macOS (x64) 2 | 3 | Some people install [Elm](https://elm-lang.org/) with `npm`. This package helps make [`npm install elm`](https://www.npmjs.com/package/elm) a bit faster and a bit more reliable. It is not intended for direct use! 4 | 5 | If you do not need to use `npm`, the official binaries are published via [GitHub releases](https://github.com/elm/compiler/releases) with installation instructions. 6 | -------------------------------------------------------------------------------- /installers/npm/packages/darwin_x64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@elm_binaries/darwin_x64", 3 | "version": "0.19.1-0", 4 | "description": "Download the Elm binary for macOS (x64)", 5 | "repository": "https://github.com/elm/compiler", 6 | "license": "BSD-3-Clause", 7 | "os": [ "darwin" ], 8 | "cpu": [ "x64" ] 9 | } 10 | -------------------------------------------------------------------------------- /installers/npm/packages/linux_arm64/README.md: -------------------------------------------------------------------------------- 1 | # Elm Binary for Linux (arm64) 2 | 3 | Some people install [Elm](https://elm-lang.org/) with `npm`. This package helps make [`npm install elm`](https://www.npmjs.com/package/elm) a bit faster and a bit more reliable. It is not intended for direct use! 4 | 5 | If you do not need to use `npm`, the official binaries are published via [GitHub releases](https://github.com/elm/compiler/releases) with installation instructions. 6 | -------------------------------------------------------------------------------- /installers/npm/packages/linux_arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@elm_binaries/linux_arm64", 3 | "version": "0.19.1-0", 4 | "description": "Download the Elm binary for Linux (arm64)", 5 | "repository": "https://github.com/elm/compiler", 6 | "license": "BSD-3-Clause", 7 | "os": [ "linux" ], 8 | "cpu": [ "arm64" ] 9 | } 10 | -------------------------------------------------------------------------------- /installers/npm/packages/linux_x64/README.md: -------------------------------------------------------------------------------- 1 | # Elm Binary for Linux (x64) 2 | 3 | Some people install [Elm](https://elm-lang.org/) with `npm`. This package helps make [`npm install elm`](https://www.npmjs.com/package/elm) a bit faster and a bit more reliable. It is not intended for direct use! 4 | 5 | If you do not need to use `npm`, the official binaries are published via [GitHub releases](https://github.com/elm/compiler/releases) with installation instructions. 6 | -------------------------------------------------------------------------------- /installers/npm/packages/linux_x64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@elm_binaries/linux_x64", 3 | "version": "0.19.1-0", 4 | "description": "Download the Elm binary for Linux (x64)", 5 | "repository": "https://github.com/elm/compiler", 6 | "license": "BSD-3-Clause", 7 | "os": [ "linux" ], 8 | "cpu": [ "x64" ] 9 | } 10 | -------------------------------------------------------------------------------- /installers/npm/packages/win32_x64/README.md: -------------------------------------------------------------------------------- 1 | # Elm Binary for Windows (x64) 2 | 3 | Some people install [Elm](https://elm-lang.org/) with `npm`. This package helps make [`npm install elm`](https://www.npmjs.com/package/elm) a bit faster and a bit more reliable. It is not intended for direct use! 4 | 5 | If you do not need to use `npm`, the official binaries are published via [GitHub releases](https://github.com/elm/compiler/releases) with installation instructions. 6 | -------------------------------------------------------------------------------- /installers/npm/packages/win32_x64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@elm_binaries/win32_x64", 3 | "version": "0.19.1-0", 4 | "description": "Download the Elm binary for Windows (x64)", 5 | "repository": "https://github.com/elm/compiler", 6 | "license": "BSD-3-Clause", 7 | "os": [ "win32" ], 8 | "cpu": [ "x64" ] 9 | } 10 | -------------------------------------------------------------------------------- /installers/npm/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | I very highly recommend asking for help on [the Elm slack](https://elmlang.herokuapp.com). 4 | 5 | There are a lot of things that can go wrong when installing software, and it can really help to have a second pair of eyes on your situation! 6 | 7 | This document goes through a couple options that may help you out. 8 | 9 |
10 | 11 | 12 | ## Can you skip npm entirely? 13 | 14 | The most reliable way to get Elm installed using the official installers for Mac and Windows [here][download]. 15 | 16 | You can also download the binaries directly. On Linux, you could do it in the terminal like this: 17 | 18 | ```bash 19 | cd ~/Desktop/ 20 | curl -L -o elm.gz https://github.com/elm/compiler/releases/download/0.19.1/binary-for-linux-64-bit.gz 21 | gunzip elm.gz # unzip the file 22 | chmod +x elm # make the file executable 23 | sudo mv elm /usr/local/bin/ # put the executable in a directory likely to be listed in your PATH variable 24 | ``` 25 | 26 | If these exact commands do not work for you, you can try to do the same thing by hand. 27 | 28 | Read the section below on `PATH` variables if you are not sure what that is! 29 | 30 | [download]: https://github.com/elm/compiler/releases/tag/0.19.1 31 | 32 |
33 | 34 | 35 | ## Do you need to use npm for some reason? 36 | 37 | The company running npm has a list of common troubleshooting situations [here](https://npm.community/c/support/troubleshooting), but it may be better to just try to find your specific case on Stack Overflow. Often there are permissions issues where you may need to use `sudo` with some command. 38 | 39 | ### Firewalls 40 | 41 | Some companies have a firewall. 42 | 43 | These companies usually have set the `HTTP_PROXY` or `HTTPS_PROXY` environment variable on your computer. This is more common with Windows computers. 44 | 45 | The result is that the requests for npm packages are being sent to a "proxy server" where they monitor traffic. Maybe they rule out certain domains, maybe they check data when it comes back from the actual URL, etc. 46 | 47 | It is probably best to ask someone about the situation on this, but you can test things out by temporarily using an alternate `HTTPS_PROXY` value with something like this: 48 | 49 | ``` 50 | # Mac and Linux 51 | HTTPS_PROXY=http://proxy.example.com npm install -g elm 52 | 53 | # Windows 54 | set HTTPS_PROXY=http://proxy.example.com 55 | npm install -g elm 56 | ``` 57 | 58 | Check out [this document](https://docs.npmjs.com/cli/using-npm/config/) for more information on how environment variables like [NO_PROXY](https://docs.npmjs.com/cli/using-npm/config#noproxy), [HTTP_PROXY](https://docs.npmjs.com/cli/using-npm/config#proxy), and [HTTPS_PROXY](https://docs.npmjs.com/cli/using-npm/config#https-proxy) are handled by npm. 59 | 60 |
61 | 62 | 63 | ## Do you know what a `PATH` variable is? 64 | 65 | When you run a command like `elm make src/Main.elm`, your computer starts by trying to find a file called `elm`. 66 | 67 | The `PATH` is a list of directories to search within. On Mac and Linux, you can see these directories by running: 68 | 69 | ``` 70 | $ echo $PATH 71 | /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/git/bin 72 | ``` 73 | 74 | The are separated by `:` for some reason. So running `elm make src/Main.elm` starts by searching the `PATH` for files named `elm`. On my computer, it finds `/usr/local/bin/elm` and then can actually run the command. 75 | 76 | Is `elm` in one of the directories listed in your `PATH` variable? I recommend asking for help if you are in this scenario and unsure how to proceed. 77 | -------------------------------------------------------------------------------- /installers/win/CreateInternetShortcut.nsh: -------------------------------------------------------------------------------- 1 | !macro CreateInternetShortcut FILENAME URL ICONFILE ICONINDEX 2 | WriteINIStr "${FILENAME}.url" "InternetShortcut" "URL" "${URL}" 3 | WriteINIStr "${FILENAME}.url" "InternetShortcut" "IconFile" "${ICONFILE}" 4 | WriteINIStr "${FILENAME}.url" "InternetShortcut" "IconIndex" "${ICONINDEX}" 5 | !macroend -------------------------------------------------------------------------------- /installers/win/README.md: -------------------------------------------------------------------------------- 1 | # Installing on Windows 2 | 3 | The installer for Windows is available [here](https://guide.elm-lang.org/install.html). 4 | 5 | 6 |
7 | 8 | ## Uninstall 9 | 10 | First run the `C:\Program Files (x86)\Elm\0.19\uninstall.exe` file. This will remove Elm stuff from your `PATH`. 11 | 12 | Then remove the whole `C:\Users\\AppData\Roaming\elm` directory. Elm caches some packages and build artifacts to reduce compile times and to help you work offline. Getting rid of this directory will clear that information out! 13 | 14 |
15 | 16 | ## Building the Windows installer 17 | 18 | You will need the [NSIS installer](http://nsis.sourceforge.net/Download) to be installed. 19 | 20 | Once everything is installed, run something like this command: 21 | 22 | make_installer.cmd 0.19.0 23 | 24 | It will build an installer called `Elm-0.19.0-setup.exe`. 25 | -------------------------------------------------------------------------------- /installers/win/inst.dat: -------------------------------------------------------------------------------- 1 | SetOutPath "$INSTDIR\bin" 2 | File "${FILES_SOURCE_PATH}\bin\elm.exe" 3 | 4 | SetOutPath "$INSTDIR" 5 | File "updatepath.vbs" 6 | File "removefrompath.vbs" 7 | -------------------------------------------------------------------------------- /installers/win/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm/compiler/2f6dd29258e880dbb7effd57a829a0470d8da48b/installers/win/logo.ico -------------------------------------------------------------------------------- /installers/win/make_installer.cmd: -------------------------------------------------------------------------------- 1 | 2 | set version=%1 3 | 4 | mkdir files 5 | mkdir files\bin 6 | 7 | xcopy ..\..\dist\build\elm\elm.exe files\bin /s /e 8 | xcopy updatepath.vbs files 9 | 10 | if EXIST "%ProgramFiles%\NSIS" ( 11 | set nsis="%ProgramFiles%\NSIS\makensis.exe" 12 | ) else ( 13 | set nsis="%ProgramFiles(x86)%\NSIS\makensis.exe" 14 | ) 15 | 16 | %nsis% /DPLATFORM_VERSION=%version% Nsisfile.nsi 17 | 18 | rd /s /q files 19 | -------------------------------------------------------------------------------- /installers/win/removefrompath.vbs: -------------------------------------------------------------------------------- 1 | Set WshShell = CreateObject("WScript.Shell") 2 | ' Make sure there is no trailing slash at the end of elmBasePath 3 | elmBasePath = WScript.Arguments(0) 4 | 'const PathRegKey = "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\Path" 5 | const PathRegKey = "HKCU\Environment\Path" 6 | 7 | on error resume next 8 | path = WshShell.RegRead(PathRegKey) 9 | if err.number = 0 then 10 | Set regEx = New RegExp 11 | elmBasePath = Replace(Replace(Replace(elmBasePath, "\", "\\"), "(", "\("), ")", "\)") 12 | regEx.Pattern = elmBasePath & "\\\d+\.\d+(\.\d+|)\\bin(;|)" 13 | regEx.Global = True 14 | newPath = regEx.Replace(path, "") 15 | Call WshShell.RegWrite(PathRegKey, newPath, "REG_EXPAND_SZ") 16 | end if 17 | on error goto 0 18 | -------------------------------------------------------------------------------- /installers/win/uninst.dat: -------------------------------------------------------------------------------- 1 | Delete "$INSTDIR\bin\elm.exe" 2 | RmDir "$INSTDIR\bin" 3 | 4 | Delete "$INSTDIR\updatepath.vbs" 5 | Delete "$INSTDIR\removefrompath.vbs" 6 | RmDir "$INSTDIR" 7 | -------------------------------------------------------------------------------- /installers/win/updatepath.vbs: -------------------------------------------------------------------------------- 1 | Set WshShell = CreateObject("WScript.Shell") 2 | elmPath = WScript.Arguments(0) 3 | 'const PathRegKey = "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\Path" 4 | const PathRegKey = "HKCU\Environment\Path" 5 | 6 | on error resume next 7 | path = WshShell.RegRead(PathRegKey) 8 | if err.number <> 0 then 9 | path = "" 10 | end if 11 | on error goto 0 12 | 13 | newPath = elmPath & ";" & path 14 | Call WshShell.RegWrite(PathRegKey, newPath, "REG_EXPAND_SZ") 15 | -------------------------------------------------------------------------------- /installers/win/welcome.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm/compiler/2f6dd29258e880dbb7effd57a829a0470d8da48b/installers/win/welcome.bmp -------------------------------------------------------------------------------- /reactor/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm/compiler/2f6dd29258e880dbb7effd57a829a0470d8da48b/reactor/assets/favicon.ico -------------------------------------------------------------------------------- /reactor/assets/source-code-pro.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm/compiler/2f6dd29258e880dbb7effd57a829a0470d8da48b/reactor/assets/source-code-pro.ttf -------------------------------------------------------------------------------- /reactor/assets/source-sans-pro.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm/compiler/2f6dd29258e880dbb7effd57a829a0470d8da48b/reactor/assets/source-sans-pro.ttf -------------------------------------------------------------------------------- /reactor/assets/styles.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | 4 | /* FONTS */ 5 | 6 | @font-face { 7 | font-family: 'Source Code Pro'; 8 | font-style: normal; 9 | font-weight: 400; 10 | src: local('Source Code Pro'), local('SourceCodePro-Regular'), url(/_elm/source-code-pro.ttf) format('truetype'); 11 | } 12 | 13 | @font-face { 14 | font-family: 'Source Sans Pro'; 15 | font-style: normal; 16 | font-weight: 400; 17 | src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(/_elm/source-sans-pro.ttf) format('truetype'); 18 | } 19 | 20 | 21 | /* GENERIC STUFF */ 22 | 23 | html, head, body { 24 | margin: 0; 25 | height: 100%; 26 | } 27 | 28 | body { 29 | font-family: 'Source Sans Pro', 'Trebuchet MS', 'Lucida Grande', 'Bitstream Vera Sans', 'Helvetica Neue', sans-serif; 30 | color: #293c4b; 31 | } 32 | 33 | a { 34 | color: #60B5CC; 35 | text-decoration: none; 36 | } 37 | 38 | a:hover { 39 | text-decoration: underline; 40 | } 41 | 42 | 43 | /* INDEX */ 44 | 45 | .header { 46 | width: 100%; 47 | background-color: #60B5CC; 48 | height: 8px; 49 | } 50 | 51 | .content { 52 | width: 960px; 53 | margin-left: auto; 54 | margin-right: auto; 55 | } 56 | 57 | 58 | /* COLUMNS */ 59 | 60 | .left-column { 61 | float: left; 62 | width: 600px; 63 | padding-bottom: 80px; 64 | } 65 | 66 | .right-column { 67 | float: right; 68 | width: 300px; 69 | padding-bottom: 80px; 70 | } 71 | 72 | 73 | /* BOXES */ 74 | 75 | .box { 76 | border: 1px solid #c7c7c7; 77 | border-radius: 5px; 78 | margin-bottom: 40px; 79 | } 80 | 81 | .box-header { 82 | display: block; 83 | overflow: hidden; 84 | padding: 7px 12px; 85 | background-color: #fafafa; 86 | text-align: center; 87 | border-radius: 5px; 88 | } 89 | 90 | .box-item { 91 | display: block; 92 | overflow: hidden; 93 | padding: 7px 12px; 94 | border-top: 1px solid #e1e1e1; 95 | } 96 | 97 | .box-footer { 98 | display: block; 99 | overflow: hidden; 100 | padding: 2px 12px; 101 | border-top: 1px solid #e1e1e1; 102 | text-align: center; 103 | background-color: #fafafa; 104 | height: 16px; 105 | } 106 | 107 | 108 | /* ICONS */ 109 | 110 | .icon { 111 | display: inline-block; 112 | vertical-align: middle; 113 | padding-right: 0.5em; 114 | } 115 | 116 | 117 | /* PAGES */ 118 | 119 | .page-name { 120 | float: left; 121 | } 122 | 123 | .page-size { 124 | float: right; 125 | color: #293c4b; 126 | } 127 | 128 | .page-size:hover { 129 | color: #60B5CC; 130 | } 131 | 132 | 133 | /* WAITING */ 134 | 135 | .waiting { 136 | width: 100%; 137 | height: 100%; 138 | display: flex; 139 | flex-direction: column; 140 | justify-content: center; 141 | align-items: center; 142 | color: #9A9A9A; 143 | } 144 | 145 | 146 | /* NOT FOUND */ 147 | 148 | .not-found { 149 | width: 100%; 150 | height: 100%; 151 | display: flex; 152 | flex-direction: column; 153 | justify-content: center; 154 | align-items: center; 155 | background-color: #F5F5F5; 156 | color: #9A9A9A; 157 | } 158 | -------------------------------------------------------------------------------- /reactor/check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | 7 | ## FIGURE OUT NEW MODIFICATION TIME 8 | 9 | def mostRecentModification(directory): 10 | mostRecent = 0 11 | 12 | for dirpath, dirs, files in os.walk(directory): 13 | for f in files: 14 | lastModified = os.path.getmtime(dirpath + '/' + f) 15 | mostRecent = max(int(lastModified), mostRecent) 16 | 17 | return mostRecent 18 | 19 | 20 | srcTime = mostRecentModification('ui/src') 21 | assetTime = mostRecentModification('ui/assets') 22 | mostRecent = max(srcTime, assetTime) 23 | 24 | 25 | ## FIGURE OUT OLD MODIFICATION TIME 26 | 27 | with open('ui/last-modified', 'a') as handle: 28 | pass 29 | 30 | 31 | prevMostRecent = 0 32 | 33 | 34 | with open('ui/last-modified', 'r+') as handle: 35 | line = handle.read() 36 | prevMostRecent = int(line) if line else 0 37 | 38 | 39 | ## TOUCH FILES IF NECESSARY 40 | 41 | if mostRecent > prevMostRecent: 42 | print "+------------------------------------------------------------+" 43 | print "| Some ui/ code changed. Touching src/Reactor/StaticFiles.hs |" 44 | print "| to trigger a recompilation of the Template Haskell stuff. |" 45 | print "+------------------------------------------------------------+" 46 | os.utime('src/Reactor/StaticFiles.hs', None) 47 | with open('ui/last-modified', 'w') as handle: 48 | handle.write(str(mostRecent)) 49 | -------------------------------------------------------------------------------- /reactor/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/browser": "1.0.1", 10 | "elm/core": "1.0.2", 11 | "elm/html": "1.0.0", 12 | "elm/http": "2.0.0", 13 | "elm/json": "1.1.2", 14 | "elm/project-metadata-utils": "1.0.0", 15 | "elm/svg": "1.0.1", 16 | "elm-explorations/markdown": "1.0.0" 17 | }, 18 | "indirect": { 19 | "elm/bytes": "1.0.7", 20 | "elm/file": "1.0.1", 21 | "elm/parser": "1.1.0", 22 | "elm/time": "1.0.0", 23 | "elm/url": "1.0.0", 24 | "elm/virtual-dom": "1.0.2" 25 | } 26 | }, 27 | "test-dependencies": { 28 | "direct": {}, 29 | "indirect": {} 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /reactor/src/Index/Icon.elm: -------------------------------------------------------------------------------- 1 | module Index.Icon exposing 2 | ( home 3 | , image 4 | , file 5 | , gift 6 | , folder 7 | , package 8 | , plus 9 | , lookup 10 | ) 11 | 12 | import Dict 13 | import Html exposing (Html) 14 | import Svg exposing (..) 15 | import Svg.Attributes exposing (class, width, height, viewBox, d, fill) 16 | 17 | 18 | 19 | -- ICON 20 | 21 | 22 | icon : String -> String -> String -> Html msg 23 | icon color size pathString = 24 | svg 25 | [ class "icon" 26 | , width size 27 | , height size 28 | , viewBox "0 0 1792 1792" 29 | ] 30 | [ path [ fill color, d pathString ] [] 31 | ] 32 | 33 | 34 | 35 | -- NECESSARY ICONS 36 | 37 | 38 | home : Html msg 39 | home = 40 | icon "#babdb6" "36px" "M1472 992v480q0 26-19 45t-45 19h-384v-384h-256v384h-384q-26 0-45-19t-19-45v-480q0-1 .5-3t.5-3l575-474 575 474q1 2 1 6zm223-69l-62 74q-8 9-21 11h-3q-13 0-21-7l-692-577-692 577q-12 8-24 7-13-2-21-11l-62-74q-8-10-7-23.5t11-21.5l719-599q32-26 76-26t76 26l244 204v-195q0-14 9-23t23-9h192q14 0 23 9t9 23v408l219 182q10 8 11 21.5t-7 23.5z" 41 | 42 | 43 | image : Html msg 44 | image = 45 | icon "#babdb6" "16px" "M1596 380q28 28 48 76t20 88v1152q0 40-28 68t-68 28h-1344q-40 0-68-28t-28-68v-1600q0-40 28-68t68-28h896q40 0 88 20t76 48zm-444-244v376h376q-10-29-22-41l-313-313q-12-12-41-22zm384 1528v-1024h-416q-40 0-68-28t-28-68v-416h-768v1536h1280zm-128-448v320h-1024v-192l192-192 128 128 384-384zm-832-192q-80 0-136-56t-56-136 56-136 136-56 136 56 56 136-56 136-136 56z" 46 | 47 | 48 | file : Html msg 49 | file = 50 | icon "#babdb6" "16px" "M1596 380q28 28 48 76t20 88v1152q0 40-28 68t-68 28h-1344q-40 0-68-28t-28-68v-1600q0-40 28-68t68-28h896q40 0 88 20t76 48zm-444-244v376h376q-10-29-22-41l-313-313q-12-12-41-22zm384 1528v-1024h-416q-40 0-68-28t-28-68v-416h-768v1536h1280zm-1024-864q0-14 9-23t23-9h704q14 0 23 9t9 23v64q0 14-9 23t-23 9h-704q-14 0-23-9t-9-23v-64zm736 224q14 0 23 9t9 23v64q0 14-9 23t-23 9h-704q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h704zm0 256q14 0 23 9t9 23v64q0 14-9 23t-23 9h-704q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h704z" 51 | 52 | 53 | gift : Html msg 54 | gift = 55 | icon "#babdb6" "16px" "M1056 1356v-716h-320v716q0 25 18 38.5t46 13.5h192q28 0 46-13.5t18-38.5zm-456-844h195l-126-161q-26-31-69-31-40 0-68 28t-28 68 28 68 68 28zm688-96q0-40-28-68t-68-28q-43 0-69 31l-125 161h194q40 0 68-28t28-68zm376 256v320q0 14-9 23t-23 9h-96v416q0 40-28 68t-68 28h-1088q-40 0-68-28t-28-68v-416h-96q-14 0-23-9t-9-23v-320q0-14 9-23t23-9h440q-93 0-158.5-65.5t-65.5-158.5 65.5-158.5 158.5-65.5q107 0 168 77l128 165 128-165q61-77 168-77 93 0 158.5 65.5t65.5 158.5-65.5 158.5-158.5 65.5h440q14 0 23 9t9 23z" 56 | 57 | 58 | folder : Html msg 59 | folder = 60 | icon "#babdb6" "16px" "M1728 608v704q0 92-66 158t-158 66h-1216q-92 0-158-66t-66-158v-960q0-92 66-158t158-66h320q92 0 158 66t66 158v32h672q92 0 158 66t66 158z" 61 | 62 | 63 | package : Html msg 64 | package = 65 | icon "#babdb6" "16px" "M1088 832q0-26-19-45t-45-19h-256q-26 0-45 19t-19 45 19 45 45 19h256q26 0 45-19t19-45zm576-192v960q0 26-19 45t-45 19h-1408q-26 0-45-19t-19-45v-960q0-26 19-45t45-19h1408q26 0 45 19t19 45zm64-448v256q0 26-19 45t-45 19h-1536q-26 0-45-19t-19-45v-256q0-26 19-45t45-19h1536q26 0 45 19t19 45z" 66 | 67 | 68 | plus : Html msg 69 | plus = 70 | icon "#babdb6" "16px" "M1600 736v192q0 40-28 68t-68 28h-416v416q0 40-28 68t-68 28h-192q-40 0-68-28t-28-68v-416h-416q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h416v-416q0-40 28-68t68-28h192q40 0 68 28t28 68v416h416q40 0 68 28t28 68z" 71 | 72 | 73 | 74 | -- LOOKUP 75 | 76 | 77 | lookup : String -> Html msg 78 | lookup fileName = 79 | let 80 | extension = 81 | getExtension fileName 82 | in 83 | Maybe.withDefault file (Dict.get extension extensionIcons) 84 | 85 | 86 | extensionIcons : Dict.Dict String (Html msg) 87 | extensionIcons = 88 | Dict.fromList 89 | [ ("jpg" , image) 90 | , ("jpeg", image) 91 | , ("png" , image) 92 | , ("gif" , image) 93 | ] 94 | 95 | 96 | getExtension : String -> String 97 | getExtension str = 98 | getExtensionHelp (String.split "." str) 99 | 100 | 101 | getExtensionHelp : List String -> String 102 | getExtensionHelp segments = 103 | case segments of 104 | [] -> 105 | "" 106 | 107 | [ext] -> 108 | String.toLower ext 109 | 110 | _ :: rest -> 111 | getExtensionHelp rest 112 | -------------------------------------------------------------------------------- /reactor/src/Index/Navigator.elm: -------------------------------------------------------------------------------- 1 | module Index.Navigator exposing (view) 2 | 3 | 4 | import Html exposing (..) 5 | import Html.Attributes exposing (..) 6 | import Index.Icon as Icon 7 | 8 | 9 | 10 | -- VIEW 11 | 12 | 13 | view : String -> List String -> Html msg 14 | view root dirs = 15 | div 16 | [ style "font-size" "2em" 17 | , style "padding" "20px 0" 18 | , style "display" "flex" 19 | , style "align-items" "center" 20 | , style "height" "40px" 21 | ] 22 | (makeLinks root dirs "" []) 23 | 24 | 25 | makeLinks : String -> List String -> String -> List (Html msg) -> List (Html msg) 26 | makeLinks root dirs oldPath revAnchors = 27 | case dirs of 28 | dir :: otherDirs -> 29 | let 30 | newPath = 31 | oldPath ++ "/" ++ dir 32 | 33 | anchor = 34 | a [ href newPath ] [ text dir ] 35 | in 36 | makeLinks root otherDirs newPath (anchor :: revAnchors) 37 | 38 | [] -> 39 | let 40 | home = 41 | a [ href "/" 42 | , title root 43 | , style "display" "inherit" 44 | ] 45 | [ Icon.home 46 | ] 47 | in 48 | case revAnchors of 49 | [] -> 50 | [home] 51 | 52 | lastAnchor :: otherRevAnchors -> 53 | home :: slash :: List.foldl addSlash [lastAnchor] otherRevAnchors 54 | 55 | 56 | addSlash : Html msg -> List (Html msg) -> List (Html msg) 57 | addSlash front back = 58 | front :: slash :: back 59 | 60 | 61 | slash : Html msg 62 | slash = 63 | span [ style "padding" "0 8px" ] [ text "/" ] 64 | -------------------------------------------------------------------------------- /reactor/src/Index/Skeleton.elm: -------------------------------------------------------------------------------- 1 | module Index.Skeleton exposing 2 | ( box 3 | , readmeBox 4 | ) 5 | 6 | import Html exposing (..) 7 | import Html.Attributes exposing (..) 8 | import Markdown 9 | 10 | import Index.Icon as Icon 11 | 12 | 13 | 14 | -- VIEW BOXES 15 | 16 | 17 | type alias BoxArgs msg = 18 | { title : String 19 | , items : List (List (Html msg)) 20 | , footer : Maybe (String, String) 21 | } 22 | 23 | 24 | box : BoxArgs msg -> Html msg 25 | box { title, items, footer } = 26 | let 27 | realItems = 28 | List.map (div [ class "box-item" ]) items 29 | in 30 | boxHelp title realItems footer 31 | 32 | 33 | readmeBox : String -> Html msg 34 | readmeBox markdown = 35 | let 36 | readme = 37 | Markdown.toHtml [ class "box-item" ] markdown 38 | in 39 | boxHelp "README" [readme] Nothing 40 | 41 | 42 | boxHelp : String -> List (Html msg) -> Maybe (String, String) -> Html msg 43 | boxHelp boxTitle items footer = 44 | div [ class "box" ] <| 45 | div [ class "box-header" ] [ text boxTitle ] 46 | :: items 47 | ++ [ boxFooter footer ] 48 | 49 | 50 | boxFooter : Maybe (String, String) -> Html msg 51 | boxFooter maybeFooter = 52 | case maybeFooter of 53 | Nothing -> 54 | text "" 55 | 56 | Just (path, description) -> 57 | a [ href path 58 | , title description 59 | ] 60 | [ div [ class "box-footer" ] [ Icon.plus ] 61 | ] 62 | -------------------------------------------------------------------------------- /reactor/src/NotFound.elm: -------------------------------------------------------------------------------- 1 | module NotFound exposing (main) 2 | 3 | 4 | import Browser 5 | import Html exposing (..) 6 | import Html.Attributes exposing (..) 7 | 8 | 9 | 10 | main : Program () () () 11 | main = 12 | Browser.document 13 | { init = \_ -> ((), Cmd.none) 14 | , update = \_ _ -> ((), Cmd.none) 15 | , subscriptions = \_ -> Sub.none 16 | , view = \_ -> page 17 | } 18 | 19 | 20 | page : Browser.Document () 21 | page = 22 | { title = "Page not found" 23 | , body = 24 | [ div [ class "not-found" ] 25 | [ div [ style "font-size" "12em" ] [ text "404" ] 26 | , div [ style "font-size" "3em" ] [ text "Page not found" ] 27 | ] 28 | ] 29 | } -------------------------------------------------------------------------------- /reactor/src/mock.txt: -------------------------------------------------------------------------------- 1 | # Dependency Explorer 2 | 3 | Mass Updates: | RESET | PATCH | MINOR | MAJOR | 4 | 5 | ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇ ←→ 6 | 7 | DEPENDENCIES 8 | 9 | DIRECT 10 | NoRedInk/elm-json-decode-pipeline 1.0.0 → 3.0.0 (MAJOR) 11 | elm/browser 1.0.0 → 1.0.2 (MINOR) 12 | elm/core 1.0.0 → 1.0.5 (CUSTOM: 1.0.0 <= v < 2.0.0) 13 | elm/html 1.0.0 → 6.0.2 (ANY) 14 | elm/http 1.0.0 → 1.0.0 (LOCKED) 15 | elm/json 1.0.0 → 1.0.0 (LOCKED) 16 | elm/time 1.0.0 → 1.0.0 (LOCKED) 17 | elm/url 1.0.0 → 1.0.0 (LOCKED) 18 | elm-explorations/markdown 1.0.0 → 1.0.0 (LOCKED) 19 | rtfeldman/elm-iso8601-date-strings 1.1.0 → (REMOVE) 20 | ADD 21 | 22 | INDIRECT 23 | elm/parser 1.0.0 → 1.0.0 (LOCKED) 24 | elm/virtual-dom 1.0.0 → 1.0.0 (LOCKED) 25 | 26 | TEST DEPENDENCIES 27 | 28 | DIRECT 29 | elm-explorations/test 1.0.0 → 1.0.0 (LOCKED) 30 | ADD 31 | 32 | INDIRECT 33 | elm/random 1.0.0 → 1.0.0 (LOCKED) 34 | -------------------------------------------------------------------------------- /roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | If you like what you see now, that's pretty much what Elm is going to be for a while. 4 | 5 | I'm currently doing some exploratory work with compiler techniques and targets, but it is still too early to tell which parts of that might work out. I know many readers take "exploring X" as a promise that "X will definitely happen", so I want to be careful in setting expectations low since there is still so much uncertainty in the work. So while there is no real news to share at this point, I can give some expectations about future stability of Elm: 6 | 7 | **If this exploratory work goes well**, even in the wildest version of success, I wouldn't expect the language or core packages to change very much. Maybe two or three years down the line it could reveal something that'd be good to fix up, but I don't really foresee notable changes right now. 8 | 9 | **If this exploratory work does not work out**, I have some more conservative projects that are good ideas that I want to circle back to at some point. Things like: 10 | 11 | - add a constrained type variable `eq` to get rid of the runtime error for `(==)` on functions 12 | - try to integrate `elm test` so it can run only tests that changed 13 | - try to get `elm format` using the latest parsing infrastructure so it's not a perf bottleneck during development anymore 14 | - do another round on perf because I have one last idea that can squeeze out a bit more speed 15 | 16 | These are all nice quality of life things, but with 0.19.0 and 0.19.1 taking so long, I got pretty burnt out on "incremental improvements that are good ideas, but take a very long time and aren't very exciting to non-Elm users." Without getting too into the details, I really needed to change things up before returning to these particular ideas. 17 | 18 | If someone has a security issue from the compiler or core libraries, please DM me about it on the Elm Slack. Outside of security issues, I think capacity for more discretionary changes will increase once the lessons from the compiler explorations become more clear. Hopefully the discussion [here](https://discourse.elm-lang.org/t/costs-funding-in-open-source-languages/5722) clarifies the thinking on these capacity questions a bit. 19 | 20 |
21 |
22 | 23 | **Taking a step back**, I have found that working in this looser style has produced a high baseline of quality, and I think that is an important part of Elm. For example, all the error message work in Elm began as a project to implement the `--report=json` flag. That work happened to reveal some cool ideas on improving error messages, and if I had been using a rigid roadmap, I might have skipped those ideas to meet the publicly-stated arbitrary deadline. We'd have a clearer roadmap, but error messages no different than other type-inferred languages. 24 | 25 | So I think having more flexibility in planning is a competitive advantage for Elm in certain ways, but it is obviously a trade off that does not work for everyone. If someone needs more certainty, I generally recommend looking into other languages to see if they have a balance that works better for their needs. For example, languages made by big corporations are generally "less risky" on things like this, but I think you see the fruits of that kind of process in the design decisions as well. Trade offs! 26 | 27 | Anyway, like I said at the beginning, if you like what you see now, that's what it's going to be for a while. Even in the best case scenario with my current explorations! 28 | 29 | I hope this is helpful information, and I hope you have a good experience with Elm, even if you ultimately find a different language that works better for you! 30 | -------------------------------------------------------------------------------- /terminal/impl/Terminal/Helpers.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Terminal.Helpers 3 | ( version 4 | , elmFile 5 | , package 6 | ) 7 | where 8 | 9 | 10 | import qualified Data.ByteString.UTF8 as BS_UTF8 11 | import qualified Data.Char as Char 12 | import qualified Data.List as List 13 | import qualified Data.Map as Map 14 | import qualified Data.Utf8 as Utf8 15 | import qualified System.FilePath as FP 16 | 17 | import Terminal (Parser(..)) 18 | import qualified Deps.Registry as Registry 19 | import qualified Elm.Package as Pkg 20 | import qualified Elm.Version as V 21 | import qualified Parse.Primitives as P 22 | import qualified Stuff 23 | import qualified Reporting.Suggest as Suggest 24 | 25 | 26 | 27 | -- VERSION 28 | 29 | 30 | version :: Parser V.Version 31 | version = 32 | Parser 33 | { _singular = "version" 34 | , _plural = "versions" 35 | , _parser = parseVersion 36 | , _suggest = suggestVersion 37 | , _examples = return . exampleVersions 38 | } 39 | 40 | 41 | parseVersion :: String -> Maybe V.Version 42 | parseVersion chars = 43 | case P.fromByteString V.parser (,) (BS_UTF8.fromString chars) of 44 | Right vsn -> Just vsn 45 | Left _ -> Nothing 46 | 47 | 48 | suggestVersion :: String -> IO [String] 49 | suggestVersion _ = 50 | return [] 51 | 52 | 53 | exampleVersions :: String -> [String] 54 | exampleVersions chars = 55 | let 56 | chunks = map Utf8.toChars (Utf8.split 0x2E {-.-} (Utf8.fromChars chars)) 57 | isNumber cs = not (null cs) && all Char.isDigit cs 58 | in 59 | if all isNumber chunks then 60 | case chunks of 61 | [x] -> [ x ++ ".0.0" ] 62 | [x,y] -> [ x ++ "." ++ y ++ ".0" ] 63 | x:y:z:_ -> [ x ++ "." ++ y ++ "." ++ z ] 64 | _ -> ["1.0.0", "2.0.3"] 65 | 66 | else 67 | ["1.0.0", "2.0.3"] 68 | 69 | 70 | 71 | -- ELM FILE 72 | 73 | 74 | elmFile :: Parser FilePath 75 | elmFile = 76 | Parser 77 | { _singular = "elm file" 78 | , _plural = "elm files" 79 | , _parser = parseElmFile 80 | , _suggest = \_ -> return [] 81 | , _examples = exampleElmFiles 82 | } 83 | 84 | 85 | parseElmFile :: String -> Maybe FilePath 86 | parseElmFile chars = 87 | if FP.takeExtension chars == ".elm" then 88 | Just chars 89 | else 90 | Nothing 91 | 92 | 93 | exampleElmFiles :: String -> IO [String] 94 | exampleElmFiles _ = 95 | return ["Main.elm","src/Main.elm"] 96 | 97 | 98 | 99 | -- PACKAGE 100 | 101 | 102 | package :: Parser Pkg.Name 103 | package = 104 | Parser 105 | { _singular = "package" 106 | , _plural = "packages" 107 | , _parser = parsePackage 108 | , _suggest = suggestPackages 109 | , _examples = examplePackages 110 | } 111 | 112 | 113 | parsePackage :: String -> Maybe Pkg.Name 114 | parsePackage chars = 115 | case P.fromByteString Pkg.parser (,) (BS_UTF8.fromString chars) of 116 | Right pkg -> Just pkg 117 | Left _ -> Nothing 118 | 119 | 120 | suggestPackages :: String -> IO [String] 121 | suggestPackages given = 122 | do cache <- Stuff.getPackageCache 123 | maybeRegistry <- Registry.read cache 124 | return $ 125 | case maybeRegistry of 126 | Nothing -> 127 | [] 128 | 129 | Just (Registry.Registry _ versions) -> 130 | filter (List.isPrefixOf given) $ 131 | map Pkg.toChars (Map.keys versions) 132 | 133 | 134 | examplePackages :: String -> IO [String] 135 | examplePackages given = 136 | do cache <- Stuff.getPackageCache 137 | maybeRegistry <- Registry.read cache 138 | return $ 139 | case maybeRegistry of 140 | Nothing -> 141 | [ "elm/json" 142 | , "elm/http" 143 | , "elm/random" 144 | ] 145 | 146 | Just (Registry.Registry _ versions) -> 147 | map Pkg.toChars $ take 4 $ 148 | Suggest.sort given Pkg.toChars (Map.keys versions) 149 | -------------------------------------------------------------------------------- /terminal/impl/Terminal/Internal.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GADTs #-} 2 | module Terminal.Internal 3 | ( Command(..) 4 | , toName 5 | , Summary(..) 6 | , Flags(..) 7 | , Flag(..) 8 | , Parser(..) 9 | , Args(..) 10 | , CompleteArgs(..) 11 | , RequiredArgs(..) 12 | ) 13 | where 14 | 15 | 16 | import Text.PrettyPrint.ANSI.Leijen (Doc) 17 | 18 | 19 | 20 | -- COMMAND 21 | 22 | 23 | data Command where 24 | Command 25 | :: String 26 | -> Summary 27 | -> String 28 | -> Doc 29 | -> Args args 30 | -> Flags flags 31 | -> (args -> flags -> IO ()) 32 | -> Command 33 | 34 | 35 | toName :: Command -> String 36 | toName (Command name _ _ _ _ _ _) = 37 | name 38 | 39 | 40 | 41 | {-| The information that shows when you run the executable with no arguments. 42 | If you say it is `Common`, you need to tell people what it does. Try to keep 43 | it to two or three lines. If you say it is `Uncommon` you can rely on `Details` 44 | for a more complete explanation. 45 | -} 46 | data Summary = Common String | Uncommon 47 | 48 | 49 | 50 | -- FLAGS 51 | 52 | 53 | data Flags a where 54 | FDone :: a -> Flags a 55 | FMore :: Flags (a -> b) -> Flag a -> Flags b 56 | 57 | 58 | data Flag a where 59 | Flag :: String -> Parser a -> String -> Flag (Maybe a) 60 | OnOff :: String -> String -> Flag Bool 61 | 62 | 63 | 64 | -- PARSERS 65 | 66 | 67 | data Parser a = 68 | Parser 69 | { _singular :: String 70 | , _plural :: String 71 | , _parser :: String -> Maybe a 72 | , _suggest :: String -> IO [String] 73 | , _examples :: String -> IO [String] 74 | } 75 | 76 | 77 | 78 | -- ARGS 79 | 80 | 81 | newtype Args a = 82 | Args [CompleteArgs a] 83 | 84 | 85 | data CompleteArgs args where 86 | Exactly :: RequiredArgs args -> CompleteArgs args 87 | Multiple :: RequiredArgs ([a] -> args) -> Parser a -> CompleteArgs args 88 | Optional :: RequiredArgs (Maybe a -> args) -> Parser a -> CompleteArgs args 89 | 90 | 91 | data RequiredArgs a where 92 | Done :: a -> RequiredArgs a 93 | Required :: RequiredArgs (a -> b) -> Parser a -> RequiredArgs b 94 | -------------------------------------------------------------------------------- /terminal/src/Develop/Generate/Help.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE QuasiQuotes #-} 4 | module Develop.Generate.Help 5 | ( makePageHtml 6 | , makeCodeHtml 7 | ) 8 | where 9 | 10 | 11 | import qualified Data.ByteString.Builder as B 12 | import Data.Monoid ((<>)) 13 | import qualified Data.Name as Name 14 | import Text.RawString.QQ (r) 15 | 16 | import qualified Json.Encode as Encode 17 | 18 | 19 | 20 | -- PAGES 21 | 22 | 23 | makePageHtml :: Name.Name -> Maybe Encode.Value -> B.Builder 24 | makePageHtml moduleName maybeFlags = 25 | [r| 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | |] 39 | 40 | 41 | 42 | -- CODE 43 | 44 | 45 | makeCodeHtml :: FilePath -> B.Builder -> B.Builder 46 | makeCodeHtml title code = 47 | [r| 48 | 49 | 50 | 51 | |] <> B.stringUtf8 title <> [r| 52 | 57 | 58 | 59 | 60 | 61 | 62 |
|] <> code <> [r|
63 | 64 | 65 | |] 66 | -------------------------------------------------------------------------------- /terminal/src/Develop/Socket.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Develop.Socket (watchFile) where 4 | 5 | import Control.Concurrent (forkIO, threadDelay) 6 | import Control.Exception (SomeException, catch) 7 | import qualified Data.ByteString.Char8 as BS 8 | import qualified Network.WebSockets as WS 9 | import qualified System.FSNotify.Devel as Notify 10 | import qualified System.FSNotify as Notify 11 | 12 | 13 | 14 | watchFile :: FilePath -> WS.PendingConnection -> IO () 15 | watchFile watchedFile pendingConnection = 16 | do connection <- WS.acceptRequest pendingConnection 17 | 18 | Notify.withManager $ \mgmt -> 19 | do stop <- Notify.treeExtAny mgmt "." ".elm" print 20 | tend connection 21 | stop 22 | 23 | 24 | tend :: WS.Connection -> IO () 25 | tend connection = 26 | let 27 | pinger :: Integer -> IO a 28 | pinger n = 29 | do threadDelay (5 * 1000 * 1000) 30 | WS.sendPing connection (BS.pack (show n)) 31 | pinger (n + 1) 32 | 33 | receiver :: IO () 34 | receiver = 35 | do _ <- WS.receiveDataMessage connection 36 | receiver 37 | 38 | shutdown :: SomeException -> IO () 39 | shutdown _ = 40 | return () 41 | in 42 | do _pid <- forkIO (receiver `catch` shutdown) 43 | pinger 1 `catch` shutdown 44 | -------------------------------------------------------------------------------- /terminal/src/Develop/StaticFiles.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE TemplateHaskell #-} 4 | module Develop.StaticFiles 5 | ( lookup 6 | , cssPath 7 | , elmPath 8 | , waitingPath 9 | ) 10 | where 11 | 12 | import Prelude hiding (lookup) 13 | import qualified Data.ByteString as BS 14 | import Data.FileEmbed (bsToExp) 15 | import qualified Data.HashMap.Strict as HM 16 | import Language.Haskell.TH (runIO) 17 | import System.FilePath (()) 18 | 19 | import qualified Develop.StaticFiles.Build as Build 20 | 21 | 22 | 23 | -- FILE LOOKUP 24 | 25 | 26 | type MimeType = 27 | BS.ByteString 28 | 29 | 30 | lookup :: FilePath -> Maybe (BS.ByteString, MimeType) 31 | lookup path = 32 | HM.lookup path dict 33 | 34 | 35 | dict :: HM.HashMap FilePath (BS.ByteString, MimeType) 36 | dict = 37 | HM.fromList 38 | [ faviconPath ==> (favicon , "image/x-icon") 39 | , elmPath ==> (elm , "application/javascript") 40 | , cssPath ==> (css , "text/css") 41 | , codeFontPath ==> (codeFont, "font/ttf") 42 | , sansFontPath ==> (sansFont, "font/ttf") 43 | ] 44 | 45 | 46 | (==>) :: a -> b -> (a,b) 47 | (==>) a b = 48 | (a, b) 49 | 50 | 51 | 52 | -- PATHS 53 | 54 | 55 | faviconPath :: FilePath 56 | faviconPath = 57 | "favicon.ico" 58 | 59 | 60 | waitingPath :: FilePath 61 | waitingPath = 62 | "_elm" "waiting.gif" 63 | 64 | 65 | elmPath :: FilePath 66 | elmPath = 67 | "_elm" "elm.js" 68 | 69 | 70 | cssPath :: FilePath 71 | cssPath = 72 | "_elm" "styles.css" 73 | 74 | 75 | codeFontPath :: FilePath 76 | codeFontPath = 77 | "_elm" "source-code-pro.ttf" 78 | 79 | 80 | sansFontPath :: FilePath 81 | sansFontPath = 82 | "_elm" "source-sans-pro.ttf" 83 | 84 | 85 | 86 | -- ELM 87 | 88 | 89 | elm :: BS.ByteString 90 | elm = 91 | $(bsToExp =<< runIO Build.buildReactorFrontEnd) 92 | 93 | 94 | 95 | -- CSS 96 | 97 | 98 | css :: BS.ByteString 99 | css = 100 | $(bsToExp =<< runIO (Build.readAsset "styles.css")) 101 | 102 | 103 | 104 | -- FONTS 105 | 106 | 107 | codeFont :: BS.ByteString 108 | codeFont = 109 | $(bsToExp =<< runIO (Build.readAsset "source-code-pro.ttf")) 110 | 111 | 112 | sansFont :: BS.ByteString 113 | sansFont = 114 | $(bsToExp =<< runIO (Build.readAsset "source-sans-pro.ttf")) 115 | 116 | 117 | 118 | -- IMAGES 119 | 120 | 121 | favicon :: BS.ByteString 122 | favicon = 123 | $(bsToExp =<< runIO (Build.readAsset "favicon.ico")) 124 | -------------------------------------------------------------------------------- /terminal/src/Develop/StaticFiles/Build.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Develop.StaticFiles.Build 3 | ( readAsset 4 | , buildReactorFrontEnd 5 | ) 6 | where 7 | 8 | 9 | import qualified Data.ByteString as BS 10 | import qualified Data.ByteString.Builder as B 11 | import qualified Data.ByteString.Lazy as LBS 12 | import qualified Data.NonEmptyList as NE 13 | import qualified System.Directory as Dir 14 | import System.FilePath (()) 15 | 16 | import qualified BackgroundWriter as BW 17 | import qualified Build 18 | import qualified Elm.Details as Details 19 | import qualified Generate 20 | import qualified Reporting 21 | import qualified Reporting.Exit as Exit 22 | import qualified Reporting.Task as Task 23 | 24 | 25 | 26 | -- ASSETS 27 | 28 | 29 | readAsset :: FilePath -> IO BS.ByteString 30 | readAsset path = 31 | BS.readFile ("reactor" "assets" path) 32 | 33 | 34 | 35 | -- BUILD REACTOR ELM 36 | 37 | 38 | buildReactorFrontEnd :: IO BS.ByteString 39 | buildReactorFrontEnd = 40 | BW.withScope $ \scope -> 41 | Dir.withCurrentDirectory "reactor" $ 42 | do root <- Dir.getCurrentDirectory 43 | runTaskUnsafe $ 44 | do details <- Task.eio Exit.ReactorBadDetails $ Details.load Reporting.silent scope root 45 | artifacts <- Task.eio Exit.ReactorBadBuild $ Build.fromPaths Reporting.silent root details paths 46 | javascript <- Task.mapError Exit.ReactorBadGenerate $ Generate.prod root details artifacts 47 | return (LBS.toStrict (B.toLazyByteString javascript)) 48 | 49 | 50 | paths :: NE.List FilePath 51 | paths = 52 | NE.List 53 | ("src" "NotFound.elm") 54 | [ "src" "Errors.elm" 55 | , "src" "Index.elm" 56 | ] 57 | 58 | 59 | runTaskUnsafe :: Task.Task Exit.Reactor a -> IO a 60 | runTaskUnsafe task = 61 | do result <- Task.run task 62 | case result of 63 | Right a -> 64 | return a 65 | 66 | Left exit -> 67 | do Exit.toStderr (Exit.reactorToReport exit) 68 | error 69 | "\n--------------------------------------------------------\ 70 | \\nError in Develop.StaticFiles.Build.buildReactorFrontEnd\ 71 | \\nCompile with `elm make` directly to figure it out faster\ 72 | \\n--------------------------------------------------------\ 73 | \\n" 74 | -------------------------------------------------------------------------------- /terminal/src/Init.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Init 3 | ( run 4 | ) 5 | where 6 | 7 | 8 | import Prelude hiding (init) 9 | import qualified Data.Map as Map 10 | import qualified Data.NonEmptyList as NE 11 | import qualified System.Directory as Dir 12 | 13 | import qualified Deps.Solver as Solver 14 | import qualified Elm.Constraint as Con 15 | import qualified Elm.Outline as Outline 16 | import qualified Elm.Package as Pkg 17 | import qualified Elm.Version as V 18 | import qualified Reporting 19 | import qualified Reporting.Doc as D 20 | import qualified Reporting.Exit as Exit 21 | 22 | 23 | 24 | -- RUN 25 | 26 | 27 | run :: () -> () -> IO () 28 | run () () = 29 | Reporting.attempt Exit.initToReport $ 30 | do exists <- Dir.doesFileExist "elm.json" 31 | if exists 32 | then return (Left Exit.InitAlreadyExists) 33 | else 34 | do approved <- Reporting.ask question 35 | if approved 36 | then init 37 | else 38 | do putStrLn "Okay, I did not make any changes!" 39 | return (Right ()) 40 | 41 | 42 | question :: D.Doc 43 | question = 44 | D.stack 45 | [ D.fillSep 46 | ["Hello!" 47 | ,"Elm","projects","always","start","with","an",D.green "elm.json","file." 48 | ,"I","can","create","them!" 49 | ] 50 | , D.reflow 51 | "Now you may be wondering, what will be in this file? How do I add Elm files to\ 52 | \ my project? How do I see it in the browser? How will my code grow? Do I need\ 53 | \ more directories? What about tests? Etc." 54 | , D.fillSep 55 | ["Check","out",D.cyan (D.fromChars (D.makeLink "init")) 56 | ,"for","all","the","answers!" 57 | ] 58 | , "Knowing all that, would you like me to create an elm.json file now? [Y/n]: " 59 | ] 60 | 61 | 62 | 63 | -- INIT 64 | 65 | 66 | init :: IO (Either Exit.Init ()) 67 | init = 68 | do eitherEnv <- Solver.initEnv 69 | case eitherEnv of 70 | Left problem -> 71 | return (Left (Exit.InitRegistryProblem problem)) 72 | 73 | Right (Solver.Env cache _ connection registry) -> 74 | do result <- Solver.verify cache connection registry defaults 75 | case result of 76 | Solver.Err exit -> 77 | return (Left (Exit.InitSolverProblem exit)) 78 | 79 | Solver.NoSolution -> 80 | return (Left (Exit.InitNoSolution (Map.keys defaults))) 81 | 82 | Solver.NoOfflineSolution -> 83 | return (Left (Exit.InitNoOfflineSolution (Map.keys defaults))) 84 | 85 | Solver.Ok details -> 86 | let 87 | solution = Map.map (\(Solver.Details vsn _) -> vsn) details 88 | directs = Map.intersection solution defaults 89 | indirects = Map.difference solution defaults 90 | in 91 | do Dir.createDirectoryIfMissing True "src" 92 | Outline.write "." $ Outline.App $ 93 | Outline.AppOutline V.compiler (NE.List (Outline.RelativeSrcDir "src") []) directs indirects Map.empty Map.empty 94 | putStrLn "Okay, I created it. Now read that link!" 95 | return (Right ()) 96 | 97 | 98 | defaults :: Map.Map Pkg.Name Con.Constraint 99 | defaults = 100 | Map.fromList 101 | [ (Pkg.core, Con.anything) 102 | , (Pkg.browser, Con.anything) 103 | , (Pkg.html, Con.anything) 104 | ] 105 | -------------------------------------------------------------------------------- /worker/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/browser": "1.0.1", 10 | "elm/core": "1.0.2", 11 | "elm/html": "1.0.0", 12 | "elm/json": "1.1.3", 13 | "elm/project-metadata-utils": "1.0.0" 14 | }, 15 | "indirect": { 16 | "elm/parser": "1.1.0", 17 | "elm/time": "1.0.0", 18 | "elm/url": "1.0.0", 19 | "elm/virtual-dom": "1.0.2" 20 | } 21 | }, 22 | "test-dependencies": { 23 | "direct": {}, 24 | "indirect": {} 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /worker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name worker.elm-lang.org; 4 | 5 | location / { 6 | proxy_pass http://localhost:8000; 7 | } 8 | } 9 | 10 | server { 11 | listen 443 ssl; 12 | server_name worker.elm-lang.org; 13 | 14 | location / { 15 | proxy_pass http://localhost:8000; 16 | } 17 | 18 | ssl_certificate /etc/letsencrypt/live/worker.elm-lang.org/fullchain.pem; # managed by Certbot 19 | ssl_certificate_key /etc/letsencrypt/live/worker.elm-lang.org/privkey.pem; # managed by Certbot 20 | include /etc/letsencrypt/options-ssl-nginx.conf; 21 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 22 | } 23 | -------------------------------------------------------------------------------- /worker/outlines/compile/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "../../src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/browser": "1.0.1", 10 | "elm/core": "1.0.2", 11 | "elm/file": "1.0.5", 12 | "elm/html": "1.0.0", 13 | "elm/http": "2.0.0", 14 | "elm/json": "1.1.3", 15 | "elm/random": "1.0.0", 16 | "elm/svg": "1.0.1", 17 | "elm/time": "1.0.0", 18 | "elm-explorations/linear-algebra": "1.0.3", 19 | "elm-explorations/webgl": "1.1.0", 20 | "evancz/elm-playground": "1.0.2" 21 | }, 22 | "indirect": { 23 | "elm/bytes": "1.0.8", 24 | "elm/url": "1.0.0", 25 | "elm/virtual-dom": "1.0.2" 26 | } 27 | }, 28 | "test-dependencies": { 29 | "direct": {}, 30 | "indirect": {} 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /worker/outlines/repl/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "../../src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/core": "1.0.2" 10 | }, 11 | "indirect": { 12 | "elm/json": "1.1.3" 13 | } 14 | }, 15 | "test-dependencies": { 16 | "direct": {}, 17 | "indirect": {} 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /worker/src/Cors.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | module Cors 3 | ( allow 4 | ) 5 | where 6 | 7 | 8 | import qualified Data.HashSet as HashSet 9 | import Network.URI (parseURI) 10 | import Snap.Core (Snap, Method, method) 11 | import Snap.Util.CORS (CORSOptions(..), HashableMethod(..), OriginList(Origins), applyCORS, mkOriginSet) 12 | 13 | 14 | 15 | -- ALLOW 16 | 17 | 18 | allow :: Method -> [String] -> Snap () -> Snap () 19 | allow method_ origins snap = 20 | applyCORS (toOptions method_ origins) $ method method_ $ 21 | snap 22 | 23 | 24 | 25 | -- TO OPTIONS 26 | 27 | 28 | toOptions :: (Monad m) => Method -> [String] -> CORSOptions m 29 | toOptions method_ origins = 30 | let 31 | allowedOrigins = toOriginList origins 32 | allowedMethods = HashSet.singleton (HashableMethod method_) 33 | in 34 | CORSOptions 35 | { corsAllowOrigin = return allowedOrigins 36 | , corsAllowCredentials = return True 37 | , corsExposeHeaders = return HashSet.empty 38 | , corsAllowedMethods = return allowedMethods 39 | , corsAllowedHeaders = return 40 | } 41 | 42 | 43 | toOriginList :: [String] -> OriginList 44 | toOriginList origins = 45 | Origins $ mkOriginSet $ 46 | case traverse parseURI origins of 47 | Just uris -> uris 48 | Nothing -> error "invalid entry given to toOriginList list" 49 | -------------------------------------------------------------------------------- /worker/src/Endpoint/Slack.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Endpoint.Slack 3 | ( endpoint 4 | ) 5 | where 6 | 7 | 8 | import Control.Monad.IO.Class (liftIO) 9 | import Network.HTTP.Client 10 | import Snap.Core 11 | 12 | import qualified Cors 13 | 14 | import qualified Data.ByteString.Char8 as BSC 15 | import qualified Data.Map as Map 16 | 17 | 18 | 19 | -- Send invitations to the Elm Slack to whoever asks. 20 | -- 21 | -- NOTE: The API to invite users is not officially documented, but the people 22 | -- here looked in the Network tab of Developer Tools to figure it out: 23 | -- 24 | -- https://levels.io/slack-typeform-auto-invite-sign-ups/ 25 | -- https://github.com/outsideris/slack-invite-automation 26 | -- 27 | 28 | 29 | 30 | -- ALLOWED ORIGINS 31 | 32 | 33 | allowedOrigins :: [String] 34 | allowedOrigins = 35 | [ "https://elm-lang.org" 36 | ] 37 | 38 | 39 | 40 | -- ENDPOINT 41 | 42 | 43 | endpoint :: String -> Manager -> Snap () 44 | endpoint token manager = 45 | Cors.allow POST allowedOrigins $ 46 | do req <- getRequest 47 | case Map.findWithDefault [] "email" (rqQueryParams req) of 48 | [email] -> 49 | do response <- liftIO $ httpLbs (request email) manager 50 | modifyResponse $ setContentType "application/json" 51 | writeLBS (responseBody response) 52 | 53 | _ -> 54 | do modifyResponse $ setResponseStatus 400 "Bad Request" 55 | modifyResponse $ setContentType "text/html; charset=utf-8" 56 | writeBS "expecting query parameter like ?email=you@example.com" 57 | where 58 | slack_token = 59 | BSC.pack token 60 | 61 | request email = 62 | urlEncodedBody 63 | [ ("email", email) 64 | , ("token", slack_token) 65 | , ("set_active","true") 66 | ] 67 | (parseRequest_ "https://elmlang.slack.com/api/users.admin.invite") 68 | -------------------------------------------------------------------------------- /worker/src/Main.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wall #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Main 4 | ( main 5 | ) 6 | where 7 | 8 | 9 | import Control.Monad (msum) 10 | import qualified Data.ByteString as BS 11 | import Network.HTTP.Client.TLS (newTlsManager) 12 | import Snap.Core 13 | import Snap.Http.Server 14 | import qualified System.Environment as Env 15 | 16 | import qualified Artifacts 17 | import qualified Cors 18 | import qualified Endpoint.Compile as Compile 19 | import qualified Endpoint.Quotes as Quotes 20 | import qualified Endpoint.Repl as Repl 21 | import qualified Endpoint.Slack as Slack 22 | 23 | 24 | 25 | -- RUN THE DEV SERVER 26 | 27 | 28 | main :: IO () 29 | main = 30 | do manager <- newTlsManager 31 | slackToken <- Env.getEnv "SLACK_TOKEN" 32 | rArtifacts <- Artifacts.loadRepl 33 | cArtifacts <- Artifacts.loadCompile 34 | errorJS <- Compile.loadErrorJS 35 | let depsInfo = Artifacts.toDepsInfo cArtifacts 36 | 37 | httpServe config $ msum $ 38 | [ ifTop $ status 39 | , path "repl" $ Repl.endpoint rArtifacts 40 | , path "compile" $ Compile.endpoint_V1 cArtifacts 41 | , path "compile/v2" $ Compile.endpoint_V2 cArtifacts 42 | , path "compile/errors.js" $ serveJavaScript errorJS 43 | , path "compile/deps-info.json" $ serveDepsInfo depsInfo 44 | , path "quotes" $ Quotes.endpoint 45 | , path "slack-invite" $ Slack.endpoint slackToken manager 46 | , notFound 47 | ] 48 | 49 | 50 | config :: Config Snap a 51 | config = 52 | setPort 8000 $ setAccessLog ConfigNoLog $ setErrorLog ConfigNoLog $ defaultConfig 53 | 54 | 55 | status :: Snap () 56 | status = 57 | do modifyResponse $ setContentType "text/plain" 58 | writeBuilder "Status: OK" 59 | 60 | 61 | notFound :: Snap () 62 | notFound = 63 | do modifyResponse $ setResponseStatus 404 "Not Found" 64 | modifyResponse $ setContentType "text/html; charset=utf-8" 65 | writeBuilder "Not Found" 66 | 67 | 68 | serveJavaScript :: BS.ByteString -> Snap () 69 | serveJavaScript javascript = 70 | do modifyResponse $ setContentType "application/javascript" 71 | writeBS javascript 72 | 73 | 74 | serveDepsInfo :: BS.ByteString -> Snap () 75 | serveDepsInfo json = 76 | Cors.allow GET ["https://elm-lang.org"] $ 77 | do modifyResponse $ setContentType "application/json" 78 | writeBS json 79 | 80 | --------------------------------------------------------------------------------