just95 / free-compiler

A Haskell to Coq compiler that represents effectful programs with the free monad

Home Page:https://freeproving.github.io/free-compiler

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Free Compiler

Logo

CI Pipeline

A compiler for the monadic translation of Haskell programs to Coq that uses the Free monad as presented by Dylus et al. to model partiality and other ambient effects.

Table of Contents

  1. Documentation
  2. Getting Started
  3. Usage
  4. Development
  5. License

Documentation

This compiler was originally developed as part of a bachelor's thesis. The compiler has been extended with additional features and its architecture changed compared to the version presented in the thesis but the explanation of the monadic translation in the thesis is still up to date.

The compiler's source code is documented using Haddock. The documentation is automatically build by our CI pipeline and published here.

Additional documentation can be found in the doc/ directory.

Getting Started

Required Software

The Free Compiler is written in Haskell and uses Cabal to manage its dependencies. To build the compiler, the GHC and Cabal are required. To use the Coq code generated by our compiler, the Coq proof assistant must be installed. The compiler has been tested with the following software versions on a Debian based operating system.

  • GHC, version 8.6.5
  • Cabal, version 2.4.1.0
  • Coq, versions 8.8 through 8.11

Base Library

In order to use the base library, the Coq files in the base library need to be compiled first. Make sure to compile the base library before installing the compiler. We provide a shell script for the compilation of Coq files. To compile the base library with that shell script, run the following command in the root directory of the compiler.

./tool/compile-coq.sh base

Note: If you add or remove files from the base library (or any other directory that contains Coq code that you wish to compile using the script above), the automatically generated Makefile needs to be updated. For this purpose the script provides the command line option --recompile.

./tool/compile-coq.sh --recompile base

Installation

First, make sure that the Cabal package lists are up to date by running the following command.

cabal new-update

To build and install the compiler and its dependencies, change into the compiler’s root directory and run the following command.

cabal new-install freec

The command above copies the base library and the compiler’s executable to Cabal’s installation directory and creates a symbolic link to the executable in ~/.cabal/bin. To test whether the installation was successful, make sure that ~/.cabal/bin is in your PATH environment variable and run the following command.

freec --help

Running without Installation

If you want to run the compiler without installing it on your machine (i.e., for debugging purposes), execute the following command in the root directory of the compiler instead of the freec command.

cabal new-run freec -- [options...] <input-files...>

The two dashes are needed to separate the arguments to pass to the compiler from Cabal’s arguments. Alternatively, you can use the ./tool/run.sh bash script.

./tool/run.sh [options...] <input-files...>

Besides invoking Cabal and forwarding its command line arguments to the compiler, the script also sets the -Wwarn flag of the GHC automatically. This overwrites the -Werror flag of the GHC that is set in addition to the -Wall flag by default. The default configuration causes compilation to fail when there are unhandled warnings in the source code. As a result, the CI pipeline rejects commits that introduce such warnings. While it is important that no warnings are reported in a production setting, fatal warnings can be disrupting when debugging. Thus, we recommend using the ./tool/run.sh script during development and running ./tool/full-test.sh (which uses the default settings) once before pushing your local changes.

Usage

To compile a Haskell module, pass the file name of the module to freec. For example, to compile the examples from the Data.List module run the the following command.

freec ./example/Data/List.hs

In order to compile multiple modules which import on each other, multiple file names can be passed as an argument.

freec ./example/Data/List.hs ./example/Data/Function.hs

Both commands above print the generated Coq code directly to the console. See the --output option below for how to write the generated Coq code into files instead.

Command line options

--output=DIR, -o DIR

By default generated Coq code is printed to the console. To write to a file instead, specify an output directory using the --output option. A file X/Y/Z.v is placed into the output directory for every module X.Y.Z that is compiled. For example, the following command creates the files example/generated/Data/List.v and example/generated/Data/Function.v

