Skip to content
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

Remote imports #16

Merged
merged 29 commits into from
Sep 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
38d6b31
Simple remote imports
mmhat Sep 10, 2021
730abc4
Dedicated resolver type and settings for interpretation
mmhat Sep 10, 2021
64a1b22
Applied some HLint suggestions
mmhat Sep 10, 2021
244f9fc
Added `mainWith` function
mmhat Sep 10, 2021
ac60866
URI authorities shall not be empty
mmhat Sep 15, 2021
f5a13de
Fixed remaining lexing errors
mmhat Sep 15, 2021
21abfd9
Added env:// resolver
mmhat Sep 15, 2021
dab917d
Added tests
mmhat Sep 15, 2021
2430687
Updated README
mmhat Sep 15, 2021
7594c8e
Added some missing module headers
mmhat Sep 15, 2021
2709a5c
Run HLint
mmhat Sep 15, 2021
20c0c90
Use example.com in README
mmhat Sep 16, 2021
eaa832e
Replaced InterpretSettings with ImportCallback
mmhat Sep 16, 2021
b367393
Added grace-resolver-builtin package
mmhat Sep 16, 2021
a0c4a79
Use builtin resolver from grace-resolver-builtin
mmhat Sep 16, 2021
c2f187a
Updated CI
mmhat Sep 16, 2021
d60ad05
Resolver and ImportCallback return Syntax instead of source code
mmhat Sep 16, 2021
6b12160
Removed slash replacement from env resolver
mmhat Sep 16, 2021
e9e3fb7
Removed customizability of interpreter
mmhat Sep 17, 2021
05e995f
Fixed CI
mmhat Sep 17, 2021
eeb30be
Fixed: REPL should not crash
mmhat Sep 17, 2021
db9d916
Removed external resolver
mmhat Sep 20, 2021
b5ad8ac
Applied suggestions
mmhat Sep 21, 2021
74a993c
Update grace-core/src/Grace/Lexer.hs
mmhat Sep 21, 2021
0524bc8
Changed ImportCallback to a newtype
mmhat Sep 22, 2021
84e78ef
Merge branch 'main' into gabriel/remote-imports
Gabriella439 Sep 22, 2021
7fa97e0
Consolidate logic
Gabriella439 Sep 24, 2021
5d9baeb
Merge pull request #1 from Gabriel439/gabriel/remote-imports
mmhat Sep 24, 2021
6794fe8
Fixed errors in grace package
mmhat Sep 24, 2021
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 .github/workflows/haskell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ jobs:
- name: Build
run: cabal build --enable-tests --enable-benchmarks all
- name: Run tests
run: cabal test tasty
run: cabal test grace-core:tasty
68 changes: 68 additions & 0 deletions .hlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# HLint configuration file
# https://github.com/ndmitchell/hlint
##########################

# This file contains a template configuration file, which is typically
# placed as .hlint.yaml in the root of your project


# Specify additional command line arguments
#
# - arguments: [--color, --cpp-simple, -XQuasiQuotes]


# Control which extensions/flags/modules/functions can be used
#
# - extensions:
# - default: false # all extension are banned by default
# - name: [PatternGuards, ViewPatterns] # only these listed extensions can be used
# - {name: CPP, within: CrossPlatform} # CPP can only be used in a given module
#
# - flags:
# - {name: -w, within: []} # -w is allowed nowhere
#
# - modules:
# - {name: [Data.Set, Data.HashSet], as: Set} # if you import Data.Set qualified, it must be as 'Set'
# - {name: Control.Arrow, within: []} # Certain modules are banned entirely
#
# - functions:
# - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules


# Add custom hints for this project
#
# Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar"
# - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x}

# The hints are named by the string they display in warning messages.
# For example, if you see a warning starting like
#
# Main.hs:116:51: Warning: Redundant ==
#
# You can refer to that hint with `{name: Redundant ==}` (see below).

# Turn on hints that are off by default
#
# Ban "module X(module X) where", to require a real export list
# - warn: {name: Use explicit module export list}
#
# Replace a $ b $ c with a . b $ c
# - group: {name: dollar, enabled: true}
#
# Generalise map to fmap, ++ to <>
# - group: {name: generalise, enabled: true}


# Ignore some builtin hints
# - ignore: {name: Use let}
# - ignore: {name: Use const, within: SpecialModule} # Only within certain modules
- ignore: {name: Use fmap, within: Grace.Parser}
- ignore: {name: Use <$>, within: Grace.Parser}


# Define some custom infix operators
# - fixity: infixr 3 ~^#^~


# To generate a suitable file for HLint do:
# $ hlint --default > .hlint.yaml
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,11 @@ annotation as a last resort.

### Imports

Grace has two ways to import expressions from other sources: Filepath-based
imports and imports using URIs.

#### Imports from files

You can import a Grace subexpression stored within a separate file by
referencing the file's relative or absolute path.

Expand Down Expand Up @@ -974,6 +979,50 @@ $ grace interpret - <<< './greet.ffg "John"'

Any subexpression can be imported in this way.

#### Imports using URIs

Imports with URIs work similar to the ones using a simple filepath.

Suppose you do not have the `greet.ffg` stored locally but instead it resides
on a web server: `http://example.com/grace/greet.ffg`
You could either download it and reference it by its filepath like demonstrated
in the example above or let the Grace interpreter do the job:

