diff --git a/.gitignore b/.gitignore
index 8c55080..4634098 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,5 +9,11 @@
!/import_map.json
!/core
+/core/*
!/ren
+/ren/*
+!/par
+/par/*
+
+!/**/*.ts
diff --git a/par/md.test.ts b/par/md.test.ts
new file mode 100644
index 0000000..436708b
--- /dev/null
+++ b/par/md.test.ts
@@ -0,0 +1,23 @@
+import { assertEquals } from "testing/asserts.ts";
+import { HtmlStrRenderer } from "../ren/html_str.ts";
+import { MarkdownParser } from "./md.ts";
+
+const ren = new HtmlStrRenderer();
+
+Deno.test({
+ name: "should parse header",
+ fn: () => {
+ const par = new MarkdownParser();
+ const res = par.parse("#");
+ assertEquals(ren.render(res), "
");
+ },
+});
+
+Deno.test({
+ name: "should parse header with text",
+ fn: () => {
+ const par = new MarkdownParser();
+ const res = par.parse("# hello");
+ assertEquals(ren.render(res), "hello
");
+ },
+});
diff --git a/par/md.ts b/par/md.ts
new file mode 100644
index 0000000..e6a275a
--- /dev/null
+++ b/par/md.ts
@@ -0,0 +1,73 @@
+import { AnyNode, Elem, Fragment, TextNode } from "../core/node.ts";
+import { isNil } from "../core/utils.ts";
+import { Parser } from "./types.ts";
+
+const RE_OPEN_HEADING = /^\s{0,3}(#{1,6})(\s|$)/;
+
+export class MarkdownParser implements Parser {
+ parse(input: string): AnyNode {
+ const ast: AstDocument = { kind: AstKind.Document, content: [] };
+
+ let readStr = input;
+
+ const match = RE_OPEN_HEADING.exec(readStr);
+ if (!isNil(match)) {
+ readStr = readStr.slice(match[0].length);
+
+ console.log({ match });
+
+ const heading: AstHeading = {
+ kind: AstKind.Heading,
+ level: match[1].length as HeadingLevel,
+ content: [],
+ };
+ ast.content.push(heading);
+
+ if (match[2].length > 0) {
+ const textContent = readStr.split("\n", 1)[0];
+ readStr = readStr.slice(textContent.length);
+
+ const text: AstText = {
+ kind: AstKind.Text,
+ content: textContent,
+ };
+
+ heading.content.push(text);
+ }
+ }
+
+ return new Fragment(ast.content.map(Heading));
+ }
+}
+
+function Heading(ast: AstHeading): Elem {
+ return new Elem(`h${ast.level}`, {}, ast.content.map(Text));
+}
+
+function Text(ast: AstText): TextNode {
+ return new TextNode(ast.content);
+}
+
+// AST
+
+type AstDocument = BaseAstItem;
+type AstDocumentChild = AstHeading;
+
+interface AstHeading extends BaseAstItem {
+ level: HeadingLevel;
+}
+
+type AstText = BaseAstItem;
+
+type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
+
+interface BaseAstItem {
+ kind: K;
+ content: Cont;
+}
+
+enum AstKind {
+ Document,
+ Heading,
+ Text,
+}
diff --git a/par/types.ts b/par/types.ts
new file mode 100644
index 0000000..ecbf894
--- /dev/null
+++ b/par/types.ts
@@ -0,0 +1,5 @@
+import { AnyNode } from "../core/node.ts";
+
+export interface Parser {
+ parse(input: string): AnyNode;
+}