swarm/app/Main.hs
Brent Yorgey e071252d72
Add format --v0.5 option to port code from older syntax (#1851)
This is a followup on top of #1583 which turns `swarm format` into a tool for porting existing Swarm code into the newest syntax, via an extra `--v0.5` argument.  In particular, this PR:

- Generalizes the parser to take a configuration record, which among other things contains the language version being parsed.
- Adds code to allow the parser to parse either the current syntax or one version ago (when types did not start with capital letter) depending on the version in the configuration.
    - The idea is to have the parser always support the current version and one older version, so we can always upgrade version n to version n+1.
- Adds a new flag `--v0.5` to the `format` subcommand which causes the input to be parsed in v0.5 mode.  However, the output of `format` will always use the latest syntax.  Thus, `swarm format --v0.5` reads code in v0.5 format and prints it in the latest format, so this can be used to automatically port existing `.sw` files.

This PR also makes a few minor improvements to pretty-printing.
2024-05-22 00:09:31 +00:00

136 lines
4.8 KiB
Haskell

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
-- |
-- SPDX-License-Identifier: BSD-3-Clause
module Main where
import Data.Foldable qualified
import GitHash (GitInfo, giBranch, giHash, tGitInfoCwdTry)
import Options.Applicative
import Swarm.App (appMain)
import Swarm.Language.Format
import Swarm.Language.LSP (lspMain)
import Swarm.Language.Parser.Core (LanguageVersion (..))
import Swarm.TUI.Model (AppOpts (..), ColorMode (..))
import Swarm.TUI.Model.UI (defaultInitLgTicksPerSecond)
import Swarm.Version
import Swarm.Web (defaultPort)
import System.IO (hPrint, stderr)
import Text.Read (readMaybe)
gitInfo :: Maybe GitInfo
gitInfo = either (const Nothing) Just $$tGitInfoCwdTry
commitInfo :: String
commitInfo = case gitInfo of
Nothing -> ""
Just git -> " (" <> giBranch git <> "@" <> take 10 (giHash git) <> ")"
data CLI
= Run AppOpts
| Format FormatConfig
| LSP
| Version
cliParser :: Parser CLI
cliParser =
subparser
( mconcat
[ command "format" (info (Format <$> parseFormat) (progDesc "Format a file"))
, command "lsp" (info (pure LSP) (progDesc "Start the LSP"))
, command "version" (info (pure Version) (progDesc "Get current and upstream version."))
]
)
<|> Run
<$> ( AppOpts
<$> seed
<*> scenario
<*> run
<*> autoplay
<*> speedFactor
<*> cheat
<*> color
<*> webPort
<*> pure gitInfo
)
where
input :: Parser FormatInput
input =
flag' Stdin (long "stdin" <> help "Read code from stdin")
<|> (InputFile <$> strArgument (metavar "FILE"))
output :: Parser FormatOutput
output =
flag Stdout Stdout (long "stdout" <> help "Write formatted code to stdout (default)")
<|> (OutputFile <$> strOption (long "output" <> short 'o' <> metavar "FILE" <> help "Write formatted code to an output file"))
<|> flag' Inplace (long "inplace" <> short 'i' <> help "Format file in place")
widthOpt :: Parser FormatWidth
widthOpt = option auto (long "width" <> metavar "COLUMNS" <> help "Use layout with maximum width")
langVer :: Parser LanguageVersion
langVer = flag SwarmLangLatest SwarmLang0_5 (long "v0.5" <> help "Read (& convert) code from Swarm version 0.5")
parseFormat :: Parser FormatConfig
parseFormat = FormatConfig <$> input <*> output <*> optional widthOpt <*> langVer <**> helper
seed :: Parser (Maybe Int)
seed = optional $ option auto (long "seed" <> short 's' <> metavar "INT" <> help "Seed to use for world generation")
webPort :: Parser (Maybe Int)
webPort =
optional $
option
auto
( long "web"
<> metavar "PORT"
<> help ("Set the web service port (or disable it with 0). Default to " <> show defaultPort <> ".")
)
scenario :: Parser (Maybe String)
scenario = optional $ strOption (long "scenario" <> short 'i' <> metavar "FILE" <> help "Name of an input scenario to load")
run :: Parser (Maybe String)
run = optional $ strOption (long "run" <> short 'r' <> metavar "FILE" <> help "Run the commands in a file at startup")
autoplay :: Parser Bool
autoplay = switch (long "autoplay" <> short 'a' <> help "Automatically run the solution defined in the scenario, if there is one. Mutually exclusive with --run.")
speedFactor :: Parser Int
speedFactor = option auto (long "speed" <> short 'm' <> value defaultInitLgTicksPerSecond <> help "Initial game speed multiplier")
cheat :: Parser Bool
cheat = switch (long "cheat" <> short 'x' <> help "Enable cheat mode. This allows toggling Creative Mode with Ctrl+v and unlocks \"Testing\" scenarios in the menu.")
color :: Parser (Maybe ColorMode)
color = optional $ option colorModeParser (long "color" <> short 'c' <> metavar "MODE" <> help "Use none/8/16/full color mode.")
colorModeParser =
Data.Foldable.asum
[ ColorMode8 <$ text "8"
, ColorMode16 <$ text "16"
, ColorMode240 <$> maybeReader (\case ('2' : '4' : '0' : '_' : w) -> readMaybe w; _ -> Nothing)
, FullColor <$ text "full"
, NoColor <$ text "none"
]
text t = maybeReader (\x -> if x == t then Just x else Nothing)
cliInfo :: ParserInfo CLI
cliInfo =
info
(cliParser <**> helper)
( header ("Swarm game - " <> version <> commitInfo)
<> progDesc "To play the game simply run without any command."
<> fullDesc
)
showVersion :: IO ()
showVersion = do
putStrLn $ "Swarm game - " <> version <> commitInfo
up <- getNewerReleaseVersion gitInfo
either (hPrint stderr) (putStrLn . ("New upstream release: " <>)) up
main :: IO ()
main = do
cli <- execParser cliInfo
case cli of
Run opts -> appMain opts
Format cfg -> formatSwarmIO cfg
LSP -> lspMain
Version -> showVersion