206 lines
4.9 KiB
Nim
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
|