import std/[ options, strutils, strformat, collections/sequtils, sugar, ] import results import fp/[ maybe, ] import ../utils/str import ./parser_types # -- Types type parserFnT = proc(t0: Parser): ParserResult # -- Utilities proc lookBack*(count: int): (parserFnT) = return proc(parser: Parser): ParserResult = let state = parser.state let newIndex = state.position - 1 Parser( state: ParserState( stream: state.stream, position: newIndex, lastPosition: parser.state.position, ), tokens: parser.tokens, ).ok() # -- Parsing Proctions proc ch*(expectedChars: set[char]): parserFnT {.inline.} = return proc(parser: Parser): ParserResult = let state = parser.state let newIndex = state.position + 1 if newIndex > (state.stream.len - 1): return err(ParserError( kind: endOfStringErr, expected: expectedChars.prettyExpectedSet(), index: newIndex, parser: parser, )) else: let foundChar = state.stream[newIndex] if foundChar in expectedChars: return Parser( state: ParserState( stream: state.stream, position: newIndex, lastPosition: parser.state.position, ), tokens: parser.tokens & initParserToken(foundChar) ).ok() else: return err(ParserError( kind: charMismatchErr, unexpected: $foundChar, expected: expectedChars.prettyExpectedSet(), index: newIndex, parser: parser, )) proc ch*(expectedChar: char): parserFnT {.inline.} = return proc(parser: Parser): ParserResult = let state = parser.state let newIndex = state.position + 1 if newIndex > (state.stream.len - 1): return err(ParserError( kind: endOfStringErr, expected: &"{expectedChar}", index: newIndex, parser: parser, )) else: let foundChar = state.stream[newIndex] if expectedChar == foundChar: return Parser( state: ParserState( stream: state.stream, position: newIndex, lastPosition: parser.state.position, ), tokens: parser.tokens & initParserToken(foundChar) ).ok() else: return err(ParserError( kind: charMismatchErr, unexpected: &"{foundChar}", expected: &"{expectedChar}", index: newIndex, parser: parser, )) let anyCh* = ch(AllChars) proc str*(s: string): parserFnT {.inline.} = return proc(parser: Parser): ParserResult = var p = parser.ok() for c in s.items: p = p.flatMap(ch(c)) return p # -- Parsing API proc optional*(parserFn: parserFnT): parserFnT {.inline.} = ## Parse characters and ignore failure return proc(parser: Parser): ParserResult = let newParser = parserFn(parser) if newParser.isOk(): newParser else: parser.ok() proc ignore*(parserFn: parserFnT): parserFnT {.inline.} = ## Parse characters but throw success tokens away return proc(parser: Parser): ParserResult = return parserFn(parser) .map((x: Parser) => Parser( state: x.state, tokens: parser.tokens, )) proc manyUntilPerformant*(acceptFn: parserFnT, stopFn: parserFnT): parserFnT {.inline.} = ## Parse characters but throw success tokens away return proc(parser: Parser): ParserResult = let startPosition = parser.state.position var res: ParserResult = parser.ok() while res.isOk() and res.flatMap(stopFn).isErr(): res = res.flatMap(acceptFn) return res.map((p: Parser) => Parser( state: ParserState( stream: p.state.stream, position: p.state.position, lastPosition: p.state.lastPosition, ), tokens: @[ ParserToken( kind: parserTokenString, stringValue: p.state.stream[(startPosition - 1)..p.state.position], ) ] )) proc anyUntilPerformant*(stopFn: parserFnT): parserFnT {.inline.} = manyUntilPerformant(ch(AllChars), stopFn) proc manyUntil*(acceptFn: parserFnT, stopFn: parserFnT): parserFnT {.inline.} = ## Parse characters but throw success tokens away return proc(parser: Parser): ParserResult = var res: ParserResult = parser.ok() while res.isOk() and res.flatMap(stopFn).isErr(): res = res.flatMap(acceptFn) return res proc anyUntil*(stopFn: parserFnT): parserFnT {.inline.} = manyUntil(anyCh, stopFn) proc choice*(parsers: seq[parserFnT]): parserFnT {.inline} = return proc(parser: Parser): ParserResult {.closure.} = var errors: seq[ParserResult] = newSeq[ParserResult]() var found = Nothing[ParserResult]() for fn in parsers: let fnResult: ParserResult = fn(parser) if fnResult.isOk(): found = fnResult.just break else: errors = errors & fnResult return found .fold( proc(): ParserResult = let prettyErrors = errors.map((x: ParserResult) => x.error().expected) err(ParserError( kind: choiceMismatchErr, index: parser.state.position + 1, expected: &"Choice ({prettyErrors})", unexpected: errors[0].error().unexpected, parser: parser, )), proc(x: ParserResult): ParserResult = x, ) proc `+`*(parserFnA: parserFnT, parserFnB: parserFnT): parserFnT {.inline.} = ## Parse characters and ignore failure return proc(parser: Parser): ParserResult = parserFnA(parser).flatMap(parserFnB) proc parseSeq*(parser: ParserResult, xs: seq[parserFnT]): ParserResult {.inline.} = xs.foldl(a.flatMap(b), parser) # -- Parsing Aliases proc endOfStream*(parser: Parser): ParserResult = let index = parser.state.position + 1 if index == parser.state.stream.len: ok(parser) else: err(ParserError( kind: endOfStringErr, expected: &"EndOfString", index: index, parser: parser, )) let newlineParser = choice(@[ ch(NewLines), endOfStream, ]) proc newline*(parser: Parser): ParserResult = newlineParser(parser) .mapErr((x: ParserError) => x.setErrorExpectedField("Newline")) let whitespaceParser = choice(@[ ch(Whitespace), newlineParser, ]) proc whitespace*(parser: Parser): ParserResult = whitespaceParser(parser) .mapErr((x: ParserError) => x.setErrorExpectedField("Whitespace")) # -- Parsing Helpers let parseBetweenDelimiter* = proc(start: parserFnT, stop: parserFnT): parserFnT {.closure.} = ignore(start) + anyUntil(stop + whitespace) + ignore(start) let parseBetweenPair* = proc(delimiterParser: parserFnT): parserFnT {.closure.} = parseBetweenDelimiter(delimiterParser, delimiterParser) # -- Tests when isMainModule: let test1 = initParserResult("123") let ch1 = ch('1') let chDigits = @[ch(Digits)] # echo type ch1 # echo test1.flatMap((x: Parser) => ParserResult.ok(x)) echo test1.flatMap(newlineParser) # .flatMap(ch1)