Skip to content

Repl API #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 35 commits into
base: lamdera-next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d8ae362
llvm@13 fixes local M1 build, try on buildserver
supermario Sep 11, 2023
15a7069
Patch in repl API functionality for elm-notebook exploration
supermario Sep 14, 2023
c097d3e
change alllowed origins to http://localhost:8007 only
jxxcarlson Sep 17, 2023
b2a2e56
Exclude Jim's experimental files
jxxcarlson Sep 17, 2023
e0d55f0
Change Test.hs so as to talk to elm-notebook
jxxcarlson Oct 2, 2023
7c8f310
Add debug statements
jxxcarlson Oct 2, 2023
8290dee
Added: '_ -> error $ "unreachable:" ++ show e' to function 'watch'. …
jxxcarlson Oct 2, 2023
fa3cfe3
I am committing this, but I can't see what has changed.
jxxcarlson Oct 2, 2023
441f1ce
Add elm-community/list-extra to outlines/repl/elm.json
jxxcarlson Oct 2, 2023
a27d4bb
Remove duplicate elm.json entries
jxxcarlson Oct 4, 2023
d591f19
Add module Endpoint.Package from extra/, (2) Chane Develop (in termin…
jxxcarlson Oct 5, 2023
d1d0cd9
Fix path for writing the elm.json file
jxxcarlson Oct 5, 2023
92c1c80
Fix stray space in word "dependencies"
jxxcarlson Oct 5, 2023
1e775be
Add type ElmPackage and decoder for it.
jxxcarlson Oct 5, 2023
9e57721
Return outlines/repl/elm.json to its original state
jxxcarlson Oct 5, 2023
c9af64d
Introduced a deterministic delay for executing Notebook.Package.nowSe…
jxxcarlson Oct 5, 2023
87727e6
When packages are added to elm.json, report to the client how many we…
jxxcarlson Oct 12, 2023
e544b92
Draft 1
jxxcarlson Oct 13, 2023
48def3e
Draft 2
jxxcarlson Oct 13, 2023
3391d5f
Fix JSON output
jxxcarlson Oct 13, 2023
b727101
Implement dynamic loading of packages submitted to the Elm compiler.
jxxcarlson Oct 14, 2023
8046918
Return outlines/repl/elm.json to its original state
jxxcarlson Oct 14, 2023
3935f9d
fix but in properly computng evalstate before compilation.
jxxcarlson Oct 15, 2023
effbe40
Add debug code to Endpoint/Repl.hs
jxxcarlson Oct 30, 2023
8a8a994
Add 'compiler.iml' to .gitignore
jxxcarlson Oct 30, 2023
bdf9ffc
Return to previous working state
jxxcarlson Oct 30, 2023
7d4f078
add back extra/Artifacts.hs
jxxcarlson Oct 30, 2023
be46c86
Restore elm.json to original state
jxxcarlson Oct 30, 2023
ac87fe8
Renamed: extra/Artifacts.hs -> extra/ReplArtifacts.hs
jxxcarlson Oct 30, 2023
dc1fbe5
Add comment explaining the purpose of module Package
jxxcarlson Oct 30, 2023
b422951
Add comments to explain the purpose and operation of extra/Endpoint/P…
jxxcarlson Oct 30, 2023
14b46e6
More comments
jxxcarlson Oct 30, 2023
20bb71e
Comments for ReplArtifacts
jxxcarlson Oct 30, 2023
9173633
Add clause 'Types.OnConnected sessionId clientId' to Backend.update:
jxxcarlson Jan 17, 2024
1505d77
Merge branch 'lamdera-next' into elm-notebook-repl
supermario Jan 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ghci
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
:set -fbyte-code
:set -fobject-code
:set -fwarn-name-shadowing
:def rr const $ return $ unlines ["Ext.Common.killTrackedThreads",":r","Test.target"]
:def rr const $ return $ unlines ["Ext.Common.killTrackedThreads",":r","Test.target"]
2 changes: 1 addition & 1 deletion .github/workflows/build-macos-arm64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ jobs:
- name: Run distribution script
run: |
test -x "$(which ghcup)" && curl https://downloads.haskell.org/~ghcup/aarch64-apple-darwin-ghcup -o ~/.local/bin/ghcup && chmod a+x ~/.local/bin/ghcup
brew install llvm@12
brew install llvm@13
cd distribution
./build-macos-arm64.sh
1 change: 0 additions & 1 deletion .github/workflows/build-macos-x86_64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,5 @@ jobs:
- name: Run distribution script
run: |
test -x "$(which ghcup)" && curl https://downloads.haskell.org/~ghcup/aarch64-apple-darwin-ghcup -o ~/.local/bin/ghcup && chmod a+x ~/.local/bin/ghcup
brew install llvm@12
cd distribution
./build-macos-x86_64.sh
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ extra/.cache
# @TESTS
elm-home

# Jim
experimental/
.vscode/
2 changes: 1 addition & 1 deletion distribution/build-macos-arm64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ git submodule init && git submodule update
ffiLibs="$(xcrun --show-sdk-path)/usr/include/ffi" # Workaround for GHC9.0.2 bug until we can use GHC9.2.3+
export C_INCLUDE_PATH=$ffiLibs # https://gitlab.haskell.org/ghc/ghc/-/issues/20592#note_436353

export PATH="/opt/homebrew/opt/llvm@12/bin:$PATH" # The arm64 build currently requires llvm until we get to GHC 9.4+
export PATH="/opt/homebrew/opt/llvm@13/bin:$PATH" # The arm64 build currently requires llvm until we get to GHC 9.4+

$stack install --local-bin-path $dist

Expand Down
4 changes: 4 additions & 0 deletions elm.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ Executable lamdera
Test.Wire
Lamdera.Evergreen.TestMigrationHarness
Lamdera.Evergreen.TestMigrationGenerator
Endpoint.Repl
Artifacts
Cors


-- Debug helpers --
Expand Down Expand Up @@ -397,6 +400,7 @@ Executable lamdera
-- Debug
unicode-show,
network-info,
network-uri,

-- Future
conduit-extra,
Expand Down
1 change: 1 addition & 0 deletions ext-sentry/Ext/Filewatch.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ watch root action =
Modified f _ _ -> f
Removed f _ _ -> f
Unknown f _ _ _ -> f
_ -> error $ "unreachable:" ++ show e

-- @TODO it would be better to not listen to these folders in the `watchTree` when available
-- https://github.com/haskell-fswatch/hfsnotify/issues/101
Expand Down
155 changes: 155 additions & 0 deletions extra/Artifacts.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
{-# OPTIONS_GHC -Wall #-}
module Artifacts
( Artifacts(..)
, loadCompile
, loadRepl
, toDepsInfo
)
where


import Control.Concurrent (readMVar)
import Control.Monad (liftM2)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Map as Map
import qualified Data.Name as N
import qualified Data.OneOrMore as OneOrMore
import qualified System.Directory as Dir
import System.FilePath ((</>))

import qualified AST.Canonical as Can
import qualified AST.Optimized as Opt
import qualified BackgroundWriter as BW
import qualified Elm.Details as Details
import qualified Elm.Interface as I
import qualified Elm.ModuleName as ModuleName
import qualified Elm.Package as Pkg
import Json.Encode ((==>))
import qualified Json.Encode as E
import qualified Json.String as Json
import qualified Reporting



-- ARTIFACTS


data Artifacts =
Artifacts
{ _ifaces :: Map.Map ModuleName.Raw I.Interface
, _graph :: Opt.GlobalGraph
}


loadCompile :: IO Artifacts
loadCompile =
load ("outlines" </> "compile")


loadRepl :: IO Artifacts
loadRepl =
load ("outlines" </> "repl")



-- LOAD


load :: FilePath -> IO Artifacts
load dir =
BW.withScope $ \scope ->
do putStrLn $ "Loading " ++ dir </> "elm.json"
style <- Reporting.terminal
root <- fmap (</> dir) Dir.getCurrentDirectory
result <- Details.load style scope root
case result of
Left _ ->
error $ "Ran into some problem loading elm.json\nTry running `lamdera make` in: " ++ dir

Right details ->
do omvar <- Details.loadObjects root details
imvar <- Details.loadInterfaces root details
mdeps <- readMVar imvar
mobjs <- readMVar omvar
case liftM2 (,) mdeps mobjs of
Nothing ->
error $ "Ran into some weird problem loading elm.json\nTry running `lamdera make` in: " ++ dir

Just (deps, objs) ->
return $ Artifacts (toInterfaces deps) objs


toInterfaces :: Map.Map ModuleName.Canonical I.DependencyInterface -> Map.Map ModuleName.Raw I.Interface
toInterfaces deps =
Map.mapMaybe toUnique $ Map.fromListWith OneOrMore.more $
Map.elems (Map.mapMaybeWithKey getPublic deps)


getPublic :: ModuleName.Canonical -> I.DependencyInterface -> Maybe (ModuleName.Raw, OneOrMore.OneOrMore I.Interface)
getPublic (ModuleName.Canonical _ name) dep =
case dep of
I.Public iface -> Just (name, OneOrMore.one iface)
I.Private _ _ _ -> Nothing


toUnique :: OneOrMore.OneOrMore a -> Maybe a
toUnique oneOrMore =
case oneOrMore of
OneOrMore.One value -> Just value
OneOrMore.More _ _ -> Nothing



-- TO DEPS INFO


toDepsInfo :: Artifacts -> BS.ByteString
toDepsInfo (Artifacts ifaces _) =
LBS.toStrict $ B.toLazyByteString $ E.encodeUgly $ encode ifaces



-- ENCODE


encode :: Map.Map ModuleName.Raw I.Interface -> E.Value
encode ifaces =
E.dict Json.fromName encodeInterface ifaces


encodeInterface :: I.Interface -> E.Value
encodeInterface (I.Interface pkg values unions aliases binops) =
E.object
[ "pkg" ==> E.chars (Pkg.toChars pkg)
, "ops" ==> E.list E.name (Map.keys binops)
, "values" ==> E.list E.name (Map.keys values)
, "aliases" ==> E.list E.name (Map.keys (Map.filter isPublicAlias aliases))
, "types" ==> E.dict Json.fromName (E.list E.name) (Map.mapMaybe toPublicUnion unions)
]


isPublicAlias :: I.Alias -> Bool
isPublicAlias alias =
case alias of
I.PublicAlias _ -> True
I.PrivateAlias _ -> False


toPublicUnion :: I.Union -> Maybe [N.Name]
toPublicUnion union =
case union of
I.OpenUnion (Can.Union _ variants _ _) ->
Just (map getVariantName variants)

I.ClosedUnion _ ->
Just []

I.PrivateUnion _ ->
Nothing


getVariantName :: Can.Ctor -> N.Name
getVariantName (Can.Ctor name _ _ _) =
name
48 changes: 48 additions & 0 deletions extra/Cors.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{-# OPTIONS_GHC -Wall #-}
module Cors
( allow
)
where


import qualified Data.HashSet as HashSet
import Network.URI (parseURI)
import Snap.Core (Snap, Method, method)
import Snap.Util.CORS (CORSOptions(..), HashableMethod(..), OriginList(Origins), applyCORS, mkOriginSet)



-- ALLOW


allow :: Method -> [String] -> Snap () -> Snap ()
allow method_ origins snap =
applyCORS (toOptions method_ origins) $ method method_ $
snap



-- TO OPTIONS


toOptions :: (Monad m) => Method -> [String] -> CORSOptions m
toOptions method_ origins =
let
allowedOrigins = toOriginList origins
allowedMethods = HashSet.singleton (HashableMethod method_)
in
CORSOptions
{ corsAllowOrigin = return allowedOrigins
, corsAllowCredentials = return True
, corsExposeHeaders = return HashSet.empty
, corsAllowedMethods = return allowedMethods
, corsAllowedHeaders = return
}


toOriginList :: [String] -> OriginList
toOriginList origins =
Origins $ mkOriginSet $
case traverse parseURI origins of
Just uris -> uris
Nothing -> error "invalid entry given to toOriginList list"
96 changes: 96 additions & 0 deletions extra/Endpoint/Package.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}

module Endpoint.Package (handlePost, reportOnInstalledPackages) where

import GHC.Generics (Generic)


import Snap.Core
import Snap.Http.Server
import Data.Aeson (FromJSON, eitherDecode, encode, ToJSON, toJSON, object, (.=))
import Data.ByteString (ByteString)
import Data.ByteString.Lazy (toStrict)
import qualified Data.ByteString.Lazy.Char8 as BL
import qualified Data.ByteString.Char8 as ByteString
import GHC.Generics
import System.IO (writeFile)
import Control.Monad.IO.Class (liftIO)
import qualified Data.Map as Map
---
import Snap.Util.FileServe
import qualified Data.ByteString.Lazy as LBS
import qualified Data.HashMap.Strict as HM
import Data.Text.Encoding (decodeUtf8)
import Snap.Http.Server.Config (setPort, defaultConfig)
import qualified Artifacts
import Data.IORef


data Package = Package { name :: String, version :: String } deriving (Show, Generic)

instance FromJSON Package
instance ToJSON Package


type PackageList = [Package]

writeElmJson :: PackageList -> IO ()
writeElmJson pkgs = do
let directDeps = Map.fromList $ ("elm/core", "1.0.5"):[(name p, version p) | p <- pkgs]
elmJson = object [
"type" .= ("application" :: String),
"source-directories" .= (["../../repl-src"] :: [String]),
"elm-version" .= ("0.19.1" :: String),
"dependencies" .= object [
"direct" .= directDeps,
"indirect" .= object [
"elm/json" .= ("1.1.3" :: String)
]
],
"test-dependencies" .= object [
"direct" .= (Map.empty :: Map.Map String String),
"indirect" .= (Map.empty :: Map.Map String String)
]
]
writeFile "./outlines/repl/elm.json" ( BL.unpack $ encode elmJson)


handlePost :: IORef Artifacts.Artifacts -> Snap ()
handlePost artifactRef = do
body <- readRequestBody 10000
let maybePackageList = eitherDecode body :: Either String PackageList
case maybePackageList of
Left err -> writeBS $ "Error: Could not decode JSON: " <> (ByteString.pack err)
Right packages -> do
liftIO $ writeElmJson packages
let message = ByteString.pack $ "Packages added: " ++ (show $ length packages)
writeBS message
newArtifacts <- liftIO Artifacts.loadRepl
liftIO $ writeIORef artifactRef newArtifacts



data Dependencies = Dependencies {
direct :: HM.HashMap String String
} deriving (Generic, Show)

data TopLevel = TopLevel {
dependencies :: Dependencies
} deriving (Generic, Show)

instance FromJSON TopLevel

instance FromJSON Dependencies

--- curl -X POST -H "Content-Length: 0" http://localhost:8000/reportOnInstalledPackages

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For testing the endpoint that returns a list of installed packages, for example:

curl -X POST -H "Content-Length: 0" http://localhost:8000/reportOnInstalledPackages
[{"name":"elm/parser","version":"1.1.0"},{"name":"elm/core","version":"1.0.5"}]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I not add comments explaining the purpose of this file?

reportOnInstalledPackages :: Snap ()
reportOnInstalledPackages = do
jsonData <- liftIO $ LBS.readFile "./outlines/repl/elm.json"
case eitherDecode jsonData :: Either String TopLevel of
Left err -> writeBS $ "Failed to parse JSON: " <> (LBS.toStrict jsonData)
Right topLevel -> do
let directDeps = HM.toList $ direct $ dependencies topLevel
let outputList = map (\(name, version) -> object ["name" .= name, "version" .= version]) directDeps
writeBS . LBS.toStrict . encode $ outputList
Loading