Skip to content
This repository has been archived by the owner on Oct 18, 2021. It is now read-only.

Commit

Permalink
Feature: What Is That Error (#204)
Browse files Browse the repository at this point in the history
This was the worst PR in the history of PRs, maybe ever
  • Loading branch information
matheus authored Nov 4, 2019
1 parent 70f18c1 commit a948da8
Show file tree
Hide file tree
Showing 229 changed files with 1,626 additions and 248 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ doc/*.pdf
doc/*.synctex.gz

/*.xml

*.tags
/.tags
12 changes: 9 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ language: generic

cache:
directories:
- $HOME/.stack
- "$HOME/.stack"

addons:
apt:
packages:
- libgmp-dev
- lld-6.0
- libgmp-dev
- lld-6.0

before_install:
- mkdir -p ~/.local/bin
Expand All @@ -23,3 +23,9 @@ script:
- stack --no-terminal build --fast --test --no-run-tests --ghc-options "-Werror -fmax-pmcheck-iterations=5000000"
- env AMC_LIBRARY_PATH=$PWD/lib/ stack --no-terminal test --fast --ghc-options "-Werror -fmax-pmcheck-iterations=5000000" --test-arguments "--xml junit.xml --display t --hedgehog-tests 10000 --timeout 45"
- stack --no-terminal exec --package=hlint -- hlint --git

notifications:
slack:
secure: TbIOHm1fK3MZIsgSwbNXZh3017o47Hc1Q2DUOjNPOFhULOa7QoaUZLrcDFlPOKa9w3rVtivjEkdKac1Hw7kV8Zi18Rx7pEyu6ydtqfD5GLYpFNAZ/Mu/x9ODw8/iNBhr+HDPVal1pN5gBGTCtWZwd7cMeOAmPxl/5UoVBwvsSuUomFbpAgnfrVrPisO03ZTVxOECtDFQsT3PjGjVi3QoPNclO/blJlQ+2N+Lq6WbAyUWngGwwvYqX6SsmOQWFNOQOTLMn28GekUZzkYX14b6P26MO6t/zaJU2218Q43/y/Jhljig1kM5C/LSsVntpaumsSYzr3F6azc9hfB3eDA0+tdk2jduUil5DO0ZhZqO+NzRKHbh4aLE2e5t10a5DG+Z5waxa/OSvFAglfgkoyOayq4JpJW1dp2SpOU9DnLLB45cltYlqOCnoWFSI9G9iHCMUyaaKVGThCTRug3Sg2eXvoQe6dQgej441qUZCUOHNIGOVlBodRU9bXzlPaBgB5jsOlf77/GxJTBknXQ3hOJIsJbQuCrlKew91gS/rCQMTVYTrnAlJ57LGNQ9qXyznNZt1HakYh83tQYTCzk7dq3aDYlJ3VBcT21apijitUavCVoyYbrzIbZ4oOHQjC3dc4CZTZUfkBDxI14hfZ3BY2BygHh/UqoSfJ1JAiNVdhDaHcM=
on_success: change
on_failure: always
2 changes: 2 additions & 0 deletions amuletml.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ executable amc
, Amc.Debug
, Amc.Compile
, Amc.Repl
, Amc.Explain
, Amc.Explain.TH
, Amc.Repl.Display
default-language: Haskell2010

Expand Down
11 changes: 11 additions & 0 deletions bin/Amc.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Frontend.Errors
import qualified Amc.Debug as D
import qualified Amc.Repl as R
import qualified Amc.Compile as C
import Amc.Explain

import Version

Expand Down Expand Up @@ -64,6 +65,7 @@ data Command
{ remoteCmd :: String
, serverPort :: Int
}
| Explain { errId :: Int }
deriving (Show)

newtype Args
Expand Down Expand Up @@ -96,8 +98,15 @@ argParser = info (args <**> helper <**> version)
<> command "connect"
( info connectCommand
$ fullDesc <> progDesc "Connect to an already running REPL instance." )
<> command "explain"
( info explainCommand
$ fullDesc <> progDesc "Explain an error message." )
) <|> pure (Repl Nothing defaultPort DefaultPrelude (CompilerOptions D.Void [] False))

explainCommand :: Parser Command
explainCommand = Explain
<$> argument auto (metavar "ERROR" <> help "The error message code to explain")

compileCommand :: Parser Command
compileCommand = Compile
<$> argument str (metavar "FILE" <> help "The file to compile.")
Expand Down Expand Up @@ -193,6 +202,8 @@ main = do
toLoad
Args Connect { remoteCmd, serverPort } -> R.runRemoteReplCommand serverPort remoteCmd

Args Explain { errId } -> explainError errId

Args Compile { input, output = Just output } | input == output -> do
hPutStrLn stderr ("Cannot overwrite input file " ++ input)
exitWith (ExitFailure 1)
Expand Down
48 changes: 48 additions & 0 deletions bin/Amc/Explain.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{-# LANGUAGE TemplateHaskell #-}
module Amc.Explain where

import qualified Data.IntMap.Strict as Map

import Amc.Explain.TH

import Control.Exception

import System.Process
import System.Exit

import System.IO.Error
import System.IO

errors :: Map.IntMap String
errors = Map.fromList
$(qEmbedFiles . map ("doc/errors/" ++) =<< qReadFilesList "doc/errors.txt")

explainError :: Int -> IO ()
explainError code =
case code `Map.lookup` errors of
Just err -> do
let line_no = length (lines err)
if line_no > 20
then
withCreateProcess ((proc "less" ["-R"]) { std_in = CreatePipe }) (\stdin _ _ ph ->
case stdin of
Just handle -> do
hPutStr handle err
hFlush handle
hClose handle
exitWith =<< waitForProcess ph
Nothing -> do
putStr err
exitSuccess)
`catch`
\ioe ->
if isDoesNotExistError ioe
then do
putStr err
exitSuccess
else throwIO ioe
else putStr err
exitSuccess
Nothing -> do
putStrLn $ "No explanation for error E" ++ show code
exitFailure
33 changes: 33 additions & 0 deletions bin/Amc/Explain/TH.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{-# LANGUAGE TemplateHaskell #-}
module Amc.Explain.TH where

import Language.Haskell.TH.Syntax
import Language.Haskell.TH

import System.FilePath

qReadFile :: FilePath -> Q String
qReadFile path = do
qAddDependentFile path
str <- qRunIO (readFile path)
length str `seq` pure str

qReadFilesList :: FilePath -> Q [FilePath]
qReadFilesList path = do
qAddDependentFile path
contents <- qRunIO (readFile path)
let files = lines contents
fileP = map (head . words) files
pure fileP

qEmbedFile :: FilePath -> ExpQ
qEmbedFile path = stringE =<< qReadFile path

qEmbedFiles :: [FilePath] -> ExpQ
qEmbedFiles = listE . go where
go :: [FilePath] -> [ExpQ]
go (p:ps) = [| ($(fileNo p), $(qEmbedFile p)) |] : go ps
go [] = []

fileNo :: FilePath -> ExpQ
fileNo = lift . (read :: String -> Int) . takeBaseName
11 changes: 11 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Amulet Compiler Documentation
=============================

Currently this directory has all the text files that get embedded for
error message explanations.

### Adding an error

1. Bump this integer: 0002; Let that be the new error code.
2. Make a file `errors/$code.txt`
3. Add it to the `errors.txt` file.
62 changes: 62 additions & 0 deletions doc/errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
parser/0004.txt Parser: UnclosedString
parser/0005.txt Parser: UnclosedComment
parser/0007.txt Parser: MalformedClass
parser/0008.txt Parser: MalformedInstance
parser/0009.txt Parser: MisplacedWith
parser/0010.txt Parser: BindQualified
parser/0011.txt Parser: InvalidEscapeCode
parser/0012.txt Parser: UnalignedIn
resolve/1001.txt Resolver: NotInScope
resolve/1002.txt Resolver: Ambiguous
resolve/1003.txt Resolver: NonLinearPattern
resolve/1004.txt Resolver: NonLinearRecord
resolve/1007.txt Resolver: IllegalMethod
resolve/1008.txt Resolver: LastStmtNotExpr
resolve/1009.txt Resolver: LetOpenStruct
resolve/1010.txt Resolver: UnresolvedImport
resolve/1011.txt Resolver: ImportLoop
resolve/1012.txt Resolver: TFClauseWrongHead
resolve/1013.txt Resolver: TFClauseWrongArity
types/2001.txt TC: NotEqual
types/2002.txt TC: Occurs
types/2003.txt TC: CustomTypeError
types/2004.txt TC: NotInScope
types/2005.txt TC: FoundHole
types/2006.txt TC: ImpredicativeApp
types/2008.txt TC: EscapedSkolems
types/2009.txt TC: SkolBinding
types/2011.txt TC: CanNotInstance
types/2013.txt TC: PatternRecursive
types/2014.txt TC: DeadBranch
types/2015.txt TC: AmbiguousType
types/2016.txt TC: NotValue/ValueRestriction
types/2017.txt TC: AmbiguousMethodTy
types/2018.txt TC: UnsatClassCon
types/2019.txt TC: ClassStackOverflow
types/2020.txt TC: WrongClass
types/2021.txt TC: Overlap
types/2022.txt TC: UndefinedMethods
types/2023.txt TC: UndefinedTyFam
types/2024.txt TC: TyFamLackingArgs
types/2025.txt TC: MagicInstance
types/2026.txt TC: TypeFamInInstHead
types/2027.txt TC: InvalidContext
types/2028.txt TC: OrphanInstance
types/2030.txt TC: CanNotVta
types/2031.txt TC: NotPromotable
types/2032.txt TC: WildcardNotAllowed
types/2034.txt TC: UnsaturatedTS
types/2035.txt TC: NotCovered
types/2036.txt TC: MightNotTerminate
types/2037.txt TC: TyFunInLhs
types/2038.txt TC: DICan'tDerive
types/2039.txt TC: NotAnIdiom
verify/3001.txt Verify: MalformedRecursiveRhs
verify/3002.txt Verify: DefinedUnused
verify/3003.txt Verify: ParseErrorInForeign
verify/3004.txt Verify: LazyLet
verify/3005.txt Verify: RedundantArm
verify/3006.txt Verify: MissingPattern
verify/3007.txt Verify: MatchToLet
verify/3008.txt Verify: MatchToFun
verify/3009.txt Verify: ToplevelRefBinding
8 changes: 8 additions & 0 deletions doc/errors/parser/0004.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
A string was opened with a double quote ('"'), but never closed.

• Amulet strings cannot span multiple lines. If you want to write
a multi-line string, use `^` to concatenate multiple ones together.

• You can use a backslash ('\') to escape "special" characters within a
string, such as single or double quotes. Make sure you've not
accidentally escaped this string's closing quote.
9 changes: 9 additions & 0 deletions doc/errors/parser/0005.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
A comment was opened (using `(*`), but never closed. Add `*)` in order
to end the comment.

• Note that comments may be nested, meaning that you may need to close
multiple comments.

• It is possible to accidentally introduce a comment, especially when
using operator sections. In these cases, add a space within the
section's parenthesis.
9 changes: 9 additions & 0 deletions doc/errors/parser/0007.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
This class's head could not be parsed. Every class should be composed of
a (lower case) class name, followed by any number of type variables.

class eq 'a

Classes may also require several "super classes", by placing them before
the class name in a tuple.

class ord 'a => eq 'a
11 changes: 11 additions & 0 deletions doc/errors/parser/0008.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
This instance's head could not be parsed. Every instance should be
composed of a (lower case) class name, which should be applied with
several argument.

instance eq int

Instances may also impose additional constraints on its arguments, such
as requiring the elements of a list to also have an instance of this
class.

instance eq 'a => eq (list 'a)
22 changes: 22 additions & 0 deletions doc/errors/parser/0009.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Amulet has sugar for monadic computations, through the use of begin/end
blocks. In order to bind a monadic value, one should use a with
statement:

begin
with x <- f ()
pure (x + 1)
end

These statements cannot be used outside of monadic blocks. For instance
the following is invalid:

let x = with x <- f ()

When using begin/end, make sure that all terms are aligned to the same
column:

begin
let y = 2
with z <- f () (* Incorrect! *)
pure (x + 1)
end
14 changes: 14 additions & 0 deletions doc/errors/parser/0010.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Amulet distinguishes between qualified names (those belonging to a
module, such as `Foo.bar`) and unqualified names (those accessed
directly, such as `foo`). While qualified and unqualified names may be
accessed interchangeably, it makes no sense to declare a qualified
name:

let Foo.x = 1

Instead, one should just write the following:

let x = 1

The same restriction applies to any declaration, including patterns,
types and constructors.
17 changes: 17 additions & 0 deletions doc/errors/parser/0011.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Amulet allows you to escape special characters within a string by
prefixing them with a `\`. If you need to write a backslash on its own,
this can be done by escaping the slash using `\\`.

The following escape codes are supported:
• `\a` - Bell (0x07 in ASCII)
• `\b` - Backspace. (0x08 in ASCII)
• `\f` - Form feed page break. (0x0C in ASCII)
• `\n` - Newline/Line feed. (0x0A in ASCII)
• `\r` - Return carriage. (0x0D in ASCII)
• `\t` - Horizontal tab. (0x09 in ASCII)
• `\v` - Vertical tab. (0x0B in ASCII)
• `\"` - Double quote (`"`) (0x22 in ASCII)
• `\\` - Backslash (`\`) (0x5C in ASCII)

• For any other character, you can use the hexadecimal ASCII value with
`\x`. For instance, `\x61` represents the character `a`.
23 changes: 23 additions & 0 deletions doc/errors/parser/0012.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Amulet heavily relies on indentation in order to determine how a program
should be parsed. As a result, it's a good idea to ensure that your code
is laid out correctly.

Here, the warning is triggered when the `in` separator is not aligned
with the corresponding `let`:

let x = 0
in ()

The easiest way to fix this is just to remove the `in` token and adjust
the indentation:

let x = 0
()

The following are also acceptable alternatives:

let x = 0 in
()

let x = 0
in ()
28 changes: 28 additions & 0 deletions doc/errors/resolve/1001.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
This name cannot be found in the current scope. The most likely cause of
this is that you have misspelled the name you are trying to use.
However, if you would expect this variable to be available, there are
several other things to try:

- If the name is something you'd expect to be built in to Amulet, make
sure you've imported the prelude:

open import "prelude.ml" (* Needed for `None` *)
let x = None

- If the name is defined in another file or module, make sure you have
imported and opened the file.

open import "./my_lib.ml"
(* ^ Needed in order for `my_function` to be in scope. *)

let x = my_function ()

- When writing recursive functions, make sure you have used the `rec`
modifier:

(* Without `rec`, you won't be able to use `fib` within its
definition. *)
let rec fib = function
| 0 -> 1
| 1 -> 1
| n -> fib (n - 1) * fib (n - 2)
Loading

0 comments on commit a948da8

Please sign in to comment.