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

Formatter - initial basic implementation #144

Merged
merged 36 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b037ff9
Add a draft (PoC) for the formatter
aabounegm Nov 14, 2023
de6d2ae
Add a state parameter to the formatting function
aabounegm Nov 19, 2023
acc3ad7
Switch HIE from cabal to stack
aabounegm Nov 19, 2023
fa4c1b9
Update Stack resolver
aabounegm Nov 19, 2023
a26b8eb
Enforce single space (or new line) around bin ops
aabounegm Nov 27, 2023
0816b8b
Decouple the formatter from LSP types
aabounegm Nov 27, 2023
a35c596
Replace the auxiliary `mkEdit` with the new data
aabounegm Nov 27, 2023
f0d96a5
Implement applying text edits to a source string
aabounegm Nov 27, 2023
f2dda6e
Add some utility functions to the formatter
aabounegm Nov 27, 2023
1408e34
Map the new `FormattingEdit` to LSP's `TextEdit`
aabounegm Nov 27, 2023
282a540
Add a CLI subcommand for the formatter
aabounegm Nov 27, 2023
c94c9ee
Handle postulates like defines (for colon indent)
aabounegm Nov 27, 2023
d224f70
Move formatting handler to the handlers module
aabounegm Nov 28, 2023
b96c7e7
Remove trailing whitespace when moving to new line
aabounegm Nov 28, 2023
8d79c16
Remove '->' from bin ops around which to add space
aabounegm Nov 28, 2023
3b8b978
WIP: add server config and send notification when formatting
aabounegm Nov 28, 2023
e19fb7f
Remove displaying a prompt on formatting
aabounegm Nov 29, 2023
2babfc3
Combine the overlapping rules into one
aabounegm Nov 29, 2023
7898eb2
Keep the lambda arrow as the last char on line
aabounegm Nov 29, 2023
07364e3
Add a handler for workspace/didChangeConfiguration
aabounegm Nov 29, 2023
a01eee3
Ensure formatDocument sends a response either way
aabounegm Nov 29, 2023
167df03
Move the more specific pattern above the catch-all
aabounegm Nov 29, 2023
7857df8
Consider '=' as a binary operator
aabounegm Dec 5, 2023
f390bb6
Remove 2 spaces when moving bin op to new line
aabounegm Dec 5, 2023
0fc1f74
Improve rule for space inside parenthesis
aabounegm Dec 5, 2023
ca773d9
Add space after token only if not last in the line
aabounegm Dec 5, 2023
1c4bc8f
Prevent adding a trailing space
aabounegm Dec 5, 2023
13c6fd0
Replace `words` with a space in allowed characters
aabounegm Dec 6, 2023
60ad07b
Exclude Language.Rzk.VSCode.Config from GHCJS build
fizruk Dec 7, 2023
89a2c2a
Fix lower bound on optparse-generic
fizruk Dec 7, 2023
8a70b7f
fix: nix expressions
deemp Dec 8, 2023
e9d12dc
fix: hpack config
deemp Dec 8, 2023
21c82b5
fix: move cli code into app
deemp Dec 8, 2023
bcd7b12
fix: shadowed names
deemp Dec 8, 2023
db30892
upd: .cabal
deemp Dec 8, 2023
555ba5b
upd: nixpkgs
deemp Dec 8, 2023
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,26 @@ Inside of such directory, you can run `rzk typecheck` on all files using wildcar
rzk typecheck *-*.md
```

### Formatting

Formatting can be done by calling `rzk format` and specifying the files to be formatted, e.g.:

```sh
rzk format file1.rzk file2.rzk
```

This prints the formatted version of the file to `stdout`.

To overwrite the file content, you must use the `--write` flag as such:

```sh
rzk format --write examples/*.rzk related/*.rzk.md
```

Note that if no files are specified, `rzk format` will format all files listed in `rzk.yaml`.

The CLI also supports the `--check` flag, which will exit with a non-zero exit code if any of the files are not formatted correctly. This is useful in CI pipelines to ensure that all files are formatted correctly.

## How to contribute to `rzk`

### Building the Documentation Locally
Expand Down
8 changes: 4 additions & 4 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:NixOS/nixpkgs/e1b4c97ed4ce160afd9ef1574b6a2ff168482f2a";
nixpkgs.url = "github:NixOS/nixpkgs/d1c3a8112f9d5d4840e0669727222e5fd9243451";
miso = {
url = "github:dmjio/miso/49edf0677253bbcdd473422b5dd5b4beffd83910";
flake = false;
Expand Down Expand Up @@ -47,6 +47,7 @@
packages = {
default = default.packages.default;
rzk = default.packages.${rzk};
rzk-ghcjs = ghcjs.packages.${rzk};
rzk-js = ghcjs.packages.${rzk-js};
} // scripts;

Expand Down
2 changes: 1 addition & 1 deletion hie.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
cradle:
cabal:
stack:
48 changes: 27 additions & 21 deletions nix/ghcjs.nix
Original file line number Diff line number Diff line change
@@ -1,34 +1,40 @@
{ inputs, pkgs, scripts, rzk, rzk-js, rzk-src, rzk-js-src, ghcVersion, tools }:
let
inherit (pkgs.haskell.lib) overrideCabal;
misoNix = (import "${inputs.miso.outPath}/default.nix" { inherit (pkgs) system; });
misoNix = (import "${inputs.miso}/default.nix" { inherit (pkgs) system; });
pkgsMiso = misoNix.pkgs;

hpkgs =
# This isn't equivalent to `pkgsMiso.haskell.packages.ghcjs.override` ([link](https://nixos.wiki/wiki/Haskell#Overrides))
# but avoids multiple rebuilds
pkgsMiso.haskell.packages.ghcjs //
{
rzk = overrideCabal
(hpkgs.callCabal2nix rzk rzk-src { })
(x: {
isLibrary = true;
isExecutable = false;
pkgsMiso.haskell.packages.ghcjs.override {
overrides = final: prev: {
mkDerivation = args: prev.mkDerivation (args // {
doCheck = false;
doHaddock = false;
libraryToolDepends = [ pkgs.hpack pkgs.alex pkgs.happy ] ++ (x.libraryToolDepends or [ ]);
testToolDepends = [ pkgs.hpack pkgs.alex pkgs.happy ] ++ (x.testToolDepends or [ ]);
prePatch = "hpack --force";
});
rzk-js = overrideCabal
(hpkgs.callCabal2nix rzk-js rzk-js-src { inherit (hpkgs) rzk; })
(x: {
postInstall = (x.postInstall or "") + ''
cp $out/bin/${rzk-js} .
rm -r $out
cp ${rzk-js} $out
'';
hpack = pkgs.hpack;
rzk = (
(pkgsMiso.haskell.lib.overrideCabal
(prev.callCabal2nix rzk rzk-src { })
(x: {
isLibrary = true;
isExecutable = false;
buildTarget = "lib:rzk";
libraryToolDepends = [ pkgs.hpack pkgs.alex pkgs.happy ] ++ (x.libraryToolDepends or [ ]);
testToolDepends = [ pkgs.alex pkgs.happy ] ++ (x.testToolDepends or [ ]);
}))
).overrideAttrs (x: {
installPhase = builtins.replaceStrings [ "Setup copy" ] [ "Setup copy lib:rzk" ] x.installPhase;
});
rzk-js = overrideCabal
(final.callCabal2nix rzk-js rzk-js-src { })
(x: {
postInstall = (x.postInstall or "") + ''
cp $out/bin/${rzk-js} .
rm -r $out
cp ${rzk-js} $out
'';
});
};
};

hpkgsGHCJS_8_10_7 = pkgs.haskell.packages.ghcjs810.override ({
Expand Down
89 changes: 84 additions & 5 deletions rzk/app/Main.hs
Original file line number Diff line number Diff line change
@@ -1,14 +1,93 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NamedFieldPuns #-}

module Main (main) where

#ifndef __GHCJS__
import Main.Utf8 (withUtf8)
#endif
import qualified Rzk.Main

import Control.Monad (forM, forM_, unless, when,
(>=>))
import Data.Version (showVersion)

#ifdef LSP
import Language.Rzk.VSCode.Lsp (runLsp)
#endif

import Options.Generic
import System.Exit (exitFailure, exitSuccess)

import Data.Functor ((<&>))
import Paths_rzk (version)
import Rzk.Format (formatFile, formatFileWrite,
isWellFormattedFile)
import Rzk.TypeCheck
import Rzk.Main

data FormatOptions = FormatOptions
{ check :: Bool
, write :: Bool
} deriving (Generic, Show, ParseRecord, Read, ParseField)

instance ParseFields FormatOptions where
parseFields _ _ _ _ = FormatOptions
<$> parseFields (Just "Check if the files are correctly formatted") (Just "check") (Just 'c') Nothing
<*> parseFields (Just "Write formatted file to disk") (Just "write") (Just 'w') Nothing

data Command
= Typecheck [FilePath]
| Lsp
| Format FormatOptions [FilePath]
| Version
deriving (Generic, Show, ParseRecord)

main :: IO ()
main =
main = do
#ifndef __GHCJS__
withUtf8
withUtf8 $
#endif
getRecord "rzk: an experimental proof assistant for synthetic ∞-categories" >>= \case
Typecheck paths -> do
modules <- parseRzkFilesOrStdin paths
case defaultTypeCheck (typecheckModulesWithLocation modules) of
Left err -> do
putStrLn "An error occurred when typechecking!"
putStrLn $ unlines
[ "Type Error:"
, ppTypeErrorInScopedContext' BottomUp err
]
exitFailure
Right _decls -> putStrLn "Everything is ok!"

Lsp ->
#ifdef LSP
void runLsp
#else
error "rzk lsp is not supported with this build"
#endif
Rzk.Main.main

Format (FormatOptions {check, write}) paths -> do
when (check && write) (error "Options --check and --write are mutually exclusive")
expandedPaths <- expandRzkPathsOrYaml paths
case expandedPaths of
[] -> error "No files found"
filePaths -> do
when (not check && not write) $ forM_ filePaths (formatFile >=> putStrLn)
when write $ forM_ filePaths formatFileWrite
when check $ do
results <- forM filePaths $ \path -> isWellFormattedFile path <&> (path,)
let notFormatted = map fst $ filter (not . snd) results
unless (null notFormatted) $ do
putStrLn "Some files are not well formatted:"
forM_ notFormatted $ \path -> putStrLn (" " <> path)
exitFailure
exitSuccess

Version -> putStrLn (showVersion version)

5 changes: 3 additions & 2 deletions rzk/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ dependencies:
directory: ">= 1.2.7.0"
Glob: ">= 0.9.3"
mtl: ">= 2.2.2"
optparse-generic: ">= 1.3.0"
template-haskell: ">= 2.14.0.0"
text: ">= 1.2.3.1"
yaml: ">= 0.11.0.0"
Expand All @@ -72,11 +71,12 @@ library:
- Language.Rzk.Syntax.Skel
- condition: flag(lsp) && !impl(ghcjs)
exposed-modules:
- Language.Rzk.VSCode.Config
- Language.Rzk.VSCode.Env
- Language.Rzk.VSCode.Handlers
- Language.Rzk.VSCode.Logging
- Language.Rzk.VSCode.Lsp
- Language.Rzk.VSCode.Tokenize
- Language.Rzk.VSCode.Logging
dependencies:
aeson: ">= 1.4.2.0"
co-log-core: ">= 0.3.2.0"
Expand All @@ -102,6 +102,7 @@ executables:
- condition: "!impl(ghcjs)"
dependencies:
with-utf8: ">= 1.0.2.4"
optparse-generic: ">= 1.4.0"

tests:
rzk-test:
Expand Down
11 changes: 5 additions & 6 deletions rzk/rzk.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ library
Language.Rzk.Syntax.Par
Language.Rzk.Syntax.Print
Rzk
Rzk.Format
Rzk.Main
Rzk.Project.Config
Rzk.TypeCheck
Expand All @@ -70,18 +71,18 @@ library
, bytestring >=0.10.8.2
, directory >=1.2.7.0
, mtl >=2.2.2
, optparse-generic >=1.3.0
, template-haskell >=2.14.0.0
, text >=1.2.3.1
, yaml >=0.11.0.0
default-language: Haskell2010
if flag(lsp) && !impl(ghcjs)
exposed-modules:
Language.Rzk.VSCode.Config
Language.Rzk.VSCode.Env
Language.Rzk.VSCode.Handlers
Language.Rzk.VSCode.Logging
Language.Rzk.VSCode.Lsp
Language.Rzk.VSCode.Tokenize
Language.Rzk.VSCode.Logging
cpp-options: -DLSP
build-depends:
aeson >=1.4.2.0
Expand Down Expand Up @@ -113,15 +114,15 @@ executable rzk
, bytestring >=0.10.8.2
, directory >=1.2.7.0
, mtl >=2.2.2
, optparse-generic >=1.3.0
, rzk
, template-haskell >=2.14.0.0
, text >=1.2.3.1
, yaml >=0.11.0.0
default-language: Haskell2010
if !impl(ghcjs)
build-depends:
with-utf8 >=1.0.2.4
optparse-generic >=1.4.0
, with-utf8 >=1.0.2.4

test-suite doctests
type: exitcode-stdio-1.0
Expand All @@ -144,7 +145,6 @@ test-suite doctests
, directory >=1.2.7.0
, doctest
, mtl >=2.2.2
, optparse-generic >=1.3.0
, template-haskell
, text >=1.2.3.1
, yaml >=0.11.0.0
Expand All @@ -171,7 +171,6 @@ test-suite rzk-test
, bytestring >=0.10.8.2
, directory >=1.2.7.0
, mtl >=2.2.2
, optparse-generic >=1.3.0
, rzk
, template-haskell >=2.14.0.0
, text >=1.2.3.1
Expand Down
34 changes: 34 additions & 0 deletions rzk/src/Language/Rzk/VSCode/Config.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Language.Rzk.VSCode.Config (
ServerConfig(..),
) where

import Data.Aeson
import Data.Default.Class (Default, def)

data ServerConfig = ServerConfig
{ formatEnabled :: Bool
} deriving Show

instance Default ServerConfig where
def = ServerConfig
{ formatEnabled = True
}

-- We need to derive the FromJSON instance manually in order to provide defaults
-- for absent fields.
instance FromJSON ServerConfig where
-- Note: "configSection" in ServerDefinition already filters by the "rzk." prefix
parseJSON = withObject "rzkSettings" $ \rzkSettings -> do
formatSettings <- rzkSettings .: "format" -- TODO: how to make this optional?
formatEnabled <- formatSettings .:? "enable" .!= formatEnabled def
return ServerConfig { .. }

instance ToJSON ServerConfig where
toJSON (ServerConfig { .. }) = object
[ "format" .= object
[ "enable" .= formatEnabled
]
]
5 changes: 3 additions & 2 deletions rzk/src/Language/Rzk/VSCode/Env.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ module Language.Rzk.VSCode.Env where
import Control.Concurrent.STM
import Control.Monad.Reader
import Language.LSP.Server
import Rzk.TypeCheck (Decl')
import Language.Rzk.VSCode.Config (ServerConfig)
import Rzk.TypeCheck (Decl')

type RzkTypecheckCache = [(FilePath, [Decl'])]

Expand All @@ -18,7 +19,7 @@ defaultRzkEnv = do
{ rzkEnvTypecheckCache = typecheckCache }


type LSP = LspT () (ReaderT RzkEnv IO)
type LSP = LspT ServerConfig (ReaderT RzkEnv IO)

-- | Override the cache with given typechecked modules.
cacheTypecheckedModules :: RzkTypecheckCache -> LSP ()
Expand Down
Loading
Loading