Files
org-parser/src/test.nim
Florian Schroedl d4f4a8a4d7 Implement manyUntil
2022-02-06 07:30:00 +01:00

206 lines
4.9 KiB
Nim

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 `$`*(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(index - 1)
&"""Parsing Error (Character Mismatch Error):
{original}
{errSpace}^ Wanted '{unexpected}' but got '{expected}'"""
else: "ParseError"
proc initParser(str: string): ParserResult =
Parser(
state: ParserState(
stream: str,
position: -1,
lastPosition: 0,
),
tokens: newSeq[Token](),
).ok()
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