diff --git a/docs/spec.org b/docs/spec.org new file mode 100644 index 0000000..e66448f --- /dev/null +++ b/docs/spec.org @@ -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 diff --git a/nim_org_parse.nimble b/nim_org_parse.nimble index a54b730..8a3169a 100644 --- a/nim_org_parse.nimble +++ b/nim_org_parse.nimble @@ -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" diff --git a/src/org/org_block_heading.nim b/src/org/org_block_heading.nim new file mode 100644 index 0000000..fb9f351 --- /dev/null +++ b/src/org/org_block_heading.nim @@ -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 diff --git a/src/org/org_builder.nim b/src/org/org_builder.nim index b102e51..9c74a76 100644 --- a/src/org/org_builder.nim +++ b/src/org/org_builder.nim @@ -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) diff --git a/src/org/org_document.nim b/src/org/org_document.nim new file mode 100644 index 0000000..a328c89 --- /dev/null +++ b/src/org/org_document.nim @@ -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 diff --git a/src/org/org_types.nim b/src/org/org_types.nim index 9deeb66..e6f0d77 100644 --- a/src/org/org_types.nim +++ b/src/org/org_types.nim @@ -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) diff --git a/src/org_parser.nim b/src/org_parser.nim index 0adc8aa..6e02f62 100644 --- a/src/org_parser.nim +++ b/src/org_parser.nim @@ -42,6 +42,8 @@ when isMainModule: :PROP_NAME: Value :PROP_NAME: Value :PROPERTIES_END: + +Foo """), tree: newSeq[StringBuilderT](), ))) diff --git a/src/parser/builder_api.nim b/src/parser/builder_api.nim index 78f2094..d6ad42e 100644 --- a/src/parser/builder_api.nim +++ b/src/parser/builder_api.nim @@ -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[ diff --git a/src/parser/parser_types.nim b/src/parser/parser_types.nim index 14e57d1..da90d4d 100644 --- a/src/parser/parser_types.nim +++ b/src/parser/parser_types.nim @@ -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], diff --git a/src/utils/fp.nim b/src/utils/fp.nim index 9c1e9c1..aad5f52 100644 --- a/src/utils/fp.nim +++ b/src/utils/fp.nim @@ -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)