commit 9ff845fb09744292ac2349a594e7346fdb832708 Author: Michael Ochmann Date: Sat Feb 26 18:20:31 2022 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd896f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +.vscode +.DS_Store + +vendor diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f238230 --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "massivedynamic/parkdown", + "type": "library", + "license": "MIT", + "autoload": { + "psr-4": { + "parkdown\\": "src/" + } + }, + "authors": [ + { + "name": "Michael Ochmann", + "email": "miko@massivedynamic.eu" + } + ], + "require": {} +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..85b1e10 --- /dev/null +++ b/composer.lock @@ -0,0 +1,18 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "cdece622f692fc25b3cb5a87fd3368e3", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.1.0" +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..60f4a72 --- /dev/null +++ b/index.php @@ -0,0 +1,9 @@ +html(); \ No newline at end of file diff --git a/src/Lexer.php b/src/Lexer.php new file mode 100644 index 0000000..8a6cb03 --- /dev/null +++ b/src/Lexer.php @@ -0,0 +1,69 @@ +source = explode("\n", trim($unifiedSource, "\n")); + } + + public function tokenize() : array { + $tokens = []; + + foreach ($this->source as $line) { + if (strlen($line) < 1) + continue; + + $buffer = ""; + $number = false; + + $clearBuffer = function() use (&$buffer, &$tokens) { + if (strlen($buffer) < 1) + return; + array_push($tokens, new Token(TokenType::TEXT, $buffer)); + $buffer = ""; + }; + + foreach(str_split($line) as $char) { + if (is_numeric($char) && !$number) { + $clearBuffer(); + $number = true; + } else if (!is_numeric($char) && $number) { + array_push($tokens, new Token(TokenType::NUMBER, $buffer)); + $buffer = ""; + $number = false; + } + switch($char) { + case '#': + $clearBuffer(); + array_push($tokens, new Token(TokenType::HASH, $char)); + break; + case '*': + $clearBuffer(); + array_push($tokens, new Token(TokenType::ASTERISK, $char)); + break; + case '.': + $clearBuffer(); + array_push($tokens, new Token(TokenType::DOT, $char)); + break; + case '`': + $clearBuffer(); + array_push($tokens, new Token(TokenType::BACKTICK, $char)); + break; + default: + $buffer .= $char; + break; + } + } + $clearBuffer(); + array_push($tokens, new Token(TokenType::EOL)); + } + $clearBuffer(); + array_push($tokens, new Token(TokenType::EOF)); + + return $tokens; + } +} \ No newline at end of file diff --git a/src/Parkdown.php b/src/Parkdown.php new file mode 100644 index 0000000..b53fc90 --- /dev/null +++ b/src/Parkdown.php @@ -0,0 +1,18 @@ +sourceCode= $sourceCode; + } + + public function html() : void { + $lexer = new Lexer($this->sourceCode); + $parser = new Parser($lexer->tokenize()); + + echo $parser->parse(); + } +} \ No newline at end of file diff --git a/src/Parser.php b/src/Parser.php new file mode 100644 index 0000000..47f05cf --- /dev/null +++ b/src/Parser.php @@ -0,0 +1,202 @@ +tokenStream = $tokenStream; + $this->pointer = 0; + $this->document = new DOMDocument(); + } + + private function current() : Token { + return $this->peek(); + } + + private function next() : Token { + return $this->peek(1); + } + + private function peek(int $amount = 0) : Token { + $amount += $this->pointer; + if ($amount < 0 || $amount >= count($this->tokenStream)) + return new Token(TokenType::EOF); + + return $this->tokenStream[$amount]; + } + + private function consume() : Token { + $char = $this->current(); + $this->pointer++; + + return $char; + } + + private function parseBold() : DOMNode { + $buffer = ""; + while ($this->current()->type !== TokenType::ASTERISK) { + $buffer .= $this->consume()->data; + } + $this->consume(); + $this->consume(); + + return $this->document->createElement("b", $buffer); + } + + private function parseItalic() : DOMNode { + $buffer = ""; + while ($this->current()->type !== TokenType::ASTERISK) { + $buffer .= $this->consume()->data; + } + $this->consume(); + + return $this->document->createElement("i", $buffer); + } + + private function parseCode() : DOMNode { + $buffer = ""; + $this->consume(); + while ($this->current()->type !== TokenType::BACKTICK && $this->current()->type !== TokenType::EOL) + $buffer .= $this->consume()->data; + + $this->consume(); + + return $this->document->createElement("code", $buffer); + } + + private function parseText() : array { + $elms = []; + $buffer = ""; + + $clearBuffer = function() use (&$elms, &$buffer) { + array_push($elms, $this->document->createTextNode($buffer)); + $buffer = ""; + }; + + while ($this->current()->type !== TokenType::EOL) { + if ($this->current()->type === TokenType::ASTERISK) { + $clearBuffer(); + if ($this->next()->type === TokenType::ASTERISK) { + $this->consume(); + $this->consume(); + array_push($elms, $this->parseBold()); + } else { + $this->consume(); + array_push($elms, $this->parseItalic()); + } + continue; + } elseif ($this->current()->type === TokenType::BACKTICK) { + $clearBuffer(); + array_push($elms, $this->parseCode()); + continue; + } else + $buffer .= $this->consume()->data; + } + if (strlen($buffer) > 0) + array_push($elms, $this->document->createTextNode($buffer)); + + return $elms; + } + + private function parseUnorderedList() : void { + $list = $this->document->createElement("ul"); + + while (!($this->current()->type === TokenType::EOL && $this->next()->type !== TokenType::ASTERISK) && $this->current()->type !== TokenType::EOF) { + if ($this->current()->type === TokenType::EOL) { + $this->consume(); + continue; + } + if ($this->current()->type === TokenType::ASTERISK) { + $asterisk = $this->consume(); + $elm = $this->document->createElement("li"); + foreach($this->parseText() as $node) + $elm->appendChild($node); + $list->appendChild($elm); + } + } + $this->consume(); + $this->document->appendChild($list); + } + + private function parseOrderedList() : void { + $list = $this->document->createElement("ol"); + + while (!($this->current()->type === TokenType::EOL && $this->next()->type !== TokenType::NUMBER) && + $this->current()->type !== TokenType::EOF) { + if ($this->current()->type === TokenType::EOL) { + $this->consume(); + continue; + } + if ($this->current()->type === TokenType::NUMBER && + $this->next()->type === TokenType::DOT) { + $number = $this->consume(); + $dot = $this->consume(); + $elm = $this->document->createElement("li"); + foreach($this->parseText() as $node) + $elm->appendChild($node); + $list->appendChild($elm); + } else { + $elm = $this->document->createElement("p"); + $elms = $this->parseText(); + foreach ($elms as $node) + $elm->appendChild($node); + $this->document->appendChild($elm); + continue; + } + } + $this->consume(); + $this->document->appendChild($list); + } + + private function parseHeading() : void { + $level = 0; + + while ($this->current()->type === TokenType::HASH) { + $level++; + $this->consume(); + } + $elm = $this->document->createElement("h".$level); + foreach ($this->parseText() as $node) + $elm->appendChild($node); + $this->document->appendChild($elm); + } + + public function parse() : string { + while ($this->current()->type !== TokenType::EOF) { + switch($this->current()->type) { + case TokenType::ASTERISK: + $this->parseUnorderedList(); + break; + case TokenType::HASH: + $this->parseHeading(); + break; + case TokenType::TEXT: + $elm = $this->document->createElement("p"); + foreach ($this->parseText() as $node) + $elm->appendChild($node); + $this->document->appendChild($elm); + break; + case TokenType::NUMBER: + $this->parseOrderedList(); + break; + case TokenType::EOL: + $this->consume(); + break; + default: + $c = $this->consume(); + echo "::".$c->type->name."::"; + break; + } + + } + return $this->document->saveHTML(); + } +} \ No newline at end of file diff --git a/src/Token.php b/src/Token.php new file mode 100644 index 0000000..5bf1172 --- /dev/null +++ b/src/Token.php @@ -0,0 +1,24 @@ +type = $type; + $this->data = $data; + } +} \ No newline at end of file diff --git a/test/test1.md b/test/test1.md new file mode 100644 index 0000000..b80f1e2 --- /dev/null +++ b/test/test1.md @@ -0,0 +1,16 @@ +# Heading 1 +#### lol **lol** + +* this **bold** and somewhat +* kind of *italic* thing +* is +* a +* list + +1. this is +2. an ordered +3. list + +Lorem **ipsum** dolor sit *amet*, `consetetur` sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.