From c2998756efe50ccf2c8544d12e70cca32d8f5809 Mon Sep 17 00:00:00 2001 From: brekk Date: Wed, 19 Jun 2024 16:24:45 -1000 Subject: [PATCH 01/16] feat(internalLink): updates to make things more robust --- FIXTURE.md | 5 +++ YamlFixture.yaml | 14 +++++++++ src/Combinators.mad | 14 +++++++++ src/Main.mad | 36 ++++++++++++++------- src/Main.spec.mad | 77 +++++++++++++++++++++------------------------ src/Test.mad | 20 ++++++++++++ src/Yaml.mad | 38 ++++++++++++++++++++++ 7 files changed, 151 insertions(+), 53 deletions(-) create mode 100644 YamlFixture.yaml create mode 100644 src/Combinators.mad create mode 100644 src/Test.mad create mode 100644 src/Yaml.mad diff --git a/FIXTURE.md b/FIXTURE.md index bb8c886..3ad2548 100644 --- a/FIXTURE.md +++ b/FIXTURE.md @@ -31,3 +31,8 @@ more more more more text [link](//madlib.biz) And another thing… + +[[internal links are magical]] + +Sometimes you want an [[internal link|with different display text]] +Other times you have text before [[an internal link]] diff --git a/YamlFixture.yaml b/YamlFixture.yaml new file mode 100644 index 0000000..8879ff2 --- /dev/null +++ b/YamlFixture.yaml @@ -0,0 +1,14 @@ +name: Madlib +year: 2020 +functional: true +imperative: # this is an empty field +contributors: + - Brekk + - Arnaud +link: "[[Installation]]" +links: + - "[[The Fence]]" + - "[[Comments]]" +effort: 4.5 +start: 2020-08-27 +starttime: 2020-08-27T08:00:00 diff --git a/src/Combinators.mad b/src/Combinators.mad new file mode 100644 index 0000000..ea357c0 --- /dev/null +++ b/src/Combinators.mad @@ -0,0 +1,14 @@ +import type { Parser } from "Parse" + +import { apL } from "Applicative" +import { identity } from "Function" +import { mapL } from "Functor" + + + +between :: Parser a -> Parser b -> Parser c -> Parser b +export between = (start, mid, end) => pipe( + mapL(identity), + ap($, mid), + apL($, end), +)(start) diff --git a/src/Main.mad b/src/Main.mad index b097161..70ad036 100644 --- a/src/Main.mad +++ b/src/Main.mad @@ -10,6 +10,8 @@ import { Just, Nothing } from "Maybe" import P from "Parse" import String from "String" +import { between } from "@/Combinators" + export type ContentPart @@ -38,13 +40,6 @@ export type Block export alias Markdown = List Block -between :: P.Parser a -> P.Parser b -> P.Parser c -> P.Parser b -export between = (start, mid, end) => pipe( - mapL(identity), - ap($, mid), - apL($, end), -)(start) - // https://stackoverflow.com/questions/1547899/which-characters-make-a-url-invalid linkCharacter :: P.Parser Char export linkCharacter = P.choice([ @@ -106,8 +101,8 @@ export italic = do { // https://help.obsidian.md/Linking+notes+and+files/Internal+links // [[01 - Hello mad, mad world#Installation|Installing]] -internalLink :: P.Parser ContentPart -export internalLink = do { +internalLinkWithDisplay :: P.Parser ContentPart +export internalLinkWithDisplay = do { _ <- P.string("[[") url <- pipe( P.many, @@ -125,6 +120,24 @@ export internalLink = do { )(url) } +// [[02 - Cool data for lyfe]] +shortInternalLink :: P.Parser ContentPart +export shortInternalLink = do { + _ <- P.string("[[") + ref <- pipe( + P.manyTill(P.letter), + map(String.fromList), + )(P.string("]]")) + _ <- P.string("]]") + return pipe( + InternalLink(ref), + of, + )(ref) +} + +internalLink :: P.Parser ContentPart +export internalLink = P.choice([shortInternalLink, internalLinkWithDisplay]) + inlineCode :: P.Parser ContentPart export inlineCode = pipe( mapL(InlineCode), @@ -192,6 +205,7 @@ export image = pipe( textTerminals :: P.Parser String export textTerminals = P.choice([ + map(always(""), internalLink), map(always(""), bold), map(always(""), italic), map(always(""), inlineCode), @@ -219,7 +233,7 @@ content :: P.Parser Content export content = pipe( P.choice, P.many, -)([bold, italic, inlineCode, image, link, text]) +)([internalLink, bold, italic, inlineCode, image, link, text]) coerceEmpty :: Maybe a -> P.Parser ContentPart export coerceEmpty = pipe( @@ -245,7 +259,7 @@ export contentWithLineReturn = (delimiter) => pipe( P.choice, P.some, map(dropWhile(equals(LineReturn))), -)([bold, italic, inlineCode, image, link, text, lineReturnExceptBefore(delimiter)]) +)([internalLink, bold, italic, inlineCode, image, link, text, lineReturnExceptBefore(delimiter)]) heading :: (Content -> Block) -> String -> P.Parser Block export heading = (constructor) => pipe( diff --git a/src/Main.spec.mad b/src/Main.spec.mad index 13dba3e..5d63dcc 100644 --- a/src/Main.spec.mad +++ b/src/Main.spec.mad @@ -1,9 +1,6 @@ -import type { Wish } from "Wish" - import { Left, Right } from "Either" import File from "File" import { always } from "Function" -import { Just, Nothing } from "Maybe" import Parse from "Parse" import Test from "Test" import Wish from "Wish" @@ -13,8 +10,6 @@ import Wish from "Wish" assertEquals = Test.assertEquals test = Test.test TestError = Test.Error -ParseError = Parse.Error - import { Blockquote, Bold, @@ -33,7 +28,6 @@ import { Link, Paragraph, Text, - between, block, blockquote, bold, @@ -61,37 +55,10 @@ import { unorderedList, unorderedListItem, } from "./Main" +import { testParser } from "@/Test" -testParser :: (Show a, Eq a) => Parse.Parser a -> String -> a -> Wish Test.AssertionError {} -testParser = (parser, toParse, expected) => pipe( - Parse.runParser(parser), - where { - Left(Parse.Error(Parse.Loc(a, b, c))) => - Wish.bad(TestError(`Error during parsing ${show(a)} ${show(b)} ${show(c)}`)) - - Right(res) => - assertEquals(res, expected) - }, -)(toParse) - -/* -test( - "parseMarkdown", - () => pipe( - parseMarkdown, - where { - Left(x) => - Wish.bad(TestError(x)) - - Right(y) => - assertEquals(y, []) - }, - )("`````"), -) -*/ - test( "linkCharacter", () => do { @@ -119,6 +86,14 @@ test( }, ) +test( + "internalLink", + () => do { + _ <- testParser(internalLink, "[[xyz|abc]]", InternalLink("abc", "xyz")) + return testParser(internalLink, "[[xyz]]", InternalLink("xyz", "xyz")) + }, +) + test("block", () => testParser(block, "# hey", H1([Text("hey")]))) test("*italic*", () => testParser(italic, "*Firenze*", Italic("Firenze"))) @@ -137,14 +112,6 @@ test( () => testParser(link, "[madlib](https://madlib.space)", Link("madlib", "https://madlib.space")), ) -test( - "internalLink", - () => testParser( - internalLink, - "[[https://madlib.space|madlib]]", - InternalLink("madlib", "https://madlib.space"), - ), -) test("textTerminals", () => testParser(textTerminals, "*x*", "")) test("text", () => testParser(text, "hooray", Text("hooray"))) @@ -241,3 +208,29 @@ test( ), )("./FIXTURE.md"), ) + +/* +test( + "parseMarkdown on real example file", + () => pipe( + File.read, + Wish.mapRej(always(TestError("barf"))), + chain( + pipe( + parseMarkdown, + where { + Left(x) => + pipe( + show, + TestError, + Wish.bad, + )(x) + + Right(res) => + assertEquals(res, []) + }, + ), + ), + )("./notes/Reference/The Fence.md"), +) +*/ diff --git a/src/Test.mad b/src/Test.mad new file mode 100644 index 0000000..19decb0 --- /dev/null +++ b/src/Test.mad @@ -0,0 +1,20 @@ +import { Left, Right } from "Either" +import Parse from "Parse" +import Test from "Test" +import Wish from "Wish" + + + +TestError = Test.Error + +testParser :: (Show a, Eq a) => Parse.Parser a -> String -> a -> Wish Test.AssertionError {} +export testParser = (parser, toParse, expected) => pipe( + Parse.runParser(parser), + where { + Left(Parse.Error(Parse.Loc(a, b, c))) => + Wish.bad(TestError(`Error during parsing ${show(a)} ${show(b)} ${show(c)}`)) + + Right(res) => + Test.assertEquals(res, expected) + }, +)(toParse) diff --git a/src/Yaml.mad b/src/Yaml.mad new file mode 100644 index 0000000..472294f --- /dev/null +++ b/src/Yaml.mad @@ -0,0 +1,38 @@ +import type { Either } from "Either" +import type { Maybe } from "Maybe" +import type { Parser } from "Parse" + +import { apL } from "Applicative" +import { mapLeft } from "Either" +import { always, equals, identity } from "Function" +import { mapL } from "Functor" +import { dropWhile, mapMaybe } from "List" +import { Just, Nothing } from "Maybe" +import P from "Parse" +import String from "String" + + + +// https://help.obsidian.md/Editing+and+formatting/Properties +// MadMarkdownParser aims to support the YAML that is supported by Obsidian, +// _not_ the entire YAML spec +export type YamlValue + = YamlString(String) + | YamlFloat(Float) + | YamlInteger(Integer) + | YamlBoolean(Boolean) + | YamlLink(String) + | YamlDate(String) + | YamlList(List YamlValue) + +// key / value / comment +export type YamlPair = YamlPair(String, YamlValue, String) + +export alias YamlData = List YamlPair + +internalLink :: Parser YamlValue +internalLink = do { + _ <- P.string(`"[[`) + + _ <- P.string(`]]"`) +} From 1828f3c6bacdaa8bc2b3a3554dba7d13b09f73c0 Mon Sep 17 00:00:00 2001 From: brekk Date: Wed, 19 Jun 2024 16:39:05 -1000 Subject: [PATCH 02/16] test(internalLink): update expected values in test --- src/Main.spec.mad | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Main.spec.mad b/src/Main.spec.mad index 5d63dcc..66a1231 100644 --- a/src/Main.spec.mad +++ b/src/Main.spec.mad @@ -173,9 +173,17 @@ PARSED_FIXTURE = [ ), Blockquote([Text("This is a blockquote.")]), Paragraph([Link("link", "//madlib.biz")]), - Paragraph([Text("And another thing…"), LineReturn]), + Paragraph([Text("And another thing…")]), + Paragraph([InternalLink("internal links are magical", "internal links are magical")]), + Paragraph([ + Text("Sometimes you want an "), + InternalLink("with different display text", "internal link"), + LineReturn, + Text("Other times you have text before "), + InternalLink("an internal link", "an internal link"), + LineReturn, + ]), ] - test( "markdownParser", () => pipe( From e9bd71a0ccc60c8fb85b9d6e1cf5ac6a1b60622a Mon Sep 17 00:00:00 2001 From: brekk Date: Wed, 19 Jun 2024 19:59:22 -1000 Subject: [PATCH 03/16] feat(orderedList): currently not working, but added ordered list parser --- FIXTURE.md | 20 ++++++++++ src/Link.mad | 30 +++++++++++++++ src/Main.mad | 94 +++++++++++++++++++++++------------------------ src/Main.spec.mad | 2 +- src/Yaml.mad | 38 +++++++++++++++++-- 5 files changed, 133 insertions(+), 51 deletions(-) create mode 100644 src/Link.mad diff --git a/FIXTURE.md b/FIXTURE.md index 3ad2548..457f431 100644 --- a/FIXTURE.md +++ b/FIXTURE.md @@ -10,6 +10,26 @@ ###### are needed +List of stuff: + * spades + * hearts + * clubs + * diamonds + +List of stuff in order: + 1. January + 1. February + 1. March + 1. April + 1. May + 1. June + 1. July + 1. August + 1. September + 1. October + 1. November + 1. December + Impossibly *charming* and _sophisticated_. **Delightfully** jejeune. How __droll__. ```javascript diff --git a/src/Link.mad b/src/Link.mad new file mode 100644 index 0000000..270b619 --- /dev/null +++ b/src/Link.mad @@ -0,0 +1,30 @@ +import P from "Parse" +import String from "String" + + + +// https://stackoverflow.com/questions/1547899/which-characters-make-a-url-invalid +linkCharacter :: P.Parser Char +export linkCharacter = P.choice([ + P.letter, + P.digit, + P.char('!'), + P.char('#'), + P.char('$'), + P.char('%'), + P.char('&'), + P.char('\''), + P.char('*'), + P.char('+'), + P.char(','), + P.char('-'), + P.char('.'), + P.char('/'), + P.char(':'), + P.char(';'), + P.char('='), + P.char('?'), + P.char('@'), + P.char('_'), + P.char('~'), +]) diff --git a/src/Main.mad b/src/Main.mad index 70ad036..bfe2028 100644 --- a/src/Main.mad +++ b/src/Main.mad @@ -11,9 +11,12 @@ import P from "Parse" import String from "String" import { between } from "@/Combinators" +import { linkCharacter } from "@/Link" +NOTHING = always("") + export type ContentPart = Text(String) | Bold(String) @@ -37,36 +40,10 @@ export type Block | Blockquote(Content) | Code(String, String) | UnorderedList(List Content) + | OrderedList(List Content) export alias Markdown = List Block -// https://stackoverflow.com/questions/1547899/which-characters-make-a-url-invalid -linkCharacter :: P.Parser Char -export linkCharacter = P.choice([ - P.letter, - P.digit, - P.char('!'), - P.char('#'), - P.char('$'), - P.char('%'), - P.char('&'), - P.char('\''), - P.char('*'), - P.char('+'), - P.char(','), - P.char('-'), - P.char('.'), - P.char('/'), - P.char(':'), - P.char(';'), - P.char('='), - P.char('?'), - P.char('@'), - P.char('_'), - P.char('~'), -]) - - boldDelimiter = P.choice([P.string("**"), P.string("__")]) @@ -205,13 +182,13 @@ export image = pipe( textTerminals :: P.Parser String export textTerminals = P.choice([ - map(always(""), internalLink), - map(always(""), bold), - map(always(""), italic), - map(always(""), inlineCode), - map(always(""), image), - map(always(""), link), - map(always(""), P.eof), + map(NOTHING, internalLink), + map(NOTHING, bold), + map(NOTHING, italic), + map(NOTHING, inlineCode), + map(NOTHING, image), + map(NOTHING, link), + map(NOTHING, P.eof), P.string("\n"), ]) @@ -270,12 +247,12 @@ export heading = (constructor) => pipe( ) singleReturnTerminal :: P.Parser String -export singleReturnTerminal = alt(P.string("\n"), map(always(""), P.eof)) +export singleReturnTerminal = alt(P.string("\n"), map(NOTHING, P.eof)) doubleReturnTerminal :: P.Parser String export doubleReturnTerminal = P.choice([ P.string("\n\n"), - map(always(""), P.eof), + map(NOTHING, P.eof), pipe( ap(pure((_, _) => "")), ap($, P.eof), @@ -284,7 +261,7 @@ export doubleReturnTerminal = P.choice([ code :: P.Parser Block export code = pipe( - mapL((lang, c) => Code(lang, c)), + mapL(Code), ap($, alt(map(String.fromList, P.letters), pure(""))), apL($, P.char('\n')), ap($, map(String.fromList, P.manyTill(P.anyChar, P.lookAhead(P.string("\n```"))))), @@ -301,16 +278,34 @@ export blockquote = pipe( ), )(alt(P.symbol(">"), P.string(">"))) +SPACE = P.char(' ') + +listItemStart :: P.Parser a -> P.Parser Content +export listItemStart = (starter) => pipe( + chain(always(apL(content, singleReturnTerminal))), +)(starter) + +export olistItemStart = map( + NOTHING, + apL(P.many(SPACE), apL(P.someTill(P.digit, P.char('.')), P.some(SPACE))), +) + +orderedListItem :: P.Parser Content +export orderedListItem = listItemStart(olistItemStart) + +orderedList :: P.Parser Block +export orderedList = pipe( + P.some, + map(OrderedList), +)(orderedListItem) -export listItemStart = map( - always(""), - apL(P.many(P.char(' ')), apL(P.oneOf(['*', '-', '+']), P.some(P.char(' ')))), +export ulistItemStart = map( + NOTHING, + apL(P.many(SPACE), apL(P.oneOf(['*', '-', '+']), P.some(SPACE))), ) unorderedListItem :: P.Parser Content -export unorderedListItem = pipe( - chain(always(apL(content, singleReturnTerminal))), -)(listItemStart) +export unorderedListItem = listItemStart(ulistItemStart) unorderedList :: P.Parser Block export unorderedList = pipe( @@ -318,7 +313,6 @@ export unorderedList = pipe( map(UnorderedList), )(unorderedListItem) - paragraph :: P.Parser Block export paragraph = pipe( map(Paragraph), @@ -328,10 +322,15 @@ export paragraph = pipe( doubleReturnTerminal, P.lookAhead(P.string("\n```")), P.lookAhead(P.string("\n>")), - P.lookAhead(apL(P.string("\n"), listItemStart)), + P.lookAhead(apL(P.string("\n"), olistItemStart)), + P.lookAhead(apL(P.string("\n"), ulistItemStart)), ]), ), -)(contentWithLineReturn(P.choice([listItemStart, P.string("\n"), P.string("```"), P.string(">")]))) +)( + contentWithLineReturn( + P.choice([olistItemStart, ulistItemStart, P.string("\n"), P.string("```"), P.string(">")]), + ), +) block :: P.Parser Block export block = P.choice([ @@ -342,6 +341,7 @@ export block = P.choice([ heading(H2, "##"), heading(H1, "#"), unorderedList, + orderedList, blockquote, code, paragraph, @@ -351,7 +351,7 @@ markdownParser :: P.Parser Markdown export markdownParser = pipe( P.choice, P.many, - map(mapMaybe((x) => x)), + map(mapMaybe(identity)), )([map(always(Nothing), P.spaces), map(Just, block)]) parseMarkdown :: String -> Either String Markdown diff --git a/src/Main.spec.mad b/src/Main.spec.mad index 66a1231..c3f02b4 100644 --- a/src/Main.spec.mad +++ b/src/Main.spec.mad @@ -44,7 +44,6 @@ import { lineReturn, lineReturnExceptBefore, link, - linkCharacter, listItemStart, markdownParser, paragraph, @@ -55,6 +54,7 @@ import { unorderedList, unorderedListItem, } from "./Main" +import { linkCharacter } from "@/Link" import { testParser } from "@/Test" diff --git a/src/Yaml.mad b/src/Yaml.mad index 472294f..4e6fc81 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -11,6 +11,8 @@ import { Just, Nothing } from "Maybe" import P from "Parse" import String from "String" +import { linkCharacter } from "@/Link" + // https://help.obsidian.md/Editing+and+formatting/Properties @@ -21,7 +23,8 @@ export type YamlValue | YamlFloat(Float) | YamlInteger(Integer) | YamlBoolean(Boolean) - | YamlLink(String) + | YamlLink(String, String) + | YamlInternalLink(String, String) | YamlDate(String) | YamlList(List YamlValue) @@ -30,9 +33,38 @@ export type YamlPair = YamlPair(String, YamlValue, String) export alias YamlData = List YamlPair -internalLink :: Parser YamlValue -internalLink = do { +shortInternalLink :: Parser YamlValue +export shortInternalLink = do { _ <- P.string(`"[[`) + ref <- pipe( + P.manyTill(P.letter), + map(String.fromList), + )(P.string(`]]"`)) + _ <- P.string(`]]"`) + return pipe( + YamlInternalLink(ref), + of, + )(ref) +} +internalLinkWithDisplay :: Parser YamlValue +export internalLinkWithDisplay = do { + _ <- P.string(`"[[`) + url <- pipe( + P.many, + map(String.fromList), + )(linkCharacter) + _ <- P.char('|') + ref <- pipe( + P.manyTill(P.letter), + map(String.fromList), + )(P.string(`]]"`)) _ <- P.string(`]]"`) + return pipe( + YamlInternalLink(ref), + of, + )(ref) } + +internalLink :: P.Parser YamlValue +export internalLink = P.choice([shortInternalLink, internalLinkWithDisplay]) From 5b701553602649c86b12c4994fa64f6dd7cd5900 Mon Sep 17 00:00:00 2001 From: brekk Date: Thu, 20 Jun 2024 11:05:02 -1000 Subject: [PATCH 04/16] progress! --- src/Combinators.mad | 4 ++ src/Main.mad | 12 +++-- src/Main.spec.mad | 120 +++++++++++++++++++++++++++++--------------- src/Test.mad | 10 ++++ src/Yaml.mad | 6 +++ src/Yaml.spec.mad | 19 +++++++ 6 files changed, 126 insertions(+), 45 deletions(-) create mode 100644 src/Yaml.spec.mad diff --git a/src/Combinators.mad b/src/Combinators.mad index ea357c0..4ed56db 100644 --- a/src/Combinators.mad +++ b/src/Combinators.mad @@ -3,6 +3,7 @@ import type { Parser } from "Parse" import { apL } from "Applicative" import { identity } from "Function" import { mapL } from "Functor" +import { lookAhead, someTill } from "Parse" @@ -12,3 +13,6 @@ export between = (start, mid, end) => pipe( ap($, mid), apL($, end), )(start) + +someUntil :: Parser a -> Parser b -> Parser (List a) +export someUntil = (prefix, suffix) => someTill(prefix, lookAhead(suffix)) diff --git a/src/Main.mad b/src/Main.mad index bfe2028..921a940 100644 --- a/src/Main.mad +++ b/src/Main.mad @@ -10,7 +10,7 @@ import { Just, Nothing } from "Maybe" import P from "Parse" import String from "String" -import { between } from "@/Combinators" +import { between, someUntil } from "@/Combinators" import { linkCharacter } from "@/Link" @@ -287,7 +287,7 @@ export listItemStart = (starter) => pipe( export olistItemStart = map( NOTHING, - apL(P.many(SPACE), apL(P.someTill(P.digit, P.char('.')), P.some(SPACE))), + apL(P.many(SPACE), apL(someUntil(P.digit, P.char('.')), P.some(SPACE))), ) orderedListItem :: P.Parser Content @@ -313,6 +313,9 @@ export unorderedList = pipe( map(UnorderedList), )(unorderedListItem) +markdownList :: P.Parser Block +export markdownList = P.choice([orderedList, unorderedList]) + paragraph :: P.Parser Block export paragraph = pipe( map(Paragraph), @@ -328,7 +331,7 @@ export paragraph = pipe( ), )( contentWithLineReturn( - P.choice([olistItemStart, ulistItemStart, P.string("\n"), P.string("```"), P.string(">")]), + P.choice([P.string("\n"), P.string("```"), P.string(">"), olistItemStart, ulistItemStart]), ), ) @@ -340,8 +343,7 @@ export block = P.choice([ heading(H3, "###"), heading(H2, "##"), heading(H1, "#"), - unorderedList, - orderedList, + markdownList, blockquote, code, paragraph, diff --git a/src/Main.spec.mad b/src/Main.spec.mad index c3f02b4..dbc5e9d 100644 --- a/src/Main.spec.mad +++ b/src/Main.spec.mad @@ -1,6 +1,7 @@ import { Left, Right } from "Either" import File from "File" import { always } from "Function" +import P from "Parse" import Parse from "Parse" import Test from "Test" import Wish from "Wish" @@ -26,8 +27,10 @@ import { Italic, LineReturn, Link, + OrderedList, Paragraph, Text, + UnorderedList, block, blockquote, bold, @@ -46,45 +49,85 @@ import { link, listItemStart, markdownParser, + olistItemStart, + orderedList, paragraph, parseMarkdown, singleReturnTerminal, text, textTerminals, + ulistItemStart, unorderedList, unorderedListItem, } from "./Main" import { linkCharacter } from "@/Link" -import { testParser } from "@/Test" +import { parseTest, testParser } from "@/Test" +test( + "ulistItemStart", + () => do { + _ <- testParser(ulistItemStart, " * ", "") + return testParser(ulistItemStart, " * ", "") + }, +) +/* +test( + "olistItemStart", + () => do { + _ <- testParser(olistItemStart, " 1. ", "") + return testParser(olistItemStart, " 1. ", "") + }, +) +*/ + + test( "linkCharacter", () => do { - _ <- testParser(linkCharacter, "a", 'a') - _ <- testParser(linkCharacter, "1", '1') - _ <- testParser(linkCharacter, "!", '!') - _ <- testParser(linkCharacter, "#", '#') - _ <- testParser(linkCharacter, "$", '$') - _ <- testParser(linkCharacter, "%", '%') - _ <- testParser(linkCharacter, "&", '&') - _ <- testParser(linkCharacter, "'", '\'') - _ <- testParser(linkCharacter, "*", '*') - _ <- testParser(linkCharacter, "+", '+') - _ <- testParser(linkCharacter, ",", ',') - _ <- testParser(linkCharacter, "-", '-') - _ <- testParser(linkCharacter, ".", '.') - _ <- testParser(linkCharacter, "/", '/') - _ <- testParser(linkCharacter, ":", ':') - _ <- testParser(linkCharacter, ";", ';') - _ <- testParser(linkCharacter, "=", '=') - _ <- testParser(linkCharacter, "?", '?') - _ <- testParser(linkCharacter, "@", '@') - _ <- testParser(linkCharacter, "_", '_') - return testParser(linkCharacter, "~", '~') + charTest = testParser(linkCharacter) + _ <- charTest("a", 'a') + _ <- charTest("1", '1') + _ <- charTest("!", '!') + _ <- charTest("#", '#') + _ <- charTest("$", '$') + _ <- charTest("%", '%') + _ <- charTest("&", '&') + _ <- charTest("'", '\'') + _ <- charTest("*", '*') + _ <- charTest("+", '+') + _ <- charTest(",", ',') + _ <- charTest("-", '-') + _ <- charTest(".", '.') + _ <- charTest("/", '/') + _ <- charTest(":", ':') + _ <- charTest(";", ';') + _ <- charTest("=", '=') + _ <- charTest("?", '?') + _ <- charTest("@", '@') + _ <- charTest("_", '_') + return charTest("~", '~') }, ) +/* +parseTest( + "orderedList", + orderedList, + ` 1. Omar + 2. Marlo + 3. Snoop`, + OrderedList([[Text("Hooray")], [Text("Nice")], [Text("Dope")]]), +) +*/ +parseTest( + "unorderedList", + unorderedList, + ` * a + * b + * c`, + UnorderedList([[Text("a")], [Text("b")], [Text("c")]]), +) test( "internalLink", @@ -94,28 +137,21 @@ test( }, ) -test("block", () => testParser(block, "# hey", H1([Text("hey")]))) +parseTest("block", block, "# hey", H1([Text("hey")])) -test("*italic*", () => testParser(italic, "*Firenze*", Italic("Firenze"))) -test("_italic_", () => testParser(italic, "_Venezia_", Italic("Venezia"))) -test("**bold**", () => testParser(bold, "**asterisk**", Bold("asterisk"))) -test("__bold__", () => testParser(bold, "__underscore__", Bold("underscore"))) +parseTest("*italic*", italic, "*Firenze*", Italic("Firenze")) +parseTest("_italic_", italic, "_Venezia_", Italic("Venezia")) +parseTest("**bold**", bold, "**asterisk**", Bold("asterisk")) +parseTest("__bold__", bold, "__underscore__", Bold("underscore")) -test("inlineCode", () => testParser(inlineCode, "`() => {}`", InlineCode("() => {}"))) -test( - "image", - () => testParser(image, "![description](//image.biz)", Image("description", "//image.biz")), -) +parseTest("image", image, "![description](//image.biz)", Image("description", "//image.biz")) -test( - "link", - () => testParser(link, "[madlib](https://madlib.space)", Link("madlib", "https://madlib.space")), -) +parseTest("link", link, "[madlib](https://madlib.space)", Link("madlib", "https://madlib.space")) -test("textTerminals", () => testParser(textTerminals, "*x*", "")) -test("text", () => testParser(text, "hooray", Text("hooray"))) -test("lineReturn", () => testParser(lineReturn, "\n", LineReturn)) +parseTest("textTerminals", textTerminals, "*x*", "") +parseTest("text", text, "hooray", Text("hooray")) +parseTest("lineReturn", lineReturn, "\n", LineReturn) test( "content", () => testParser( @@ -184,6 +220,7 @@ PARSED_FIXTURE = [ LineReturn, ]), ] +/* test( "markdownParser", () => pipe( @@ -192,7 +229,7 @@ test( chain((FIXTURE) => testParser(markdownParser, FIXTURE, PARSED_FIXTURE)), )("./FIXTURE.md"), ) - +/* test( "parseMarkdown", () => pipe( @@ -242,3 +279,6 @@ test( )("./notes/Reference/The Fence.md"), ) */ + + +test("inlineCode", () => testParser(inlineCode, "`() => {}`", InlineCode("() => {}"))) diff --git a/src/Test.mad b/src/Test.mad index 19decb0..4e907f2 100644 --- a/src/Test.mad +++ b/src/Test.mad @@ -18,3 +18,13 @@ export testParser = (parser, toParse, expected) => pipe( Test.assertEquals(res, expected) }, )(toParse) + +parseTest :: (Show a, Eq a) => String + -> Parse.Parser a + -> String + -> a + -> Wish Test.TestResult Test.TestResult +export parseTest = (testName, parser, toParse, expected) => Test.test( + testName, + () => testParser(parser, toParse, expected), +) diff --git a/src/Yaml.mad b/src/Yaml.mad index 4e6fc81..f400965 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -68,3 +68,9 @@ export internalLinkWithDisplay = do { internalLink :: P.Parser YamlValue export internalLink = P.choice([shortInternalLink, internalLinkWithDisplay]) + +// yamlTag :: P.Parser String +// yamlPair :: P.Parser YamlPair +// export yamlPair = do { +// +// } diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad new file mode 100644 index 0000000..974d47a --- /dev/null +++ b/src/Yaml.spec.mad @@ -0,0 +1,19 @@ +import { Left, Right } from "Either" +import File from "File" +import { always } from "Function" +import P from "Parse" +import Parse from "Parse" +import Test from "Test" +import Wish from "Wish" + + + +assertEquals = Test.assertEquals +test = Test.test +TestError = Test.Error + +import { testParser } from "@/Test" +import { internalLink } from "@/Yaml" + + + From 7051c9a173eaa7bf549c19fc73852c1aae01c5ba Mon Sep 17 00:00:00 2001 From: brekk Date: Sat, 22 Jun 2024 11:58:32 -1000 Subject: [PATCH 05/16] progress! --- src/Combinators.mad | 5 +- src/Main.mad | 35 +++++-------- src/Main.spec.mad | 25 +++++++-- src/Shared.mad | 14 +++++ src/Yaml.mad | 123 ++++++++++++++++++++++++++++++++++++++++---- src/Yaml.spec.mad | 43 +++++++++++++++- 6 files changed, 204 insertions(+), 41 deletions(-) create mode 100644 src/Shared.mad diff --git a/src/Combinators.mad b/src/Combinators.mad index 4ed56db..6d4efbd 100644 --- a/src/Combinators.mad +++ b/src/Combinators.mad @@ -3,7 +3,7 @@ import type { Parser } from "Parse" import { apL } from "Applicative" import { identity } from "Function" import { mapL } from "Functor" -import { lookAhead, someTill } from "Parse" +import { lookAhead, manyTill } from "Parse" @@ -13,6 +13,3 @@ export between = (start, mid, end) => pipe( ap($, mid), apL($, end), )(start) - -someUntil :: Parser a -> Parser b -> Parser (List a) -export someUntil = (prefix, suffix) => someTill(prefix, lookAhead(suffix)) diff --git a/src/Main.mad b/src/Main.mad index 921a940..18406e6 100644 --- a/src/Main.mad +++ b/src/Main.mad @@ -10,13 +10,12 @@ import { Just, Nothing } from "Maybe" import P from "Parse" import String from "String" -import { between, someUntil } from "@/Combinators" +import { between } from "@/Combinators" import { linkCharacter } from "@/Link" +import { NOTHING, SPACE, blank, end, singleReturnTerminal } from "@/Shared" -NOTHING = always("") - export type ContentPart = Text(String) | Bold(String) @@ -182,13 +181,13 @@ export image = pipe( textTerminals :: P.Parser String export textTerminals = P.choice([ - map(NOTHING, internalLink), - map(NOTHING, bold), - map(NOTHING, italic), - map(NOTHING, inlineCode), - map(NOTHING, image), - map(NOTHING, link), - map(NOTHING, P.eof), + blank(internalLink), + blank(bold), + blank(italic), + blank(inlineCode), + blank(image), + blank(link), + end, P.string("\n"), ]) @@ -246,13 +245,11 @@ export heading = (constructor) => pipe( apL($, singleReturnTerminal), ) -singleReturnTerminal :: P.Parser String -export singleReturnTerminal = alt(P.string("\n"), map(NOTHING, P.eof)) doubleReturnTerminal :: P.Parser String export doubleReturnTerminal = P.choice([ P.string("\n\n"), - map(NOTHING, P.eof), + end, pipe( ap(pure((_, _) => "")), ap($, P.eof), @@ -278,16 +275,13 @@ export blockquote = pipe( ), )(alt(P.symbol(">"), P.string(">"))) -SPACE = P.char(' ') - listItemStart :: P.Parser a -> P.Parser Content export listItemStart = (starter) => pipe( chain(always(apL(content, singleReturnTerminal))), )(starter) -export olistItemStart = map( - NOTHING, - apL(P.many(SPACE), apL(someUntil(P.digit, P.char('.')), P.some(SPACE))), +export olistItemStart = blank( + apL(P.many(SPACE), apL(P.someTill(P.digit, P.char('.')), P.some(SPACE))), ) orderedListItem :: P.Parser Content @@ -299,10 +293,7 @@ export orderedList = pipe( map(OrderedList), )(orderedListItem) -export ulistItemStart = map( - NOTHING, - apL(P.many(SPACE), apL(P.oneOf(['*', '-', '+']), P.some(SPACE))), -) +export ulistItemStart = blank(apL(P.many(SPACE), apL(P.oneOf(['*', '-', '+']), P.some(SPACE)))) unorderedListItem :: P.Parser Content export unorderedListItem = listItemStart(ulistItemStart) diff --git a/src/Main.spec.mad b/src/Main.spec.mad index dbc5e9d..e656625 100644 --- a/src/Main.spec.mad +++ b/src/Main.spec.mad @@ -53,7 +53,6 @@ import { orderedList, paragraph, parseMarkdown, - singleReturnTerminal, text, textTerminals, ulistItemStart, @@ -82,6 +81,24 @@ test( ) */ +test( + "smoke testing", + () => do { + numdot = P.someTill(P.digit, P.char('.')) + digits = P.some(P.digit) + // _ <- testParser(P.someTill(P.digit, P.lookAhead(P.char('.'))), "02.", []) + // _ <- testParser(alt(P.char('a'), P.lookAhead(P.anyChar)), "ab", 'a') + // _ <- testParser(alt(digits, P.lookAhead(P.char('.'))), "1.", "1.") + _ <- testParser(map(always("digimon"), digits), "102932092", "digimon") + _ <- testParser(map(always("digimon many"), P.many(P.digit)), "102932092", "digimon many") + _ <- testParser(map(always("period!"), P.char('.')), ".", "period!") + _ <- testParser(map(always(""), digits), "1", "") + // this fails to parse + // _ <- testParser(map(always(""), numdot), "1.", "") + // _ <- testParser(map(always(""), numdot), "93031.", "") + return testParser(map(always(""), digits), "1", "") + }, +) test( "linkCharacter", @@ -120,6 +137,7 @@ parseTest( OrderedList([[Text("Hooray")], [Text("Nice")], [Text("Dope")]]), ) */ + parseTest( "unorderedList", unorderedList, @@ -220,6 +238,7 @@ PARSED_FIXTURE = [ LineReturn, ]), ] + /* test( "markdownParser", @@ -229,7 +248,7 @@ test( chain((FIXTURE) => testParser(markdownParser, FIXTURE, PARSED_FIXTURE)), )("./FIXTURE.md"), ) -/* + test( "parseMarkdown", () => pipe( @@ -254,7 +273,7 @@ test( )("./FIXTURE.md"), ) -/* +// /* test( "parseMarkdown on real example file", () => pipe( diff --git a/src/Shared.mad b/src/Shared.mad new file mode 100644 index 0000000..76aba3f --- /dev/null +++ b/src/Shared.mad @@ -0,0 +1,14 @@ +import { always } from "Function" +import {} from "Functor" +import P from "Parse" + + + +export NOTHING = always("") +export blank = map(NOTHING) +export end = blank(P.eof) + +export SPACE = P.char(' ') + +singleReturnTerminal :: P.Parser String +export singleReturnTerminal = alt(P.string("\n"), end) diff --git a/src/Yaml.mad b/src/Yaml.mad index f400965..2d3b24c 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -4,14 +4,18 @@ import type { Parser } from "Parse" import { apL } from "Applicative" import { mapLeft } from "Either" +import {} from "Float" import { always, equals, identity } from "Function" import { mapL } from "Functor" +import IO from "IO" import { dropWhile, mapMaybe } from "List" -import { Just, Nothing } from "Maybe" +import { Just, Nothing, fromMaybe } from "Maybe" import P from "Parse" import String from "String" +import { between } from "@/Combinators" import { linkCharacter } from "@/Link" +import { SPACE, blank, singleReturnTerminal } from "@/Shared" @@ -28,11 +32,36 @@ export type YamlValue | YamlDate(String) | YamlList(List YamlValue) -// key / value / comment -export type YamlPair = YamlPair(String, YamlValue, String) +// key / value +export type YamlPair = YamlPair(String, YamlValue) export alias YamlData = List YamlPair +link :: P.Parser YamlValue +export link = pipe( + map(YamlLink), + ap( + $, + between( + P.char('('), + pipe( + P.many, + map(String.fromList), + )(linkCharacter), + P.char(')'), + ), + ), +)( + between( + P.char('['), + pipe( + P.many, + map(String.fromList), + )(P.notOneOf([']', '\n'])), + P.char(']'), + ), +) + shortInternalLink :: Parser YamlValue export shortInternalLink = do { _ <- P.string(`"[[`) @@ -63,14 +92,88 @@ export internalLinkWithDisplay = do { return pipe( YamlInternalLink(ref), of, - )(ref) + )(url) } internalLink :: P.Parser YamlValue -export internalLink = P.choice([shortInternalLink, internalLinkWithDisplay]) +export internalLink = P.choice([internalLinkWithDisplay, shortInternalLink]) -// yamlTag :: P.Parser String -// yamlPair :: P.Parser YamlPair -// export yamlPair = do { -// -// } +boolTrue :: P.Parser YamlValue +export boolTrue = do { + matched <- P.string("true") + return pipe( + YamlBoolean, + of, + )(true) +} + +boolFalse :: P.Parser YamlValue +export boolFalse = do { + matched <- P.string("false") + return pipe( + YamlBoolean, + of, + )(false) +} + +export boolean = P.choice([boolTrue, boolFalse]) + +numeric = map( + pipe( + String.fromList, + scan, + fromMaybe(0), + ), +) + +integer :: P.Parser YamlValue +export integer = do { + num <- numeric(P.manyTill(P.digit, P.lookAhead(P.char('\n')))) + return pipe( + YamlInteger, + of, + )(num) +} +float :: P.Parser YamlValue +export float = do { + num <- numeric(P.manyTill(P.digit, P.lookAhead(P.char('.')))) + _ <- P.char('.') + floating <- numeric(P.manyTill(P.digit, P.lookAhead(P.char('\n')))) + return pipe( + scan, + fromMaybe(0), + YamlFloat, + of, + )(`${show(num)}.${show(floating)}`) +} + +export text = pipe( + P.someTill($, P.lookAhead(P.char('\n'))), + map( + pipe( + String.fromList, + YamlString, + ), + ), +)(P.notChar('\n')) + +export yamlContent = pipe( + P.choice, + P.many, +)([link, internalLink, integer, float, boolean, text]) + +export list = pipe( + chain(always(apL(yamlContent, singleReturnTerminal))), +)(blank(apL(P.many(SPACE), apL(P.char('-'), P.some(SPACE))))) + +yamlPair :: P.Parser YamlPair +export yamlPair = do { + key <- map(String.fromList, P.manyTill(P.choice([P.letter, P.digit]), P.lookAhead(P.char(':')))) + value <- map(String.fromList, between(P.char(':'), P.many(P.notChar('\n')), P.char('\n'))) + return pipe( + String.trim, + YamlString, + YamlPair(key, $), + of, + )(value) +} diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad index 974d47a..d6981e6 100644 --- a/src/Yaml.spec.mad +++ b/src/Yaml.spec.mad @@ -12,8 +12,47 @@ assertEquals = Test.assertEquals test = Test.test TestError = Test.Error -import { testParser } from "@/Test" -import { internalLink } from "@/Yaml" +import { parseTest, testParser } from "@/Test" +import { + YamlBoolean, + YamlFloat, + YamlInteger, + YamlInternalLink, + YamlLink, + YamlPair, + YamlString, + boolean, + float, + integer, + internalLink, + internalLinkWithDisplay, + link, + yamlPair, +} from "@/Yaml" +parseTest("yamlPair", yamlPair, "cool: nice\n", YamlPair("cool", YamlString("nice"))) + +parseTest("float", float, "209392002.3992", YamlFloat(209392002.3992)) +parseTest("integer", integer, "13902291", YamlInteger(13902291)) +parseTest( + "internalLink - short", + internalLink, + `"[[heynow]]"`, + YamlInternalLink("heynow", "heynow"), +) +parseTest( + "internalLink - long", + internalLink, + `"[[link|display]]"`, + YamlInternalLink("display", "link"), +) +parseTest("boolean - true", boolean, "true", YamlBoolean(true)) +parseTest("boolean - false", boolean, "false", YamlBoolean(false)) +parseTest( + "link", + link, + "[website](https://madlib.space)", + YamlLink("website", "https://madlib.space"), +) From f22182c7ab77f7c044f726fc99c8e4761256f3b0 Mon Sep 17 00:00:00 2001 From: brekk Date: Sat, 22 Jun 2024 13:22:08 -1000 Subject: [PATCH 06/16] testing helps! --- src/Yaml.mad | 9 +++++---- src/Yaml.spec.mad | 13 ++++++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Yaml.mad b/src/Yaml.mad index 2d3b24c..35384f1 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -152,27 +152,28 @@ export text = pipe( map( pipe( String.fromList, + String.trim, YamlString, ), ), )(P.notChar('\n')) +export yamlSingleContent = P.choice([link, internalLink, integer, float, boolean, text]) + export yamlContent = pipe( P.choice, P.many, )([link, internalLink, integer, float, boolean, text]) export list = pipe( - chain(always(apL(yamlContent, singleReturnTerminal))), + chain(always(apL(yamlSingleContent, singleReturnTerminal))), )(blank(apL(P.many(SPACE), apL(P.char('-'), P.some(SPACE))))) yamlPair :: P.Parser YamlPair export yamlPair = do { key <- map(String.fromList, P.manyTill(P.choice([P.letter, P.digit]), P.lookAhead(P.char(':')))) - value <- map(String.fromList, between(P.char(':'), P.many(P.notChar('\n')), P.char('\n'))) + value <- between(P.char(':'), yamlSingleContent, P.char('\n')) return pipe( - String.trim, - YamlString, YamlPair(key, $), of, )(value) diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad index d6981e6..f000014 100644 --- a/src/Yaml.spec.mad +++ b/src/Yaml.spec.mad @@ -27,12 +27,23 @@ import { internalLink, internalLinkWithDisplay, link, + text, + yamlContent, yamlPair, + yamlSingleContent, } from "@/Yaml" -parseTest("yamlPair", yamlPair, "cool: nice\n", YamlPair("cool", YamlString("nice"))) +// parseTest("yamlPair", yamlPair, "cool: nice\n", YamlPair("cool", YamlString("nice"))) +test( + "yamlSingleContent", + () => do { + run = testParser(yamlSingleContent) + return run("whatever whatever ", YamlString("whatever whatever")) + }, +) +parseTest("text", text, "whatever whatever ", YamlString("whatever whatever")) parseTest("float", float, "209392002.3992", YamlFloat(209392002.3992)) parseTest("integer", integer, "13902291", YamlInteger(13902291)) From 684cd98778eecf55c3a4a389de101dfbd94f857d Mon Sep 17 00:00:00 2001 From: brekk Date: Sat, 22 Jun 2024 15:21:23 -1000 Subject: [PATCH 07/16] more --- src/Shared.mad | 1 + src/Yaml.mad | 16 +++++++--------- src/Yaml.spec.mad | 10 ++++++++-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Shared.mad b/src/Shared.mad index 76aba3f..7485425 100644 --- a/src/Shared.mad +++ b/src/Shared.mad @@ -7,6 +7,7 @@ import P from "Parse" export NOTHING = always("") export blank = map(NOTHING) export end = blank(P.eof) +export lineEnd = P.choice([end, blank(P.char('\n'))]) export SPACE = P.char(' ') diff --git a/src/Yaml.mad b/src/Yaml.mad index 35384f1..1e96781 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -147,16 +147,14 @@ export float = do { )(`${show(num)}.${show(floating)}`) } -export text = pipe( - P.someTill($, P.lookAhead(P.char('\n'))), - map( - pipe( - String.fromList, - String.trim, - YamlString, - ), +export text = map( + pipe( + String.fromList, + String.trim, + YamlString, ), -)(P.notChar('\n')) + P.many(P.notChar('\n')), +) export yamlSingleContent = P.choice([link, internalLink, integer, float, boolean, text]) diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad index f000014..bb0e7c3 100644 --- a/src/Yaml.spec.mad +++ b/src/Yaml.spec.mad @@ -36,15 +36,21 @@ import { // parseTest("yamlPair", yamlPair, "cool: nice\n", YamlPair("cool", YamlString("nice"))) +// parseTest("yamlPair", yamlPair, "cool: true\n", YamlPair("cool", YamlBoolean(true))) test( "yamlSingleContent", () => do { run = testParser(yamlSingleContent) - return run("whatever whatever ", YamlString("whatever whatever")) + // _ <- run("whatever whatever ", YamlString("whatever whatever")) + // _ <- run("true", YamlBoolean(true)) + // _ <- run("false", YamlBoolean(false)) + _ <- run("39309402334827", YamlInteger(39309402334827)) + // _ <- run("209392002.3992", YamlFloat(209392002.3992)) + _ <- run(`"[[whatever]]"`, YamlInternalLink("whatever", "whatever")) + return run("[website](https://website.biz)", YamlLink("website", "https://website.biz")) }, ) parseTest("text", text, "whatever whatever ", YamlString("whatever whatever")) - parseTest("float", float, "209392002.3992", YamlFloat(209392002.3992)) parseTest("integer", integer, "13902291", YamlInteger(13902291)) parseTest( From af3ba7e35cec9c2e8c641397c5f4cb605ababb8b Mon Sep 17 00:00:00 2001 From: brekk Date: Sun, 23 Jun 2024 10:56:26 -1000 Subject: [PATCH 08/16] something is borkies --- YamlFixture.yaml => YAML_FIXTURE.yaml | 0 src/Main.mad | 18 ++++++++++++++++++ src/Test.mad | 19 +++++++++++++++++++ src/Yaml.spec.mad | 4 +++- 4 files changed, 40 insertions(+), 1 deletion(-) rename YamlFixture.yaml => YAML_FIXTURE.yaml (100%) diff --git a/YamlFixture.yaml b/YAML_FIXTURE.yaml similarity index 100% rename from YamlFixture.yaml rename to YAML_FIXTURE.yaml diff --git a/src/Main.mad b/src/Main.mad index 18406e6..47bcd82 100644 --- a/src/Main.mad +++ b/src/Main.mad @@ -1,6 +1,8 @@ import type { Either } from "Either" import type { Maybe } from "Maybe" +import type { YamlData, YamlPair } from "@/Yaml" + import { apL } from "Applicative" import { mapLeft } from "Either" import { always, equals, identity } from "Function" @@ -13,6 +15,7 @@ import String from "String" import { between } from "@/Combinators" import { linkCharacter } from "@/Link" import { NOTHING, SPACE, blank, end, singleReturnTerminal } from "@/Shared" +import { yamlPair } from "@/Yaml" @@ -24,6 +27,7 @@ export type ContentPart | Link(String, String) | InternalLink(String, String) | Image(String, String) + | Yaml(YamlPair) | LineReturn export alias Content = List ContentPart @@ -40,6 +44,8 @@ export type Block | Code(String, String) | UnorderedList(List Content) | OrderedList(List Content) + | YamlBlock(Content) + export alias Markdown = List Block @@ -113,6 +119,17 @@ export shortInternalLink = do { internalLink :: P.Parser ContentPart export internalLink = P.choice([shortInternalLink, internalLinkWithDisplay]) +/* +yaml :: P.Parser ContentPart +export yaml = do { + data <- between(P.string("---"), P.some(yamlPair), P.string("---")) + return pipe( + Yaml, + of, + YamlBlock, + )(data) +} +*/ inlineCode :: P.Parser ContentPart export inlineCode = pipe( @@ -328,6 +345,7 @@ export paragraph = pipe( block :: P.Parser Block export block = P.choice([ + // yaml, heading(H6, "######"), heading(H5, "#####"), heading(H4, "####"), diff --git a/src/Test.mad b/src/Test.mad index 4e907f2..5070d9e 100644 --- a/src/Test.mad +++ b/src/Test.mad @@ -1,4 +1,7 @@ import { Left, Right } from "Either" +import File from "File" +import { always } from "Function" +import IO from "IO" import Parse from "Parse" import Test from "Test" import Wish from "Wish" @@ -28,3 +31,19 @@ export parseTest = (testName, parser, toParse, expected) => Test.test( testName, () => testParser(parser, toParse, expected), ) + +parseFile :: (Show a, Eq a) => String + -> String + -> Parse.Parser a + -> a + -> Wish Test.TestResult Test.TestResult +export parseFile = (filePath, testName, parser, expected) => Test.test( + testName, + () => pipe( + IO.pTrace("reading file..."), + File.read, + IO.pTrace("read file!"), + Wish.mapRej(always(TestError("File reading error"))), + chain((FIXTURE) => testParser(parser, FIXTURE, expected)), + )(filePath), +) diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad index bb0e7c3..95d7505 100644 --- a/src/Yaml.spec.mad +++ b/src/Yaml.spec.mad @@ -12,7 +12,7 @@ assertEquals = Test.assertEquals test = Test.test TestError = Test.Error -import { parseTest, testParser } from "@/Test" +import { parseFile, parseTest, testParser } from "@/Test" import { YamlBoolean, YamlFloat, @@ -73,3 +73,5 @@ parseTest( "[website](https://madlib.space)", YamlLink("website", "https://madlib.space"), ) + +parseFile("./YAML_FIXTURE.yaml", "parse yaml file", yamlContent, []) From b90a0536fc880b722630c7b62bc635b2b46f4fb0 Mon Sep 17 00:00:00 2001 From: brekk Date: Sun, 23 Jun 2024 19:45:44 -1000 Subject: [PATCH 09/16] renamed Main to something for a library --- src/MadMarkdownParser.mad | 372 +++++++++++++++++ ...in.spec.mad => MadMarkdownParser.spec.mad} | 2 +- src/Main.mad | 390 ++---------------- src/Yaml.mad | 7 +- src/Yaml.spec.mad | 2 +- 5 files changed, 401 insertions(+), 372 deletions(-) create mode 100644 src/MadMarkdownParser.mad rename src/{Main.spec.mad => MadMarkdownParser.spec.mad} (99%) diff --git a/src/MadMarkdownParser.mad b/src/MadMarkdownParser.mad new file mode 100644 index 0000000..47bcd82 --- /dev/null +++ b/src/MadMarkdownParser.mad @@ -0,0 +1,372 @@ +import type { Either } from "Either" +import type { Maybe } from "Maybe" + +import type { YamlData, YamlPair } from "@/Yaml" + +import { apL } from "Applicative" +import { mapLeft } from "Either" +import { always, equals, identity } from "Function" +import { mapL } from "Functor" +import { dropWhile, mapMaybe } from "List" +import { Just, Nothing } from "Maybe" +import P from "Parse" +import String from "String" + +import { between } from "@/Combinators" +import { linkCharacter } from "@/Link" +import { NOTHING, SPACE, blank, end, singleReturnTerminal } from "@/Shared" +import { yamlPair } from "@/Yaml" + + + +export type ContentPart + = Text(String) + | Bold(String) + | Italic(String) + | InlineCode(String) + | Link(String, String) + | InternalLink(String, String) + | Image(String, String) + | Yaml(YamlPair) + | LineReturn + +export alias Content = List ContentPart + +export type Block + = H1(Content) + | H2(Content) + | H3(Content) + | H4(Content) + | H5(Content) + | H6(Content) + | Paragraph(Content) + | Blockquote(Content) + | Code(String, String) + | UnorderedList(List Content) + | OrderedList(List Content) + | YamlBlock(Content) + + +export alias Markdown = List Block + + +boldDelimiter = P.choice([P.string("**"), P.string("__")]) + +bold :: P.Parser ContentPart +export bold = pipe( + mapL(Bold), + ap( + $, + pipe( + (a) => P.someTill(a, P.lookAhead(boldDelimiter)), + map(String.fromList), + )(P.notChar('\n')), + ), + apL($, boldDelimiter), +)(boldDelimiter) + +italicDelimiter = P.choice([P.char('*'), P.char('_')]) + +italic :: P.Parser ContentPart +export italic = do { + _ <- italicDelimiter + firstChar <- P.notChar(' ') + nextChars <- P.many(P.notOneOf(['_', '*', '\n'])) + _ <- italicDelimiter + + return pipe( + String.fromList, + Italic, + of, + )([firstChar, ...nextChars]) +} + +// https://help.obsidian.md/Linking+notes+and+files/Internal+links +// [[01 - Hello mad, mad world#Installation|Installing]] +internalLinkWithDisplay :: P.Parser ContentPart +export internalLinkWithDisplay = do { + _ <- P.string("[[") + url <- pipe( + P.many, + map(String.fromList), + )(linkCharacter) + _ <- P.char('|') + displayText <- pipe( + P.manyTill(P.letter), + map(String.fromList), + )(P.string("]]")) + _ <- P.string("]]") + return pipe( + InternalLink(displayText), + of, + )(url) +} + +// [[02 - Cool data for lyfe]] +shortInternalLink :: P.Parser ContentPart +export shortInternalLink = do { + _ <- P.string("[[") + ref <- pipe( + P.manyTill(P.letter), + map(String.fromList), + )(P.string("]]")) + _ <- P.string("]]") + return pipe( + InternalLink(ref), + of, + )(ref) +} + +internalLink :: P.Parser ContentPart +export internalLink = P.choice([shortInternalLink, internalLinkWithDisplay]) +/* +yaml :: P.Parser ContentPart +export yaml = do { + data <- between(P.string("---"), P.some(yamlPair), P.string("---")) + return pipe( + Yaml, + of, + YamlBlock, + )(data) +} +*/ + +inlineCode :: P.Parser ContentPart +export inlineCode = pipe( + mapL(InlineCode), + ap( + $, + pipe( + P.many, + map(String.fromList), + )(P.notOneOf(['`', '\n'])), + ), + apL($, P.char('`')), +)(P.char('`')) + +link :: P.Parser ContentPart +export link = pipe( + map(Link), + ap( + $, + between( + P.char('('), + pipe( + P.many, + map(String.fromList), + )(linkCharacter), + P.char(')'), + ), + ), +)( + between( + P.char('['), + pipe( + P.many, + map(String.fromList), + )(P.notOneOf([']', '\n'])), + P.char(']'), + ), +) + +image :: P.Parser ContentPart +export image = pipe( + mapL(Image), + ap( + $, + between( + P.char('['), + pipe( + P.many, + map(String.fromList), + )(P.notOneOf([']', '\n'])), + P.char(']'), + ), + ), + ap( + $, + between( + P.char('('), + pipe( + P.many, + map(String.fromList), + )(linkCharacter), + P.char(')'), + ), + ), +)(P.char('!')) + +textTerminals :: P.Parser String +export textTerminals = P.choice([ + blank(internalLink), + blank(bold), + blank(italic), + blank(inlineCode), + blank(image), + blank(link), + end, + P.string("\n"), +]) + +text :: P.Parser ContentPart +export text = pipe( + P.someTill($, P.lookAhead(textTerminals)), + map( + pipe( + String.fromList, + Text, + ), + ), +)(P.notChar('\n')) + +lineReturn :: P.Parser ContentPart +export lineReturn = map(always(LineReturn), P.char('\n')) + +content :: P.Parser Content +export content = pipe( + P.choice, + P.many, +)([internalLink, bold, italic, inlineCode, image, link, text]) + +coerceEmpty :: Maybe a -> P.Parser ContentPart +export coerceEmpty = pipe( + where { + Just(_) => + aempty + + Nothing => + lineReturn + }, +) + +lineReturnExceptBefore :: P.Parser a -> P.Parser ContentPart +export lineReturnExceptBefore = (before) => pipe( + mapL(identity), + ap($, alt(map(always(Just({})), before), pure(Nothing))), + P.lookAhead, + chain(coerceEmpty), +)(lineReturn) + +contentWithLineReturn :: P.Parser a -> P.Parser Content +export contentWithLineReturn = (delimiter) => pipe( + P.choice, + P.some, + map(dropWhile(equals(LineReturn))), +)([internalLink, bold, italic, inlineCode, image, link, text, lineReturnExceptBefore(delimiter)]) + +heading :: (Content -> Block) -> String -> P.Parser Block +export heading = (constructor) => pipe( + P.symbol, + mapL(constructor), + ap($, content), + apL($, singleReturnTerminal), +) + + +doubleReturnTerminal :: P.Parser String +export doubleReturnTerminal = P.choice([ + P.string("\n\n"), + end, + pipe( + ap(pure((_, _) => "")), + ap($, P.eof), + )(P.char('\n')), +]) + +code :: P.Parser Block +export code = pipe( + mapL(Code), + ap($, alt(map(String.fromList, P.letters), pure(""))), + apL($, P.char('\n')), + ap($, map(String.fromList, P.manyTill(P.anyChar, P.lookAhead(P.string("\n```"))))), + apL($, P.choice([map((_) => "", apL(P.string("\n```"), P.eof)), P.string("\n```\n")])), +)(P.string("```")) + +blockquote :: P.Parser Block +export blockquote = pipe( + mapL(Blockquote), + ap($, contentWithLineReturn(P.choice([P.string("\n"), P.string("```"), P.string(">")]))), + apL( + $, + P.choice([doubleReturnTerminal, P.lookAhead(P.string("\n```")), P.lookAhead(P.string("\n>"))]), + ), +)(alt(P.symbol(">"), P.string(">"))) + +listItemStart :: P.Parser a -> P.Parser Content +export listItemStart = (starter) => pipe( + chain(always(apL(content, singleReturnTerminal))), +)(starter) + +export olistItemStart = blank( + apL(P.many(SPACE), apL(P.someTill(P.digit, P.char('.')), P.some(SPACE))), +) + +orderedListItem :: P.Parser Content +export orderedListItem = listItemStart(olistItemStart) + +orderedList :: P.Parser Block +export orderedList = pipe( + P.some, + map(OrderedList), +)(orderedListItem) + +export ulistItemStart = blank(apL(P.many(SPACE), apL(P.oneOf(['*', '-', '+']), P.some(SPACE)))) + +unorderedListItem :: P.Parser Content +export unorderedListItem = listItemStart(ulistItemStart) + +unorderedList :: P.Parser Block +export unorderedList = pipe( + P.some, + map(UnorderedList), +)(unorderedListItem) + +markdownList :: P.Parser Block +export markdownList = P.choice([orderedList, unorderedList]) + +paragraph :: P.Parser Block +export paragraph = pipe( + map(Paragraph), + apL( + $, + P.choice([ + doubleReturnTerminal, + P.lookAhead(P.string("\n```")), + P.lookAhead(P.string("\n>")), + P.lookAhead(apL(P.string("\n"), olistItemStart)), + P.lookAhead(apL(P.string("\n"), ulistItemStart)), + ]), + ), +)( + contentWithLineReturn( + P.choice([P.string("\n"), P.string("```"), P.string(">"), olistItemStart, ulistItemStart]), + ), +) + +block :: P.Parser Block +export block = P.choice([ + // yaml, + heading(H6, "######"), + heading(H5, "#####"), + heading(H4, "####"), + heading(H3, "###"), + heading(H2, "##"), + heading(H1, "#"), + markdownList, + blockquote, + code, + paragraph, +]) + +markdownParser :: P.Parser Markdown +export markdownParser = pipe( + P.choice, + P.many, + map(mapMaybe(identity)), +)([map(always(Nothing), P.spaces), map(Just, block)]) + +parseMarkdown :: String -> Either String Markdown +export parseMarkdown = pipe( + P.runParser(markdownParser), + mapLeft(always("Malformed markdown input")), +) diff --git a/src/Main.spec.mad b/src/MadMarkdownParser.spec.mad similarity index 99% rename from src/Main.spec.mad rename to src/MadMarkdownParser.spec.mad index e656625..d221626 100644 --- a/src/Main.spec.mad +++ b/src/MadMarkdownParser.spec.mad @@ -58,7 +58,7 @@ import { ulistItemStart, unorderedList, unorderedListItem, -} from "./Main" +} from "./MadMarkdownParser" import { linkCharacter } from "@/Link" import { parseTest, testParser } from "@/Test" diff --git a/src/Main.mad b/src/Main.mad index 47bcd82..6400bae 100644 --- a/src/Main.mad +++ b/src/Main.mad @@ -1,372 +1,30 @@ -import type { Either } from "Either" -import type { Maybe } from "Maybe" +import { Left, Right } from "Either" +import File from "File" +import { always } from "Function" +import IO from "IO" +import Parse from "Parse" +import Wish from "Wish" -import type { YamlData, YamlPair } from "@/Yaml" +import { yamlContent } from "@/Yaml" -import { apL } from "Applicative" -import { mapLeft } from "Either" -import { always, equals, identity } from "Function" -import { mapL } from "Functor" -import { dropWhile, mapMaybe } from "List" -import { Just, Nothing } from "Maybe" -import P from "Parse" -import String from "String" -import { between } from "@/Combinators" -import { linkCharacter } from "@/Link" -import { NOTHING, SPACE, blank, end, singleReturnTerminal } from "@/Shared" -import { yamlPair } from "@/Yaml" - - -export type ContentPart - = Text(String) - | Bold(String) - | Italic(String) - | InlineCode(String) - | Link(String, String) - | InternalLink(String, String) - | Image(String, String) - | Yaml(YamlPair) - | LineReturn - -export alias Content = List ContentPart - -export type Block - = H1(Content) - | H2(Content) - | H3(Content) - | H4(Content) - | H5(Content) - | H6(Content) - | Paragraph(Content) - | Blockquote(Content) - | Code(String, String) - | UnorderedList(List Content) - | OrderedList(List Content) - | YamlBlock(Content) - - -export alias Markdown = List Block - - -boldDelimiter = P.choice([P.string("**"), P.string("__")]) - -bold :: P.Parser ContentPart -export bold = pipe( - mapL(Bold), - ap( - $, - pipe( - (a) => P.someTill(a, P.lookAhead(boldDelimiter)), - map(String.fromList), - )(P.notChar('\n')), - ), - apL($, boldDelimiter), -)(boldDelimiter) - -italicDelimiter = P.choice([P.char('*'), P.char('_')]) - -italic :: P.Parser ContentPart -export italic = do { - _ <- italicDelimiter - firstChar <- P.notChar(' ') - nextChars <- P.many(P.notOneOf(['_', '*', '\n'])) - _ <- italicDelimiter - - return pipe( - String.fromList, - Italic, - of, - )([firstChar, ...nextChars]) -} - -// https://help.obsidian.md/Linking+notes+and+files/Internal+links -// [[01 - Hello mad, mad world#Installation|Installing]] -internalLinkWithDisplay :: P.Parser ContentPart -export internalLinkWithDisplay = do { - _ <- P.string("[[") - url <- pipe( - P.many, - map(String.fromList), - )(linkCharacter) - _ <- P.char('|') - displayText <- pipe( - P.manyTill(P.letter), - map(String.fromList), - )(P.string("]]")) - _ <- P.string("]]") - return pipe( - InternalLink(displayText), - of, - )(url) -} - -// [[02 - Cool data for lyfe]] -shortInternalLink :: P.Parser ContentPart -export shortInternalLink = do { - _ <- P.string("[[") - ref <- pipe( - P.manyTill(P.letter), - map(String.fromList), - )(P.string("]]")) - _ <- P.string("]]") - return pipe( - InternalLink(ref), - of, - )(ref) -} - -internalLink :: P.Parser ContentPart -export internalLink = P.choice([shortInternalLink, internalLinkWithDisplay]) -/* -yaml :: P.Parser ContentPart -export yaml = do { - data <- between(P.string("---"), P.some(yamlPair), P.string("---")) - return pipe( - Yaml, - of, - YamlBlock, - )(data) -} -*/ - -inlineCode :: P.Parser ContentPart -export inlineCode = pipe( - mapL(InlineCode), - ap( - $, - pipe( - P.many, - map(String.fromList), - )(P.notOneOf(['`', '\n'])), - ), - apL($, P.char('`')), -)(P.char('`')) - -link :: P.Parser ContentPart -export link = pipe( - map(Link), - ap( - $, - between( - P.char('('), - pipe( - P.many, - map(String.fromList), - )(linkCharacter), - P.char(')'), - ), - ), -)( - between( - P.char('['), - pipe( - P.many, - map(String.fromList), - )(P.notOneOf([']', '\n'])), - P.char(']'), - ), -) - -image :: P.Parser ContentPart -export image = pipe( - mapL(Image), - ap( - $, - between( - P.char('['), - pipe( - P.many, - map(String.fromList), - )(P.notOneOf([']', '\n'])), - P.char(']'), - ), - ), - ap( - $, - between( - P.char('('), +main = () => { + pipe( + File.read, + Wish.mapRej(always("Reading is hard!")), + chain( pipe( - P.many, - map(String.fromList), - )(linkCharacter), - P.char(')'), + Parse.runParser(yamlContent), + where { + Left(Parse.Error(Parse.Loc(abs, line, col))) => + Wish.bad(`Parsing is hard! (${show(abs)}, ${show(line)}, ${show(col)})`) + + Right(raw) => + Wish.good(raw) + }, + ), ), - ), -)(P.char('!')) - -textTerminals :: P.Parser String -export textTerminals = P.choice([ - blank(internalLink), - blank(bold), - blank(italic), - blank(inlineCode), - blank(image), - blank(link), - end, - P.string("\n"), -]) - -text :: P.Parser ContentPart -export text = pipe( - P.someTill($, P.lookAhead(textTerminals)), - map( - pipe( - String.fromList, - Text, - ), - ), -)(P.notChar('\n')) - -lineReturn :: P.Parser ContentPart -export lineReturn = map(always(LineReturn), P.char('\n')) - -content :: P.Parser Content -export content = pipe( - P.choice, - P.many, -)([internalLink, bold, italic, inlineCode, image, link, text]) - -coerceEmpty :: Maybe a -> P.Parser ContentPart -export coerceEmpty = pipe( - where { - Just(_) => - aempty - - Nothing => - lineReturn - }, -) - -lineReturnExceptBefore :: P.Parser a -> P.Parser ContentPart -export lineReturnExceptBefore = (before) => pipe( - mapL(identity), - ap($, alt(map(always(Just({})), before), pure(Nothing))), - P.lookAhead, - chain(coerceEmpty), -)(lineReturn) - -contentWithLineReturn :: P.Parser a -> P.Parser Content -export contentWithLineReturn = (delimiter) => pipe( - P.choice, - P.some, - map(dropWhile(equals(LineReturn))), -)([internalLink, bold, italic, inlineCode, image, link, text, lineReturnExceptBefore(delimiter)]) - -heading :: (Content -> Block) -> String -> P.Parser Block -export heading = (constructor) => pipe( - P.symbol, - mapL(constructor), - ap($, content), - apL($, singleReturnTerminal), -) - - -doubleReturnTerminal :: P.Parser String -export doubleReturnTerminal = P.choice([ - P.string("\n\n"), - end, - pipe( - ap(pure((_, _) => "")), - ap($, P.eof), - )(P.char('\n')), -]) - -code :: P.Parser Block -export code = pipe( - mapL(Code), - ap($, alt(map(String.fromList, P.letters), pure(""))), - apL($, P.char('\n')), - ap($, map(String.fromList, P.manyTill(P.anyChar, P.lookAhead(P.string("\n```"))))), - apL($, P.choice([map((_) => "", apL(P.string("\n```"), P.eof)), P.string("\n```\n")])), -)(P.string("```")) - -blockquote :: P.Parser Block -export blockquote = pipe( - mapL(Blockquote), - ap($, contentWithLineReturn(P.choice([P.string("\n"), P.string("```"), P.string(">")]))), - apL( - $, - P.choice([doubleReturnTerminal, P.lookAhead(P.string("\n```")), P.lookAhead(P.string("\n>"))]), - ), -)(alt(P.symbol(">"), P.string(">"))) - -listItemStart :: P.Parser a -> P.Parser Content -export listItemStart = (starter) => pipe( - chain(always(apL(content, singleReturnTerminal))), -)(starter) - -export olistItemStart = blank( - apL(P.many(SPACE), apL(P.someTill(P.digit, P.char('.')), P.some(SPACE))), -) - -orderedListItem :: P.Parser Content -export orderedListItem = listItemStart(olistItemStart) - -orderedList :: P.Parser Block -export orderedList = pipe( - P.some, - map(OrderedList), -)(orderedListItem) - -export ulistItemStart = blank(apL(P.many(SPACE), apL(P.oneOf(['*', '-', '+']), P.some(SPACE)))) - -unorderedListItem :: P.Parser Content -export unorderedListItem = listItemStart(ulistItemStart) - -unorderedList :: P.Parser Block -export unorderedList = pipe( - P.some, - map(UnorderedList), -)(unorderedListItem) - -markdownList :: P.Parser Block -export markdownList = P.choice([orderedList, unorderedList]) - -paragraph :: P.Parser Block -export paragraph = pipe( - map(Paragraph), - apL( - $, - P.choice([ - doubleReturnTerminal, - P.lookAhead(P.string("\n```")), - P.lookAhead(P.string("\n>")), - P.lookAhead(apL(P.string("\n"), olistItemStart)), - P.lookAhead(apL(P.string("\n"), ulistItemStart)), - ]), - ), -)( - contentWithLineReturn( - P.choice([P.string("\n"), P.string("```"), P.string(">"), olistItemStart, ulistItemStart]), - ), -) - -block :: P.Parser Block -export block = P.choice([ - // yaml, - heading(H6, "######"), - heading(H5, "#####"), - heading(H4, "####"), - heading(H3, "###"), - heading(H2, "##"), - heading(H1, "#"), - markdownList, - blockquote, - code, - paragraph, -]) - -markdownParser :: P.Parser Markdown -export markdownParser = pipe( - P.choice, - P.many, - map(mapMaybe(identity)), -)([map(always(Nothing), P.spaces), map(Just, block)]) - -parseMarkdown :: String -> Either String Markdown -export parseMarkdown = pipe( - P.runParser(markdownParser), - mapLeft(always("Malformed markdown input")), -) + Wish.fulfill((e) => { IO.pTrace("error", e) }, (x) => { IO.pTrace("success", x) }), + )("./YAML_FIXTURE.yaml") +} diff --git a/src/Yaml.mad b/src/Yaml.mad index 1e96781..9e5da58 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -158,10 +158,7 @@ export text = map( export yamlSingleContent = P.choice([link, internalLink, integer, float, boolean, text]) -export yamlContent = pipe( - P.choice, - P.many, -)([link, internalLink, integer, float, boolean, text]) + export list = pipe( chain(always(apL(yamlSingleContent, singleReturnTerminal))), @@ -176,3 +173,5 @@ export yamlPair = do { of, )(value) } + +export yamlContent = P.many(yamlPair) diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad index 95d7505..e3c7190 100644 --- a/src/Yaml.spec.mad +++ b/src/Yaml.spec.mad @@ -74,4 +74,4 @@ parseTest( YamlLink("website", "https://madlib.space"), ) -parseFile("./YAML_FIXTURE.yaml", "parse yaml file", yamlContent, []) +// parseFile("./YAML_FIXTURE.yaml", "parse yaml file", yamlContent, []) From 09083396932469395dd72a8f5ce3bbdd397f2a86 Mon Sep 17 00:00:00 2001 From: brekk Date: Sun, 23 Jun 2024 21:06:54 -1000 Subject: [PATCH 10/16] progress! --- madlib.json | 2 +- src/Yaml.mad | 30 +++++++++++++++++++++++------- src/Yaml.spec.mad | 18 +++++++++++++----- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/madlib.json b/madlib.json index 39b0018..6484103 100644 --- a/madlib.json +++ b/madlib.json @@ -2,7 +2,7 @@ "name": "MadMarkdownParser", "version": "0.0.6", "madlibVersion": "0.23.14", - "main": "src/Main.mad", + "main": "src/MadMarkdownParser.mad", "importAliases": { ".": "src" }, diff --git a/src/Yaml.mad b/src/Yaml.mad index 9e5da58..6b30d39 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -15,7 +15,7 @@ import String from "String" import { between } from "@/Combinators" import { linkCharacter } from "@/Link" -import { SPACE, blank, singleReturnTerminal } from "@/Shared" +import { SPACE, blank, end, singleReturnTerminal } from "@/Shared" @@ -138,7 +138,7 @@ float :: P.Parser YamlValue export float = do { num <- numeric(P.manyTill(P.digit, P.lookAhead(P.char('.')))) _ <- P.char('.') - floating <- numeric(P.manyTill(P.digit, P.lookAhead(P.char('\n')))) + floating <- numeric(P.manyTill(P.digit, singleReturnTerminal)) return pipe( scan, fromMaybe(0), @@ -147,18 +147,27 @@ export float = do { )(`${show(num)}.${show(floating)}`) } +export textTerminals = P.choice([ + blank(boolean), + blank(internalLink), + blank(link), + blank(float), + blank(integer), + end, + P.string("\n"), +]) + export text = map( pipe( String.fromList, String.trim, + IO.pTrace("TRIM TRAM"), YamlString, ), - P.many(P.notChar('\n')), + P.someTill(P.notChar('\n'), textTerminals), ) -export yamlSingleContent = P.choice([link, internalLink, integer, float, boolean, text]) - - +export yamlSingleContent = P.choice([boolean, link, internalLink, float, integer, text]) export list = pipe( chain(always(apL(yamlSingleContent, singleReturnTerminal))), @@ -166,7 +175,14 @@ export list = pipe( yamlPair :: P.Parser YamlPair export yamlPair = do { - key <- map(String.fromList, P.manyTill(P.choice([P.letter, P.digit]), P.lookAhead(P.char(':')))) + key <- map( + pipe( + String.fromList, + String.trim, + ), + P.manyTill(P.anyChar, singleReturnTerminal), + ) + IO.pTrace("KEY", key) value <- between(P.char(':'), yamlSingleContent, P.char('\n')) return pipe( YamlPair(key, $), diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad index e3c7190..940ab83 100644 --- a/src/Yaml.spec.mad +++ b/src/Yaml.spec.mad @@ -36,21 +36,29 @@ import { // parseTest("yamlPair", yamlPair, "cool: nice\n", YamlPair("cool", YamlString("nice"))) -// parseTest("yamlPair", yamlPair, "cool: true\n", YamlPair("cool", YamlBoolean(true))) test( "yamlSingleContent", () => do { run = testParser(yamlSingleContent) // _ <- run("whatever whatever ", YamlString("whatever whatever")) - // _ <- run("true", YamlBoolean(true)) - // _ <- run("false", YamlBoolean(false)) + _ <- run("true", YamlBoolean(true)) + _ <- run("false", YamlBoolean(false)) _ <- run("39309402334827", YamlInteger(39309402334827)) - // _ <- run("209392002.3992", YamlFloat(209392002.3992)) + _ <- run("209392002.3992", YamlFloat(209392002.3992)) _ <- run(`"[[whatever]]"`, YamlInternalLink("whatever", "whatever")) + _ <- run(`"[[whatever|whenever]]"`, YamlInternalLink("whenever", "whatever")) return run("[website](https://website.biz)", YamlLink("website", "https://website.biz")) }, ) -parseTest("text", text, "whatever whatever ", YamlString("whatever whatever")) +test( + "text", + () => do { + run = testParser(text) + _ <- run("whatever whatever ", YamlString("whatever whatever")) + _ <- run("200.399", YamlString("200.399")) + return run("12932929", YamlString("12932929")) + }, +) parseTest("float", float, "209392002.3992", YamlFloat(209392002.3992)) parseTest("integer", integer, "13902291", YamlInteger(13902291)) parseTest( From ae76a0a65c401ee8750b8b8f5b11fd05cf21f66e Mon Sep 17 00:00:00 2001 From: brekk Date: Sun, 23 Jun 2024 21:14:17 -1000 Subject: [PATCH 11/16] closer! --- src/Yaml.mad | 12 ++---------- src/Yaml.spec.mad | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Yaml.mad b/src/Yaml.mad index 6b30d39..b64f4a6 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -147,15 +147,7 @@ export float = do { )(`${show(num)}.${show(floating)}`) } -export textTerminals = P.choice([ - blank(boolean), - blank(internalLink), - blank(link), - blank(float), - blank(integer), - end, - P.string("\n"), -]) +export textTerminals = P.choice([end, P.string("\n")]) export text = map( pipe( @@ -180,7 +172,7 @@ export yamlPair = do { String.fromList, String.trim, ), - P.manyTill(P.anyChar, singleReturnTerminal), + P.manyTill(P.anyChar, P.lookAhead(P.char(':'))), ) IO.pTrace("KEY", key) value <- between(P.char(':'), yamlSingleContent, P.char('\n')) diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad index 940ab83..5a68866 100644 --- a/src/Yaml.spec.mad +++ b/src/Yaml.spec.mad @@ -55,7 +55,7 @@ test( () => do { run = testParser(text) _ <- run("whatever whatever ", YamlString("whatever whatever")) - _ <- run("200.399", YamlString("200.399")) + _ <- run("costs 200.399", YamlString("costs 200.399")) return run("12932929", YamlString("12932929")) }, ) From 73515209865deb1bed07fe3ca867d151edef8d98 Mon Sep 17 00:00:00 2001 From: brekk Date: Mon, 24 Jun 2024 12:45:21 -1000 Subject: [PATCH 12/16] fuck yes! --- src/Shared.mad | 3 ++ src/Yaml.mad | 91 +++++++++++++++++++++++++++++------------------ src/Yaml.spec.mad | 6 ++-- 3 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/Shared.mad b/src/Shared.mad index 7485425..b9ef44c 100644 --- a/src/Shared.mad +++ b/src/Shared.mad @@ -13,3 +13,6 @@ export SPACE = P.char(' ') singleReturnTerminal :: P.Parser String export singleReturnTerminal = alt(P.string("\n"), end) + +maybeMinus :: P.Parser (List Char) +export maybeMinus = map(of, P.char('-')) <|> pure([]) diff --git a/src/Yaml.mad b/src/Yaml.mad index b64f4a6..7f87bab 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -15,7 +15,7 @@ import String from "String" import { between } from "@/Combinators" import { linkCharacter } from "@/Link" -import { SPACE, blank, end, singleReturnTerminal } from "@/Shared" +import { SPACE, blank, end, maybeMinus, singleReturnTerminal } from "@/Shared" @@ -98,25 +98,11 @@ export internalLinkWithDisplay = do { internalLink :: P.Parser YamlValue export internalLink = P.choice([internalLinkWithDisplay, shortInternalLink]) -boolTrue :: P.Parser YamlValue -export boolTrue = do { - matched <- P.string("true") - return pipe( - YamlBoolean, - of, - )(true) -} - -boolFalse :: P.Parser YamlValue -export boolFalse = do { - matched <- P.string("false") - return pipe( - YamlBoolean, - of, - )(false) -} - -export boolean = P.choice([boolTrue, boolFalse]) +boolean :: P.Parser YamlValue +export boolean = pipe( + alt(map(always(false), P.symbol("false"))), + map(YamlBoolean), +)(map(always(true), P.symbol("true"))) numeric = map( pipe( @@ -128,23 +114,30 @@ numeric = map( integer :: P.Parser YamlValue export integer = do { - num <- numeric(P.manyTill(P.digit, P.lookAhead(P.char('\n')))) + minus <- maybeMinus + digits <- P.some(P.digit) return pipe( + String.fromList, + scan, + fromMaybe(0), YamlInteger, of, - )(num) + )([...minus, ...digits]) } float :: P.Parser YamlValue export float = do { - num <- numeric(P.manyTill(P.digit, P.lookAhead(P.char('.')))) - _ <- P.char('.') - floating <- numeric(P.manyTill(P.digit, singleReturnTerminal)) + minus <- maybeMinus + whole <- P.some(P.digit) + dot <- P.char('.') + fraction <- P.some(P.digit) + return pipe( + String.fromList, scan, fromMaybe(0), YamlFloat, of, - )(`${show(num)}.${show(floating)}`) + )([...minus, ...whole, dot, ...fraction]) } export textTerminals = P.choice([end, P.string("\n")]) @@ -156,7 +149,7 @@ export text = map( IO.pTrace("TRIM TRAM"), YamlString, ), - P.someTill(P.notChar('\n'), textTerminals), + P.someTill(P.anyChar, textTerminals), ) export yamlSingleContent = P.choice([boolean, link, internalLink, float, integer, text]) @@ -165,17 +158,45 @@ export list = pipe( chain(always(apL(yamlSingleContent, singleReturnTerminal))), )(blank(apL(P.many(SPACE), apL(P.char('-'), P.some(SPACE))))) +alphanumeric :: Parser String +alphanumeric = do { + firstChar <- P.letter + rest <- P.many(P.choice([P.letter, P.digit])) + return pipe( + String.fromList, + of, + )([firstChar, ...rest]) +} + +/* +alphaNumericalName :: Parser String +alphaNumericalName = do { + firstChar <- Parse.letter + rest <- Parse.many(Parse.choice([Parse.letter, Parse.digit])) + + return pipe( + String.fromList, + of, + )([firstChar, ...rest]) +} + +recordField :: Parser #[String, Value] +recordField = do { + fieldName <- Parse.token(alphaNumericalName) + _ <- Parse.symbol(":") + fieldValue <- Parse.token(value) + + return of(#[fieldName, fieldValue]) +} +*/ + yamlPair :: P.Parser YamlPair export yamlPair = do { - key <- map( - pipe( - String.fromList, - String.trim, - ), - P.manyTill(P.anyChar, P.lookAhead(P.char(':'))), - ) + key <- P.token(alphanumeric) IO.pTrace("KEY", key) - value <- between(P.char(':'), yamlSingleContent, P.char('\n')) + _ <- P.symbol(":") + value <- P.token(yamlSingleContent) + IO.pTrace("VALUE", value) return pipe( YamlPair(key, $), of, diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad index 5a68866..bab50fa 100644 --- a/src/Yaml.spec.mad +++ b/src/Yaml.spec.mad @@ -35,12 +35,14 @@ import { -// parseTest("yamlPair", yamlPair, "cool: nice\n", YamlPair("cool", YamlString("nice"))) +parseTest("yamlPair - newline", yamlPair, "cool: nice\n", YamlPair("cool", YamlString("nice"))) +parseTest("yamlPair - eof", yamlPair, "cool: nice", YamlPair("cool", YamlString("nice"))) +parseTest("yamlPair - boolean", yamlPair, "cool: true", YamlPair("cool", YamlBoolean(true))) test( "yamlSingleContent", () => do { run = testParser(yamlSingleContent) - // _ <- run("whatever whatever ", YamlString("whatever whatever")) + _ <- run("whatever whatever ", YamlString("whatever whatever")) _ <- run("true", YamlBoolean(true)) _ <- run("false", YamlBoolean(false)) _ <- run("39309402334827", YamlInteger(39309402334827)) From 9f9e53b78bd79c610faa937a7fdb18e7495a29ba Mon Sep 17 00:00:00 2001 From: brekk Date: Mon, 24 Jun 2024 15:59:24 -1000 Subject: [PATCH 13/16] a day of great progress --- src/MadMarkdownParser.mad | 51 +++++++++++++++++++++++----------- src/MadMarkdownParser.spec.mad | 11 ++++---- src/Yaml.mad | 46 ++++++++++-------------------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/MadMarkdownParser.mad b/src/MadMarkdownParser.mad index 47bcd82..43b4937 100644 --- a/src/MadMarkdownParser.mad +++ b/src/MadMarkdownParser.mad @@ -2,6 +2,7 @@ import type { Either } from "Either" import type { Maybe } from "Maybe" import type { YamlData, YamlPair } from "@/Yaml" +import type { YamlValue } from "@/Yaml" import { apL } from "Applicative" import { mapLeft } from "Either" @@ -15,7 +16,7 @@ import String from "String" import { between } from "@/Combinators" import { linkCharacter } from "@/Link" import { NOTHING, SPACE, blank, end, singleReturnTerminal } from "@/Shared" -import { yamlPair } from "@/Yaml" +import { markYaml } from "@/Yaml" @@ -27,7 +28,7 @@ export type ContentPart | Link(String, String) | InternalLink(String, String) | Image(String, String) - | Yaml(YamlPair) + | Yaml(Dictionary String YamlValue) | LineReturn export alias Content = List ContentPart @@ -119,17 +120,6 @@ export shortInternalLink = do { internalLink :: P.Parser ContentPart export internalLink = P.choice([shortInternalLink, internalLinkWithDisplay]) -/* -yaml :: P.Parser ContentPart -export yaml = do { - data <- between(P.string("---"), P.some(yamlPair), P.string("---")) - return pipe( - Yaml, - of, - YamlBlock, - )(data) -} -*/ inlineCode :: P.Parser ContentPart export inlineCode = pipe( @@ -222,11 +212,20 @@ export text = pipe( lineReturn :: P.Parser ContentPart export lineReturn = map(always(LineReturn), P.char('\n')) +yaml :: P.Parser ContentPart +yaml = do { + data <- markYaml + return pipe( + Yaml, + of, + )(data) +} + content :: P.Parser Content export content = pipe( P.choice, P.many, -)([internalLink, bold, italic, inlineCode, image, link, text]) +)([yaml, internalLink, bold, italic, inlineCode, image, link, text]) coerceEmpty :: Maybe a -> P.Parser ContentPart export coerceEmpty = pipe( @@ -252,7 +251,17 @@ export contentWithLineReturn = (delimiter) => pipe( P.choice, P.some, map(dropWhile(equals(LineReturn))), -)([internalLink, bold, italic, inlineCode, image, link, text, lineReturnExceptBefore(delimiter)]) +)([ + yaml, + internalLink, + bold, + italic, + inlineCode, + image, + link, + text, + lineReturnExceptBefore(delimiter), +]) heading :: (Content -> Block) -> String -> P.Parser Block export heading = (constructor) => pipe( @@ -343,9 +352,18 @@ export paragraph = pipe( ), ) +yamlBlock :: P.Parser Block +export yamlBlock = do { + parsed <- yaml + return pipe( + of, + YamlBlock, + of, + )(parsed) +} + block :: P.Parser Block export block = P.choice([ - // yaml, heading(H6, "######"), heading(H5, "#####"), heading(H4, "####"), @@ -356,6 +374,7 @@ export block = P.choice([ blockquote, code, paragraph, + yamlBlock, ]) markdownParser :: P.Parser Markdown diff --git a/src/MadMarkdownParser.spec.mad b/src/MadMarkdownParser.spec.mad index d221626..8f2ed7a 100644 --- a/src/MadMarkdownParser.spec.mad +++ b/src/MadMarkdownParser.spec.mad @@ -6,11 +6,6 @@ import Parse from "Parse" import Test from "Test" import Wish from "Wish" - - -assertEquals = Test.assertEquals -test = Test.test -TestError = Test.Error import { Blockquote, Bold, @@ -64,6 +59,10 @@ import { parseTest, testParser } from "@/Test" +assertEquals = Test.assertEquals +test = Test.test +TestError = Test.Error + test( "ulistItemStart", () => do { @@ -239,7 +238,6 @@ PARSED_FIXTURE = [ ]), ] -/* test( "markdownParser", () => pipe( @@ -248,6 +246,7 @@ test( chain((FIXTURE) => testParser(markdownParser, FIXTURE, PARSED_FIXTURE)), )("./FIXTURE.md"), ) +/* test( "parseMarkdown", diff --git a/src/Yaml.mad b/src/Yaml.mad index 7f87bab..8a104a3 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -3,6 +3,8 @@ import type { Maybe } from "Maybe" import type { Parser } from "Parse" import { apL } from "Applicative" +import Dict from "Dictionary" +import { fromRight } from "Either" import { mapLeft } from "Either" import {} from "Float" import { always, equals, identity } from "Function" @@ -104,14 +106,6 @@ export boolean = pipe( map(YamlBoolean), )(map(always(true), P.symbol("true"))) -numeric = map( - pipe( - String.fromList, - scan, - fromMaybe(0), - ), -) - integer :: P.Parser YamlValue export integer = do { minus <- maybeMinus @@ -146,7 +140,6 @@ export text = map( pipe( String.fromList, String.trim, - IO.pTrace("TRIM TRAM"), YamlString, ), P.someTill(P.anyChar, textTerminals), @@ -168,35 +161,12 @@ alphanumeric = do { )([firstChar, ...rest]) } -/* -alphaNumericalName :: Parser String -alphaNumericalName = do { - firstChar <- Parse.letter - rest <- Parse.many(Parse.choice([Parse.letter, Parse.digit])) - - return pipe( - String.fromList, - of, - )([firstChar, ...rest]) -} - -recordField :: Parser #[String, Value] -recordField = do { - fieldName <- Parse.token(alphaNumericalName) - _ <- Parse.symbol(":") - fieldValue <- Parse.token(value) - - return of(#[fieldName, fieldValue]) -} -*/ yamlPair :: P.Parser YamlPair export yamlPair = do { key <- P.token(alphanumeric) - IO.pTrace("KEY", key) _ <- P.symbol(":") value <- P.token(yamlSingleContent) - IO.pTrace("VALUE", value) return pipe( YamlPair(key, $), of, @@ -204,3 +174,15 @@ export yamlPair = do { } export yamlContent = P.many(yamlPair) + +markYaml :: P.Parser (Dictionary String YamlValue) +export markYaml = do { + _ <- P.symbol("---") + pairs <- P.many(yamlPair) + _ <- P.symbol("---") + return pipe( + map(where { YamlPair(k, v) => #[k, v] }), + Dict.fromList, + of, + )(pairs) +} From 6bd218e8022125049290b18cb0e2b946520cb66c Mon Sep 17 00:00:00 2001 From: brekk Date: Mon, 24 Jun 2024 21:36:48 -1000 Subject: [PATCH 14/16] closer, but still not working --- src/MadMarkdownParser.mad | 49 +++++++++++++++++++++++++++------- src/MadMarkdownParser.spec.mad | 28 +++++++++++++------ 2 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/MadMarkdownParser.mad b/src/MadMarkdownParser.mad index 43b4937..f1bb460 100644 --- a/src/MadMarkdownParser.mad +++ b/src/MadMarkdownParser.mad @@ -8,6 +8,7 @@ import { apL } from "Applicative" import { mapLeft } from "Either" import { always, equals, identity } from "Function" import { mapL } from "Functor" +import IO from "IO" import { dropWhile, mapMaybe } from "List" import { Just, Nothing } from "Maybe" import P from "Parse" @@ -221,11 +222,11 @@ yaml = do { )(data) } +contentPart :: P.Parser ContentPart +contentPart = P.choice([yaml, internalLink, bold, italic, inlineCode, image, link, text]) + content :: P.Parser Content -export content = pipe( - P.choice, - P.many, -)([yaml, internalLink, bold, italic, inlineCode, image, link, text]) +export content = P.many(contentPart) coerceEmpty :: Maybe a -> P.Parser ContentPart export coerceEmpty = pipe( @@ -306,18 +307,46 @@ export listItemStart = (starter) => pipe( chain(always(apL(content, singleReturnTerminal))), )(starter) -export olistItemStart = blank( - apL(P.many(SPACE), apL(P.someTill(P.digit, P.char('.')), P.some(SPACE))), -) +// export olistItemStart = blank( +// apL(P.many(SPACE), apL(P.someTill(P.digit, P.lookAhead(P.char('.'))), P.some(SPACE))), +// ) + +export olistItemStart = do { + space <- P.some(P.char(' ')) + digits <- P.token(P.some(P.digit)) + dot <- P.char('.') + return pipe( + of, + )("") +} -orderedListItem :: P.Parser Content -export orderedListItem = listItemStart(olistItemStart) +// orderedListItem :: P.Parser Content +// export orderedListItem = listItemStart(olistItemStart) -orderedList :: P.Parser Block +/* export orderedList = pipe( P.some, map(OrderedList), )(orderedListItem) +*/ + +export orderedListItem = do { + _ <- P.some(P.char(' ')) <|> pure([]) + _ <- P.token(P.some(P.digit)) + _ <- P.symbol(".") <|> pure("") + lineContent <- P.token(content) + return pipe( + of, + )(lineContent) +} +export orderedList = do { + items <- P.sepBy(orderedListItem, P.char('\n')) + IO.pTrace("ITEMS!", items) + return pipe( + OrderedList, + of, + )([...items]) +} export ulistItemStart = blank(apL(P.many(SPACE), apL(P.oneOf(['*', '-', '+']), P.some(SPACE)))) diff --git a/src/MadMarkdownParser.spec.mad b/src/MadMarkdownParser.spec.mad index 8f2ed7a..d3a209a 100644 --- a/src/MadMarkdownParser.spec.mad +++ b/src/MadMarkdownParser.spec.mad @@ -1,6 +1,7 @@ import { Left, Right } from "Either" import File from "File" import { always } from "Function" +import IO from "IO" import P from "Parse" import Parse from "Parse" import Test from "Test" @@ -46,6 +47,7 @@ import { markdownParser, olistItemStart, orderedList, + orderedListItem, paragraph, parseMarkdown, text, @@ -74,20 +76,30 @@ test( test( "olistItemStart", () => do { - _ <- testParser(olistItemStart, " 1. ", "") - return testParser(olistItemStart, " 1. ", "") + _ <- testParser(olistItemStart, "1.", "") + return testParser(olistItemStart, "2021.", "") }, ) -*/ +// */ +test( + "orderedList", + () => do { + return testParser( + olistItemStart, + ` 1. test + 2. every + 3. day`, + "", + ) + }, +) +// */ +parseTest("orderedListItem", orderedListItem, " 2. cool", [Text("cool")]) test( "smoke testing", () => do { - numdot = P.someTill(P.digit, P.char('.')) digits = P.some(P.digit) - // _ <- testParser(P.someTill(P.digit, P.lookAhead(P.char('.'))), "02.", []) - // _ <- testParser(alt(P.char('a'), P.lookAhead(P.anyChar)), "ab", 'a') - // _ <- testParser(alt(digits, P.lookAhead(P.char('.'))), "1.", "1.") _ <- testParser(map(always("digimon"), digits), "102932092", "digimon") _ <- testParser(map(always("digimon many"), P.many(P.digit)), "102932092", "digimon many") _ <- testParser(map(always("period!"), P.char('.')), ".", "period!") @@ -238,6 +250,7 @@ PARSED_FIXTURE = [ ]), ] +/* test( "markdownParser", () => pipe( @@ -246,7 +259,6 @@ test( chain((FIXTURE) => testParser(markdownParser, FIXTURE, PARSED_FIXTURE)), )("./FIXTURE.md"), ) -/* test( "parseMarkdown", From 8e129dbca7e21173f874b3c1f86cca02a2f0ad8e Mon Sep 17 00:00:00 2001 From: brekk Date: Tue, 25 Jun 2024 08:33:06 -1000 Subject: [PATCH 15/16] way closer, still need yaml lists to work in aggregate, and ordered lists to work at all --- src/MadMarkdownParser.mad | 5 ++- src/MadMarkdownParser.spec.mad | 15 ++++----- src/Yaml.mad | 27 +++++++++++++-- src/Yaml.spec.mad | 61 ++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 12 deletions(-) diff --git a/src/MadMarkdownParser.mad b/src/MadMarkdownParser.mad index f1bb460..c8b0d6a 100644 --- a/src/MadMarkdownParser.mad +++ b/src/MadMarkdownParser.mad @@ -330,17 +330,20 @@ export orderedList = pipe( )(orderedListItem) */ +// the crap in olistItemStart above squeezes out the spaces +// so we need to do that in the morning export orderedListItem = do { _ <- P.some(P.char(' ')) <|> pure([]) _ <- P.token(P.some(P.digit)) _ <- P.symbol(".") <|> pure("") lineContent <- P.token(content) + _ <- P.symbol("\n") <|> pure("") return pipe( of, )(lineContent) } export orderedList = do { - items <- P.sepBy(orderedListItem, P.char('\n')) + items <- P.sepBy(orderedListItem, P.token(P.char('\n'))) IO.pTrace("ITEMS!", items) return pipe( OrderedList, diff --git a/src/MadMarkdownParser.spec.mad b/src/MadMarkdownParser.spec.mad index d3a209a..0b57608 100644 --- a/src/MadMarkdownParser.spec.mad +++ b/src/MadMarkdownParser.spec.mad @@ -81,15 +81,15 @@ test( }, ) // */ +/* test( "orderedList", () => do { return testParser( - olistItemStart, - ` 1. test - 2. every - 3. day`, - "", + orderedList, + ` 1. hey + 2. every`, + OrderedList([]), ) }, ) @@ -283,8 +283,7 @@ test( ), )("./FIXTURE.md"), ) - -// /* +// / * test( "parseMarkdown on real example file", () => pipe( @@ -308,7 +307,7 @@ test( ), )("./notes/Reference/The Fence.md"), ) -*/ +// */ test("inlineCode", () => testParser(inlineCode, "`() => {}`", InlineCode("() => {}"))) diff --git a/src/Yaml.mad b/src/Yaml.mad index 8a104a3..e2c0944 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -147,9 +147,20 @@ export text = map( export yamlSingleContent = P.choice([boolean, link, internalLink, float, integer, text]) -export list = pipe( +export listItem = pipe( chain(always(apL(yamlSingleContent, singleReturnTerminal))), -)(blank(apL(P.many(SPACE), apL(P.char('-'), P.some(SPACE))))) +)(blank(apL(P.some(SPACE), apL(P.char('-'), P.some(SPACE))))) + +export yamlList = pipe( + P.some, + map(YamlList), +)(listItem) + +newlineYamlList = do { + _ <- P.char('\n') + list <- yamlList + return of(list) +} alphanumeric :: Parser String alphanumeric = do { @@ -166,7 +177,7 @@ yamlPair :: P.Parser YamlPair export yamlPair = do { key <- P.token(alphanumeric) _ <- P.symbol(":") - value <- P.token(yamlSingleContent) + value <- P.token(P.choice([yamlSingleContent, newlineYamlList])) return pipe( YamlPair(key, $), of, @@ -175,6 +186,16 @@ export yamlPair = do { export yamlContent = P.many(yamlPair) +yaml :: P.Parser (Dictionary String YamlValue) +export yaml = do { + pairs <- P.many(yamlPair) + return pipe( + map(where { YamlPair(k, v) => #[k, v] }), + Dict.fromList, + of, + )(pairs) +} + markYaml :: P.Parser (Dictionary String YamlValue) export markYaml = do { _ <- P.symbol("---") diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad index bab50fa..5782dda 100644 --- a/src/Yaml.spec.mad +++ b/src/Yaml.spec.mad @@ -1,3 +1,4 @@ +import Dict from "Dictionary" import { Left, Right } from "Either" import File from "File" import { always } from "Function" @@ -19,6 +20,7 @@ import { YamlInteger, YamlInternalLink, YamlLink, + YamlList, YamlPair, YamlString, boolean, @@ -28,7 +30,9 @@ import { internalLinkWithDisplay, link, text, + yaml, yamlContent, + yamlList, yamlPair, yamlSingleContent, } from "@/Yaml" @@ -52,6 +56,63 @@ test( return run("[website](https://website.biz)", YamlLink("website", "https://website.biz")) }, ) + +parseTest( + "yamlList", + yamlList, + ` - a + - b + - c`, + YamlList([YamlString("a"), YamlString("b"), YamlString("c")]), +) +test( + "yaml", + () => do { + FIRST_CASE = `a: yo +b: true +c: false +d: 123850.23 +e: 240 +f: [link](site) +g: "[[hoho|yoyo]]"` + parseYaml = testParser(yaml) + return parseYaml( + FIRST_CASE, + {{ + "a": YamlString("yo"), + "b": YamlBoolean(true), + "c": YamlBoolean(false), + "d": YamlFloat(123850.23), + "e": YamlInteger(240), + "f": YamlLink("link", "site"), + "g": YamlInternalLink("yoyo", "hoho"), + }}, + ) + /* + return parseYaml( + FIRST_CASE + ++ ` + h: + - i + - j + - k`, + {{ + "a": YamlString("yo"), + "b": YamlBoolean(true), + "c": YamlBoolean(false), + "d": YamlFloat(123850.23), + "e": YamlInteger(240), + "f": YamlLink("link", "site"), + "g": YamlInternalLink("yoyo", "hoho"), + "h": YamlList([YamlString("i"), YamlString("j"), YamlString("k")]), + }}, + ) + */ + }, +) + +// parseFile("./YAML_FIXTURE.yaml", "yaml fixture", yaml, {{}}) + test( "text", () => do { From c100b84925375d0aedb1af8c0ad7b771bc084368 Mon Sep 17 00:00:00 2001 From: brekk Date: Tue, 2 Jul 2024 13:26:12 -1000 Subject: [PATCH 16/16] resolve latest --- .gitignore | 1 + src/Yaml.mad | 15 +++++++++------ src/Yaml.spec.mad | 39 ++++++++++++++++++--------------------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 45c7026..a32d4e5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ src/Example.mad .DS_Store .coverage .tests +.vscode diff --git a/src/Yaml.mad b/src/Yaml.mad index e2c0944..4afcb52 100644 --- a/src/Yaml.mad +++ b/src/Yaml.mad @@ -147,16 +147,19 @@ export text = map( export yamlSingleContent = P.choice([boolean, link, internalLink, float, integer, text]) -export listItem = pipe( - chain(always(apL(yamlSingleContent, singleReturnTerminal))), -)(blank(apL(P.some(SPACE), apL(P.char('-'), P.some(SPACE))))) +export ulistItemStart = blank(apL(P.many(SPACE), apL(P.char('-'), P.some(SPACE)))) + +export listItem = chain(always(apL(yamlSingleContent, singleReturnTerminal)), ulistItemStart) + +yamlList :: P.Parser YamlValue export yamlList = pipe( P.some, map(YamlList), )(listItem) -newlineYamlList = do { +newlineYamlList :: P.Parser YamlValue +export newlineYamlList = do { _ <- P.char('\n') list <- yamlList return of(list) @@ -177,7 +180,7 @@ yamlPair :: P.Parser YamlPair export yamlPair = do { key <- P.token(alphanumeric) _ <- P.symbol(":") - value <- P.token(P.choice([yamlSingleContent, newlineYamlList])) + value <- P.token(P.choice([newlineYamlList, yamlSingleContent])) return pipe( YamlPair(key, $), of, @@ -188,7 +191,7 @@ export yamlContent = P.many(yamlPair) yaml :: P.Parser (Dictionary String YamlValue) export yaml = do { - pairs <- P.many(yamlPair) + pairs <- P.sepBy(yamlPair, P.char('\n')) return pipe( map(where { YamlPair(k, v) => #[k, v] }), Dict.fromList, diff --git a/src/Yaml.spec.mad b/src/Yaml.spec.mad index 5782dda..9445352 100644 --- a/src/Yaml.spec.mad +++ b/src/Yaml.spec.mad @@ -29,6 +29,7 @@ import { internalLink, internalLinkWithDisplay, link, + newlineYamlList, text, yaml, yamlContent, @@ -65,6 +66,17 @@ parseTest( - c`, YamlList([YamlString("a"), YamlString("b"), YamlString("c")]), ) + +parseTest( + "newlineYamlList", + newlineYamlList, + ` + - a + - b + - c`, + YamlList([YamlString("a"), YamlString("b"), YamlString("c")]), +) + test( "yaml", () => do { @@ -76,7 +88,7 @@ e: 240 f: [link](site) g: "[[hoho|yoyo]]"` parseYaml = testParser(yaml) - return parseYaml( + _ <- parseYaml( FIRST_CASE, {{ "a": YamlString("yo"), @@ -88,26 +100,11 @@ g: "[[hoho|yoyo]]"` "g": YamlInternalLink("yoyo", "hoho"), }}, ) - /* - return parseYaml( - FIRST_CASE - ++ ` - h: - - i - - j - - k`, - {{ - "a": YamlString("yo"), - "b": YamlBoolean(true), - "c": YamlBoolean(false), - "d": YamlFloat(123850.23), - "e": YamlInteger(240), - "f": YamlLink("link", "site"), - "g": YamlInternalLink("yoyo", "hoho"), - "h": YamlList([YamlString("i"), YamlString("j"), YamlString("k")]), - }}, - ) - */ + return parseYaml( + `h: + - i`, + {{ "h": YamlList([YamlString("i")]) }}, + ) }, )