A modern and more abstract version of the Arcsecond library made with Coffeescript.
Arcsecond is a simple parsing library inspired by Haskell's Parsec, made from scratch by francisrstrokes who runs the LLJS YouTube channel.
Arcthird makes it more abstract by introducing a new class called PStream
, from which your parsers are gonna parse from. This allows for example to make a parser of tokens, represented as JS objects.
The documentation is still very empty. It will get more full once I have more free time. If you'd like to contribute, don't hesitate to create pull requests!
I've decided to change some of the functions from Arcsecond. Overall:
- The
ParserState
is stored in its own class. Therefore:createParserState
is replaced by theParserState
constructor;updateError
is replaced by theerrorify
method;updateResult
is replaced by theresultify
method;updateData
is replaced by thedataify
method;updateParserState
is replaced by theupdate
method.
- The
Parser
'srun
method is calledparse
, and has to take aPStream
. Thus the curriedparse
function also takes aPSteam
as an argument. - For string / buffer / typed array specific parsers from Arcsecond, I created a
StringPStream
class instance ofPSteam
. So you would write for examplechar('h').parse(new StringPStream("hello world")) parse(str("hello"))(new StringPStream("hello world"))
- Because writing
new StringPStream
everytime would be kinda annoying, I created thestrparse
function to directly take a string instead:strparse(str("hello"))("hello world")
- There is no
many1
function. Instead, there isatLeast
andatLeast1
, which is more general.
PStream
is an abstract class which is gonna be the input stream of every parser you're gonna use and create.
-- This is just for type annotation
class PStream
constructor :: t -> PStream t
length :: PStream t ~> () -> Int
elementAt :: PStream t ~> Int -> a
next :: PStream t ~> () -> a
nexts :: PStream t ~> Int -> [a]
next
: gives the next element.nexts
: gives an array of the next n elements.
The next
and nexts
methods are automatically defined in terms of elementAt
. Although they aren't used anywhere as I didn't realize that I had used the index
property of the ParserState
class all this time. Replacing that with the index
property of the PStream
is an improvement to make in the future.
Because it is an abstract class, you cannot use it as is, you have to create your own extension and implement the length
and elementAt
functions yourself. For example:
class NumberStream extends PStream {
constructor(target) {
if (!(target instanceof Array))
throw new TypeError("Target must be an Array");
super(target);
}
length() {
return this.structure.length;
}
elementAt(i) {
try {
return this.structure[i];
} catch (err) {
return null;
}
}
}
const nstream = new NumberStream([1, 2, 3, 42, 69]);
console.log(nstream.next()); // 1
console.log(nstream.nexts(2)); // [2, 3]
console.log(nstream.elementAt(0)); // 1
console.log(nstream.nexts(3)); // [42, 69, null]
console.log(nstream.next()); // null
console.log(nstream.length()); // 5
The type of a parser as used in the documentation is different from Arcsecond. Because of the new abstraction, there is now a new generic type to take into account: t
as a PStream
.
-- This is just for type annotation
class Parser
constructor :: PStream t => (ParserState t * d -> ParserState t a d) -> Parser t a d
parse :: PStream t => Parser t a d ~> t -> ParserState t a d
fork :: PStream t => Parser t a d ~> (t, (ParserState t a d -> x), (ParserState t a d -> y)) -> (Either x y)
map :: PStream t => Parser t a d ~> (a -> b) -> Parser t b d
chain :: PStream t => Parser t a d ~> (a -> Parser t b d) -> Parser t b d
ap :: PStream t => Parser t a d ~> Parser t (a -> b) d -> Parser t b d
errorMap :: PStream t => Parser t a d ~> ({d, String, Int} -> String) -> Parser t a d
errorChain :: PStream t => Parser t a d ~> ({d, String, Int} -> Parser t a d) -> Parser t a d
mapFromData :: PStream t => Parser t a d ~> ({a, d} -> b) -> Parser t b d
chainFromData :: PStream t => Parser t a d ~> ({a, d} -> Parser t b e) -> Parser t b e
mapData :: PStream t => Parser t a d ~> (d -> e) -> Parser t a e
of :: PStream t => x -> Parser t x d
All the errors returned in parser states are strings. Now this doesn't change anything from Arcsecond, although Arcsecond explicitly allowed for different error types. After verification, I realized that my code does also allow for different error types; however, this isn't reflected in the documentation where errors are described as strings even for parser combinators.
constructor
: constructs a new parser by taking aParserState
transformer as an argument, which is a function that takes aParserState
and returns a newParserState
.const consume = new Parser(state => new ParserState({ ...state.props(), index: state.target.length() }));
parse
: directly parses aPStream
into aParserState
.char('a').parse(new StringPStream("abc"));
char :: StringPStream t => Char -> Parser t Char d
Parses a single character.
strparse(char('h'))("hello world")
// -> ParserState result='h' index=1
anyChar :: StringPStream t => Parser t Char d
Parses any character.
strparse(anyChar)("bruh")
// -> ParserState result='b' index=1
peek :: PStream t => Parser t a d
Returns the element at the current index as a result without moving forward.
strparse(peek)("something")
// -> ParserState result='s' index=0
str :: StringPStream t => String -> Parser t String d
regex :: StringPStream t => RegExp -> Parser t String d
digit :: StringPStream t => Parser t String d
digits :: StringPStream t => Parser t String d
letter :: StringPStream t => Parser t String d
letters :: StringPStream t => Parser t String d
anyOfString :: StringPStream t => String -> Parser t Char d
endOfInput :: PStream t => Parser t Null d
whitespace :: StringPStream t => Parser t String d
optionalWhitespace :: StringPStream t => Parser t String d
getData :: PStream t => Parser t a d
setData :: PStream t => d -> Parser t a d
mapData :: PStream t => (d -> e) -> Parser t a e
withData :: PStream t => Parser t a d -> e -> Parser t a e
pipe :: PStream t => [Parser t * *] -> Parser t * *
compose :: PStream t => [Parser t * *] -> Parser t * *
tap :: PStream t => (ParserState t a d -> IO ()) -> Parser t a d
parse :: PStream t => Parser t a d -> t -> Either String a
strparse :: StringPStream t => Parser t a d -> String -> Either String a
decide :: PStream t => (a -> Parser t b d) -> Parser t b d
fail :: PStream t => String -> Parser t a d
succeedWith :: PStream t => a -> Parser t a d
either :: PStream t => Parser t a d -> Parser t (Either String a) d
coroutine :: PStream t => (() -> Iterator (Parser t a d)) -> Parser t a d
exactly :: PStream t => Int -> Parser t a d -> Parser t [a] d
many :: PStream t => Parser t a d -> Parser t [a] d
atLeast :: PStream t => Int -> Parser t a d -> Parser t [a] d
atLeast1 :: PStream t => Parser t a d -> Parser t [a] d
mapTo :: PStream t => (a -> b) -> Parser t b d
errorMapTo :: PStream t => ({d, String, Int} -> String) -> Parser t a d
namedSequenceOf :: PStream t => [(String, Parser t * *)] -> Parser t (StrMap *) d
sequenceOf :: PStream t => [Parser t * *] -> Parser t [*] *
sepBy :: PStream t => Parser t a d -> Parser t b d -> Parser t [b] d
sepBy1 :: PStream t => Parser t a d -> Parser t b d -> Parser t [b] d
choice :: PStream t => [Parser t * *] -> Parser t * *
between :: PStream t => Parser t a d -> Parser t b d -> Parser t c d -> Parser t b d
everythingUntil :: StringPStream t => Parser t a d -> Parser t String d
everyCharUntil :: StringPStream t => Parser t a d -> Parser t String d
anythingExcept :: StringPStream t => Parser t a d -> Parser t Char d
anyCharExcept :: StringPStream t => Parser t a d -> Parser t Char d
lookAhead :: PStream t => Parser t a d -> Parser t a d
possibly :: PStream t => Parser t a d -> Parser t (a | Null) d
skip :: PStream t => Parser t a d -> Parser t a d
recursiveParser :: PStream t => (() -> Parser t a d) -> Parser t a d
takeRight :: PStream t => Parser t a d -> Parser t b d -> Parser t b d
takeLeft :: PStream t => Parser t a d -> Parser t b d -> Parser t a d
toPromise :: PStream t => ParserState t a d -> Promise (String, Integer, d) a
toValue :: PStream t => ParserState t a d -> a