Core parser effect
This commit is contained in:
parent
7f4bcf5f51
commit
045fab1b4b
3 changed files with 173 additions and 0 deletions
1
SSG.ipkg
1
SSG.ipkg
|
@ -15,6 +15,7 @@ authors = "Nathan McCarty"
|
||||||
-- packages to add to search path
|
-- packages to add to search path
|
||||||
depends = structures
|
depends = structures
|
||||||
, eff
|
, eff
|
||||||
|
, refined
|
||||||
|
|
||||||
-- modules to install
|
-- modules to install
|
||||||
modules = SSG.Parser.Core
|
modules = SSG.Parser.Core
|
||||||
|
|
|
@ -1 +1,171 @@
|
||||||
module SSG.Parser.Core
|
module SSG.Parser.Core
|
||||||
|
|
||||||
|
import Data.Vect
|
||||||
|
import Data.String
|
||||||
|
|
||||||
|
import Control.Eff
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
-- Internal parser state --
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
||| The location of a parser within a string
|
||||||
|
public export
|
||||||
|
data ParserLocation : Type where
|
||||||
|
AtPoint : (length : Int) -> (location : Int) -> ParserLocation
|
||||||
|
EndOfInput : (length : Int) -> ParserLocation
|
||||||
|
|
||||||
|
namespace ParserLocation
|
||||||
|
export
|
||||||
|
fromString : String -> ParserLocation
|
||||||
|
fromString str =
|
||||||
|
if strLength str > 0
|
||||||
|
then AtPoint 0 (strLength str)
|
||||||
|
else EndOfInput 0
|
||||||
|
|
||||||
|
||| The state of a parser
|
||||||
|
public export
|
||||||
|
record ParserState where
|
||||||
|
constructor MkPS
|
||||||
|
input : String
|
||||||
|
location : ParserLocation
|
||||||
|
|
||||||
|
namespace ParserState
|
||||||
|
export
|
||||||
|
fromString : String -> ParserState
|
||||||
|
fromString str = MkPS str (fromString str)
|
||||||
|
|
||||||
|
export
|
||||||
|
toString : ParserState -> String
|
||||||
|
toString (MkPS input (AtPoint length location)) = strSubstr location length input
|
||||||
|
toString (MkPS input (EndOfInput length)) = ""
|
||||||
|
|
||||||
|
|
||||||
|
||| Three way result of parsing
|
||||||
|
public export
|
||||||
|
data ParseResult : Type -> Type where
|
||||||
|
Got : t -> ParseResult t
|
||||||
|
Failed : t -> ParseResult t
|
||||||
|
EoF : ParseResult t
|
||||||
|
|
||||||
|
-----------------
|
||||||
|
-- Effect Type --
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
||| The parser effect
|
||||||
|
export
|
||||||
|
data Parser : Type -> Type where
|
||||||
|
-- Save and load the state
|
||||||
|
Save : Parser ParserState
|
||||||
|
Load : ParserState -> Parser ()
|
||||||
|
-- Peek the next `n` characters, if there are enough, without altering the parser state
|
||||||
|
Peek : (n : Nat) -> Parser (Maybe (Vect n Char))
|
||||||
|
-- Parse a single char, if it satisfies a predicate
|
||||||
|
ParseChar : (predicate : Char -> Bool) -> Parser (ParseResult Char)
|
||||||
|
-- Parse an exact string match
|
||||||
|
ParseExact : (str : String) -> Parser (ParseResult String)
|
||||||
|
-- Skip a run of 0 or more chars all satisfying a predicate
|
||||||
|
SkipChars : (predicate : Char -> Bool) -> Parser ()
|
||||||
|
-- Detect the end of input
|
||||||
|
ParseEoF : Parser Bool
|
||||||
|
|
||||||
|
-----------------------
|
||||||
|
-- Effectful Actions --
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
||| Aquire a copy of the current parser state
|
||||||
|
|||
|
||||||
|
||| Intended to be used for backtracking
|
||||||
|
export
|
||||||
|
save : Has Parser fs => Eff fs ParserState
|
||||||
|
save = send Save
|
||||||
|
|
||||||
|
||| Restore the parser state to a previousy persisted copy
|
||||||
|
export
|
||||||
|
load : Has Parser fs => ParserState -> Eff fs ()
|
||||||
|
load x = send $ Load x
|
||||||
|
|
||||||
|
||| Look ahead exactly `n` characters
|
||||||
|
export
|
||||||
|
peek : Has Parser fs => (n : Nat) -> Eff fs (Maybe (Vect n Char))
|
||||||
|
peek n = send $ Peek n
|
||||||
|
|
||||||
|
||| Attempt to parse a single char satisfying the given predicate, leaving the parser
|
||||||
|
||| state unchanged on failure
|
||||||
|
export
|
||||||
|
parseChar : Has Parser fs => (predicate : Char -> Bool) -> Eff fs (ParseResult Char)
|
||||||
|
parseChar predicate = send $ ParseChar predicate
|
||||||
|
|
||||||
|
||| Parse an exact match of the given string
|
||||||
|
export
|
||||||
|
parseExact : Has Parser fs => (str : String) -> Eff fs (ParseResult String)
|
||||||
|
parseExact str = send $ ParseExact str
|
||||||
|
|
||||||
|
||| Skip a run of any number of characters satisfying the given predicate
|
||||||
|
export
|
||||||
|
skipChars : Has Parser fs => (predicate : Char -> Bool) -> Eff fs ()
|
||||||
|
skipChars predicate = send $ SkipChars predicate
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
-- Effect handler --
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
export
|
||||||
|
unParser : ParserState -> Parser a -> (a, ParserState)
|
||||||
|
-- Save the parser state
|
||||||
|
unParser s Save =
|
||||||
|
(s, s)
|
||||||
|
-- Load a provided parser state
|
||||||
|
unParser s (Load x) =
|
||||||
|
((), x)
|
||||||
|
-- Look ahead exactly `n` characters
|
||||||
|
unParser s@(MkPS input (AtPoint length location)) (Peek n) =
|
||||||
|
let cs = unpack . strSubstr location (cast n) $ input
|
||||||
|
in (toVect n cs, s)
|
||||||
|
unParser s@(MkPS input (EndOfInput length)) (Peek n) =
|
||||||
|
(Nothing, s)
|
||||||
|
-- Attempt to parse a single character matching a given predicate, bail out without
|
||||||
|
-- affecting the parser state otherwise
|
||||||
|
unParser s@(MkPS input (AtPoint length location)) (ParseChar predicate) =
|
||||||
|
-- Saftey: We maintain location < length as a type invariant
|
||||||
|
let c = assert_total $ strIndex input location
|
||||||
|
in if predicate c
|
||||||
|
then if location + 1 < length
|
||||||
|
then (Got c, MkPS input (AtPoint length location))
|
||||||
|
else (Got c, MkPS input (EndOfInput length))
|
||||||
|
else (Failed c, s)
|
||||||
|
unParser s@(MkPS input (EndOfInput length)) (ParseChar predicate) =
|
||||||
|
(EoF, s)
|
||||||
|
-- Parse an exact string match
|
||||||
|
unParser s@(MkPS input (AtPoint length location)) (ParseExact str) =
|
||||||
|
let substr = strSubstr location (strLength str) input
|
||||||
|
in if substr == str
|
||||||
|
then if location + (strLength str) < length
|
||||||
|
then (Got substr, MkPS input (AtPoint length (location + strLength str)))
|
||||||
|
else (Got substr, MkPS input (EndOfInput length))
|
||||||
|
else (Failed substr, s)
|
||||||
|
unParser s@(MkPS input (EndOfInput length)) (ParseExact str) = (EoF, s)
|
||||||
|
-- Skip any number of chars satisfying a given predicate
|
||||||
|
unParser s@(MkPS input (AtPoint length location)) (SkipChars predicate) =
|
||||||
|
case unParser s (ParseChar predicate) of
|
||||||
|
(Got _, s) => unParser s (SkipChars predicate)
|
||||||
|
(_, s) => ((), s)
|
||||||
|
unParser s@(MkPS input (EndOfInput length)) (SkipChars predicate) =
|
||||||
|
((), s)
|
||||||
|
-- "Parse" the end of input
|
||||||
|
unParser s@(MkPS input (AtPoint length location)) ParseEoF = (False, s)
|
||||||
|
unParser s@(MkPS input (EndOfInput length)) ParseEoF = (True, s)
|
||||||
|
|
||||||
|
export
|
||||||
|
runParser' : Has Parser fs =>
|
||||||
|
(s : ParserState) -> Eff fs t -> Eff (fs - Parser) (t, ParserState)
|
||||||
|
runParser' s =
|
||||||
|
handleRelayS s (\state, t => pure (t, state)) $ \s2, ps, f =>
|
||||||
|
let (a, st) = unParser s2 ps
|
||||||
|
in f st a
|
||||||
|
|
||||||
|
export
|
||||||
|
runParser : Has Parser fs =>
|
||||||
|
(s : String) -> Eff fs t -> Eff (fs - Parser) (t, String)
|
||||||
|
runParser s x =
|
||||||
|
map (\(x,y) => (x, toString y)) $ runParser' (fromString s) x
|
||||||
|
|
2
todo.org
2
todo.org
|
@ -4,3 +4,5 @@
|
||||||
Probably want to refine the attribute and value strings, and be smarter about how we choose to quote the value string.
|
Probably want to refine the attribute and value strings, and be smarter about how we choose to quote the value string.
|
||||||
** NO Move =Raw= out of =Tag=
|
** NO Move =Raw= out of =Tag=
|
||||||
Decided to rename =Tag= to =Html=, and =Raw= to =Text=, which makes this make sense
|
Decided to rename =Tag= to =Html=, and =Raw= to =Text=, which makes this make sense
|
||||||
|
* Parser Core
|
||||||
|
** TODO Refine =location= in =ParserLocation=
|
||||||
|
|
Loading…
Add table
Reference in a new issue