Files
org-parser/src/parser/parser_internals.nim
2022-05-17 08:05:39 +02:00

224 lines
6.6 KiB
Nim

import std/options
import std/strutils
import std/strformat
import std/collections/sequtils
import std/sugar
import std/collections/tables
import results
import fusion/matching
import fp/maybe
import ./parser_types
import ../utils/str
{.experimental: "caseStmtMacros".}
# -- Parsing Functions
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 & initParserToken(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 & initParserToken(foundChar)
).ok()
else:
return err(ParserError(
kind: charMismatchErr,
unexpected: &"{foundChar}",
expected: &"{expectedChar}",
index: newIndex,
parser: parser,
))
let anyCh* = ch(AllChars)
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
# -- Parsing API
func optional*(parserFn: Parser -> ParserResult): (Parser -> ParserResult) {.inline.} =
## Parse characters and ignore failure
return proc(parser: Parser): ParserResult =
let newParser = parserFn(parser)
if newParser.isOk():
newParser
else:
parser.ok()
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 manyUntilPerformant*(acceptFn: Parser -> ParserResult, stopFn: Parser -> ParserResult): (Parser -> ParserResult) {.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: Parser -> ParserResult): (Parser -> ParserResult) {.inline.} =
manyUntilPerformant(anyCh, stopFn)
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 = parser.ok()
while res.isOk() and res.flatMap(stopFn).isErr():
res = res.flatMap(acceptFn)
return res
proc anyUntil*(stopFn: Parser -> ParserResult): (Parser -> ParserResult) {.inline.} =
manyUntil(anyCh, stopFn)
func choice*(parsers: seq[Parser -> ParserResult]): (Parser -> ParserResult) {.inline.} =
return proc(parser: Parser): ParserResult =
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: Parser -> ParserResult, parserFnB: Parser -> ParserResult): (Parser -> ParserResult) {.inline.} =
## Parse characters and ignore failure
return proc(parser: Parser): ParserResult =
parserFnA(parser).flatMap(parserFnB)
proc parseSeq*(parser: ParserResult, xs: seq[Parser -> ParserResult]): ParserResult =
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: (Parser -> ParserResult), stop: (Parser -> ParserResult)): (Parser -> ParserResult) {.closure.} =
ignore(start) +
anyUntil(stop + whitespace) +
ignore(start)
let parseBetweenPair* = proc(delimiterParser: (Parser -> ParserResult)): (Parser -> ParserResult) {.closure.} =
parseBetweenDelimiter(delimiterParser, delimiterParser)