freec -o ./example/generated ./example/Data/*.hs

In addition to the .v files .json files are generated as well. The JSON files contain the module interfaces for the translated modules. The module interface files can be used to import modules that have been translated already without specifying them on the command line. For example, if Data.List and Data.Functor have been translated already, the ListFunctor example can be compiled on its own since its imports can be served from the module interface files.

freec -o ./example/generated ./example/ListFunctor.hs

--import=DIR, -i DIR

The compiler searches for module interfaces in the output directory (see --output option) and the current working directory by default. If you want to compile modules that import files that have been written to a different output directory, you can use the --import command line option to specify additional paths to search for modules interfaces in. For example, if Data.List and Data.Functor have been translated already and their output has been written to ./examples/generated, you can translate ListFunctor and print its output to the console as follows.

freec -i ./example/generated ./example/ListFunctor.hs

To add multiple import paths just add one --import option for each path. There can be arbitrarily many import paths and the --import and --output options can be mixed.

./tool/run.sh -o <dir0> -i <dir1> -i <dir2><input-files...>

In the example above, the compiler would search for a module interface file first in the current working directory and then in the directories in the order they have been specified on the command line (i.e., first ., then <dir0>, then <dir1>, then <dir2> and so on).

--base-library=DIR, -b DIR

Predefined data types and operations are not build directly into the compiler but part of the base library that accompanies the compiler. The compiler uses the same mechanism that is used to load module dependencies to load modules from the base library, i.e., module interface files. In contrast to automatically generated module interface files, the base library does not use the JSON file format but TOML since the module interfaces of the base library are maintained manually and TOML is a more user friendly format. The module interface file format is documented in doc/ModuleInterfaceFileFormat.md.

In order for the compiler to locate the Prelude.toml module interface file, the location of the base library must be known. If the compiler is installed as described above or executed using cabal, it will usually be able to locate the base library automatically. Otherwise, it may be necessary to tell the compiler where the base library can be found using the --base-library option.

freec -b ./base ./example/Data/List.hs

--no-coq-project

When an output directory has been specified using the --output option and the output directory does not contain a _CoqProject file, it is created automatically. The _CoqProject file tells Coq where to find the compiler’s base library and assigns the logical name Generated to the directory that contains the generated Coq code. The file has the following format where <base-library> is the path to the base library (see --base-library command line option).

-R <base-library> Base
-R . Generated

The logical names Base and Generated are used in the generated import sentences. For example, a generated file could contain the following lines.

From Base Require Import Prelude.
From Generated Require Export Data.List.

You can safely edit the _CoqProject file or supply your own configuration as long as Coq is still able to locate the .v files under their logical prefixes. The compiler will not overwrite your changes since the _CoqProject file is only written to if it does not exist.

If you don't want the _CoqProject file to be generated at all, you can add the --no-coq-project flag. However, note that you may not be able to compile the generated Coq code if the _CoqProject file is missing.

Proving properties

The main goal for the translation of Haskell code to Coq is to prove properties of the Haskell program in Coq. In order to do so, we have to formulate the properties to prove first. Due to the overhead involved with our translation, stating propositions in Coq directly is very tedious. Therefore, we provide a mechanism for deriving Coq properties from QuickCheck properties. This allows us to state the proposition in Haskell instead of Coq.

Consult doc/ProvingQuickCheckProperties.md for more details and examples.

Experimental Features

Pattern-Matching Compilation

By default the compiler does support a limited subset of the Haskell programming language only. There is experimental support to relax some of those restrictions. Add the --transform-pattern-matching command line option to automatically transform the input modules using a pattern matching compiler library for haskell-src-exts before they are translated by our compiler. For example, the PatternMatching example can be translated as follows.

freec --transform-pattern-matching ./example/PatternMatching.hs

Consult doc/ExperimentalFeatures/PatternMatchingCompilation.md for more details and examples.

Development

Additional Software

We recommend using the following additional software during development. Both of these tools are used to make sure that we are using a consistent code style throughout the project and are described in more detail in the next section below.

The versions mentioned above are the versions used by our CI pipeline.

Code Style

Brittany

Brittany is a code formatter for Haskell. It can be installed via Cabal as follows.

cabal new-install brittant

The CI pipeline runs brittany on all Haskell source files in the src and example directories and compares its output with the committed files. If there are Haskell source files that have not been formatted using brittany, the CI pipeline fails. The same check is performed by the ./tool/check-formatting.sh and ./tool/full-test.sh scripts.

There is Brittany support for various editors (see also Editor Integration). If your editor is not supported, we are also use the following shell script that we provide.

./tool/format-code.sh [files...]

If no files are specified all Haskell source files in the src and example directory are formatted by default. Note that the script overwrites the formatted files. Thus, you should create a backup beforehand by git adding your changes, for example.

Of course Brittany is not perfect. Among others, data type declarations are not formatted at the moment. So don't rely entirely on the output of our automatic tests and manually check your code nonetheless.

HLint

HLint is a tool that gives suggestions on how to improve Haskell source code. It can be installed via Cabal as follows.

cabal new-install hlint

The CI pipeline runs hlint on all Haskell source files in the src directory. If HLint has suggestions for how the code can be improved, the CI pipeline fails. The same check is performed by the ./tool/full-test.sh script.

There are HLint plugnis for many editors. If there is no such plugin for your editor, you can run the following command instead.

hlint src

Remember, that HLint only makes suggestions and you don't have to follow these suggestions. However, since the CI pipeline fails if HLint finds possible improvements, suggestions have to be ignored explicitly. Edit the .hlint.yaml file for this purpose and leave a comment why you had to ignore that suggestion.

Testing

To test whether the compiler is working correctly, you can run the included unit tests via one of the following Cabal commands.

cabal new-test
cabal new-run freec-unit-tests -- [options...]

Similar to how we discourage using cabal new-run during development to run the compiler, we recommend using our ./tool/test.sh script to execute unit tests instead.

./tool/test.sh [options...]
  • Firstly it allows command line options such as --match to be passed to the test suite more easily. The --match command line option can be used to run specific unit tests instead of the full test suite. For more details on supported command line options use the following command

    ./tool/test.sh --help
  • Secondly the script configures HSpec (the library that we are using for testing) to create a failure report. The failure report allows you to add the --rerun command line option to run test that failed in the previous run only.

    ./tool/test.sh --rerun

    This allows you to focus on the failed test cases during debugging. Once all tests are fixed, all tests are executed again.

  • Finally the script overwrites GHC's -Werror flag that is set by default for our compiler with -Wwarn. Doing so improves the development workflow. However, in production, the compiler must compile without any warnings. Thus, the CI pipeline will reject any commits that contain unhandled warnings. To make sure that your local changes do not cause warnings to be reported, run the full-test.sh script before pushing.

    ./tool/full-test.sh

    The script simulates the CI pipeline locally but runs much faster since there is no overhead for creating test environments, uploading and downloading artifacts, initializing caches etc. If the script succeeds, it is not guaranteed that the CI pipeline will definitely pass, but it should catch the most common mistakes.

License

The Free Compiler is licensed under The 3-Clause BSD License.
See the LICENSE file for details.

About

A Haskell to Coq compiler that represents effectful programs with the free monad

https://freeproving.github.io/free-compiler

License:BSD 3-Clause "New" or "Revised" License


Languages

Language:Haskell 93.3%Language:Coq 3.9%Language:Shell 2.8%