Create an org heading builder

This commit is contained in:
Florian Schroedl
2022-05-04 17:00:00 +02:00
parent bec4953a97
commit 722e66cf85
10 changed files with 342 additions and 0 deletions

45
docs/spec.org Normal file
View File

@@ -0,0 +1,45 @@
#+TITLE: Spec
* Specs
[[https://nim-lang.org/docs/oids.html][std/oids]] :: ~genOid~
Takes bunch of builders
#+begin_src
DocumentBuilderResult(
id = (oid)
children: seq[OrgItem]
)
Heading = (
stars
)
OrgHeadingChild =
| Paragraph
OrgItem =
| OrgHeadingChild
| Heading
Heading(
id (oid)
parentId (oid)
children: seq[OrgHeadingChild]
)
#+end_src
Headline Builder
Paragraph Builder
Newline builder
List
We need something that parses continously until a stop function is hit
for heading it is newline stars parsers for instance
We need a new tryParseBuild function that tries a bunch of builders

View File

@@ -13,6 +13,7 @@ binDir = "./dst"
requires "nim >= 1.4.4"
requires "https://github.com/floscr/nimfp#master"
requires "fusion"
requires "result"
# requires "print"
# requires "zero_functional"
# requires "cascade"

View File

@@ -0,0 +1,87 @@
import std/sugar
import std/strformat
import std/collections/sequtils
import std/strutils
import results
import fusion/matching
import ../utils/fp
import ../parser/parser_internals
import ../parser/parser_types
import ../parser/utils
import ./org_properties_block
import ./org_types
import ./org_builder
{.experimental: "caseStmtMacros".}
let parseHeadingStars = @[
manyUntil(ch('*'), ch(' ')),
ignore(ch(' '))
]
proc createTodoKeywordParser(xs: seq[string]): (Parser -> ParserResult) =
choice(xs.map((x: string) => str(x) + ignore(ch(' '))))
let todoKeywords = @["TODO"]
let doneKeywords = @["DONE"]
let parseTodoKeyword = todoKeywords.createTodoKeywordParser()
let parseDoneKeyword = doneKeywords.createTodoKeywordParser()
let parseHeadingText = @[
anyUntil(newline),
ignore(newline),
]
# func buildStars(token: seq[ParserToken]): seq[ParserToken] =
# # builder.kind = orgHeading
# # builder.level = token.tokenStringValue().len
# token
let buildStars = func(tokens: seq[ParserToken], org: OrgBlock): OrgBlock {.closure.}=
org.level = tokens[0].tokenStringValue().len
org
proc tryBuildHeading(builder: OrgBuilderResult): OrgBuilderResult =
builder
.applyParsersSeqToSingle(
OrgBlock(kind: orgHeading),
@[(parseHeadingStars, buildStars)]
)
when isMainModule:
echo initOrgBuilder("**** TODO Some stars")
.tryBuildHeading()
# let sampleBuilder = StringBuilderResult
# .ok(StringBuilder((
# parser: initParser("""**** TODO Some stars
# :PROPERTIES:
# :PROP_NAME: Value
# :PROP_NAME: Value
# :PROP_NAME: Value
# :PROP_NAME: Value
# :PROPERTIES_END:
# Foo
# """),
# tree: newSeq[StringBuilderT](),
# )))
# .applyParsersSeq(@[
# (parseHeadingStars, stringConcat("Stars: ")),
# (@[optional(parseTodoKeyword)], stringConcat("TODO: ")),
# (@[optional(parseDoneKeyword)], stringConcat("DONE: ")),
# (parseHeadingText, stringConcat("Text: ")),
# (parseProperties, stringConcat("Properties: ", seperator = ", ")),
# # (@[optional(choice(parseProperties))], stringConcat("Properties: ")),
# ])
# .foldBuilder(
# err => &"Error Parsing: {err}",
# xs => "Parser Succesfull:\n" & xs.join("\n"),
# )
# echo sampleBuilder

View File

@@ -1,8 +1,10 @@
import std/sugar
import results
import std/collections/sequtils
import ./org_types
import ../parser/parser_types
## Inline Blocks
type OrgInlineBuilderT* = OrgInlineBlock
type OrgInlineBuilder* = Builder[OrgInlineBuilderT]
type OrgInlineBuilderResult* = BuilderResult[OrgInlineBuilderT]
@@ -28,3 +30,25 @@ func rawTextTokenizer*(kind: orgInlineBlockKind): string -> OrgInlineBuilderT =
kind: kind,
content: content,
)
## Blocks
type OrgBuilderT* = OrgBlock
type OrgBuilder* = Builder[OrgBuilderT]
type OrgBuilderResult* = BuilderResult[OrgBuilderT]
func initOrgBuilder*(content: string): OrgBuilderResult =
return OrgBuilderResult.ok(OrgBuilder((
parser: initParser(content),
tree: newSeq[OrgBuilderT](),
)))
# proc orgBuilderConcat*(typeInfo: OrgBuilderT, concatFn: (ParserToken, OrgBuilderT) -> OrgBuilderT):
# (seq[ParserToken], OrgBuilderT) -> OrgBuilderT =
# return proc(xs: seq[ParserToken], builder: OrgBuilderT): OrgBuilderT =
# return xs.foldl(concatFn(b, a), typeInfo)
proc orgBuilderApply*(concatFn: (ParserToken, OrgBuilderT) -> OrgBuilderT):
(seq[ParserToken], OrgBuilderT) -> OrgBuilderT =
return proc(tokens: seq[ParserToken], builder: OrgBuilderT): OrgBuilderT =
tokens.foldl(concatFn(b, a), builder)

39
src/org/org_document.nim Normal file
View File

@@ -0,0 +1,39 @@
import std/sugar
import std/collections/sequtils
import std/strformat
import std/strutils
import results
import fusion/matching
import ./org_types
import ./org_builder
import ./org_text_link
import ../utils/fp
import ../parser/parser_internals
import ../parser/parser_types
import ../parser/builder_api
when isMainModule:
let test = initOrgBuilder("""
Some text to be ignored for now
* Stars 1
Ignore me
** Stars 1-1
*** Stars 1-1-1
** Stars 1-2
* Stars 2
""")
.flatMap((builder: OrgBuilder) => tryParseBuild(
builder = builder,
builderFns = orgStyledTextBuilders,
defaultBuilderFn = makeRawTokenOrEmpty,
)
echo test

View File

@@ -3,6 +3,7 @@ import std/sugar
import std/strutils
import fp/maybe
## Inline Block
type
orgInlineBlockKind* = enum
orgRawText,
@@ -76,3 +77,49 @@ proc `$`*(x: OrgInlineBlock): string =
stringifySpecialFields(x) &
"""
)"""
## Block
type
orgBlockKind* = enum
orgHeading
# orgParagraph
OrgBlock* = ref object
children*: seq[OrgInlineBlock]
case kind*: orgBlockKind
of orgHeading:
level*: int
func stringifySpecialFields(x: OrgBlock): string =
let specialFields = case x.kind:
of orgHeading:
&"""level: {x.level}"""
else: ""
specialFields
.just()
.notEmpty()
.map(x => x.indent(2))
.map(x => "\n" & x & "\n")
.getOrElse("")
proc `$`*(x: OrgBlock): string =
&"""OrgBlock(
kind: {x.kind}""" &
stringifySpecialFields(x) &
&"""
children: {x.children}
)
"""
type
OrgDocument* = ref object
children*: seq[OrgBlock]
proc `$`*(x: OrgDocument): string =
&"""OrgDocument(
children: {x.children}
)"""
when isMainModule:
echo OrgBlock(kind: orgHeading)

View File

@@ -42,6 +42,8 @@ when isMainModule:
:PROP_NAME: Value
:PROP_NAME: Value
:PROPERTIES_END:
Foo
"""),
tree: newSeq[StringBuilderT](),
)))

View File

@@ -7,6 +7,21 @@ 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[

View File

@@ -3,8 +3,10 @@ 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".}
@@ -46,6 +48,12 @@ type
]
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)
@@ -130,6 +138,71 @@ proc mapTree*[T](builder: BuilderResult[T], fn: seq[T] -> seq[T]): BuilderResult
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,
initT: T,
): 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]) => BuilderResult[T].ok(
builder.initBuilder(
newParser.unsafeGet(),
builder.tree
.last()
.orElse(just(initT))
.map((x: T) => @[tokenFoldFn(newTokens, x)])
.getOrElse(newSeq[T]())
)
)
)
proc applyParsersSeqToSingle*[T](
builderResult: BuilderResult[T],
initT: T,
xs: seq[tuple[
parsers: seq[Parser -> ParserResult],
tokenFoldFn: (seq[ParserToken], T) -> T,
]],
): BuilderResult[T] =
xs.foldl(
a.flatMap((builder: Builder[T]) => applyParsersToSingle(builder, b[0], b[1], initT)),
builderResult
)
proc applyParsers*[T](
builder: Builder[T],
parsers: seq[Parser -> ParserResult],

View File

@@ -2,6 +2,12 @@ import std/sugar
import fp/maybe
import results
func last*[T](xs: seq[T]): Maybe[T] =
if xs.len == 0:
nothing(T)
else:
just(xs[^1])
template isSome*(self: Result): bool = self.isOk()
template isNone*(self: Result): bool = self.isErr()
@@ -26,3 +32,6 @@ when isMainModule:
echo @[
(x: int) => (if x == 2: Just("foo") else: Nothing[string]()),
].findMaybeFn(2)
assert last(@[1,2,3]) == just(3)
assert last[int](@[]) == nothing(int)