BNFC / bnfc

BNF Converter
http://bnfc.digitalgrammars.com/
578 stars 163 forks source link

how-to cabal #450

Open ScottFreeCode opened 11 months ago

ScottFreeCode commented 11 months ago

Maybe this belongs on the website repo, but, would it be helpful to have a couple examples somewhere of how to automate generating code with BNFC as part of the build process?

I recall (would take more time to track down) that there are issues around "can Cabal be taught to use BNFC like it uses Happy and Alex?" To which the answer is "Well, not like it uses Happy and Alex, no, since with BNFC there isn't a one-to-one correspondence between input files and generated files."

I figured out how to do it using Setup.hs instead.

I have a couple gists here you could base examples off of: https://gist.github.com/ScottFreeCode/9b0ea3e52fa56c93ea8ca14791e55bb4

One is a simple single package. Another is my attempt at splitting it up into two packages (say, if you want a library implementing a language but then separately want an executable interpreter). Both also demonstrate wrapping in Nix (which can be safely ignored as it doesn't affect the Haskell / Cabal code! Well, if you do use Nix, you have to either nix-build or use Cabal's v1 commands if you're working inside nix-shell, but I am talking with other developers in other issues about that problem.) Alongside them are a couple equivalent examples without BNFC, which you can use for comparison to see what is BNFC-specific / added in the BFNC ones.

Anyhow, I hope that is helpful someway or another.

ScottFreeCode commented 11 months ago

See also #7 and the various issues and PRs linked from it.

andreasabel commented 8 months ago

Thanks @ScottFreeCode, for this excellent gist! It got me started on custom setups for invoking BNFC before building.

I think it fits the cabal philosophy of preprocessors better if BNFC generates the files in the build folder and not in a source folder. This way, there is no need to clean them up either. The setup script needs at least Cabal >= 2.0 (Cabal the library) and the cabal file format version needs to be 2.0 at least to allow an autogen-modules section. Thus, it makes also sense to require GHC 8.2 at least (which ships Cabal-2.0) but it is not strictly necessary, some older GHCs could also install Cabal-2.0.

So, this is what I ended up with:

Setup.hs

import Distribution.Simple            (defaultMainWithHooks, simpleUserHooks, buildHook)
import Distribution.Simple.BuildPaths (autogenPackageModulesDir)
import System.Process                 (callProcess)

main :: IO ()
main = defaultMainWithHooks simpleUserHooks
  { buildHook = \ packageDescription localBuildInfo userHooks buildFlags -> do
      -- For simplicity, generate files in build/global-autogen;
      -- there they are available to all components of the package.
      callProcess "bnfc"
        [ "-o", autogenPackageModulesDir localBuildInfo
        , "-d"
        , "Example.cf"
        ]
      -- Run the build process.
      buildHook simpleUserHooks packageDescription localBuildInfo userHooks buildFlags
  }

.cabal file:

cabal-version: 2.0
...
build-type: Custom
custom-setup
  setup-depends:
      Cabal   >= 2.0   && < 4
        -- Distribution.Simple.BuildPaths.autogenPackageModulesDir is new in Cabal-2.0
    , base    >= 4.10  && < 5
        -- Restrict to GHC >= 8.2 which ships Cabal-2.0 (otherwise Cabal has to be reinstalled)
    , process
    , BNFC    >= 2.8.4 && < 2.10

executable ...
  ...
  other-modules:
    ...
    Example.Abs
    Example.Lex
    Example.Par
    Example.Print
  autogen-modules:
    Example.Abs
    Example.Lex
    Example.Par
    Example.Print

...

  build-tool-depends:
   -- The 'bnfc' executable is called from 'Setup.hs' only,
   -- but 'build-tool-depends' is not supported in the 'custom-setup' stanza.
   -- BNFC 2.8.4 dropped the 'Err' custom type in favour of the standard 'Either'.
      BNFC:bnfc   >= 2.8.4  && < 2.10
   -- Not sure about the lower bounds, so we pick alex and happy versions
   -- that work at least with GHC 8.0.
    , alex:alex   >= 3.1.4  && < 3.5
    , happy:happy >= 1.19.5 && < 1.21
ScottFreeCode commented 8 months ago

Nice! I'll have to look at that more closely when I'm feeling up to it.

Nix made it easy to be lazy (not in the Haskell sense of the term): it automatically generates the build in a sandbox so my source folder isn't cluttered. Well, unless I nix-shell and then call Cabal myself; even then, I opted to gitignore the generated files, though it slips my mind at the moment exactly how I implemented it (by a source subfolder vs by file type and/or name; I can imagine doing either one).

Putting them all in some build folder is definitely cleaner (and easier to gitignore), and doesn't depend on being wrapped in a Nix sandbox.