```bash
$ grace interpret - <<< 'http://example.com/grace/greet.ffg "John"'
```
```dhall
"Hello, John!"
```

Note that if a particular URI can be handled by the Grace interpreter depends
on its (compile-time) configuration: Internally it relies on a set of _resolvers_
that take care of all the things related to networking like downloading, caching
, verifying the integrity of retrieved file on so on.
For instance, the motivating example will unfortunately not work out-of-the box
since the grace executable has no builtin resolver for HTTP (yet).

Grace comes with two builtin resolvers:

1. One to resolve `env://` URIs,
2. one to resolve `file://` URIs.

Lets have a look at the `env://` resolver first:
```bash
$ MY_VAR='"Hello !"' grace interpret - <<< 'env:///MY_VAR'
```
```dhall
"Hello !"
```

The `file://` resolver is similar to the filepath-based imports we already know:
```bash
$ grace interpret - <<< 'file:///path/to/greet.ffg "John"'
```
```dhall
"Hello, John!"
```

## Name

Like all of my programming language projects, Grace is named after a
Expand Down
6 changes: 6 additions & 0 deletions grace-core/grace-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,24 @@ library
, insert-ordered-containers
, lens
, megaparsec
, modern-uri
, mtl
, parser-combinators
, prettyprinter
, prettyprinter-ansi-terminal
, safe-exceptions
, scientific
, string-interpolate
, template-haskell
, terminal-size
, text
, typed-process
, unordered-containers
exposed-modules: Grace.Context
, Grace.Domain
, Grace.Existential
, Grace.Import
, Grace.Import.Resolver
, Grace.Interpret
, Grace.Infer
, Grace.Lexer
Expand Down Expand Up @@ -59,6 +64,7 @@ test-suite tasty
, tasty-hunit
, tasty-silver
, text
other-modules: Grace.Test.Resolver
hs-source-dirs: tasty
default-language: Haskell2010
ghc-options: -Wall
Expand Down
124 changes: 124 additions & 0 deletions grace-core/src/Grace/Import.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}

{- | This module contains the functions and types that power to URI-base imports
-}

module Grace.Import
( Input(..)
, Resolver(..)
, resolverToCallback
, ImportError(..)
) where

import Control.Exception.Safe (Exception(..), throw)
import Data.List.NonEmpty (NonEmpty(..))
import Data.Text (Text)
import Grace.Location (Location)
import Grace.Pretty (Pretty(..))
import Grace.Syntax (Syntax)
import System.FilePath ((</>))

import qualified Data.Text as Text
import qualified System.FilePath as FilePath
import qualified Text.URI as URI

{-| Input to the interpreter.

You should prefer to use `Path` if possible (for better error messages and
correctly handling transitive imports). The `Code` constructor is intended
for cases like interpreting code read from standard input.
-}
data Input
= Path FilePath
-- ^ The path to the code
| Code String Text
-- ^ Source code: @Code name content@
| URI URI.URI
deriving (Eq, Show)

instance Semigroup Input where
_ <> URI uri = URI uri

_ <> Code name code = Code name code

Code _ _ <> Path child = Path child
Path parent <> Path child = Path (FilePath.takeDirectory parent </> child)
URI parent <> Path child
| FilePath.isRelative child
, Just uri <- URI.relativeTo childURI parent =
URI uri
| otherwise =
Path child
where
uriPath = do
c : cs <- traverse (URI.mkPathPiece . Text.pack) (FilePath.splitPath child)

return (FilePath.hasTrailingPathSeparator child, c :| cs)

childURI =
URI.URI
{ URI.uriScheme = Nothing
, URI.uriAuthority = Left False
, URI.uriPath = uriPath
, URI.uriQuery = []
, URI.uriFragment = Nothing
}

instance Pretty Input where
pretty (Code _ code) = pretty code
pretty (Path path) = pretty path
pretty (URI uri) = pretty uri

{- | A resolver for an URI.

When the interpreter tries to resolve an URI pointing to some source code
it will try multiple resolvers sequentially and stops if one returns a
@Just code@ value where @code@ is the source code of an expression.
It will then try to parse and interpret that expression.

Here are some good practices for the development of resolvers:

* A resolver should handle exactly one URI scheme.

* If a resolver encounters an URI which it cannot process (e.g. a
@file://@ URI is passed to a HTTP resolver) it should return @Nothing@
as fast as possible.

* Exceptions thrown in resolvers will be caught and rethrown as an
`ImportError` by the interpreter.
-}
newtype Resolver = Resolver
{ runResolver :: Input -> IO (Maybe (Syntax Location Input))
}

instance Semigroup Resolver where
x <> y = Resolver \uri -> do
maybeResult <- runResolver x uri
case maybeResult of
Nothing -> runResolver y uri
_ -> return maybeResult

instance Monoid Resolver where
mempty = Resolver (const (return Nothing))

-- | Convert a resolver to a callback function
resolverToCallback :: Resolver -> Input -> IO (Syntax Location Input)
resolverToCallback resolver uri = do
maybeResult <- runResolver resolver uri
case maybeResult of
Nothing -> throw UnsupportedInput
Just result -> return result

-- | Errors that might be raised during import resolution.
data ImportError
= UnsupportedInput
deriving stock Show

instance Exception ImportError where
displayException UnsupportedInput = "Resolving this input is not supported"
Loading