import std/[ options, strutils, strformat, collections/sequtils, sugar, ] import fp/[ maybe, resultM, ] import ../utils/str import ./parser_types # -- Types type parserFnT = proc(t0: Parser): ParserResult # -- Utilities proc isStreamCompleted*(parser: Parser): bool = ## Check if the `parser` index is at/over the stream length. parser.state.position >= parser.state.stream.len - 1 proc isStreamCompleted*(parserResult: ParserResult): bool = ## Check if the `parserResult.state` index is at/over the stream length. parserResult.fold( err => false, isStreamCompleted, ) # -- Parsing functions proc ch*(expectedChars: set[char]): parserFnT {.inline.} = ## Create parser function with set of `expectedChar`. ## When the parser has the character set at the following index return `ParserResult.ok`. 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.} = ## Creates parser function with `expectedChar` ## When the parser has the character at the following index return `ParserResult.ok` 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) let digit* = ch(Digits) proc str*(expectedString: string): parserFnT {.inline.} = ## Creates parser function with `expectedString` ## When the parser has the string at the following index return `ParserResult.ok` return proc(parser: Parser): ParserResult = var res: ParserResult = parser.ok() for c in expectedString.items: if res.isErr: break res = res.flatMap(ch(c)) return res # -- Parsing API proc optional*(parserFn: parserFnT): parserFnT {.inline.} = ## Creates parser function with a nested `parserFn`: ## Continues on succesful parser ## Ignores failing parsers return proc(parser: Parser): ParserResult = let newParser = parserFn(parser) if newParser.isOk(): newParser else: parser.ok() proc ignore*(parserFn: parserFnT): parserFnT {.inline.} = ## Creates parser function with a nested `parserFn`: ## Parses using the `parserFn` but dont capture the resulting tokens. return proc(parser: Parser): ParserResult = return parserFn(parser) .map((x: Parser) => Parser( state: x.state, tokens: parser.tokens, )) proc manyUntil*(acceptFn: parserFnT, stopFn: parserFnT): parserFnT {.inline.} = ## Creates parser function with a nested `acceptFn` parser function until the `stopFn` parserFunction is met: ## Parses until the `stopFn` is reached or on an errror. 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 testParser123 = initParserResult("123") block testParsingFunctions: let ch1 = ch('1') # Success assert testParser123.flatMap(ch1).tokensToString() == "1" assert testParser123.flatMap(anyCh).tokensToString() == "1" assert testParser123.flatMap(str("123")).tokensToString() == "123" # Mismatch assert testParser123.flatMap(ch('2')).error().kind == charMismatchErr assert testParser123.flatMap(ch(Letters)).error().kind == charMismatchErr assert testParser123.flatMap(str("1234")).error().kind == endOfStringErr assert testParser123.flatMap(str("456")).error().kind == charMismatchErr # Out of bounds # assert initParserResult("").flatMap(ch1).error().kind == endOfStringErr assert initParserResult("1").flatMap(ch1).flatMap(ch1).error().kind == endOfStringErr # Stream end reached assert initParserResult("1").flatMap(ch1).isStreamCompleted() == true assert initParserResult("12").flatMap(ch1).isStreamCompleted() == false assert initParserResult("").flatMap(ch1).isStreamCompleted() == false assert testParser123.flatMap(str("123")).isStreamCompleted() == true block testParsingApi: # Optional assert testParser123.flatMap(optional(ch('1'))).tokensToString() == "1" assert testParser123.flatMap(optional(ch('2'))).tokensToString() == "" # Ignore assert testParser123.flatMap(ignore(ch('1'))).tokensToString() == ""