import std/options import std/strutils import std/strformat import std/collections/sequtils import std/sugar import results import fusion/matching import fp/maybe import print {.experimental: "caseStmtMacros".} type ParserState* = ref object stream: string position, lastPosition: int Token* = ref object value: char Parser* = ref object state: ParserState tokens: seq[Token] ParseErrorKind = enum charMismatchErr endOfStringErr ParserError = ref object kind: ParseErrorKind unexpected: string expected: string index: int parser: Parser ParserResult* = Result[Parser, ParserError] # Builder[T]* = ref object # parser: ParserResult, # tree: seq[T], # BuilderResult* = Result[Builder, (Builder, string)] # func prettyPrintError*(err: ParserErrror,) func getOrElse*[T, E](self: Result[T, E], otherwise: T): T = if self.isOk(): self.v else: otherwise proc indentKey(x: string, count: int): string = var y = x.indent(count) y.delete(0..count - 1) y proc `$`*(x: Token): string = &"""Token( value: {x.value}, )""" proc `$`*(x: ParserState): string = &"""ParserState( stream: "{x.stream}", position: {x.position}, lastPosition: {x.lastPosition}, )""" proc `$`*(x: Parser): string = &"""Parser( state: {indentKey($x.state, 2)}, tokens: {indentKey($x.tokens, 2)}, )""" proc prettyExpectedSet(x: set[char]): string = case x: of AllChars: "AllChars {'\x00'..'\xFF'}" of Digits: "Digits {'0'..'9'}" of HexDigits: "HexDigits {'0'..'9', 'A'..'F', 'a'..'f'}" of Letters: "Letters {'A'..'Z', 'a'..'z'}" of Newlines: "Newlines {'\r', '\n'}" of Whitespace: "Whitespace {' ', '\t', '\v', '\r', '\n', '\f'}" else: $x proc `$`*(x: ParserError): string = case x: of charMismatchErr(expected: @expected, parser: @parser, index: @index, unexpected: @unexpected): # TODO: Only works for single line right now let original = parser.state.stream let errSpace = " ".repeat(max(0, index)) &"""Parsing Error (Character Mismatch Error): {original} {errSpace}^ Expected '{expected}' but got '{unexpected}'""" else: "ParseError" proc initParser(str: string): ParserResult = Parser( state: ParserState( stream: str, position: -1, lastPosition: 0, ), tokens: newSeq[Token](), ).ok() func ch(expectedChars: set[char]): (Parser -> ParserResult) {.inline.} = return func(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 & Token(value: foundChar) ).ok() else: return err(ParserError( kind: charMismatchErr, unexpected: &"{foundChar}", expected: &"{expectedChars.prettyExpectedSet()}", index: newIndex, parser: parser, )) func ch(expectedChar: char): (Parser -> ParserResult) {.inline.} = return func(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 & Token(value: foundChar) ).ok() else: return err(ParserError( kind: charMismatchErr, unexpected: &"{foundChar}", expected: &"{expectedChar}", index: newIndex, parser: parser, )) func ignore(parserFn: Parser -> ParserResult): (Parser -> ParserResult) {.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, )) func manyUntil(acceptFn: Parser -> ParserResult, stopFn: Parser -> ParserResult): (Parser -> ParserResult) {.inline.} = ## Parse characters but throw success tokens away return proc(parser: Parser): ParserResult = var res: ParserResult = acceptFn(parser) while res.isOk() and res.flatMap(stopFn).isErr(): res = res.flatMap(acceptFn) return res proc parseSeq(parser: ParserResult, xs: seq[Parser -> ParserResult]): ParserResult = xs.foldl(a.flatMap(b), parser) func str(s: string): (Parser -> ParserResult) {.inline.} = return func(parser: Parser): ParserResult = var p = parser.ok() for c in s.items: p = p.flatMap(ch(c)) return p # proc foldTokens[T]( # parserResult: ParserResult, # builderResult: BuilderResult[T], # fn: Parser -> T, # ): BuilderResult[T] = # builderResult.flatMap((b: Builder) => Builder( # parser: parserResult, # tree: parserResul # )) proc foldTokens[T]( parserResult: ParserResult, onError: ParserError -> T, onSuccess: seq[Token] -> T, ): T = if parserResult.isOk(): onSuccess(parserResult.unsafeGet().tokens) else: let err = parserResult.error() onError(err) when isMainModule: let fooParser = initParser("FOO___BAR") .parseSeq(@[ str("FOO"), ignore(str("___")), ch('B'), ch('B'), ch('A'), ch('R'), ]) .foldTokens( err => $err, xs => xs.foldl(a & b.value, "") ) # echo fooParser let manUntilTest = initParser("AAAB") .flatMap(manyUntil(ch('B'), ch('B'))) echo initParser("**** Foo") .parseSeq(@[ manyUntil(ch('*'), ch(' ')), ignore(ch ' '), str("Foo") ]) # echo initParser("AAAB").flatMap(ch 'b') # # .flatMap(ch('A')) # # .flatMap(ch('B')) # .flatMap() # echo manUntilTest