#!/usr/bin/env raku
grammar TinyMD {
token TOP { \s* <block>* \s* }
proto token block { * }
# Horizontal Rule: 3 or more -, *, or _
token block:sym<hr> { ^^ \h* <[-*_]> ** 3..* \h* \n+ }
# Header: 1 to 6 # marks
token block:sym<header> { ^^ $<lvl>=('#' ** 1..6) \h+ $<text>=\N+ \n* }
# Code Block: Indented by 4 spaces or 1 tab
token block:sym<codeblock> { [ ^^ [ ' ' ** 4 | \t ] $<text>=\N+ \n* ]+ }
# Blockquote: Starts with >
token block:sym<quote> { [ ^^ \h* '>' \h* $<text>=\N+ \n* ]+ }
# Lists: Unordered (-, *, +) or Ordered (1.)
token block:sym<list> { [ ^^ \h* $<marker>=[ <[-*+]> | \d+ '.' ] \h+ $<text>=\N+ \n* ]+ }
# Paragraph: Any lines that does NOT start with the above block indicators
token block:sym<para> { [ $<text>=\N+ \n* ]+ }
}
class MD-HTML {
method TOP($/) { make $<block>.map(*.made).join("\n\n") }
method block:sym<hr>($/) { make "<hr>" }
method block:sym<header>($/) { make "<h{$<lvl>.chars}>" ~ inline($<text>) ~ "</h{$<lvl>.chars}>" }
method block:sym<codeblock>($/) { make "<pre><code>\n" ~ $<text>.join("\n") ~ "\n</code></pre>" }
method block:sym<quote>($/) { make "<blockquote>\n" ~ $<text>.map({" <p>"~inline($_)~"</p>"}).join("\n") ~ "\n</blockquote>" }
method block:sym<para>($/) { make "<p>\n " ~ inline($<text>.join("\n")) ~ "\n</p>" }
method block:sym<list>($/) {
my $tag = $<marker>[0] ~~ /\d/ ?? "ol" !! "ul";
make "<$tag>\n" ~ $<text>.map({" <li>" ~ inline($_) ~ "</li>"}).join("\n") ~ "\n</$tag>"
}
sub inline($str) {
$str.subst(/ \! \[ (.*?) \] \( (.*?) \) /, -> $/ { "<img src=\"$1\" alt=\"$0\">" }, :g) # Images
.subst(/ \[ (.*?) \] \( (.*?) \) /, -> $/ { "<a href=\"$1\">{$0}</a>" }, :g) # Links
.subst(/ \*\* (.*?) \*\* | __ (.*?) __ /, -> $/ { "<strong>{ $0 || $1 }</strong>" }, :g) # bold
.subst(/ \* (.*?) \* | _ (.*?) _ /, -> $/ { "<em>{ $0 || $1 }</em>" }, :g) # italic
.subst(/ \` (.*?) \` /, -> $/ { "<code>{$0}</code>" }, :g) # code
}
}
my $markdown = q:to/END/;
# Markdown Core Support
This is a paragraph with a [link](https://raku.org) and an .
It supports **bold**, __bold__, *italics*, _italics_, and `code`.
---
> This is a blockquote.
> It can span multiple lines!
1. First ordered item
2. Second ordered item
* Unordered item A
- Unordered item B
print("This is a code block");
say "Because it is indented by 4 spaces";
END
say TinyMD.parse($markdown, actions => MD-HTML.new).made;