From 635c63c487d635a1dc4129714969fb19ef874a2f Mon Sep 17 00:00:00 2001 From: Hans Goor Date: Tue, 3 Dec 2024 23:22:28 +0100 Subject: [PATCH] Add solution to day 3. --- 2024/haskell/app/day3/Day3.hs | 62 ++++++++++++++++++ 2024/haskell/haskell.cabal | 15 +++++ 2024/haskell/package.yaml | 10 +++ 2024/haskell/resources/day3.txt | 6 ++ 2024/haskell/resources/day3_example.txt | 1 + 2024/haskell/src/Lib.hs | 8 +-- 2024/haskell/src/Lib/Parser.hs | 85 +++++++++++++++++++++++++ 7 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 2024/haskell/app/day3/Day3.hs create mode 100644 2024/haskell/resources/day3.txt create mode 100644 2024/haskell/resources/day3_example.txt create mode 100644 2024/haskell/src/Lib/Parser.hs diff --git a/2024/haskell/app/day3/Day3.hs b/2024/haskell/app/day3/Day3.hs new file mode 100644 index 0000000..4c4b1bf --- /dev/null +++ b/2024/haskell/app/day3/Day3.hs @@ -0,0 +1,62 @@ +module Main (main) where + +import Lib.Parser (Parser (parse), separated, char, parens, string, number, satisfy) +import Control.Applicative (Alternative (..)) +import Data.Maybe (fromJust, catMaybes) + +-- | Part 1 +data MulOper = MulOper Int Int deriving (Show, Eq) + +perform :: MulOper -> Int +perform (MulOper a b) = a * b + +mulOper :: Parser MulOper +mulOper = (\_ [a, b] -> MulOper a b) <$> string "mul" <*> parens (char ',' `separated` number) + +manyMaybeMulOper :: Parser [Maybe MulOper] +manyMaybeMulOper = many $ Just <$> mulOper <|> Nothing <$ satisfy (const True) + +extractMulOps :: Maybe ([Maybe MulOper], String) -> [MulOper] +extractMulOps = catMaybes . fst . fromJust + +part1 :: String -> IO () +part1 s = print $ sum $ map perform $ extractMulOps $ parse manyMaybeMulOper s + +-- | Part 2 +data Token + = EnableAction + | DisableAction + | MulAction MulOper + deriving (Show, Eq) + +doOper :: Parser Token +doOper = EnableAction <$ string "do()" + +dontOper :: Parser Token +dontOper = DisableAction <$ string "don't()" + +wrapMulOper :: Parser Token +wrapMulOper = MulAction <$> mulOper + +manyMaybeToken :: Parser [Maybe Token] +manyMaybeToken = many $ Just <$> (doOper <|> dontOper <|> wrapMulOper) <|> Nothing <$ satisfy (const True) + +runCommands :: [Token] -> Int +runCommands tokens = runCommands' tokens True 0 + where + runCommands' :: [Token] -> Bool -> Int -> Int + runCommands' [] _ s = s + runCommands' ((MulAction _):cs) False s = s + runCommands' cs False s + runCommands' ((MulAction (MulOper a b)):cs) True s = s + a * b + runCommands' cs True s + runCommands' (EnableAction:cs) _ s = s + runCommands' cs True s + runCommands' (DisableAction:cs) _ s = s + runCommands' cs False s + +extractTokens :: Maybe ([Maybe Token], String) -> [Token] +extractTokens = catMaybes . fst . fromJust + +part2 :: String -> IO() +part2 s = print $ runCommands $ extractTokens $ parse manyMaybeToken s + +-- | Main +main :: IO () +main = readFile "resources/day3.txt" >>= part2 diff --git a/2024/haskell/haskell.cabal b/2024/haskell/haskell.cabal index 0a83581..4c3d434 100644 --- a/2024/haskell/haskell.cabal +++ b/2024/haskell/haskell.cabal @@ -26,6 +26,7 @@ source-repository head library exposed-modules: Lib + Lib.Parser other-modules: Paths_haskell autogen-modules: @@ -65,6 +66,20 @@ executable day2 , haskell default-language: Haskell2010 +executable day3 + main-is: Day3.hs + other-modules: + Paths_haskell + autogen-modules: + Paths_haskell + hs-source-dirs: + app/day3 + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N + build-depends: + base >=4.7 && <5 + , haskell + default-language: Haskell2010 + test-suite haskell-test type: exitcode-stdio-1.0 main-is: Spec.hs diff --git a/2024/haskell/package.yaml b/2024/haskell/package.yaml index fd8cc06..e233f25 100644 --- a/2024/haskell/package.yaml +++ b/2024/haskell/package.yaml @@ -57,6 +57,16 @@ executables: dependencies: - haskell + day3: + main: Day3.hs + source-dirs: app/day3 + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + dependencies: + - haskell + tests: haskell-test: main: Spec.hs diff --git a/2024/haskell/resources/day3.txt b/2024/haskell/resources/day3.txt new file mode 100644 index 0000000..8da98d4 --- /dev/null +++ b/2024/haskell/resources/day3.txt @@ -0,0 +1,6 @@ +-~who()?!-{ where()mul(764,406)?^why()%[how(420,460)mul(69,497)where();'&>-!when()<^mul(629,650)mul(658,217))mul(553,521)][][*}>when()>]mul(927,175)]mul(364,814) &?what()/don't()#where(705,65)%@/}'#select()(mul(333,471)who()! @!,when()${mul(754,711)/don't()mul(148,921)*$&from()don't()where())mul(455,877)(from(519,591)/who()&when()select(401,718)['mul(870,745)who()@who()mul(92,899),# /<${what()mul(301,362)-mul(408,781)why()%(how()<}^/~mul(318,503);{(*,)when()mul(814,611),how()[ why(){,,mul(525,151)#):^?,~who()do()mul(306,819)!'>$(!mul(294,958)^&how()who()[mul(194,339)((mul(84,363)why()who()mul(711,25)]why()(}mul(31,323);select()!select()mul(85,830)>&where(),({what()<,mul(404,568));$select()where()mul(238,707)what(797,319)'[?mul(603,3)where()>mul(827,90)[mul(288,639)![ what()how();why()mul(278,751)~select()@mul(970,757)select()?how()mul(36,900)-@mul(835,851)+,!mul(908,82)~&?mul(452,524)?[{)what()mul(491,590)who()-[from()]~{mul(333,397)mul(615,16)/mul(132,775)why()'!@* mul(203,604)'how(258,168)[when() ?$mul(731,849)>,+-#why()>when(19,498)]mul(329,904)*/#'']mul(644,691)&+~what():why()/from()}$do():'+,, mul(942,653)what() mul(676,56)/''):[>@<~mul(6,697)'mul(369,485)+;select()-'/mul(102,637))who()$,!^do();&+^}why(483,625)select();-+]+@mul(558,499)+,]$$what()mul(73,35)where()how()+!mul(800,842)<:when()where()^who()<;/]mul(621,718)&select()^{:]mul(353,215):)#[mul(299,721),how()?,mul(911,921)&from()from()%+mul(19,957)when();!),++mul(441,742),$(^}(]]mul(622,972{]]where()<:how()$;who()mul(918,303)who()!mul(504,662)}how()when()(-+?do()~where())?-'select() [mul(533,672)@select(748,65)+where()'{'>don't():$#@:(select()mul(473,262)[how())mul(569,348)&$+*;;mul(919,482)>#mul(275,890)who()%mul(689,521)what()+[-mul(116,646):select()@'^select()when()<>-mul(184select()from(),+)where()who(312,610);mul(473,864)>mul(943,380)}[,&[)?mul(352,764)where()#mul(939,747)when()how(16,861)/select():why()mul(770,687)when()[how(),mul(289,328)mul(52,913)~+?who()mul(324,290):why()#from()mul(711,194)~~when() >[why();mul(635,124)&!from()@mul(71,301)where()&who()how()[mul(817,468)#{?,from()%}?where()(mul(991,655)?what()-why()don't():mul(324,148)mul(13,538)+[(~&/select()mul(887,419)what()<*!]:why()%$]do()@]#what()mul(864,345)<-(how()~^)where()what()&mul(991,25)how()^+-?mul(339,86)?select();@who()*)!}-mul(155,613)~$~{ }how()mul(812,654)~-#when()how()$[mul(386,847) ': -!'don't()from()from()[>mul(635,220)+<-!'&[{;when()mul(204,503)*@don't()~mul(913,575)when()!;^()%mul(313,960)from()from():/}mul(828,212):#;&($<<<-don't()?<(~who()'what()why()mul(454,310)($what();,&+~from()mul(51,471))who():mul(218,841)& mul(310,68from()mul(397,198),&] mul(107,89):mul(391,75)):?%where(),from()[mul(24,801)]}[:who()^from(){![mul(875,162)from()mul(127,591)[don't())#who(),>@>who()[mul(732,249)when()^where()@!+';)-mul(407,186)>from(675,101)what()]%#mul(461,214)$:^/%}mul(650,791)select()(where()'[:;^^~mul(200,463)[?(>$->when(){mul(970,444),when(96,186)%:(who()]/)mul(236,599)[from()-select();',why()mul(259,290)mul(698,621)select()select()!what()[where()%*$where(467,148)mul(458,533when()- #@mul(810,353) +what()/(>-when()%-do() :;how()'when()mul(928,260)*'/+what()where()mul(803,627)?#/$when()))mul(58%&!}^mul(784,330)who():'?]-[mul(480,39)/}#&do();*from()^mul(423,817)who():mul(758,858):)when()how(){-who()^ mul(986,262)<@~ *^what())mul(326,89)mul(863,330)'mul(797,517)+*&]from() }mul(681,55){*}why()who()mul(869,410)}#mul+/ mul(153,766)&^}@select()why()mul(30,619)how()who()?]mul(459,584)mul(119,326)&mul(627,843)'when()'!!@who()!!mul(211,446)(,{;>who(780,178)mul(483,646)$*how()select()%what()mul(113,924)@:mul(325,209)^[ @/%when()'{!mul(674,229)who()>]!?mul(306,16)]: mul(551,752)/@>where()'*mul(942,567)from()-from()mul(689,888)why()<*$select()how()[^[@mul(316,35)>*{+:when()mul(661,456)^$(,where()why()mul(972,157how()from()>+^!(>-mul(156,234)why()[mul^>&+what()?from()do()?#%why(){from(990,121)]how()/mul(201,545)from()<$where()>do()[when()when()'+when()~;&-mul(824,870))$-'where()when(547,415)]+/do()select()+what();#where()mul(442,767)'{%{!select()mul(131,742)mul(385,206)%&#~#@when(141,608)$/-do(){-:who()mul(241,754);}-&who(409,213)*{[mul(858,533{ $don't()from();who()]?select(459,962)who()where()when()mul(847,612why()[<]+*mul(89,985)~#&what(882,874)!!?why()mul(256,284),@}what() mul(845,617)what()from()mul(814,769)'!from()mul(685,850);>(who()who())what()%mul(142,506)what()/':?)(select()mul(159~;%}$^}what())mul(143,52)mul(2,339(>~'$(/~mul(208,220)$why()where()#][mul(622,831)~&@who()mul(841,258)select()/,@{-mul(943,197)what()mul(93,185)]]^^(select()from()*from() mul(233,470)-#@]who()#!what(721,647)mul(839,164)who(){mul(651,869)@^{&mul(28,23)> -'mul(593,606)]why() how()(mul(830,421)]mul(880!$who()/what()when()mul(299,889)mul(697,774)mul(7,535),where() }how(624,328)<%mul(670,193)}(when()[]mul(625,519)]@what()where()[what()*mul(703,883)^>(from()select()where(468,413)]]?don't()what()why() +how()#!/usr/bin/perl*mul(358,599)}>~where()what()?: mul(802,575)<;when()do(),mul(198,240) +!@%^mul(412,618)+# :mul(566,368)when()@%[!+from()&mul:when()^:*mul(139,569)where() @don't()#< (:,!mul(787,971)how()select()where()?-from() mul(609,411)/$what()#what()%mul(452,921)/how(221,30)~what()mul(673,185)%what()mul(205,923)::)^]mul(625,543):~[why()?-$mul(476,415)[why()%when()why():mul(223,252)!~what():{from()}do()how()~;%mul(41,365)'*):who() ,$?&mul(905,180):#,%}mul(882( (&[+$mul(927,165)who()(don't()@(mul(246,147),why()#@%mul(381,509)what()who()]*]-mul(593,225) {*;]mul(771,843);-mul(764,968)#[,}select()-%#)mul(696,330) what()how(),:?;when();%mul(154,412)mul(426,382)/&'who()&~>);mul(990,3);>how(552,609)from()&)# mul> {[/;:/+{mul(290,926)from()'mul(900,408)from()do()select()?%]&[-~mul(932,523)mul(477,866) ($#%mul(14,199)]mul(637,958)'where()what()$mul(777,762)$)what()mul(194,474),>what()$}[mul(521,703)$~- ]mul(897,954)+who()$mul(660,652)where()don't()where(779,173),how(753,110))]mul(488,877)+:(&from()where()~$%:mul(899,223)/why()mul(799,584)~]-(~mul(569:from())@]-(mul(247,152)?+)(from()mul(381,120)~mul(97,101)mul(448,487)when()from()mul(36,815)^}&:~!mul(35,211)*@#%/+)(,mul(288,994)why(546,78)^~)]?$from()who()mul(291,734)why()who() '@~ [mul(80,663)select()who()when()mul(288,196) &!where(212,346)<}mul(153,200)!([>mul(463,573when()%-mul(913,818);!!mul(842,31)'#%who()#-/mul(999,821),+/) -!what()why()mul(649,638)+{,/@why()]>from()mul(192,602))mul(623,469)-who()-*where()~)mul(753,337){[)mul(564,627))>how()('what()$]what()mul(422,110)*where()#]~mul(858,249) where(),+how()'*!mul(128,847){?^**@mul(424,825)mul(942,632);}when()mul(149,807)[?what(774,791)*[@mul(258,576)/~mul(13,896)where()}where(215,694)when():#/#select()#mul(528,826)#[/mul(190,170)~:)'who()'@?why()(mul(694,887)mul(873,610)where()'mul(704,477)%mul(964,286where()&what()do())mul(693,665)[<#(}?~mul(809,641)%>&<>@&mul(659,189)how()])}/-when()<&mul(278,575)?mul(383,999)(mul(321,263)who()mul(754,740) when():@*mul(720,146)?[how()*why()),~mul(836,890):#from() ~from(637,962)when()mul(922,625)&>where();mul(149,748)when()]%,?mul(429,943),&how()select()!}!&$mul(399,831)! @::mul!@how()#'(mul(516,281){mul(540,282)^/what()[&(mul(235,845)mul(858<}why()-mul(541,246)when()mul(916@+select(118,658)mul(924,828) (why()'?:mul'from(){<~$how(784,54){what()~!do()~select() mul(659,852)mul(44,508)]*-~/when()from()what()!>mul(627,533)**don't()who(251,250)};select()from()?#>**mul(169,149),mul(31,376)&why();#when()select()/mul(689,237)'{}how()^how()~!select()mul(832,456)%,*,>mul(285,352)!%,from()@mul(446,359)where()mul(619,895) +;{;]>)mul(41,286)who();mul(56,856)~]how()-mul(726,284)^mul(945,882)&+from(126,120))>){mul(797,922)mul(68,507)#-((mul(369,333)}what()>[:#^how()}+mul(185,65!!))mul(658,63))when()from()don't()' }-#mul(171,755)who()]mul(739,369)how()*when()mul(345,305)why()why()where()mul(418,411)when()mul(126,287)@, why(55,552)};mul>~mul(291,290)why()mul(578?^*#(mul(182,165)&~ !mul*from()mul(101,921)!$:when()from()%mul(387,265)[@how()don't()^)%when()+:*/mul(44,589'<{from()&select()/mul(793,478);from()mul(271,291)&:>where()where()mul(229,293)][>{where()mul(928,6)+where()>)$^?mul(459,607))from(){how()>select()>:where(264,447)mul(684,815):[mul(347,990)>from()'~ ^mul(745,814)from(964,621)@<}@select()[@~mul(107,883)where()where(393,153)mul(963,967)) 'where():^&do()]from()why()what(),^mul(385,705)how()*>mul:?who(36,654)?mul(62,39): [mul(726,597)mul(763,90)[when());%mul(100,244)who()'(mul(55,288)%,)from()}'mul(346,82)mul(931,134)@when()@where()when(426,348){what()}mul)from()]mul(876,854)when()mul(223,775)who()-'why()) mul(898,755)(mul(777,419):;#~?:- 'mul(582,230)what()-&[mul(334,496)-$'+mul(674,926)^ @%don't()what(637,807)~from()mul(40,279))^mul(269,29)why()@(%why(537,715)~[-mul(670,805)select()>!<>mul(836,684)!{)mul(152,117)who()&mul(896,602)'why()])~{mul(766,101)where()when()how()>mul(766,84)when()what()@+;?mul(550,934)~mul(6,106)when()mul(637,11)&-mul(724,305)from()!--/#mul(62,495))why()!;#?mul(906,583)(':+ from())^mul(712,122)don't()!/mul(719,98)mul(504,23)->mul(912,622)&]when()@%why()(!mul(479%@when()>^# ~[#do()#who()what()who()( >^mul(110,206)!'don't()]*[how()@)mul(323,394)select()/mul(377,958)mul(143,494)]:what() ~;how()]mul(630,173)mul(973,759who()]select(104,775)when()how()mul(776,616)when(962,62)where()>~>/&*$mul(177,110)+)*mul(477,656)*%>where()where()](mul(190,616)from());,from()mul(936,105)[> mul(809,538)}mul(563,601)*how()(select() 'mul,&'-&{?[>what()mul(380,965)(what(778,700)mul(669,732)mul(966,367)(:#mul(321,737)how(912,962)?$'where()mul(860,148)&{/when(){*&*mul(742,21)%:&/mul(114,64)&mul(706,893)[*select()how()#^]{mul(339,536)?+how()}+mul(28,905)}#why()>why()%(mul(211,426)why()!~~-]mul(958,999)mul(635,537)'what()#mul(350,45)*?>mul(392,743)mul(639,758)who()mul(342,243) (how()select()-^what()[mul(107,290)[mul(76,140)%):~{/what()what(515,174)do()^#':/?mul[/+]@mul(207,882)what()mul(448,652'?;why()when();mul(509,549)',from(343,613)select()mul(657,688)]:*&>+mul(678]) ^<]mul(809,417)mul(172,314)$[how()mul(666,262))^select()-mul(215,760)#why()from()*mul(301,529)who(86,180))*[#,select()*don't()*@who()from()+where()$mul(944,345)who(),where(631,143)@}<]mul(82,32)!where()]where(324,391)who()~?where(), mul(13,167)when()why()[]>%<'mul(24,374)?mul(932,568)who()([@?}how();$]don't()^*%:@>from()how()mul(100,246)who(147,869)-mul(743,461)>/%who()mul(733,280)-how()where()$[mul(397,950) +>,)>mul(921,777)where()/:-%what()- [do() what(655,775)from()when();}mul(513,785)?>~+~mul(656,887)?+select()<^mul(589,966)&why()mul(287,686)(who())$!<{((mul(4,157)from()}!?,what()'where()mul(617,343)<:why()]why()'&do()%@%what(5,890)what()mul(271,55)what()(where()}'-?)mul(582,649)%)/mul(176select()when()what(),mul(659,68)>>$@select()select()mul(373,308);^when()?]from()~}who()do()?[#mul(572,701)&mul(30,709)[[;why()mul(353,243)-how())>what()~~select()mul(63,496)mul(137,334)< mul(991,76)##how(),'~~?}#mul(485,797)mul(636,71)select(149,192)${{mul(774,387){where(){}~where()mul(418,125)+who()+from()where()$)$/what(368,73)mul(256,560)>when()]mul(631,272)mul(713,265))*{^where()[%select()mul(792,576)?mul(146,670)mul(73,111)<{)*[/]who(130,798)>:mul(820,581)^: (mul(899,649)(#?]mul(230,883)~/what(),%$;^>mul(145,724)*@]!>where())when()don't()what()*@{select()mul(424,670)*$mul(514,265who(),<@/#why()why();{mul(804,700),)why()#} *]]/mul(28,284)where() !:{*+when()mul(930,21)from()+who()what():?who()>mul(119,587)#mul(366,632)mul(710,996)why()select(676,739)}:@mul(427,253)[>#>mul(486,35)where()>#[%when();'* mul(368,692) '}!%+&(/?mul(233,760)+what(),:#':what()+what()mul(489,225)+{mul(977,940)%/^mul(124,752):when(577,707)%when()[mul(483,254)#:?^when()*^mul(23,268)mul(48,566)}'#mul(879,408)&from()what())[}$$why()mul(633,365)(mul(135,837)@'()&@&~mul(347,314)^<;mul(301,516) ^$((?what()(&what()do()[mul(883,466)##,*$mul(273,756)}[how() what()*~where()>mul(89,229)from()??why()~mul(622,308)%!$how()>do()](<~what() ^^!mul(941,422)[]how()don't(),>:where()>when() why(417,45)%why()mul(815,766)!]( $mul(409,145)!'?)mul(81,823)*mul(443,168)?when()?mul(475,220)/),+where():do())}when()**+{mul(651,45);#how()]%mul(837,911) >^}}!how()mul(699,375)^why(905,878)how()(}mul(936,779)[don't()mul(928,258)how()~/->mul(65,102)@*from()^]&(+mul(517,48)how() #'< >mul(846,43){from():what()why()(where()})mul(77,705)when()mul(885,675)mul(180,761)^>^(:&do()from()[#<-when()%~mul(743,829):when();$mul(99,676)who()!:&+;}mul(412,553) -]}^mul(824,352)from()+mul(282,920)who()how(){%where()~^mul(252,21)what()/{-mul(958,613)(-:mul(696,876)/why()#mul(761,290)#,mul(971,166)mul(761,359)mul(349,153):}select()where()mul(640,511)@;%where()-'mul(472,839)why()#'from()where()#--mul(823,302)! select()?;+},(>mul(255,432)mul(30,380)*/how()mul(852,651)who()$ where()<#how(),mul(683,385)mul(988,590)how()who()'>-mul(608,251)what()@*?who()!/mul(992,768)#' diff --git a/2024/haskell/resources/day3_example.txt b/2024/haskell/resources/day3_example.txt new file mode 100644 index 0000000..30032cb --- /dev/null +++ b/2024/haskell/resources/day3_example.txt @@ -0,0 +1 @@ +xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5)) diff --git a/2024/haskell/src/Lib.hs b/2024/haskell/src/Lib.hs index d36ff27..70498ce 100644 --- a/2024/haskell/src/Lib.hs +++ b/2024/haskell/src/Lib.hs @@ -1,6 +1,6 @@ module Lib - ( someFunc - ) where + ( module Lib.Parser, + ) +where -someFunc :: IO () -someFunc = putStrLn "someFunc" +import Lib.Parser diff --git a/2024/haskell/src/Lib/Parser.hs b/2024/haskell/src/Lib/Parser.hs new file mode 100644 index 0000000..00f6082 --- /dev/null +++ b/2024/haskell/src/Lib/Parser.hs @@ -0,0 +1,85 @@ +{-# LANGUAGE LambdaCase #-} + +module Lib.Parser + ( Parser (..), + many0, + many1, + skip, + satisfy, + char, + digit, + number, + string, + surrounded, + parens, + separated, + ) +where + +import Control.Applicative (Alternative (..)) +import Control.Monad (void) +import Data.Char (isDigit) + +newtype Parser a = Parser + { parse :: String -> Maybe (a, String) + } + +instance Functor Parser where + fmap f (Parser parser) = Parser $ \input -> + case parser input of + Just (result, remainder) -> Just (f result, remainder) + Nothing -> Nothing + +instance Applicative Parser where + pure a = Parser $ \input -> Just (a, input) + Parser a <*> Parser b = Parser $ \input -> + case a input of + Nothing -> Nothing + Just (f, remainderA) -> + case b remainderA of + Nothing -> Nothing + Just (resultB, remainderB) -> Just (f resultB, remainderB) + +instance Alternative Parser where + empty = Parser $ const Nothing + Parser a <|> Parser b = Parser $ \input -> + case a input of + Nothing -> b input + Just (result, remainder) -> Just (result, remainder) + +many0 :: Parser p -> Parser [p] +many0 p = many1 p <|> pure [] + +many1 :: Parser p -> Parser [p] +many1 p = (:) <$> p <*> many0 p + +skip :: Parser p -> Parser () +skip = void + +satisfy :: (Char -> Bool) -> Parser Char +satisfy predicate = Parser $ \case + [] -> Nothing + x : xs + | predicate x -> Just (x, xs) + | otherwise -> Nothing + +char :: Char -> Parser Char +char c = satisfy (== c) + +digit :: Parser Char +digit = satisfy isDigit + +number :: Parser Int +number = (\n -> read n :: Int) <$> many digit + +string :: String -> Parser String +string = foldr (\c -> (<*>) ((:) <$> char c)) (pure []) + +surrounded :: Parser start -> Parser content -> Parser end -> Parser content +surrounded s c e = (\_ c' _ -> c') <$> s <*> c <*> e + +separated :: Parser sep -> Parser content -> Parser [content] +s `separated` c = (:) <$> c <*> many0 ((\_ c' -> c') <$> s <*> c) + +parens :: Parser inner -> Parser inner +parens c = surrounded (char '(') c (char ')')