Skip to content

Commit

Permalink
Merge pull request #70 from phischu/v0.6.0
Browse files Browse the repository at this point in the history
V0.6.0
  • Loading branch information
phischu committed Dec 21, 2015
2 parents 5986b61 + 87454ce commit f56e9f4
Show file tree
Hide file tree
Showing 260 changed files with 1,020 additions and 2,987 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
Changes
=======

Version 0.6.0
-------------

* Use haskell-src-exts 1.17
* Remove dependency on haskell-packages

Version 0.5.3
------------

* Compatibility with GHC 7.8.4

Version 0.5.2
-------------

Expand Down
227 changes: 72 additions & 155 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,32 @@
haskell-names
haskell-names [![Build Status](https://travis-ci.org/phischu/haskell-names.svg?branch=master)](https://travis-ci.org/phischu/haskell-names)
=============

haskell-names does name and module resolution for haskell-src-exts AST.

Namely, it can do the following:

* for a module, compute its interface, i.e. the set of entities exported by the
module, together with their original names.
* for each name in the module, figure out what it refers to — whether it's bound
* for a liat of modules, compute the lists of symbols they export.
This is called `resolve`.
* for each name in a module, figure out what it refers to — whether it's bound
locally (say, by a `where` clause) or globally (and then give its origin).
This is called `annotate`.

Installation
------------

To install a released version:

1. Install Cabal and cabal-install from [the git repository][cabal] (the
`master` branch)
2. `cabal install haskell-names hs-gen-iface`

If you're building a development version, then you might also need to install
development versions of [haskell-src-exts][hse], [haskell-packages][hp], and [hse-cpp][].
a development version of [haskell-src-exts][hse].

[cabal]: https://github.com/haskell/cabal/
[hse]: https://github.com/haskell-suite/haskell-src-exts
[hp]: https://github.com/haskell-suite/haskell-packages
[hse-cpp]: https://github.com/haskell-suite/hse-cpp

Module interfaces
-----------------

`hs-gen-iface` is a «compiler» that generates interfaces for Haskell modules.
Environments
-----------------

An interface is a JSON file that lists all entities (types, classes, functions
etc.) exported by every module. For example, here are a couple of entries from
`Prelude.names`:
An environment is a map from module name to list of entities the module exports.
Entities are for example types, class, functions etc. We store these lists in
a JSON format.
For example, here are a couple of entries from `Prelude.names`:

``` json
[
Expand All @@ -56,65 +48,13 @@ As you see, each entity is annotated with the module where it was
originally defined. Additionally, class methods, field selectors, and data
constructors are annotated with the class or type they belong to.

### Generating interfaces

Thanks to haskell-packages, `hs-gen-iface` is fully integrated with Cabal. To
produce and install interface files, pass `--haskell-suite -w hs-gen-iface` flags
to `cabal install`, for instance

cabal install --haskell-suite -w hs-gen-iface mtl

This assumes that the `hs-gen-iface` executable is in your `PATH`. You can specify
the full path to `hs-gen-iface` after `-w`, too.

#### Core packages

haskell-names comes with the global package database populated with some core
packages:

% hs-gen-iface pkg list --global
array-0.4.0.2
base-4.7.0.0
integer-simple-0.1.1.0
ghc-prim-0.3.1.0

#### Compiling core packages by hand

Suppose you need to compile any of the core packages by hand — for example, to
get a different version than the one bundled with haskell-names.

Core packages, such as `ghc-prim`, `integer-simple`, `array`, and `base`, are
highly GHC-specific and need to be tweaked a bit before they can be processed by
haskell-names. Get our modified versions:

* [ghc-prim](https://github.com/haskell-suite/ghc-prim)
* [array](https://github.com/haskell-suite/array)
* [base](https://github.com/haskell-suite/base)
* [integer-simple](https://github.com/haskell-suite/integer-simple)

Note that Cabal's new dependency solver won't let you install `ghc-prim`
or `base` easily. There are two ways to work around this:

1. Use the old solver:

cabal install --haskell-suite -w hs-gen-iface --solver=topdown

2. Invoke all the steps manually:

cabal configure --haskell-suite -w hs-gen-iface
cabal build
cabal install --only

### Using interfaces

You can parse interface files directly, but a better idea is to use
`Distribution.HaskellSuite.Packages` API (from haskell-packages), combined with
the package database `NamesDB` defined in `Language.Haskell.Modules.Interfaces`.
`haskell-names` provides functions `readSymbols` and `writeSymbols`
to read and write interface files.

Name resolution
---------------

The `annotateModule` function annotates the module with scoping information.
The `annotate` function annotates the given module with scoping information.

Its essence is described in the article [Open your name resolution][openrec].

Expand All @@ -128,85 +68,68 @@ Let's say you have a module and you want to find out whether it uses
``` haskell
module Main where

import Language.Haskell.Exts.Annotated
import qualified Language.Haskell.Exts as UnAnn (Name(Ident))
import Language.Haskell.Names
import Language.Haskell.Names.Interfaces
import Distribution.HaskellSuite
import Distribution.Simple.Compiler

import Data.Maybe
import Data.List
import Data.Proxy
import qualified Data.Foldable as Foldable
import Text.Printf
import Control.Applicative
import Control.Monad
import Language.Haskell.Exts.Annotated (
fromParseResult, parseModuleWithMode, defaultParseMode,
parseFilename, prettyPrint, srcInfoSpan)
import Language.Haskell.Exts (
Name(Ident), ModuleName(ModuleName))
import Language.Haskell.Names (
loadBase, annotate, symbolName,
Scoped(Scoped), NameInfo(GlobalSymbol))

import qualified Data.Map as Map (
lookup)

import Data.Maybe (
fromMaybe, listToMaybe)
import Data.List (
nub)
import qualified Data.Foldable as Foldable (
toList)
import Control.Monad (
forM_, guard)

main :: IO ()
main = do

-- read the program's source from stdin
source <- getContents

let
-- parse the program (using haskell-src-exts)
ast = fromParseResult $
parseModuleWithMode defaultParseMode {parseFilename="stdin"} source

-- get all installed packages (user and global)
pkgs <-
(++) <$>
getInstalledPackages (Proxy :: Proxy NamesDB) UserPackageDB <*>
getInstalledPackages (Proxy :: Proxy NamesDB) GlobalPackageDB

headUsages <- evalNamesModuleT (findHeads ast) pkgs

forM_ headUsages $ \loc ->
printf "Prelude.head is used at %s\n" (prettyPrint $ srcInfoSpan loc)

when (null headUsages) $
printf "Congratulations! Your code doesn't use Prelude.head\n"

-- | The `findHeads` function finds all occurrences of the `head` symbol in
-- a given AST of a Haskell module. It needs access to stored name information
-- and therefore runs in `ModuleT`.
findHeads :: Module SrcSpanInfo -> ModuleT [Symbol] IO [SrcSpanInfo]
findHeads ast = do

-- First we get all symbols exported from `Prelude` with `getModuleInfo`.
symbols <- fromMaybe (error "Prelude not found") <$>
getModuleInfo "Prelude"

-- Then we filter those for the one with name `"head"`.
let
headSymbol =
fromMaybe (error "Prelude.head not found") (listToMaybe (do
symbol <- symbols
guard (symbolName symbol == UnAnn.Ident "head")
return symbol))

-- We annotate the given ast.
annotatedAst <-
annotateModule
Haskell2010 -- base language
[] -- set of extensions
ast

-- We get a list of all annotations from the annotated module.
let
annotations = Foldable.toList annotatedAst

-- A `GlobalSymbol` annotation means that the annotated name refers to a
-- global symbol. It also contains the qualified name that corresponds
-- to how it is referenced but that is not needed here.
headUsages = nub (do
Scoped (GlobalSymbol globalSymbol _) location <- annotations
guard (globalSymbol == headSymbol)
return location)

-- And finally we return all found usages.
return headUsages
-- parse the program (using haskell-src-exts)
let ast = fromParseResult (
parseModuleWithMode defaultParseMode {parseFilename="stdin"} source)

-- get base environment
baseEnvironment <- loadBase

-- get symbols defined in prelude
let preludeSymbols = fromMaybe (error "Prelude not found") (
Map.lookup (ModuleName "Prelude") baseEnvironment)

-- find a Prelude symbol with name 'head' using the List monad
let headSymbol = fromMaybe (error "Prelude.head not found") (
listToMaybe (do
preludeSymbol <- preludeSymbols
guard (symbolName preludeSymbol == Ident "head")
return preludeSymbol))

-- annotate the AST
let annotatedAST = annotate baseEnvironment ast

-- get all annotations
let annotations = Foldable.toList annotatedAST

-- filter head Usages in List monad and remove duplicates
let headUsages = nub (do
Scoped (GlobalSymbol globalSymbol _) location <- annotations
guard (globalSymbol == headSymbol)
return location)

case headUsages of
[] ->
putStrLn "Congratulations! Your code doesn't use Prelude.head"
_ -> forM_ headUsages (\location ->
putStrLn ("Prelude.head is used at " ++ (prettyPrint (srcInfoSpan location))))

```

Expand All @@ -230,25 +153,18 @@ main = do

See [haskell-names haddock documentation][doc-index].

The two modules you need are:

* [Language.Haskell.Names][] — exports the core functions and data types
* [Language.Haskell.Names.Interfaces][] — lets you work with haskell-names interface files
The core module you need is [Language.Haskell.Names][]

Other modules are more experimental, less documented, and you probably don't need
them anyway.

[doc-index]: http://haskell-suite.github.io/docs/haskell-names/
[Language.haskell.Names]: http://haskell-suite.github.io/docs/haskell-names/Language-Haskell-Names.html
[Language.Haskell.Names.Interfaces]: http://haskell-suite.github.io/docs/haskell-names/Language-Haskell-Names-Interfaces.html

### Known issues

See the [list of all issues][issues].

* Because a non-trivial amount of packages are not designed to work with
anything except GHC, hs-gen-iface currently pretends to be GHC. This is of
course not acceptable — contributions here are welcome. ([#32][])
* haskell-names doesn't perform validation yet. If a module is not valid
Haskell, then the behaviour is undefined. See the issues marked as
[validation][].
Expand All @@ -270,3 +186,4 @@ Maintainers

[Adam Bergmark](https://github.com/bergmark) is the backup maintainer. Please
get in touch with him if the primary maintainer cannot be reached.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit f56e9f4

Please sign in to comment.