packages feed

technique-0.2.5: src/TechniqueUser.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

-- |
-- Programs implementing front-end commands for users: check, format
module TechniqueUser where

import Control.Monad (forever)
import Core.Program
import Core.System
import Core.Text
import System.IO (hIsTerminalDevice)
import Technique.Builtins
import Technique.Diagnostics ()
import Technique.Failure
import Technique.Formatter ()
import Technique.Internal
import Technique.Language
import Technique.Parser
import Technique.Translate
import Text.Megaparsec (parse)

data Mode = Cycle | Once

commandCheckTechnique :: Program None ()
commandCheckTechnique = do
  params <- getCommandLine

  let procfile = case lookupArgument "filename" params of
        Just file -> file
        _ -> error "Invalid State"

  let mode = case lookupOptionFlag "watch" params of
        Just True -> Cycle
        Nothing -> Once
        _ -> error "Invalid State"

  case mode of
    Once -> do
      -- normal operation, single pass
      code <- syntaxCheck procfile
      terminate code
    Cycle -> do
      -- use inotify to rebuild on changes
      forever (syntaxCheck procfile >> waitForChange [procfile] >> writeR Reload)

syntaxCheck :: FilePath -> Program None Int
syntaxCheck procfile =
  catch
    ( do
        surface <- loadTechnique procfile
        let source =
              emptySource
                { sourceFilename = procfile,
                  sourceContents = surface
                }
        concrete <- parsingPhase source
        abstract <- translationPhase source concrete
        debugR "abstract" abstract
        -- TODO extractionPhase
        writeR Ok
        return 0
    )
    ( \(e :: CompilationError) -> do
        writeR (Failed e)
        return (exitCodeFor e)
    )

-- |
-- Load a technique file hopefully containing a procedure.
loadTechnique :: FilePath -> Program None Rope
loadTechnique filename = do
  event "Read source from technique file"

  -- This is somewhat horrible; reading into Bytes, then going through
  -- Rope to get to Text is a bit silly... except that interop was kinda
  -- the whole point of the unbeliever library. So, good? We can make
  -- this better if/when we come up with an effecient Stream Rope
  -- instance so megaparsec can use Rope directly.

  bytes <- liftIO $ withFile filename ReadMode hInput
  let contents = intoRope bytes
  return contents

-- |
-- Parse technique content into a concrete syntax object.
parsingPhase :: Source -> Program None Technique
parsingPhase source = do
  event "Parse surface language into concrete Procedures"
  let contents = sourceContents source

  let result = parse pTechnique "" (fromRope contents)

  case result of
    Right technique -> return technique
    Left bundle -> throw (extractErrorBundle source bundle)

-- |
-- Take a static Procedure definition and spin it up into a sequence of
-- "Subroutine" suitable for interpretation. In other words, translate
-- between the concrete syntax types and the abstract syntax we can feed to
-- an evaluator.

-- FIXME better return type
translationPhase :: Source -> Technique -> Program None [Function]
translationPhase source technique =
  let env0 =
        emptyEnvironment
          { environmentFunctions = builtinProcedures,
            environmentSource = source
          }
      result = runTranslate env0 (translateTechnique technique)
   in do
        event "Translate Procedures into abstract Subroutines"
        case result of
          Left failure -> do
            throw failure
          Right (xs, _) -> do
            return xs

commandFormatTechnique :: Program None ()
commandFormatTechnique = do
  params <- getCommandLine

  let raw = case lookupOptionFlag "raw-control-chars" params of
        Nothing -> False
        Just True -> True
        _ -> error "Invalid State"

  let procfile = case lookupArgument "filename" params of
        Just file -> file
        _ -> error "Invalid State"

  catch
    ( do
        surface <- loadTechnique procfile
        let source =
              emptySource
                { sourceFilename = procfile,
                  sourceContents = surface
                }

        technique <- parsingPhase source

        terminal <- liftIO $ hIsTerminalDevice stdout
        case (terminal || raw) of
          True -> writeR technique
          False -> write (renderNoAnsi 80 technique)
    )
    ( \(e :: CompilationError) -> do
        write ("failed: " <> render 78 e)
        terminate (exitCodeFor e)
    )