Started refactor

This commit is contained in:
Florian Schroedl
2022-08-09 22:07:30 +02:00
parent 9b0ad40f55
commit 892eec10d5
10 changed files with 1048 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
import std/sugar
import std/collections/sequtils
import std/strformat
import std/strutils
import results
import fusion/matching
import ./parser_internals
import ./parser_types
# proc tryParseBuildOrg*[T](
# builder: Builder[T],
# builderFns: seq[
# Builder[T] -> BuilderResult[T]
# ],
# stopAtParserFn = endOfStream
# ): BuilderResult[T] =
# # Mutating accumulators
# var builderAcc: BuilderResult[T] = BuilderResult.ok(builder)
# while builderAcc.isOk() and builderAcc.tryParser(stopAtParserFn).isErr()
# for fn in builderFns:
# fn
proc tryParseBuild*[T](
builder: Builder[T],
builderFns: seq[tuple[
parserFn: Parser -> ParserResult,
builderFn: seq[ParserToken] -> seq[T],
]],
defaultBuilderFn: seq[ParserToken] -> seq[T],
stopAtParserFn = newline,
concatFn = concat[T],
): BuilderResult[T] =
## Parse text in `builder` by checking the `builderFns` list for a sucessful `parserFn`.
## The `ok` `parserFn` result will be merged into the `Builder[T].tree` by using the `concatFn`.
## Otherwise continue taking any character until the `stopAtParserFn` condition is found.
## Any non-matching tokens will be converted using the `defaultBuilderFn`.
let (parser, tree) = builder
# Mutating accumulators
var parserAcc: ParserResult = ParserResult.ok(parser)
var builderAcc: Builder[T] = builder
while parserAcc.isOk() and parserAcc.flatMap(stopAtParserFn).isErr():
# Empty the parser tokens as we want to seperate them for the next parser in the sequence
let emptyParser = parserAcc.map(emptyTokens)
# Find the first matching parser and convert its tokens
var found = false
for fn in builderFns:
let (parserFn, builderFn) = fn
let parseResult = emptyParser.flatMap(parserFn)
if parseResult.isOk():
let okParser = parseResult.unsafeGet()
# Convert all previous unmatched tokens via the `defaultBuilderTokens`
let defaultBuilderTokens = parserAcc
.foldTokens(
onError = _ => newSeq[T](),
onSuccess = defaultBuilderFn,
)
found = true
parserAcc = parseResult.map(emptyTokens)
builderAcc = builder.initBuilder(
okParser,
concatFn(
builderAcc.tree,
defaultBuilderTokens,
builderFn(okParser.tokens),
)
)
break
if not found:
parserAcc = parserAcc.flatMap(anyCh)
let defaultBuilderTokens = parserAcc
.foldTokens(
onError = _ => newSeq[T](),
onSuccess = defaultBuilderFn,
)
BuilderResult[T].ok(builder.initBuilder(
builderAcc.parser,
concatFn(
builderAcc.tree,
defaultBuilderTokens,
),
))

View File

