Initial commit
This commit is contained in:
commit
2255e73949
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
build/
|
||||
inputs/
|
||||
*.*~
|
55
advent.ipkg
Normal file
55
advent.ipkg
Normal file
|
@ -0,0 +1,55 @@
|
|||
package advent
|
||||
version = 0.1.0
|
||||
authors = "Nathan McCarty"
|
||||
-- maintainers =
|
||||
-- license =
|
||||
-- brief =
|
||||
-- readme =
|
||||
-- homepage =
|
||||
-- sourceloc =
|
||||
-- bugtracker =
|
||||
|
||||
-- the Idris2 version required (e.g. langversion >= 0.5.1)
|
||||
-- langversion
|
||||
|
||||
-- packages to add to search path
|
||||
depends = base
|
||||
, contrib
|
||||
, structures
|
||||
, eff
|
||||
, elab-util
|
||||
, ansi
|
||||
, if-unsolved-implicit
|
||||
|
||||
-- modules to install
|
||||
modules = Runner
|
||||
, Util
|
||||
, Util.Eff
|
||||
|
||||
-- main file (i.e. file to load at REPL)
|
||||
main = Main
|
||||
|
||||
-- name of executable
|
||||
executable = "advent"
|
||||
-- opts =
|
||||
sourcedir = "src"
|
||||
-- builddir =
|
||||
-- outputdir =
|
||||
|
||||
-- script to run before building
|
||||
-- prebuild =
|
||||
|
||||
-- script to run after building
|
||||
-- postbuild =
|
||||
|
||||
-- script to run after building, before installing
|
||||
-- preinstall =
|
||||
|
||||
-- script to run after installing
|
||||
-- postinstall =
|
||||
|
||||
-- script to run before cleaning
|
||||
-- preclean =
|
||||
|
||||
-- script to run after cleaning
|
||||
-- postclean =
|
9
pack.toml
Normal file
9
pack.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[custom.all.advent]
|
||||
type = "local"
|
||||
path = "."
|
||||
ipkg = "advent.ipkg"
|
||||
|
||||
[custom.all.structures]
|
||||
type = "local"
|
||||
path = "../structures"
|
||||
ipkg = "structures.ipkg"
|
158
src/Main.idr
Normal file
158
src/Main.idr
Normal file
|
@ -0,0 +1,158 @@
|
|||
module Main
|
||||
|
||||
import System
|
||||
import System.Path
|
||||
import System.File
|
||||
import System.Directory
|
||||
import System.Console.GetOpt
|
||||
import Data.String
|
||||
import Data.Vect
|
||||
|
||||
import Control.Eff
|
||||
import Derive.Prelude
|
||||
|
||||
import Structures.Dependent.FreshList
|
||||
|
||||
import Runner
|
||||
import Util
|
||||
import Util.Eff
|
||||
|
||||
import Years.Y2015;
|
||||
|
||||
%language ElabReflection
|
||||
%default total
|
||||
|
||||
data Flag = UseExample | Verbose
|
||||
%runElab derive "Flag" [Eq, Show]
|
||||
|
||||
advent : Advent
|
||||
advent = MkAdvent [
|
||||
y2015
|
||||
]
|
||||
|
||||
options : List (OptDescr Flag)
|
||||
options =
|
||||
[
|
||||
MkOpt ['u'] ["use-example"] (NoArg UseExample) "Use example input instead of main input"
|
||||
, MkOpt ['v'] ["verbose"] (NoArg Verbose) "Enable logging to stderr"
|
||||
]
|
||||
|
||||
header : String
|
||||
header = "Usage: advent YEAR DAY [OPTION...]"
|
||||
|
||||
data Error : Type where
|
||||
OptionsError : (errs : List String) -> Error
|
||||
ArgumentsError : (count : Nat) -> Error
|
||||
InvalidYear : (year : String) -> Error
|
||||
InvalidDay : (year : Nat) -> (day : String) -> Error
|
||||
NoSuchDay : (year : Nat) -> (day : Nat) -> Error
|
||||
NoCurrentDir : Error
|
||||
InputRead : (path : String) -> (err : FileError) -> Error
|
||||
SolveError : Show e => (year, day, part : Nat) -> (err : e) -> Error
|
||||
%name Error err
|
||||
|
||||
Show Error where
|
||||
show (OptionsError errs) =
|
||||
let errs = unlines . map (" " ++) . lines . joinBy "\n" $ errs
|
||||
in "Error parsing options:\n" ++ errs ++ "\n" ++ usageInfo header options
|
||||
show (ArgumentsError count) =
|
||||
"Error parsing arguments: Expected 2 positional arguments, got \{show count}. Provide only year and date.\n" ++
|
||||
usageInfo header options
|
||||
show (InvalidYear year) =
|
||||
"Error parsing arguments: Unable to parse year argument: \{year}"
|
||||
show (InvalidDay year day) =
|
||||
"Error parsing arguments: Failed to parse day \{day} of year \{show year}"
|
||||
show (NoSuchDay year day) =
|
||||
"Error locating day: No such day \{show day} of year \{show year}"
|
||||
show NoCurrentDir =
|
||||
"Unknown error getting current working directory"
|
||||
show (InputRead path err) =
|
||||
let err = unlines . map (" " ++) . lines . show $ err
|
||||
in "Error reading input: Encountered error reading input at \{path}\n" ++ err
|
||||
show (SolveError year day part err) =
|
||||
let err = unlines . map (" " ++) . lines . show $ err
|
||||
in "Error solving day \{show day} part \{show part} of year \{show year}: \n" ++ err
|
||||
|
||||
||| Convert the non-option arguments into a Year/Day pair
|
||||
argumentsToPair : Has (Except Error) fs =>
|
||||
List String -> Eff fs (Nat, Nat)
|
||||
argumentsToPair [] = throw $ ArgumentsError 0
|
||||
argumentsToPair [x] = throw $ ArgumentsError 1
|
||||
argumentsToPair [year, day] = do
|
||||
year <- note (InvalidYear year) $ parsePositive year
|
||||
day <- note (InvalidDay year day) $ parsePositive day
|
||||
pure (year, day)
|
||||
argumentsToPair xs = throw $ ArgumentsError (length xs)
|
||||
|
||||
covering
|
||||
||| Actual main, as an effectful computation
|
||||
start : Eff [IO, Except Error] ()
|
||||
start = do
|
||||
-- Read and parse arguments/options
|
||||
args <- getArgs
|
||||
let opts = getOpt Permute options (drop 1 args)
|
||||
when (not . null $ opts.errors) $
|
||||
throw (OptionsError opts.errors)
|
||||
(year, day_n) <- argumentsToPair opts.nonOptions
|
||||
-- If the verbose flag is set, hook up the logging writer to stderr
|
||||
let verbose = any (== Verbose) opts.options
|
||||
let logHandler : Eff [IO, Except Error, WriterL "log" String] () -> Eff [IO, Except Error] () =
|
||||
if verbose
|
||||
then handleLog ePutStrLn
|
||||
else handleLog (\x => pure ())
|
||||
-- Add the logging writer to the effect
|
||||
logHandler $ do
|
||||
-- Locate and read in the input file
|
||||
Just cwd <- currentDir | _ => throw NoCurrentDir
|
||||
let cwd = parse cwd
|
||||
let use_example = any (== UseExample) opts.options
|
||||
let base_dir = cwd /> "inputs" /> (if use_example then "examples" else "real");
|
||||
let input_path = show $ base_dir /> (show year) /> (show day_n)
|
||||
info "Reading input from \{input_path}"
|
||||
Right contents <- readFile input_path | Left err => throw $ InputRead input_path err
|
||||
-- Now add the reader that provides the input to the effect
|
||||
runReaderAt "input" contents {fs = PartEff Error} $ do
|
||||
-- Attempt to locate the provided day
|
||||
Just day <- pure $ locate year day_n advent | _ => throw $ NoSuchDay year day_n
|
||||
-- Run part 1
|
||||
part_1 <- lift $ runExcept day.part1
|
||||
(part_1, ctx) <- rethrow . mapLeft (SolveError year day_n 1 @{day.showErr1}) $ part_1
|
||||
putStrLn "\{show year} day \{show day_n} part 1:"
|
||||
putStrLn $ unlines . map (" " ++) . lines . show @{day.showOut1} $ part_1
|
||||
-- Run part 2 if we have it
|
||||
case day.part2 of
|
||||
Nothing => pure ()
|
||||
Just part_2_f => do
|
||||
part_2 <- lift . runExcept $ part_2_f ctx
|
||||
part_2 <- rethrow . mapLeft (SolveError year day_n 2 @{day.showErr2}) $ part_2
|
||||
putStrLn "\{show year} day \{show day_n} part 2:"
|
||||
putStrLn $ unlines . map (" " ++) . lines . show @{day.showOut2} $ part_2
|
||||
where
|
||||
-- print to standard error
|
||||
ePutStrLn : String -> IO ()
|
||||
ePutStrLn str = do
|
||||
_ <- fPutStrLn stderr str
|
||||
pure ()
|
||||
-- Decompose a writer, eases type inference
|
||||
unwriter : WriterL "log" String a -> (a, String)
|
||||
unwriter (Tell vw) = ((), vw)
|
||||
-- Lowers logging into IO within the effect using the given IO function
|
||||
handleLog :
|
||||
Has (WriterL "log" String) fs => Has IO (fs - WriterL "log" String) =>
|
||||
(tell : String -> IO ()) -> Eff fs a -> Eff (fs - (WriterL "log" String)) a
|
||||
handleLog tell x =
|
||||
handle
|
||||
(\msg, f =>
|
||||
do let (val, msg) = unwriter msg
|
||||
_ <- send $ tell msg
|
||||
f val) x
|
||||
|
||||
covering
|
||||
||| Shim main, which simply executes the effectful computation
|
||||
main : IO ()
|
||||
main = runEff start [id, handleError]
|
||||
where
|
||||
handleError : Except Error a -> IO a
|
||||
handleError (Err err) = do
|
||||
printLn err
|
||||
exitFailure
|
88
src/Runner.idr
Normal file
88
src/Runner.idr
Normal file
|
@ -0,0 +1,88 @@
|
|||
module Runner
|
||||
|
||||
import Control.Eff
|
||||
import Structures.Dependent.FreshList
|
||||
|
||||
||| The effect type is the same in boths parts one and two, modulo potentially
|
||||
||| different error types, so we calucate it here
|
||||
public export
|
||||
PartEff : (err : Type) -> List (Type -> Type)
|
||||
PartEff err =
|
||||
[IO, Except err, WriterL "log" String, ReaderL "input" String]
|
||||
|
||||
||| Model solving a single day
|
||||
public export
|
||||
record Day where
|
||||
constructor MkDay
|
||||
day : Nat
|
||||
{0 out1, out2, err1, err2 : Type}
|
||||
{auto showOut1 : Show out1}
|
||||
{auto showOut2 : Show out2}
|
||||
{auto showErr1 : Show err1}
|
||||
{auto showErr2 : Show err2}
|
||||
part1 : Eff (PartEff err1) (out1, ctx)
|
||||
part2 : Maybe (ctx -> Eff (PartEff err2) out2)
|
||||
%name Day day, day2, day3
|
||||
|
||||
namespace Day
|
||||
||| Constructor for a day with only part one ready to run
|
||||
public export
|
||||
First : Show err => Show out =>
|
||||
(day : Nat) -> (part1 : Eff (PartEff err) (out, ctx'))
|
||||
-> Day
|
||||
First day part1 =
|
||||
MkDay day part1 Nothing {out2 = ()} {err2 = ()}
|
||||
|
||||
||| Constructor for a day with both parts ready to run
|
||||
public export
|
||||
Both : Show o1 => Show o2 => Show e1 => Show e2 =>
|
||||
(day : Nat) -> (part1 : Eff (PartEff e1) (o1, ctx')) ->
|
||||
(part2 :ctx' -> Eff (PartEff e2) o2)
|
||||
-> Day
|
||||
Both day part1 part2 =
|
||||
MkDay day part1 (Just part2)
|
||||
|
||||
||| Freshness criteria for days
|
||||
|||
|
||||
||| A day is fresh compared to another if the day number of the former day is
|
||||
||| strictly less than the day number of the latter day
|
||||
|||
|
||||
||| This ensures that the days list is always in incrimenting sorted order
|
||||
||| (since we are consing to the front of the list) with no duplicate days
|
||||
public export
|
||||
FreshDay : Day -> Day -> Bool
|
||||
FreshDay x y = x.day < y.day
|
||||
|
||||
||| Collect all the days in a given year
|
||||
public export
|
||||
record Year where
|
||||
constructor MkYear
|
||||
year : Nat
|
||||
days : FreshList FreshDay Day
|
||||
%name Year year, year2, year3
|
||||
|
||||
||| Freshness criteria for years
|
||||
|||
|
||||
||| A year is fresh compared to another if the year number of the former year is
|
||||
||| strictly less than the year number of the latter year
|
||||
|||
|
||||
||| This ensures that the years list is always in incrimenting sorted order
|
||||
||| (since we are consing to the front of the list) with no duplicate years
|
||||
public export
|
||||
FreshYear : Year -> Year -> Bool
|
||||
FreshYear x y = x.year < y.year
|
||||
|
||||
||| Collect all years
|
||||
public export
|
||||
record Advent where
|
||||
constructor MkAdvent
|
||||
years : FreshList FreshYear Year
|
||||
%name Advent advent, advent2, advent3
|
||||
|
||||
namespace Advent
|
||||
||| Attempt to locate `Day` entry corresponding to the provided day and year numbers
|
||||
export
|
||||
locate : (year, day : Nat) -> Advent -> Maybe Day
|
||||
locate year day advent = do
|
||||
year <- find (\x => x.year == year) advent.years
|
||||
find (\x => x.day == day) year.days
|
10
src/Util.idr
Normal file
10
src/Util.idr
Normal file
|
@ -0,0 +1,10 @@
|
|||
module Util
|
||||
|
||||
-----------------------------------
|
||||
-- Standard Data Type Extensions --
|
||||
-----------------------------------
|
||||
namespace Either
|
||||
export
|
||||
mapLeft : (f : a -> b) -> Either a c -> Either b c
|
||||
mapLeft f (Left x) = Left (f x)
|
||||
mapLeft f (Right x) = Right x
|
26
src/Util/Eff.idr
Normal file
26
src/Util/Eff.idr
Normal file
|
@ -0,0 +1,26 @@
|
|||
module Util.Eff
|
||||
|
||||
import Control.Eff
|
||||
import Text.ANSI
|
||||
|
||||
-----------------------
|
||||
-- Logging Utilities --
|
||||
-----------------------
|
||||
namespace Logging
|
||||
export
|
||||
info : Has (WriterL "log" String) fs => String -> Eff fs ()
|
||||
info str =
|
||||
let tag = show . bolden . show . colored Green $ "[INFO]"
|
||||
in tellAt "log" (tag ++ ": " ++ str ++ "\n")
|
||||
|
||||
export
|
||||
debug : Has (WriterL "log" String) fs => String -> Eff fs ()
|
||||
debug str =
|
||||
let tag = show . bolden . show . colored BrightWhite $ "[DEBUG]"
|
||||
in tellAt "log" (tag ++ ": " ++ str ++ "\n")
|
||||
|
||||
export
|
||||
warn : Has (WriterL "log" String) fs => String -> Eff fs ()
|
||||
warn str =
|
||||
let tag = show . bolden . show . colored Yellow $ "[WARN]"
|
||||
in tellAt "log" (tag ++ ": " ++ str ++ "\n")
|
13
src/Years/Y2015.idr
Normal file
13
src/Years/Y2015.idr
Normal file
|
@ -0,0 +1,13 @@
|
|||
module Years.Y2015
|
||||
|
||||
import Structures.Dependent.FreshList
|
||||
|
||||
import Runner
|
||||
|
||||
import Years.Y2015.Day1
|
||||
|
||||
export
|
||||
y2015 : Year
|
||||
y2015 = MkYear 2015 [
|
||||
day1
|
||||
]
|
11
src/Years/Y2015/Day1.idr
Normal file
11
src/Years/Y2015/Day1.idr
Normal file
|
@ -0,0 +1,11 @@
|
|||
module Years.Y2015.Day1
|
||||
|
||||
import Control.Eff
|
||||
|
||||
import Runner
|
||||
|
||||
part1 : Eff (PartEff String) ((), ())
|
||||
|
||||
export
|
||||
day1 : Day
|
||||
day1 = First 1 part1
|
Loading…
Reference in a new issue