@@ -0,0 +1,242 @@
import std/[
options,
strutils,
strformat,
collections/sequtils,
sugar,
]
import results
import fp/[
maybe,
]
import ../utils/str
import ./parser_types
# -- Types
type parserFnT = proc(t0: Parser): ParserResult {.nosideeffect.}
# -- Utilities
func lookBack*(count: int): (parserFnT) =
return func(parser: Parser): ParserResult =
let state = parser.state
let newIndex = state.position - 1
Parser(
state: ParserState(
stream: state.stream,
position: newIndex,
lastPosition: parser.state.position,
),
tokens: parser.tokens,
).ok()
# -- Parsing Functions
func ch*(expectedChars: set[char]): parserFnT {.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): parserFnT {.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,
))
const anyCh* = ch(AllChars)
func str*(s: string): parserFnT {.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: parserFnT): parserFnT {.inline.} =
## Parse characters and ignore failure
return func(parser: Parser): ParserResult =
let newParser = parserFn(parser)
if newParser.isOk():
newParser
else:
parser.ok()
func ignore*(parserFn: parserFnT): parserFnT {.inline.} =
## Parse characters but throw success tokens away
return func(parser: Parser): ParserResult =
return parserFn(parser)
.map((x: Parser) => Parser(
state: x.state,
tokens: parser.tokens,
))
func manyUntilPerformant*(acceptFn: parserFnT, stopFn: parserFnT): parserFnT {.inline.} =
## Parse characters but throw success tokens away
return func(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],
)
]
))
func anyUntilPerformant*(stopFn: parserFnT): parserFnT {.inline.} =
manyUntilPerformant(ch(AllChars), stopFn)
func manyUntil*(acceptFn: parserFnT, stopFn: parserFnT): parserFnT {.inline.} =
## Parse characters but throw success tokens away
return func(parser: Parser): ParserResult =
var res: ParserResult = parser.ok()
while res.isOk() and res.flatMap(stopFn).isErr():
res = res.flatMap(acceptFn)
return res
func anyUntil*(stopFn: parserFnT): parserFnT {.inline.} =
manyUntil(anyCh, stopFn)
func choice*(parsers: seq[parserFnT]): parserFnT {.inline} =
return func(parser: Parser): ParserResult {.closure, nosideeffect.} =
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(
func(): 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,
)),
func(x: ParserResult): ParserResult = x,
)
func `+`*(parserFnA: parserFnT, parserFnB: parserFnT): parserFnT {.inline.} =
## Parse characters and ignore failure
return func(parser: Parser): ParserResult =
parserFnA(parser).flatMap(parserFnB)
func parseSeq*(parser: ParserResult, xs: seq[parserFnT]): ParserResult =
xs.foldl(a.flatMap(b), parser)
# -- Parsing Aliases
func 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,
))
const newlineParser = choice(@[
ch(NewLines),
endOfStream,
])
func newline*(parser: Parser): ParserResult =
newlineParser(parser)
.mapErr((x: ParserError) => x.setErrorExpectedField("Newline"))
const whitespaceParser = choice(@[
ch(Whitespace),
newlineParser,
])
func whitespace*(parser: Parser): ParserResult =
whitespaceParser(parser)
.mapErr((x: ParserError) => x.setErrorExpectedField("Whitespace"))
# -- Parsing Helpers
const parseBetweenDelimiter* = func(start: parserFnT, stop: parserFnT): parserFnT {.closure.} =
ignore(start) + anyUntil(stop + whitespace) + ignore(start)
const parseBetweenPair* = func(delimiterParser: parserFnT): parserFnT {.closure.} =
parseBetweenDelimiter(delimiterParser, delimiterParser)

View File

@@ -0,0 +1,489 @@
import std/strutils
import std/strformat
import std/collections/sequtils
import std/sugar
import results
import fp/maybe
import fusion/matching
import ../utils/str
import ../utils/fp
{.experimental: "caseStmtMacros".}
type
ParserState* = ref object
stream*: string
position*, lastPosition*: int
parserTokenCharValueT* = char
parserTokenStringValueT* = string
ParserTokenKind* = enum
parserTokenChar
parserTokenString
ParserToken* = ref object
case kind*: ParserTokenKind
of parserTokenChar:
charValue*: parserTokenCharValueT
of parserTokenString:
stringValue*: parserTokenStringValueT
Parser* = ref object
state*: ParserState
tokens*: seq[ParserToken]
ParserErrorKind* = enum
choiceMismatchErr
charMismatchErr
endOfStringErr
ParserError* = ref object
kind*: ParserErrorKind
unexpected*: string
expected*: string
index*: int
parser*: Parser
ParserResult* = Result[Parser, ParserError]
Builder*[T] = tuple[
parser: Parser,
tree: seq[T]
]
BuilderResult*[T] = Result[Builder[T], (Builder[T], string)]
# SingleBuilder*[T] = tuple[
# parser: Parser,
# tree: T
# ]
# SingleBuilderResult*[T] = Result[SingleBuilder[T], (SingleBuilder[T], string)]
# -- Initalizers
func initParserToken*(x: char): ParserToken = ParserToken(kind: parserTokenChar, charValue: x)
func initParserToken*(x: string): ParserToken = ParserToken(kind: parserTokenString, stringValue: x)
func initParser*(str: string): Parser =
Parser(
state: ParserState(
stream: str,
position: -1,
lastPosition: 0,
),
tokens: newSeq[ParserToken](),
)
proc initParserResult*(str: string): ParserResult =
ParserResult.ok(initParser(str))
proc initBuilder*[T](t: Builder[T], parser: Parser, tree: seq[T]): Builder[T] =
Builder[T]((
parser,
tree
))
# -- Getters
func tokenStringValue*(x: ParserToken): string =
## Get the Token value `x` as a string.
case x.kind:
of parserTokenChar:
$x.charValue
of parserTokenString:
x.stringValue
func tokensToString*(tokens: seq[ParserToken]): string =
tokens.foldl(a & b.tokenStringValue(), "")
# -- Modifiers
func flattenParserTokens*(parser: Parser): ParserResult =
return ParserResult.ok(
Parser(
state: parser.state,
tokens: @[
ParserToken(
kind: parserTokenString,
stringValue: parser.tokens.foldl(a & b.tokenStringValue(), "")
)
]
)
)
func emptyTokens*(parser: Parser): Parser =
Parser(
state: parser.state,
tokens: newSeq[ParserToken](),
)
proc foldTokens*[T](
parserResult: ParserResult,
onError: ParserError -> T,
onSuccess: seq[ParserToken] -> T,
): T =
if parserResult.isOk():
onSuccess(parserResult.unsafeGet().tokens)
else:
let err = parserResult.error()
onError(err)
func setErrorExpectedField*(err: ParserError, expected: string): ParserError =
ParserError(
kind: err.kind,
unexpected: err.unexpected,
expected: expected,
index: err.index,
parser: err.parser,
)
proc mapTree*[T](builder: BuilderResult[T], fn: seq[T] -> seq[T]): BuilderResult[T] =
builder.map(proc(b: Builder[T]): Builder[T] = Builder((
parser: b[0],
tree: fn(b[1]),
)))
proc tryParser*[T](
builder: Builder[T],
parser: Parser -> ParserResult,
): BuilderResult[T] =
## Try out a `parser` on a `builder`
## When succesful return the original builder, otherwise return an error
ParserResult.ok(Parser(
state: builder[0].state,
tokens: @[]
))
.flatMap(parser)
.foldTokens(
(err: ParserError) => BuilderResult[T].err((builder, "Error")),
(newTokens: seq[ParserToken]) => BuilderResult[T].ok(builder),
)
proc tryParser*[T](
builder: BuilderResult[T],
parser: Parser -> ParserResult,
): BuilderResult[T] =
## Try out a `parser` on a `builder` result
## When succesful return the ok builder, otherwise return an error
builder.flatMap((x: Builder[T]) => tryParser(x, parser))
proc applyParsersToSingle*[T](
builder: Builder[T],
parsers: seq[Parser -> ParserResult],
tokenFoldFn: (seq[ParserToken], T) -> T,
optional = false,
initT: T,
isFirst: bool,
): BuilderResult[T] =
# Apply the current parsing functions and convert to text tokens wrapped in ParserResult
let newParser = ParserResult.ok(Parser(
state: builder[0].state,
tokens: @[]
))
.parseSeq(parsers)
newParser
.foldTokens(
(err: ParserError) => BuilderResult[T].err((builder, "foo")),
(newTokens: seq[ParserToken]) => (
if optional and newTokens.len == 0:
BuilderResult[T].ok(builder)
else:
BuilderResult[T].ok(
builder.initBuilder(
newParser.unsafeGet(),
builder.tree
.last()
.filter(x => not isFirst)
.orElse(just(initT))
.map((x: T) => @[tokenFoldFn(newTokens, x)])
.getOrElse(newSeq[T]())
)
)
)
)
## TODO Implement applyParsersSeqToSingle with this function by forming the concatFn
proc applyParsersToSeq*[T](
builder: Builder[T],
parsers: seq[Parser -> ParserResult],
tokenFoldFn: (seq[ParserToken], T) -> T,
# concatFn = `&`,
optional = false,
isFirst: bool,
): BuilderResult[T] =
# Apply the current parsing functions and convert to text tokens wrapped in ParserResult
let newParser = ParserResult.ok(Parser(
state: builder[0].state,
tokens: @[]
))
.parseSeq(parsers)
newParser
.foldTokens(
(err: ParserError) => BuilderResult[T].err((builder, "foo")),
(newTokens: seq[ParserToken]) => (
if optional and newTokens.len == 0:
BuilderResult[T].ok(builder)
else:
BuilderResult[T].ok(
builder.initBuilder(
newParser.unsafeGet(),
builder.tree & builder.tree
)
)
)
)
proc applyParsersSeqToSingle*[T](
builderResult: BuilderResult[T],
initT: T,
xs: seq[tuple[
parsers: seq[Parser -> ParserResult],
tokenFoldFn: (seq[ParserToken], T) -> T,
ignoreEmpty: bool,
]],
): BuilderResult[T] =
xs.foldl(
a.flatMap((builder: Builder[T]) => applyParsersToSingle(
builder = builder,
parsers = b.parsers,
tokenFoldFn = b.tokenFoldFn,
optional = b.ignoreEmpty,
initT = initT,
isFirst = a == builderResult
)),
builderResult
)
proc applyParsersSeqToSeq*[T](
builderResult: BuilderResult[T],
xs: seq[tuple[
parsers: seq[Parser -> ParserResult],
tokenFoldFn: (seq[ParserToken], T) -> T,
ignoreEmpty: bool,
# concatFn: (seq[T], seq[T]) -> seq[T],
]],
): BuilderResult[T] =
xs.foldl(
a.flatMap((builder: Builder[T]) => applyParsersToSeq(
builder = builder,
parsers = b.parsers,
tokenFoldFn = b.tokenFoldFn,
optional = b.ignoreEmpty,
isFirst = a == builderResult,
# concatFn = concatFn
)),
builderResult
)
proc applyParsers*[T](
builder: Builder[T],
parsers: seq[Parser -> ParserResult],
tokenFoldFn: (seq[ParserToken], seq[T]) -> seq[T],
): BuilderResult[T] =
# proc nested(b: Builder[T]): BuilderResult[T] =
let newParser = ParserResult.ok(Parser(
state: builder[0].state,
tokens: @[]
))
.parseSeq(parsers)
newParser
.foldTokens(
(err: ParserError) => BuilderResult[T].err((builder, "foo")),
(newTokens: seq[ParserToken]) => BuilderResult[T].ok(
builder.initBuilder(newParser.unsafeGet(), tokenFoldFn(newTokens, builder[1]))
),
)
proc applyParsersSeq*[T](
builder: BuilderResult[T],
xs: seq[tuple[
parsers: seq[Parser -> ParserResult],
tokenFoldFn: (seq[ParserToken], seq[T]) -> seq[T],
]]): BuilderResult[T] =
xs.foldl(a.flatMap((x: Builder[T]) => x.applyParsers(b[0], b[1])), builder)
proc foldBuilder*[T, T2](
builderResult: BuilderResult[T],
onError: string -> T2,
onSuccess: seq[T] -> T2,
): T =
if builderResult.isOk():
onSuccess(builderResult.unsafeGet().tree)
else:
let err = builderResult.error()
onError(err[1])
# proc parseBuildOr*[T](
# ## Checks
# builderResult: BuilderResult[T],
# builders: seq[tuple[
# parsers: seq[Parser -> ParserResult],
# tokenFoldFn: (seq[ParserToken], T) -> T,
# ]],
# defaultBuilder: tuple[
# defaultParser: Parser -> ParserResult,
# defaultFoldFn: (seq[ParserToken], T) -> T,
# ],
# until:
# ): BuilderResult[T] =
# -- Stringifiers
func pprint*(x: ParserToken): string =
let str = tokenStringValue(x)
let escapedChar = if str == "\n": "\\n"
else: str
&"""ParserToken("{escapedChar}")
"""
func `$`*(x: ParserToken): string = pprint(x)
const LEFT_HIGHLIGHT_CHAR = ">>"
const RIGHT_HIGHLIGHT_CHAR = "<<"
func highlightStreamPosition(stream: string, position: int): string =
if position < 0: return stream
if position > stream.len - 1: return stream
let aIndex = position - 1
let a = if aIndex < 0: ""
else: stream[0..aIndex]
let ch = stream[position]
let bIndex = position + 1
let b = if bIndex > stream.len - 1: ""
else: stream[bIndex..stream.len - 1]
return a & LEFT_HIGHLIGHT_CHAR & ch & RIGHT_HIGHLIGHT_CHAR & b
func highlightStreamPosition2(stream: string, position: int): string =
if position < 0: return stream
if position > stream.len - 1: return stream
let ch = stream[position]
let (lineStartPosition, lineEndPosition) =
case ch:
of '\n':
let lineStartPosition = (position - 1).max(0)
let lineEndPosition = (position + 1).min(stream.len - 1)
(lineStartPosition, lineEndPosition)
else:
(position, position)
let startIndex = stream.rfind("\n", 0, lineStartPosition)
let lineStartIndex =
case startIndex:
of -1: 0
else: startIndex + 1
let endIndex = stream.find("\n", lineEndPosition)
let lineEndIndex =
case endIndex:
of -1: stream.len
else: endIndex
let spaceChars = " ".repeat((position - lineStartIndex).max(0))
let lineChars = "_".repeat((lineEndIndex - position).max(0) + 10)
let escapedChar =
case ch:
of '\n': "\\n"
of ' ': "\\s"
else: $ch
let insertMessageAtIndex =
case ch:
# Print indicator for newlines on the previous line, which looks better for the reader
of '\n': (lineEndIndex - 1).max(1)
else: lineEndIndex
let beforeNewline =
case (startIndex, ch):
# Always print newline for newline at the stream begin
of (-1, '\n'): "\n"
# Don't insert a newline when the character is a newline between newlines
of (_, '\n'): ""
else: "\n"
# debugEcho "char: " & $ch
# debugEcho "position: " & $position
# debugEcho "startIndex: " & $lineStartIndex
# debugEcho "endIndex: " & $lineEndIndex
# debugEcho "insertMessageAtIndex: " & $insertMessageAtIndex
stream.dup(insert(&"{beforeNewline}{spaceChars}^{lineChars} Char at \"{escapedChar}\"\n", insertMessageAtIndex))
proc `$`*(x: ParserState): string =
&"""ParserState(
stream: "{x.stream.highlightStreamPosition(x.position)}",
position: {x.position},
lastPosition: {x.lastPosition},
)"""
proc `$`*(x: Parser): string =
&"""Parser(
state: {indentAfterNewline($x.state, 2)},
tokens: {indentAfterNewline($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
.deleteAfterNewline(parser.state.position)
let errSpace = " ".repeat(max(0, index))
&"""Parsing Error (Character Mismatch Error):
{original}
{errSpace}^ Expected '{expected}' but got '{unexpected}'"""
of choiceMismatchErr(expected: @expected, parser: @parser, index: @index, unexpected: @unexpected):
let original = parser.state.stream
.deleteAfterNewline(parser.state.position)
let errSpace = " ".repeat(max(0, index))
&"""Parsing Error (Character Mismatch Error):
{original}
{errSpace}^ Expected '{expected}' but got '{unexpected}'"""
of endOfStringErr(parser: @parser, index: @index):
let original = parser.state.stream
.deleteAfterNewline(parser.state.position)
let errSpace = " ".repeat(max(0, index))
&"""Parsing Error (EndOfString Expected):
{original}
{errSpace}^ Expected 'EndOfString' at {index} but got {original.len - 1}"""
else: "ParseError"
when isMainModule:
let test1 = """ABC
D
EFG"""
echo test1.highlightStreamPosition2(test1.find("B"))
echo "=============="
echo test1.highlightStreamPosition2(test1.find("C"))
echo "=============="
echo test1.highlightStreamPosition2(test1.find("D"))
echo "=============="
echo test1.highlightStreamPosition2(test1.find("\n"))
echo "=============="
echo test1.highlightStreamPosition2(test1.find("\n", test1.find("\n") + 1))
# echo "\n1\n".rfind('\n', 0, 2)
# block highlightStreamPosition:
# let s = "abc"
# # Out of bounds
# assert s.highlightStreamPosition(-1) == s
# assert s.highlightStreamPosition(s.len) == s
# # Regular highlighting
# assert s.highlightStreamPosition(0) == LEFT_HIGHLIGHT_CHAR & "a" & RIGHT_HIGHLIGHT_CHAR & "bc"
# assert s.highlightStreamPosition(1) == "a" & LEFT_HIGHLIGHT_CHAR & "b" & RIGHT_HIGHLIGHT_CHAR & "c"
# assert s.highlightStreamPosition(2) == "ab" & LEFT_HIGHLIGHT_CHAR & "c" & RIGHT_HIGHLIGHT_CHAR

31
src_v2/parser/utils.nim Normal file
View File

@@ -0,0 +1,31 @@
import std/sugar
import std/collections/sequtils
import results
import ./parser_types
type StringBuilderT* = string
type StringBuilder* = Builder[StringBuilderT]
type StringBuilderResult* = BuilderResult[StringBuilderT]
proc stringConcat*(typeInfo: StringBuilderT, seperator = ""):
(seq[ParserToken], seq[StringBuilderT]) -> seq[StringBuilderT] =
return proc(xs: seq[ParserToken], ys: seq[StringBuilderT]): seq[StringBuilderT] =
return ys & xs.foldl(a & b.tokenStringValue() & seperator, typeInfo)
proc initStringBuilder*(str: string): StringBuilderResult =
StringBuilderResult
.ok(StringBuilder((
parser: initParser(str),
tree: newSeq[StringBuilderT](),
)))
proc fold*[T, E, T2](
self: Result[T, E],
onError: E -> T2,
onSuccess: T -> T2,
): T2 =
if self.isOk():
onSuccess(self.unsafeGet())
else:
onError(self.error())