diff --git a/src/almo.cpp b/src/almo.cpp index 3a0768b..bd9001b 100644 --- a/src/almo.cpp +++ b/src/almo.cpp @@ -132,16 +132,107 @@ void debug_json(std::string ir_json, std::map meta_dat std::cout << output << std::endl; } -void debug_graph(almo::Block ast){ - std::cout << ast.to_dot(true) << std::endl; +void debug_graph(almo::Markdown ast){ + std::cout << "digraph G {\n graph [labelloc=\"t\"; \n ]\n" + ast.to_dot() + "}" << std::endl; } +namespace almo_preprocess { + + +// md ファイルの中身 (frot YAML を含まない) +// を受け取って、前処理 ([コメントの削除, ]) を行う +std::vector preprocess(std::vector content) { + std::string content_join = join(content, "\n"); + + // コメントを削除 + auto remove_comment = [](std::string text) { + return _remove_comment(text); + }; + + + std::vector> hooks = { + remove_comment, + }; + + for (auto hook : hooks) { + content_join = hook(content_join); + } + + content = split(content_join, "\n"); + + return content; +} + +// md ファイルの中身 (front YAML) +// を受け取って、 front YAML をパースした結果と残りの md ファイルの開始位置を返す +// TODO: きちんとした YAML パーサを使うようにする。 +std::pair>, int> parse_front(std::vector content) { + std::vector> front_yaml; + int front_yaml_end = 0; + + if (!content.empty() && content[0] == "---") { + int index = 1; + while (index < (int)content.size() && content[index] != "---") { + std::string key = std::regex_replace(content[index], std::regex("(.*):\\s(.*)"), "$1"); + std::string data = std::regex_replace(content[index], std::regex("(.*):\\s(.*)"), "$2"); + front_yaml.emplace_back(key, data); + index++; + } + front_yaml_end = index + 1; + } + + return { front_yaml, front_yaml_end }; +} + +// md ファイルの内容から +// メタデータと md の本文の組を返す +std::pair, std::map> split_markdown(std::vector content){ + auto [meta_data, meta_data_end] = parse_front(content); + + // メタデータ以降の行を取り出し + std::vector md_lines(content.begin() + meta_data_end, content.end()); + + // 前処理 + md_lines = preprocess(md_lines); + + // meta_data を std::map に変換する + std::map meta_data_map; + + // デフォルト値を設定 + meta_data_map["title"] = ""; + meta_data_map["date"] = ""; + meta_data_map["author"] = ""; + meta_data_map["twitter_id"] = ""; + meta_data_map["github_id"] = ""; + meta_data_map["mail"] = ""; + meta_data_map["ogp_url"] = "https://www.abap34.com/almo_logo.jpg"; + meta_data_map["tag"] = ""; + meta_data_map["url"] = ""; + meta_data_map["site_name"] = ""; + meta_data_map["twitter_site"] = ""; + + for (auto [key, data] : meta_data) { + meta_data_map[key] = data; + } + + return {md_lines, meta_data_map}; +} + +// md ファイルのパスから +// md と メタデータの組を返す +std::pair, std::map> split_markdown_from_path(std::string path){ + return split_markdown(read_file(path)); +} + + +} // almo_preprocess + int main(int argc, char* argv[]) { Config config; config.parse_arguments(argc, argv); // パース - auto [meta_data, ast] = almo::parse_md_file(argv[1]); + auto [md_content, meta_data] = almo_preprocess::split_markdown_from_path(argv[1]); // コマンドライン引数を meta_data に追加 meta_data["template_file"] = config.template_file; @@ -150,19 +241,21 @@ int main(int argc, char* argv[]) { meta_data["css_setting"] = config.css_setting; meta_data["editor_theme"] = config.editor_theme; meta_data["syntax_theme"] = config.syntax_theme; + meta_data["required_pyodide"] = "false"; + + almo::ParseSummary summary = almo::md_to_summary(md_content, meta_data); if (config.debug) { - std::string ir_json = ast.to_json(); - debug_json(ir_json, meta_data); + debug_json(summary.json, meta_data); } if (config.plot_graph) { - debug_graph(ast); + debug_graph(summary.ast); return 0; } - std::string result = almo::render(ast, meta_data); + std::string result = summary.html; if (config.out_path == "__stdout__") { std::cout << result << std::endl; diff --git a/src/ast.hpp b/src/ast.hpp index 4eac179..2a732e2 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -1,1006 +1,104 @@ #pragma once -#include -#include -#include -#include -#include -#include #include "utils.hpp" +#include "interfaces/ast.hpp" namespace almo { - // レスポンス時間短縮のため、pyodideが不要なら読み込みをスキップするためのフラグ. - bool loaded_pyodide = false; - std::string pyodide_loader = ""; - - // 各ノードに対して一意なIDを生成する用の関数オブジェクト. 呼ぶと前に呼ばれた時の値 + 1 が返る. - struct UUID_gen { - std::string operator()() { - static int uuid = 0; - return std::to_string(uuid++); - } - }uuid; - - // 構文木の各要素を表す抽象クラス. 全ての構文ブロックを表す構造体はこれを継承する. - // `Inline` という prefix がついているクラスは、インラインでの記法が許されている構文. - class ASTNode { - public: - virtual std::string to_json() const = 0; - virtual ~ASTNode() = default; - - // このノードが構文木の葉ノードかどうかを返す. - virtual bool is_leaf() const = 0; - - // そのノードが持つプロパティを列挙する - virtual std::map get_properties() const = 0; - - virtual std::string get_classname() const = 0; - - std::string uuid; - - std::string get_uuid() const { - return uuid; - } - - void set_uuid(std::string new_uuid) { - uuid = new_uuid; - } - }; - - - // 葉ノード。このノードは、htmlをゼロから生成する. - class LeafNode : public ASTNode { - public: - bool is_leaf() const override { - return true; - } - - // 構文木をhtmlに変換する. - virtual std::string to_html(std::map meta_data) const = 0; - - virtual std::string to_json() const override { - std::map properties = get_properties(); - std::string json = "{"; - // add classname - json += "\"class\":\"" + escape(get_classname()) + "\","; - // add uuid - json += "\"uuid\":\"" + escape(get_uuid()) + "\","; - // add other properties - for (auto property : properties) { - json += "\"" + property.first + "\":\"" + escape(property.second) + "\","; - } - // 最後のカンマを削除する - json = json.substr(0, json.length() - 1); - return json + "}"; - } - - // 部分木を dot 言語に変換する. - std::string to_dot() const { - std::map properties = get_properties(); - - std::string node = get_uuid(); - std::string label = ""; - - std::string label_header = get_classname() + " | "; - - int i = 1; - for (auto property : properties) { - label += " " + property.first + ": " + escape(property.second) + " | "; - i++; - } - - return node + "[label=\"" + label_header + label + "\", shape=\"record\"]\n"; - } - - }; - - - // 葉ノードでないノード。このノードは、プロパティを持たず、子から伝播してきた `child_html` に変換を施し親に渡す. - class NonLeafNode : public ASTNode { - public: - bool is_leaf() const override { - return false; - } - - // このノードの子ノード. - std::vector> childs = {}; - - // 構文木をhtmlに変換する。子ノードが存在するので、それぞれを html に変換したものの vector が渡される. - virtual std::string to_html(std::vector childs_html, - std::map meta_data) const = 0; - - // 部分木を html に変換する. - virtual std::string render(std::map meta_data) { - std::vector childs_html; - for (auto child : childs) { - if (child->is_leaf()) { - childs_html.push_back(std::dynamic_pointer_cast(child)->to_html(meta_data)); - } - else { - childs_html.push_back(std::dynamic_pointer_cast(child)->render(meta_data)); - } - } - return to_html(childs_html, meta_data); - } - - // 部分木を json 形式の string に変換する. - virtual std::string to_json() const override { - std::map properties = get_properties(); - std::string json = "{"; - // add classname - json += "\"class\":\"" + escape(get_classname()) + "\","; - // add uuid - json += "\"uuid\":\"" + escape(get_uuid()) + "\","; - // add other properties - for (auto property : properties) { - json += "\"" + property.first + "\":\"" + escape(property.second) + "\","; - } - - json += "\"childs\":["; - if (childs.size() > 0) { - for (auto child : childs) { - json += child->to_json() + ","; - } - json = json.substr(0, json.length() - 1); - } - json += "]}"; - return json; - } - - // 部分木を dot 言語に変換する. - virtual std::string to_dot(bool is_root = false) const { - std::map properties = get_properties(); - - std::string node = get_uuid(); - std::string label = ""; - - std::string label_header = " " + get_classname() + " | "; - - int i = 1; - for (auto property : properties) { - label += " " + property.first + ": " + escape(property.second) + " | "; - i++; - } - - // 子ノードを追加する - std::string childs_dot = ""; - for (auto child : childs) { - if (child->is_leaf()) { - childs_dot += std::dynamic_pointer_cast(child)->to_dot(); - } - else { - childs_dot += std::dynamic_pointer_cast(child)->to_dot(); - } - } - - // 子ノードと繋ぐ - std::string edges = ""; - for (auto child : childs) { - edges += node + ":f" + std::to_string(edges.length()) + " -> " + child->get_uuid() + "\n"; - } - - std::string content = node + "[label=\"" + label_header + label + "\", shape=\"record\"]\n" + childs_dot + edges; - - if (is_root) { - return "digraph G {\n graph [labelloc=\"t\"; \n ]\n" + content + "}"; - } else { - return content; - } - } - - }; - - // H1~6 に対応する構文木のノード - class Header : public NonLeafNode { - - // ヘッダのレベル. H{level} に対応する. - int level; - public: - Header(int level, std::string uuid) : level(level) { - if (level < 1 || level > 6) { - throw ParseError("Internal Error: Header level must be 1~6. But " + std::to_string(level) + " is given."); - } - set_uuid(uuid); - } - - std::string to_html(std::vector childs_html, std::map meta_data) const override { - std::string contents_push = "" - "\n"; - - return contents_push + "" + join(childs_html) + ""; - } - - std::map get_properties() const override { - return { - {"level", std::to_string(level)} - }; - } - std::string get_classname() const override { - return "Header"; - } - }; - - - // 複数のブロックをまとめるために使う構文木のノード. これ自体には意味はないことに注意。 - // 例えば一行分 parseしたとき、 `Block` を根とする木としてパース結果が帰ってくる。 - // Block のみ `render` というメソッドを持ち、 部分木を html に変換したものをそのまま返す。 - class Block : public NonLeafNode { - public: - Block(std::string uuid_) { - set_uuid(uuid); - } - - // これ自体は意味を持たないので、子ノードが変換されたものをそのまま返す. - std::string to_html(std::vector childs_html, std::map meta_data) const override { - return join(childs_html); - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "Block"; - } - }; - - // 葉ノードとして文字情報を持つ構文木のノード. これ自体には意味はないことに注意。 - // 色々なクラスで末端として情報を持ち、また再帰の終了のために使う。  - // 例えば **Hoge** という文字列をパースしたとき、 - // InlineStrong -> Raw(content="Hoge") という木として結果をもつ。 - class RawText : public LeafNode { - - // このノードが持つ文字列 - std::string content; - public: - RawText(std::string content, std::string uuid) : content(content) { - set_uuid(uuid); - } - - std::string to_html(std::map meta_data) const override { - return content; - } - - std::map get_properties() const override { - return { - {"content", content} - }; - } - std::string get_classname() const override { - return "RawText"; - } - }; - - // 数式ブロック. 内容はMathJaxでレンダリングされる. 内容は `math-block` というクラスが付与されたdivタグで囲まれる - class MathBlock : public LeafNode { - // markdownで渡された式をそのまま string で持つ。 - std::string expression; - - public: - MathBlock(std::string expression, std::string uuid) : expression(expression) { - set_uuid(uuid); - } - - // mathjax の 複数行数式用に \[ \] で囲む - std::string to_html(std::map meta_data) const override { - return "
\\[ \n" + expression + "\n \\]
"; - } - - std::map get_properties() const override { - return { - {"expression", expression} - }; - } - std::string get_classname() const override { - return "MathBlock"; - } - }; - - // インラインの数式ブロック. 内容はMathJaxでレンダリングされる. 内容は `math-inline` というクラスが付与されたspanタグで囲まれる - class InlineMath : public LeafNode { - // markdownで渡された式をそのまま string で持つ。 - std::string expr; - public: - InlineMath(std::string expr, std::string uuid) : expr(expr) { - set_uuid(uuid); - } - - // mathjax の インライン数式用に \( \) で囲む - std::string to_html(std::map meta_data) const override { - return " \\( " + expr + " \\) "; - } - - - std::map get_properties() const override { - return { - {"expr", expr} - }; - } - std::string get_classname() const override { - return "InlineMath"; - } - }; - - // 打消し. タグで囲まれる - class InlineOverline : public NonLeafNode { - public: - InlineOverline(std::string uuid) { - set_uuid(uuid); - } - std::string to_html(std::vector childs_html, std::map meta_data) const override { - - return " " + join(childs_html) + " "; - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "InlineOverline"; - } - }; - - - // 強調. タグで囲まれる - class InlineStrong : public NonLeafNode { - public: - InlineStrong(std::string uuid) { - set_uuid(uuid); - } - - std::string to_html(std::vector childs_html, std::map meta_data) const override { - return " " + join(childs_html) + " "; - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "InlineStrong"; - } - }; - - // イタリック. タグで囲まれる - class InlineItalic : public NonLeafNode { - public: - InlineItalic(std::string uuid) { - set_uuid(uuid); - } - - std::string to_html(std::vector childs_html, std::map meta_data) const override { - return " " + join(childs_html) + " "; - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "InlineItalic"; - } - }; - - // プレインテキストを表すクラス.
タグで囲まれる - class PlainText : public NonLeafNode { - public: - PlainText(std::string uuid) { - set_uuid(uuid); - } - - std::string to_html(std::vector childs_html, std::map meta_data) const override { - return "
" + join(childs_html) + ""; - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "PlainText"; - } - }; - - - // コードブロックを表すクラス.
タグで囲まれ、その中に
 タグが入る.
-    class CodeBlock : public LeafNode {
-        // コードの中身。
-        std::string code;
-
-        // Highlight.js によって正確にハイライトするために、言語を指定する必要がある。
-        // ```python -> python を持っておき、 `to_html` する際に  として出力する。
-        std::string language;
-    public:
-        CodeBlock(std::string code, std::string language, std::string uuid) : code(code), language(language) {
-            set_uuid(uuid);
-        }
-
-        std::string to_html(std::map meta_data) const override {
-            std::string code_class;
-
-            if (language == "") {
-                code_class = "language-plaintext";
-            } else {
-                code_class = "language-" + language;
-            }
-
-            return "
" + escape_for_html(code) + "
"; - } - - - std::map get_properties() const override { - return { - {"code", code}, - {"language", language} - }; - } - std::string get_classname() const override { - return "CodeBlock"; - } - }; - - // インラインのコードブロックを表すクラス.
タグで囲まれる. - class InlineCodeBlock : public LeafNode { - std::string code; - public: - InlineCodeBlock(std::string code, std::string uuid) : code(code) { - set_uuid(uuid); - } - - std::string to_html(std::map meta_data) const override { - return " " + escape_for_html(code) + " "; - } - - std::map get_properties() const override { - return { - {"code", code} - }; - } - std::string get_classname() const override { - return "InlineCodeBlock"; - } - }; - - - - // 独自記法のジャッジ付きプログラム実行環境を表すクラス。 - class Judge : public LeafNode { - // 問題のタイトル - std::string title; - - // サンプル入力(単一ファイルのパス) - std::string sample_in_path; - - // サンプル出力(単一ファイルのパス) - std::string sample_out_path; - - // ジャッジの種類. 誤差ジャッジ (err_{rate}) または 完全一致ジャッジ (equal) が指定される. - // C++側では特に処理は変わらず、 JS側に `judge_type[uuid]` を指定することで伝えれば切り替わる。 - std::string judge_type; - - // エディタにデフォルトで入力されてあるコード。 - std::string source; - - // 入力ファイルを表す glob パターン - std::string in_files_glob; - - // 出力ファイルを表す glob パターン - std::string out_files_glob; - public: - Judge(std::string title, std::string sample_in_path, std::string sample_out_path, std::string in_files_glob, std::string out_files_glob, std::string judge_type, std::string source, std::string uuid) : title(title), sample_in_path(sample_in_path), sample_out_path(sample_out_path), in_files_glob(in_files_glob), out_files_glob(out_files_glob), judge_type(judge_type), source(source) { - // ジャッジが`err_{rate}` または `equal` 以外の場合はエラーを出す. - if (judge_type.substr(0, 4) != "err_" && judge_type != "equal") { - throw ParseError("Invalid judge type. Expected `err_{rate}` or `equal`, but `" + judge_type + "` is given."); - } - set_uuid(uuid); - } - - std::string to_html(std::map meta_data) const override { - - - // タイトルを作る - std::string title_h3 = - "

WJ
" + title + "

\n"; - - // ここから ace editor を作る. - // まず editor を入れる用の div を作る. - std::string editor_div = "
\n"; - - // ace editor の設定をする. - // テーマは meta_data から取得する. - - std::string source_code = join(read_file(source), "\n"); - - std::string ace_editor = "" - "\n"; - - // サンプル入力を読み込む. - std::string sample_in = join(read_file(sample_in_path)); - std::string sample_out = join(read_file(sample_out_path)); - - // サンプル入力と出力を表示する用の div を作る. - std::string sample_in_area = - "
サンプルの入力
" - "
" + sample_in + "
\n"; - - std::string sample_out_area = - "
出力
" - "
\n";
-
-            std::string expect_out_area =
-                "
サンプルの答え
" - "
" + sample_out + "
\n"; - - // データを定義する。 all_input, all_output, all_sample_input, all_sample_output, problem_status は JS側で使うために定義する. - // また、目次生成のために page_contents にも追加する. - std::string define_data = - " \n"; - - // テスト実行ボタンと提出ボタンを作る. - std::string test_run_button = - "\n"; - - std::string submit_button = - "\n"; - - // ジャッジの種類を JS側に伝えるためのコードを作る. - std::string judge_code = - "\n"; - - - std::string output = title_h3 + editor_div + ace_editor + sample_in_area + sample_out_area + expect_out_area + define_data + test_run_button + submit_button + judge_code; - - loaded_pyodide = true; - - return output; - } - - std::map get_properties() const override { - return { - {"title", title}, - {"sample_in_path", sample_in_path}, - {"sample_out_path", sample_out_path}, - {"in_files_glob", in_files_glob}, - {"out_files_glob", out_files_glob}, - {"judge_type", judge_type}, - {"source", source} - }; - } - std::string get_classname() const override { - return "Judge"; - } - }; - - - // 実行可能なコードブロックを表すクラス. - // `Judge` と異なり、こちらはジャッジを提供せず、単に実行のみを行う。 - class ExecutableCodeBlock : public LeafNode { - std::string code; - public: - ExecutableCodeBlock(std::string code, std::string uuid) : code(code) { - set_uuid(uuid); - } - - std::string to_html(std::map meta_data) const override { - // コード全体を表示するために、何行のコードかを調べておく - int n_line = std::count(code.begin(), code.end(), '\n'); - - // `minLines` と `maxLines` を適切に設定してコード全体を表示するようにする。 - std::string ace_editor = "" - "\n"; - - // 通常の出力に加えて、matplotlib のプロットを表示するための div を作っておく。 - std::string editor_div = "
\n
\n"; - std::string out_area = "
\n";
-            std::string plot_area = "
\n"; - - std::string run_button = - "\n"; - - std::string output = editor_div + ace_editor + out_area + plot_area + run_button; - - loaded_pyodide = true; - - return output; - } - - std::map get_properties() const override { - return { - {"code", code} - }; - } - std::string get_classname() const override { - return "ExecutableCodeBlock"; - } - }; - - // ライブラリの読み込みをする独自記法. - class LoadLib : public LeafNode { - // 読み込むライブラリの名前のリスト - std::vector libs; - - public: - LoadLib(std::vector libs, std::string uuid) : libs(libs) { - set_uuid(uuid); - } - - // use_libs に追加しておくと JS側で読み込み処理を行う。 - std::string to_html(std::map meta_data) const override { - std::string output = ""; - for (std::string lib : libs) { - output += ""; - } - return output; - }; - - std::map get_properties() const override { - return { - {"libs", join(libs)} - }; - } - std::string get_classname() const override { - return "LoadLib"; - } - }; - - // リンク表記を表すクラス. タグで囲まれる. - class InlineUrl : public LeafNode { - std::string url; - std::string alt; - public: - InlineUrl(std::string url, std::string alt, std::string uuid) : url(url), alt(alt) { - set_uuid(uuid); - } - - std::string to_html(std::map meta_data) const override { - return " " + alt + " "; - } - - std::map get_properties() const override { - return { - {"url", url}, - {"alt", alt} - }; - } - std::string get_classname() const override { - return "InlineUrl"; - } - }; - - // 画像表記を表すクラス. - class InlineImage : public LeafNode { - std::string url; - std::string caption; - - public: - InlineImage(std::string url, std::string caption, std::string uuid) : url(url), caption(caption) { - set_uuid(uuid); - } - - // 
タグを使うことで キャプションなどをつける。 - std::string to_html(std::map meta_data) const override { - std::string output = ""; - std::string figcaption = "
" + caption + "
"; - return "
" + output + figcaption + "
"; - } - - std::map get_properties() const override { - return { - {"url", url}, - {"caption", caption} - }; - } - std::string get_classname() const override { - return "InlineImage"; - } - }; - - // 改行を表すクラス - class NewLine : public LeafNode { - public: - NewLine(std::string uuid) { - set_uuid(uuid); - } - - std::string to_html(std::map meta_data) const override { - return "
"; - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "NewLine"; - } - }; - - // 箇条書き(番号なし) を表すクラス - class ListBlock : public NonLeafNode { - public: - ListBlock(std::string uuid) { - set_uuid(uuid); - } - - std::string to_html(std::vector childs_html, std::map meta_data) const override { - return "
    " + join(childs_html) + "
"; - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "ListBlock"; - } - }; - - // 番号付き箇条書きを表すクラス - class EnumerateBlock : public NonLeafNode { - public: - EnumerateBlock(std::string uuid) { - set_uuid(uuid); - } - - std::string to_html(std::vector childs_html, std::map meta_data) const override { - return "
    " + join(childs_html) + "
"; - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "EnumerateBlock"; - } - }; - - // 箇条書きの要素を表すクラス - class Item : public NonLeafNode { - public: - Item(std::string uuid) { - set_uuid(uuid); - } - - std::string to_html(std::vector childs_html, std::map meta_data) const override { - return "
  • " + join(childs_html) + "
  • "; - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "Item"; - } - }; - - - // テーブルを表すクラス - class Table : public NonLeafNode { - std::vector> columns; - int n_row; - int n_col; - - // 各列について、左寄せ(0), 中央寄せ(1), 右寄せ(2) のいずれかを指定する. - std::vector col_format; - std::vector col_names; - public: - Table(std::vector> columns, int n_row, int n_col, std::vector col_format, std::string uuid) : columns(columns), n_row(n_row), n_col(n_col), col_format(col_format) { - set_uuid(uuid); - } - - // テーブル用の特別な to_html. テーブルの render でしか呼ばれないので引数が違ってもOK. - std::string to_html(std::vector headers_html, std::vector childs_html) const { - std::string output = "\n"; - output += "\n"; - output += "\n"; - for (int i = 0; i < n_col; i++) { - std::string align = col_format[i] == 0 ? "left" : col_format[i] == 1 ? "center" : "right"; - output += "\n"; - } - output += "\n"; - output += "\n"; - output += "\n"; - for (int i = 0; i < n_row; i++) { - output += "\n"; - for (int j = 0; j < n_col; j++) { - std::string align = col_format[j] == 0 ? "left" : col_format[j] == 1 ? "center" : "right"; - output += "\n"; - } - output += "\n"; - } - output += "\n"; - output += "
    " + headers_html[i] + "
    " + childs_html[i * n_col + j] + "
    \n"; - return output; - } - - // 仮想クラスにならないようにオーバーライドする. - std::string to_html(std::vector childs_html, std::map meta_data) const override { - return "dummy"; - } - - std::map get_properties() const override { - std::string col_format_str = ""; - for (int i = 0; i < n_col; i++) { - std::string align = col_format[i] == 0 ? "l" : col_format[i] == 1 ? "c" : "r"; - col_format_str += align; - } - - std::string col_names_str = "["; - - for (int i = 0; i < n_col; i++) { - col_names_str += "\"" + col_names[i] + "\""; - if (i != n_col - 1) { - col_names_str += ", "; - } - } - - col_names_str += "]"; - - return { - {"n_row", std::to_string(n_row)}, - {"n_col", std::to_string(n_col)}, - {"col_format", col_format_str}, - {"col_names", col_names_str} - }; - } - std::string get_classname() const override { - return "Table"; - } - - - std::string render(std::map meta_data) { - std::vector columns_html; - for (auto child : columns) { - columns_html.push_back(child->render(meta_data)); - } - - std::vector contents_html; - - for (auto child : childs) { - if (child->is_leaf()) { - contents_html.push_back(std::dynamic_pointer_cast(child)->to_html(meta_data)); - } - else { - contents_html.push_back(std::dynamic_pointer_cast(child)->render(meta_data)); - } - } - - return to_html(columns_html, contents_html); - } - }; - - - // 水平線を表すクラス - class HorizontalLine : public LeafNode { - public: - HorizontalLine(std::string uuid) { - set_uuid(uuid); - } - - std::string to_html(std::map meta_data) const override { - return "
    "; - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "HorizontalLine"; - } - }; - - - // 引用を表すクラス - class Quote : public NonLeafNode { - public: - Quote(std::string uuid) { - set_uuid(uuid); - } - - std::string to_html(std::vector childs_html, std::map meta_data) const override { - return "
    " + join(childs_html) + "
    "; - } - - std::map get_properties() const override { - return { - }; - } - std::string get_classname() const override { - return "Quote"; - } - }; - - - class DivBlock : public NonLeafNode { - std::string div_class; - public: - DivBlock(std::string div_class, std::string uuid) : div_class(div_class) { - set_uuid(uuid); - } - - std::string to_html(std::vector childs_html, std::map meta_data) const override { - return "
    " + join(childs_html) + "
    "; - } - - std::map get_properties() const override { - return { - {"div_classe", div_class}, - }; - } - std::string get_classname() const override { - return "DivBlock"; - } - }; -} \ No newline at end of file +struct uuid_gen_ { + int operator()(){ + static int uuid = 0; + return uuid++; + } +} uuid_gen; + +std::string ASTNode::to_json() const { + std::map properties = get_properties(); + + std::string json = "{"; + + // add classname + json += "\"class\":\"" + escape(get_classname()) + "\","; + + // add uuid + json += "\"uuid\":\"" + escape(get_uuid_str()) + "\","; + + // add other properties + for (auto property : properties){ + json += "\"" + property.first + "\":\"" + escape(property.second) + "\","; + } + + // has childs + if (!childs.empty()){ + json += "\"childs\":["; + for (auto child : childs){ + json += child->to_json(); + json += ','; + } + // has 1 or more childs, so the last char is , + // delete the , + json.pop_back(); + json += "],"; + } + + // the last char is , regardless of throughing `if` + // delete the , + json.pop_back(); + json += '}'; + return json; +} + +std::string ASTNode::to_dot() const { + std::map properties = get_properties(); + + std::string node = get_uuid_str(); + std::string label = ""; + + for (int i = 1; auto property : properties){ + label += " " + property.first + ": " + escape(property.second) + " | "; + i++; + } + + // is Leaf + if (childs.empty()){ + return node + "[label=\"" + get_classname() + " | " + label + "\", shape=\"record\"]\n"; + } + + // add child node + std::string childs_dot = ""; + for (auto child : childs){ + childs_dot += child->to_dot(); + } + + // connect child node + std::string edges = ""; + for (auto child : childs){ + edges += node + ":f" + std::to_string(edges.length()) + " -> " + child->get_uuid_str() + "\n"; + } + + // if this node is root, you must format returned string r as follows : + // r = "digraph G {\n graph [labelloc=\"t\"; \n ]\n" + r + "}"; + return node + "[label=\" " + get_classname() + " | " + label + "\", shape=\"record\"]\n" + childs_dot + edges; +} + +std::string ASTNode::get_uuid_str() const { + return std::to_string(uuid); +} + +void ASTNode::set_uuid(){ + uuid = uuid_gen(); +} + +void ASTNode::add_child(std::shared_ptr child){ + childs.push_back(child); +} + +std::string ASTNode::concatenated_childs_html() const { + std::string ret = ""; + for (auto child : childs){ + ret += child->to_html(); + } + return ret; +} + +} // namespace almo \ No newline at end of file diff --git a/src/interfaces/ast.hpp b/src/interfaces/ast.hpp new file mode 100644 index 0000000..e517625 --- /dev/null +++ b/src/interfaces/ast.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +namespace almo { + +struct ASTNode { + + // html + virtual std::string to_html() const = 0; + + // json + std::string to_json() const ; + + // dot + std::string to_dot() const ; + + // properties + virtual std::map get_properties() const = 0; + + // classname + virtual std::string get_classname() const = 0; + + // get uuid as string + std::string get_uuid_str() const ; + + // set uuid + void set_uuid(); + + // add child + void add_child(std::shared_ptr child); + + // child's html + std::string concatenated_childs_html() const ; + + protected: + std::vector> childs; + private: + int uuid; +}; + +} // namespace almo \ No newline at end of file diff --git a/src/interfaces/parse.hpp b/src/interfaces/parse.hpp new file mode 100644 index 0000000..a7ca1e4 --- /dev/null +++ b/src/interfaces/parse.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include "../reader.hpp" +#include "ast.hpp" + +namespace almo { + +// parse a line contain only inline syntax +struct InlineParser { + template + static void process_inline(const std::string &str, ASTNode &ast, Syntax&& syn, int pos); + + template + static void process_inline(const std::string &str, ASTNode &ast, Syntax&& syn, int pos, HeadSyntax&& hsyn, TailSyntax&&... tsyn); + + static void process(const std::string &str, ASTNode &ast); +}; + +// parse an entire markdown and detect block syntax +struct MarkdownParser { + Reader reader; + MarkdownParser(const std::vector &lines, std::map &meta_data); + + void process_block(std::string &str_inline, ASTNode &ast); + + template + void process_block(std::string &str_inline, ASTNode &ast, HeadSyntax&& hsyn, TailSyntax&&... tsyn); + + void process(ASTNode &ast); +}; + +} // namespace almo \ No newline at end of file diff --git a/src/interfaces/syntax.hpp b/src/interfaces/syntax.hpp new file mode 100644 index 0000000..def359b --- /dev/null +++ b/src/interfaces/syntax.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "../reader.hpp" +#include "ast.hpp" +#include +#include + +namespace almo { + +struct BlockSyntax { + // determine if it matches as the syntax + virtual bool operator()(Reader &read) const = 0; + + // update read and ast + // assume that it matches as the syntax + virtual void operator()(Reader &read, ASTNode &ast) const = 0; +}; + +struct InlineSyntax { + // determine if the string matches as the syntax + // if so, calculate minimum position of the matched location + // example : + // rex = $(.*)$; + // str = "ab$c+d$e"; + // ---> substring located at [2,7) detected : '$c+d$' + // ---> return 2 + // ( ---> captured substring is located at [3,6) : 'c+d' ) + virtual int operator()(const std::string &str) const = 0; + + // update read and ast + // assume that the string matches as the syntax + virtual void operator()(const std::string &str, ASTNode &ast) const = 0; +}; + +} // namespace almo \ No newline at end of file diff --git a/src/parse.hpp b/src/parse.hpp index 5bec2e2..167ebfc 100644 --- a/src/parse.hpp +++ b/src/parse.hpp @@ -1,865 +1,93 @@ #pragma once -#include -#include -#include -#include -#include -#include - +#include +#include "interfaces/parse.hpp" #include "ast.hpp" +#include "syntax_all.hpp" namespace almo { - struct InlineParser; - struct BlockParser; - struct InlineParser { - std::map> map; - std::shared_ptr processer(std::string s) { - Block root = Block(uuid()); - map.clear(); - while (1) { - if (std::regex_match(s, code_block_regex)) { - auto& memo = map["inline_code_block"]; - int id = memo.size(); - std::string format = "$1<__code>" + std::to_string(id) + "$3"; - memo.push_back(std::regex_replace(s, code_block_regex, "$2")); - s = std::regex_replace(s, code_block_regex, format); - } - else if (std::regex_match(s, math_regex)) { - auto& memo = map["inline_math"]; - int id = memo.size(); - std::string format = "$1<__math>" + std::to_string(id) + "$3"; - memo.push_back(std::regex_replace(s, math_regex, "$2")); - s = std::regex_replace(s, math_regex, format); - } - else if (std::regex_match(s, image_regex)) { - auto& memo = map["inline_image"]; - int id_url = memo.size(); - int id_str = id_url + 1; - std::string format = "$1<__image=" + std::to_string(id_url) + ">" + std::to_string(id_str) + "$4"; - memo.push_back(std::regex_replace(s, image_regex, "$3")); - memo.push_back(std::regex_replace(s, image_regex, "$2")); - s = std::regex_replace(s, image_regex, format); - } - else if (std::regex_match(s, url_regex)) { - auto& memo = map["inline_url"]; - int id_url = memo.size(); - int id_str = id_url + 1; - std::string format = "$1<__url=" + std::to_string(id_url) + ">" + std::to_string(id_str) + "$4"; - memo.push_back(std::regex_replace(s, url_regex, "$3")); - memo.push_back(std::regex_replace(s, url_regex, "$2")); - - s = std::regex_replace(s, url_regex, format); - } - else if (std::regex_match(s, overline_regex)) { - auto& memo = map["overline"]; - int id = memo.size(); - std::string format = "$1<__overline>" + std::to_string(id) + "$3"; - memo.push_back(std::regex_replace(s, overline_regex, "$2")); - s = std::regex_replace(s, overline_regex, format); - } - else if (std::regex_match(s, strong_regex)) { - auto& memo = map["strong"]; - int id = memo.size(); - std::string format = "$1<__strong>" + std::to_string(id) + "$3"; - memo.push_back(std::regex_replace(s, strong_regex, "$2")); - s = std::regex_replace(s, strong_regex, format); - } - else if (std::regex_match(s, italic_regex)) { - auto& memo = map["italic"]; - int id = memo.size(); - std::string format = "$1<__i>" + std::to_string(id) + "$3"; - memo.push_back(std::regex_replace(s, italic_regex, "$2")); - s = std::regex_replace(s, italic_regex, format); - } - else break; - } - root.childs.push_back(std::make_shared(dfs(s))); - return std::make_shared(root); - } - - private: - Block dfs(std::string s) { - Block root = Block(uuid()); - if (std::regex_match(s, italic_html_regex)) { - InlineItalic node = InlineItalic(uuid()); - int id = std::stoi(std::regex_replace(s, italic_html_regex, "$2")); - std::string before = std::regex_replace(s, italic_html_regex, "$1"); - std::string content = map["italic"][id]; - std::string after = std::regex_replace(s, italic_html_regex, "$3"); - - root.childs.push_back(std::make_shared(dfs(before))); - node.childs.push_back(std::make_shared(dfs(content))); - root.childs.push_back(std::make_shared(node)); - root.childs.push_back(std::make_shared(dfs(after))); - } - else if (std::regex_match(s, strong_html_regex)) { - InlineStrong node = InlineStrong(uuid()); - int id = std::stoi(std::regex_replace(s, strong_html_regex, "$2")); - std::string before = std::regex_replace(s, strong_html_regex, "$1"); - std::string content = map["strong"][id]; - std::string after = std::regex_replace(s, strong_html_regex, "$3"); - - root.childs.push_back(std::make_shared(dfs(before))); - node.childs.push_back(std::make_shared(dfs(content))); - root.childs.push_back(std::make_shared(node)); - root.childs.push_back(std::make_shared(dfs(after))); - } - else if (std::regex_match(s, overline_html_regex)) { - InlineOverline node = InlineOverline(uuid()); - int id = std::stoi(std::regex_replace(s, overline_html_regex, "$2")); - std::string before = std::regex_replace(s, overline_html_regex, "$1"); - std::string content = map["overline"][id]; - std::string after = std::regex_replace(s, overline_html_regex, "$3"); - - root.childs.push_back(std::make_shared(dfs(before))); - node.childs.push_back(std::make_shared(dfs(content))); - root.childs.push_back(std::make_shared(node)); - root.childs.push_back(std::make_shared(dfs(after))); - } - else if (std::regex_match(s, url_html_regex)) { - int id_url = std::stoi(std::regex_replace(s, url_html_regex, "$2")); - int id_str = std::stoi(std::regex_replace(s, url_html_regex, "$3")); - std::string before = std::regex_replace(s, url_html_regex, "$1"); - std::string after = std::regex_replace(s, url_html_regex, "$4"); - - std::string url = map["inline_url"][id_url]; - std::string alt = map["inline_url"][id_str]; - - InlineUrl node = InlineUrl(url, alt, uuid()); - - root.childs.push_back(std::make_shared(dfs(before))); - // Url は Leaf なので、子要素を持たないからそのまま追加する。 - root.childs.push_back(std::make_shared(node)); - root.childs.push_back(std::make_shared(dfs(after))); - - } - else if (std::regex_match(s, image_html_regex)) { - int id_url = std::stoi(std::regex_replace(s, image_html_regex, "$2")); - int id_str = std::stoi(std::regex_replace(s, image_html_regex, "$3")); - std::string before = std::regex_replace(s, image_html_regex, "$1"); - std::string after = std::regex_replace(s, image_html_regex, "$4"); - - std::string url = map["inline_image"][id_url]; - std::string caption = map["inline_image"][id_str]; - InlineImage node = InlineImage(url, caption, uuid()); - - root.childs.push_back(std::make_shared(dfs(before))); - root.childs.push_back(std::make_shared(node)); - root.childs.push_back(std::make_shared(dfs(after))); - } - else if (std::regex_match(s, math_html_regex)) { - int id = std::stoi(std::regex_replace(s, math_html_regex, "$2")); - - auto before = std::regex_replace(s, math_html_regex, "$1"); - auto after = std::regex_replace(s, math_html_regex, "$3"); - - std::string expression = map["inline_math"][id]; - - InlineMath node = InlineMath(expression, uuid()); - - root.childs.push_back(std::make_shared(dfs(before))); - root.childs.push_back(std::make_shared(node)); - root.childs.push_back(std::make_shared(dfs(after))); - } - else if (std::regex_match(s, code_block_html_regex)) { - int id = std::stoi(std::regex_replace(s, code_block_html_regex, "$2")); - std::string before = std::regex_replace(s, code_block_html_regex, "$1"); - std::string after = std::regex_replace(s, code_block_html_regex, "$3"); - std::string code = map["inline_code_block"][id]; - - InlineCodeBlock node = InlineCodeBlock(code, uuid()); - - root.childs.push_back(std::make_shared(dfs(before))); - root.childs.push_back(std::make_shared(node)); - root.childs.push_back(std::make_shared(dfs(after))); - - } - else { - std::string text = ""; - RawText node = RawText(s, uuid()); - root.childs.push_back(std::make_shared(node)); - } - return root; - } - - const std::regex code_block_regex = std::regex("(.*)\\`(.*)\\`(.*)"); - const std::regex math_regex = std::regex("(.*)\\$(.*)\\$(.*)"); - const std::regex image_regex = std::regex("(.*)\\!\\[(.*)\\]\\((.*)\\)(.*)"); - const std::regex url_regex = std::regex("(.*)\\[(.*)\\]\\((.*)\\)(.*)"); - const std::regex overline_regex = std::regex("(.*)\\~\\~(.*)\\~\\~(.*)"); - const std::regex strong_regex = std::regex("(.*)\\*\\*(.*)\\*\\*(.*)"); - const std::regex italic_regex = std::regex("(.*)\\*(.*)\\*(.*)"); - const std::regex code_block_html_regex = std::regex("(.*)<__code>(.*)(.*)"); - const std::regex math_html_regex = std::regex("(.*)<__math>(.*)(.*)"); - const std::regex image_html_regex = std::regex("(.*)<__image=(.*)>(.*)(.*)"); - const std::regex url_html_regex = std::regex("(.*)<__url=(.*)>(.*)(.*)"); - const std::regex overline_html_regex = std::regex("(.*)<__overline>(.*)(.*)"); - const std::regex strong_html_regex = std::regex("(.*)<__strong>(.*)(.*)"); - const std::regex italic_html_regex = std::regex("(.*)<__i>(.*)(.*)"); - - }; - - // md全体をパースするための関数をメンバーに持つ構造体です。 - struct BlockParser { - Block processer(std::vector lines) { - InlineParser inline_parser = InlineParser(); - Block root = Block(uuid()); - int idx = 0; - while (idx < (int)lines.size()) { - std::string line = lines[idx]; - if (line.starts_with("# ")) { - Header node = Header(1, uuid()); - node.childs.push_back(inline_parser.processer(line.substr(2))); - root.childs.push_back(std::make_shared
    (node)); - } - else if (line.starts_with("## ")) { - Header node = Header(2, uuid()); - node.childs.push_back(inline_parser.processer(line.substr(3))); - root.childs.push_back(std::make_shared
    (node)); - } - else if (line.starts_with("### ")) { - Header node = Header(3, uuid()); - node.childs.push_back(inline_parser.processer(line.substr(4))); - root.childs.push_back(std::make_shared
    (node)); - } - else if (line.starts_with("#### ")) { - Header node = Header(4, uuid()); - node.childs.push_back(inline_parser.processer(line.substr(5))); - root.childs.push_back(std::make_shared
    (node)); - } - else if (line.starts_with("##### ")) { - Header node = Header(5, uuid()); - node.childs.push_back(inline_parser.processer(line.substr(6))); - root.childs.push_back(std::make_shared
    (node)); - } - else if (line.starts_with("###### ")) { - Header node = Header(6, uuid()); - node.childs.push_back(inline_parser.processer(line.substr(6))); - root.childs.push_back(std::make_shared
    (node)); - } - else if (line == ":::judge") { - idx++; - std::map judge_info; - - // Judge の設定のうち、必ず必要なもの - std::vector required_args = { "title", "sample_in", "sample_out", "in", "out" }; - - // Judge の設定のうち、オプションのものとそのデフォルト値 - std::map optional_args = { - { "judge", "equal" }, - { "source", "" } - }; - - // 1行ずつ読み込み、judge_info に情報を追加していく - while (idx < (int)lines.size()) { - if (rtrim(lines[idx]) == ":::") break; - std::string key = lines[idx].substr(0, lines[idx].find("=")); - std::string value = lines[idx].substr(lines[idx].find("=") + 1); - judge_info[key] = value; - idx++; - } - - // 必須の引数が揃っているかチェック. なければ SyntaxError を投げる - for (std::string arg : required_args) { - if (judge_info.find(arg) == judge_info.end()) { - // title がある場合 - if (judge_info.find("title") != judge_info.end()) { - throw SyntaxError("問題" + judge_info["title"] + "の引数 " + arg + " がありません. 引数を追加してください."); - } - else { - throw SyntaxError("問題タイトルがありません. 引数を追加してください."); - } - } - } - - // オプションの引数が揃っているかチェック. なければデフォルト値を入れる - for (auto [arg, default_value] : optional_args) { - if (judge_info.find(arg) == judge_info.end()) { - judge_info[arg] = default_value; - } - } - - Judge node = Judge( - judge_info["title"], - judge_info["sample_in"], - judge_info["sample_out"], - judge_info["in"], - judge_info["out"], - judge_info["judge"], - judge_info["source"], - uuid() - ); - - root.childs.push_back(std::make_shared(node)); - } - else if (line == ":::code") { - idx++; - assert(idx < (int)lines.size()); - std::string code; - while (idx < (int)lines.size()) { - if (rtrim(lines[idx]) == ":::") break; - code += lines[idx] + "\n"; - idx++; - } - ExecutableCodeBlock node = ExecutableCodeBlock(code, uuid()); - root.childs.push_back(std::make_shared(node)); - } - else if (line == ":::loadlib") { - idx++; - assert(idx < (int)lines.size()); - std::vector libs; - while (idx < (int)lines.size()) { - if (rtrim(lines[idx]) == ":::") break; - libs.push_back(lines[idx]); - idx++; - } - LoadLib node = LoadLib(libs, uuid()); - root.childs.push_back(std::make_shared(node)); - } - else if (line.starts_with("```")) { - std::string language = line.substr(3); - idx++; - std::string code; - while (idx < (int)lines.size()) { - if (rtrim(lines[idx]) == "```") break; - code += lines[idx] + "\n"; - idx++; - } - - CodeBlock node = CodeBlock(code, language, uuid()); - root.childs.push_back(std::make_shared(node)); - } - else if (line.starts_with(":::")) { - std::stack> scopes; - std::string title = line.substr(3); - - DivBlock root_div = DivBlock(title, uuid()); - scopes.push(std::make_shared(root_div)); - - idx++; - std::vector text; - - if (rtrim(lines[idx]) == ":::") { - root.childs.push_back(scopes.top()); - } - else { - while (idx < (int)lines.size()) { - - if (rtrim(lines[idx]) == ":::") { - // 一つぶん入れ子が終了 - BlockParser parser = BlockParser(); - Block contents = parser.processer(text); - std::shared_ptr content_ptr = std::make_shared(contents); - scopes.top()->childs.push_back(content_ptr); - - text = {}; - - if (scopes.size() == 1) { - // 全ての入れ子が終了 - root.childs.push_back(scopes.top()); - break; - } - else { - // 一つぶん入れ子を抜ける - scopes.pop(); - } - } - else if (lines[idx].starts_with(":::")) { - - // 新しい DivBlock の定義 - // 収集したテキストをパースして現在のスコープに追加する。 - BlockParser parser = BlockParser(); - Block contents = parser.processer(text); - std::shared_ptr content_ptr = std::make_shared(contents); - scopes.top()->childs.push_back(content_ptr); - - // 新しいスコープを作成 - DivBlock new_node = DivBlock(lines[idx].substr(3), uuid()); - std::shared_ptr new_node_ptr = std::make_shared(new_node); - scopes.top()->childs.push_back(new_node_ptr); - - scopes.push(new_node_ptr); - - text = {}; - } - else { - text.push_back(lines[idx]); - } - idx++; - } - } - } - else if (line == "$$") { - idx++; - std::string expression; - while (idx < (int)lines.size()) { - if (lines[idx] == "$$") break; - expression += lines[idx] + "\n"; - idx++; - } - - MathBlock node = MathBlock(expression, uuid()); - root.childs.push_back(std::make_shared(node)); - } - else if (line.starts_with("- ")) { - // まずは今の行(リストの先頭)のテキストを取り出す。 - // 次の行が、 空行またはファイルの末端またはリストの次の要素の場合、テキストは終了。 - // 逆にこれ以外の場合、テキストは継続。 - // 例) - Hello, - // This is a pen. - // この場合、リストの先頭のテキストは "Hello, This is a pen." となる。(This is a pen. の部分も含む点に注意。) - - // 以下のアルゴリズムでテキストを取り出す。 - // 1. 次の行の内容を読む. - // 2. 以下の条件がどれか true になるまで読み続ける - //   条件: 次の行が、  - // 1. 空行(lines[idx + 1] == "") - // 2. ファイルの末端(idx + 1 == size) - // 3. リストの次の要素 - - - // 1, 2 が見つかったら終了. - // 3 について、 リストの次の要素は、 - // - current_prefix - // - current_prefix にインデント(スペース2) を加えたもの - // - インデントを2n個削除したもの - // から始まる - // current_prefix 始まりなら 上のアルゴリズムでテキストを取得して, Item を現在着目しているスコープの子要素の Item として追加する。 - // current_prefix + インデント 始まりなら、 リストが一つネストされているので、 - // 新しく ListBlock を作成して、 スタックに積む。 同様にテキストを取得して、 Item を現在着目しているスコープの子要素(Item) として追加する。 - // インデントを削除したものの場合、削除した個数分だけスタックから pop すればよい。 - - // 現在着目しているスコープを表すためのスタック - // 一番上にあるものが現在着目しているスコープ - std::stack> scopes; - - ListBlock root_list = ListBlock(uuid()); - scopes.push(std::make_shared(root_list)); - - std::string current_prefix = "- "; - std::string INDENT = " "; - - // 条件1, 2 に当てはまるかどうかを判定する - auto is_end = [&lines](int idx) -> bool { - if (idx == (int)(lines.size())) return true; - if (lines[idx] == "") return true; - return false; - }; - - // 先頭のスペースを削除したとき、 "- " 始まりになるかどうかを判定する・ ワーニング出してやるのに使う。 - auto is_list_def = [](std::string s) -> bool { - while (s[0] == ' ') s = s.substr(1); - return s.starts_with("- "); - }; - - // 今集めているテキスト - std::string text = ""; - - while (true) { - text += remove_listdef(ltrim(lines[idx])); - - // 条件 1, 2 - if (is_end(idx + 1)) { - Item item = Item(uuid()); - item.childs.push_back(inline_parser.processer(text)); - // 現在のスタックのトップにある ListBlock に Item を追加する。 - scopes.top()->childs.push_back(std::make_shared(item)); - - break; - } - - // 条件 3 - // current_prefix から始まる場合 - if (lines[idx + 1].starts_with(current_prefix)) { - // 継続終了。収集してきたテキストを追加。 - Item item = Item(uuid()); - item.childs.push_back(inline_parser.processer(text)); - - // 現在のスタックのトップにある ListBlock に Item を追加する。 - scopes.top()->childs.push_back(std::make_shared(item)); - - // 現在のスコープのトップの要素数 - text = ""; - - } - else if (lines[idx + 1].starts_with(INDENT + current_prefix)) { - // 継続終了。収集してきたテキストを追加。 - Item item = Item(uuid()); - item.childs.push_back(inline_parser.processer(text)); - - // 現在のスタックのトップにある ListBlock に Item を追加する。 - scopes.top()->childs.push_back(std::make_shared(item)); - - - text = ""; - - // ネストが発生しているので、現在着目しているスコープを変更する。 - // 新しく ListBlock を作成 - ListBlock new_node = ListBlock(uuid()); - std::shared_ptr new_node_ptr = std::make_shared(new_node); - - - // 今のスコープの子ノードにして、 - scopes.top()->childs.push_back(new_node_ptr); - - // スコープのスタックを更新。 - scopes.push(new_node_ptr); - - // prefix を更新する。 - current_prefix = INDENT + current_prefix; - - } - else if ((int)scopes.size() > 1) { - for (int i = 1; i < (int)scopes.size(); i++) - { - // インデントを削除していく. 1つのインデントは 2文字なので、2文字ずつ削除していく。 - current_prefix = current_prefix.substr(2); - if (lines[idx + 1].starts_with(current_prefix)) { - // 継続終了. 収集してきたテキストを追加。 - Item item = Item(uuid()); - item.childs.push_back(inline_parser.processer(text)); - - // 現在のスタックのトップにある ListBlock に Item を追加する。 - scopes.top()->childs.push_back(std::make_shared(item)); - text = ""; - - // i 個数分上のスコープに巻き戻す - for (int j = 0; j < i; j++) { - scopes.pop(); - } - break; - } - } - } - else if (is_list_def(lines[idx + 1])) { - // リストの定義っぽいのにインデントがあっていないということなので、警告を出してやる - std::cerr << "Warning " << idx + 1 << "行目:" << std::endl; - std::cerr << "リストの定義が継続している可能性がありますが、インデント幅が一致しません。" << std::endl; - std::cerr << "リストの継続を意図している場合、インデントとしてスペース2個を使っているか確認してください。" << std::endl; - } - idx++; - } - // スタックの一番最後の要素を追加する。 - while (scopes.size() > 1) { - scopes.pop(); - } - - root.childs.push_back(scopes.top()); - } - else if (std::regex_match(line, std::regex("\\d+\\. (.*)"))) { - - // 現在着目しているスコープを表すためのスタック - std::stack> scopes; - - EnumerateBlock root_enum = EnumerateBlock(uuid()); - scopes.push(std::make_shared(root_enum)); - - std::string current_match = "\\d+\\. (.*)"; - // こっちはスペース3個であることに注意!!! - std::string INDENT = " "; - - // 条件1, 2 に当てはまるかどうかを判定する - auto is_end = [&lines](int idx) -> bool { - if (idx == (int)(lines.size())) return true; - if (lines[idx] == "") return true; - return false; - }; - - // 先頭のスペースを削除したとき、 "- " 始まりになるかどうかを判定する・ ワーニング出してやるのに使う。 - auto is_enum_def = [](std::string s) -> bool { - while (s[0] == ' ') s = s.substr(1); - return std::regex_match(s, std::regex("\\d+\\. (.*)")); - }; - - // 今集めているテキスト - std::string text = ""; - - while (true) { - // 現在の行のテキストを取る。 - std::smatch match; - std::regex_match(lines[idx], match, std::regex(current_match)); - text += match[1]; - - // 条件 1, 2 - if (is_end(idx + 1)) break; - - // 条件 3 - // current_prefix から始まる場合 - if (std::regex_match(lines[idx + 1], std::regex(current_match))) { - // 継続終了。収集してきたテキストを追加。 - Item item = Item(uuid()); - item.childs.push_back(inline_parser.processer(text)); - // 現在のスタックのトップにある EnumerateBlock に Item を追加する。 - scopes.top()->childs.push_back(std::make_shared(item)); - text = ""; - - // ネストは発生していないので、現在着目しているスコープは変化しない。 - } - else if (std::regex_match(lines[idx + 1], std::regex(INDENT + current_match))) { - // 継続終了。収集してきたテキストを追加。 - Item item = Item(uuid()); - item.childs.push_back(inline_parser.processer(text)); - // 現在のスタックのトップにある EnumerateBlock に Item を追加する。 - scopes.top()->childs.push_back(std::make_shared(item)); - - text = ""; - - // ネストが発生しているので、現在着目しているスコープを変更する。 - // 新しく EnumerateBlock を作成して、 現在着目しているスコープに積む。 - EnumerateBlock new_node = EnumerateBlock(uuid()); - std::shared_ptr new_node_ptr = std::make_shared(new_node); - scopes.top()->childs.push_back(new_node_ptr); - - scopes.push(new_node_ptr); - - // current_match を更新する。 - current_match = INDENT + current_match; - } - else if ((int)scopes.size() > 1) { - // インデントを削除していく. 1つのインデントは "3"文字なので、"3"文字ずつ削除していく。 - for (int i = 1; i < (int)scopes.size(); i++) - { - current_match = current_match.substr(3); - if (std::regex_match(lines[idx + 1], std::regex(current_match))) { - // 継続終了. 収集してきたテキストを追加。 - Item item = Item(uuid()); - item.childs.push_back(inline_parser.processer(text)); - // 現在のスタックのトップにある EnumerateBlock に Item を追加する。 - scopes.top()->childs.push_back(std::make_shared(item)); - text = ""; - - // i 個数分上のスコープのリストになる - for (int j = 0; j < i; j++) { - scopes.pop(); - } - break; - } - } - } - else if (is_enum_def(lines[idx + 1])) { - // 番号付きリストの定義っぽいのにインデントがあっていないということなので、警告を出してやる - std::cerr << "Warning " << idx + 1 << "行目:" << std::endl; - std::cerr << "番号付きリストの定義が継続している可能性がありますが、インデント幅が一致しません。" << std::endl; - std::cerr << "番号付きリストの継続を意図している場合、インデントとしてスペース3個を使っているか確認してください。" << std::endl; - } - idx++; - } - // スタックの一番最後の要素を追加する。 - while (scopes.size() > 1) { - scopes.pop(); - } - - root.childs.push_back(scopes.top()); - } - else if (line.starts_with(">")) { - // > で始まる限り読み続ける - Quote node = Quote(uuid()); - - std::vector quote_contents; - while (idx < (int)lines.size()) { - if (lines[idx] == "") break; - if (lines[idx].starts_with("> ")) { - quote_contents.push_back(lines[idx].substr(2)); - } - else { - break; - } - idx++; - } - - // 引用の中身もパース - BlockParser parser = BlockParser(); - node.childs.push_back(std::make_shared(parser.processer(quote_contents))); - root.childs.push_back(std::make_shared(node)); - } - else if (line == "") { - NewLine node = NewLine(uuid()); - root.childs.push_back(std::make_shared(node)); - } - // 水平線 - else if ((line == "---") || (line == "___") || (line == "***")) { - HorizontalLine node = HorizontalLine(uuid()); - root.childs.push_back(std::make_shared(node)); - } - else if (std::regex_match(line, std::regex("(\\|[^\\|]+).+\\|"))) { - const std::regex each_col_regex = std::regex("\\|[^\\|]+"); - - int n_col = 0; - - std::vector col_names(n_col); - std::smatch match; - - while (std::regex_search(line, match, each_col_regex)) { - col_names.push_back(match[0].str().substr(1, match[0].str().size())); - line = match.suffix(); - n_col++; - } - - // 0 -> 左寄せ, 1 -> 中央寄せ, 2 -> 右寄せ - std::vector col_format(0); - - idx++; - - std::string line2 = lines[idx]; - std::smatch match2; - while (std::regex_search(line2, match2, each_col_regex)) { - if (match2[0].str().starts_with("|:") && match2[0].str().ends_with(":")) { - col_format.push_back(1); - } - else if (match2[0].str().starts_with("|:")) { - col_format.push_back(0); - } - else if (match2[0].str().ends_with(":")) { - col_format.push_back(2); - } - else { - col_format.push_back(0); - } - - line2 = match2.suffix(); - } - - - idx++; - int n_row = 0; - std::vector table; - - - while (idx < (int)(lines.size()) && lines[idx] != "") { - n_row++; - std::string line = lines[idx]; - std::smatch match; - std::regex_search(line, match, each_col_regex); - while (std::regex_search(line, match, each_col_regex)) { - table.push_back(match[0].str().substr(1, match[0].str().size())); - line = match.suffix(); - } - idx++; - } - - std::vector> columns_blocks; - - for (int i = 0; i < n_col; i++) - { - columns_blocks.push_back(inline_parser.processer(col_names[i])); - } - - - Table node = Table(columns_blocks, n_row, n_col, col_format, uuid()); - - for (int i = 0; i < (int)table.size(); i++) { - node.childs.push_back(inline_parser.processer(table[i])); - } - - root.childs.push_back(std::make_shared(node)); - } - else { - root.childs.push_back(inline_parser.processer(line)); - } - idx++; - } - return root; - } - }; - - - // md ファイルの中身 (frot YAML を含まない) - // を受け取って、前処理 ([コメントの削除, ]) を行う - std::vector preprocess(std::vector content) { - std::string content_join = join(content, "\n"); - - // コメントを削除 - auto remove_comment = [](std::string text) { - return _remove_comment(text); - }; - - - std::vector> hooks = { - remove_comment, - }; - - for (auto hook : hooks) { - content_join = hook(content_join); - } - - content = split(content_join, "\n"); - - return content; - } - - // md ファイルの中身 (front YAML を含む) - // を受け取って、 front YAML をパースした結果と残りの md ファイルの開始位置を返す - // TODO: きちんとした YAML パーサを使うようにする。 - std::pair>, int> parse_front(std::vector content) { - std::vector> front_yaml; - int front_yaml_end = 0; - - if (!content.empty() && content[0] == "---") { - int index = 1; - while (index < (int)content.size() && content[index] != "---") { - std::string key = std::regex_replace(content[index], std::regex("(.*):\\s(.*)"), "$1"); - std::string data = std::regex_replace(content[index], std::regex("(.*):\\s(.*)"), "$2"); - front_yaml.emplace_back(key, data); - index++; - } - front_yaml_end = index + 1; - } +template +void InlineParser::process_inline(const std::string &str, ASTNode &ast, Syntax&& syn, int pos){ + std::invoke(syn, str, ast); +} - return { front_yaml, front_yaml_end }; +// syn has matched at position pos +template +void InlineParser::process_inline(const std::string &str, ASTNode &ast, Syntax&& syn, int pos, HeadSyntax&& hsyn, TailSyntax&&... tsyn){ + int newpos = std::invoke(hsyn, str); + if (newpos < pos){ + // syntax with nearer match detected + return process_inline(str, ast, hsyn, newpos, tsyn...); } - - // mdファイルの中身 (fron YAML を含まない) - // を受け取って、抽象構文木を返す。 - Block parse_rest(std::vector content) { - // パース - BlockParser parser = BlockParser(); - Block ast = parser.processer(content); - - return ast; + else { + return process_inline(str, ast, syn, pos, tsyn...); } +} +void InlineParser::process(const std::string &str, ASTNode &ast){ + if (str == "") return ; + process_inline( + str, ast, + RawTextSyntax{}, std::numeric_limits::max(), + InlineMathSyntax{}, + InlineOverlineSyntax{}, + InlineStrongSyntax{}, + InlineItalicSyntax{}, + InlineCodeBlockSyntax{}, + InlineImageSyntax{}, + InlineUrlSyntax{} + ); +} - // mdファイルの内容から - // メタデータ (std::map) と - // 抽象構文木の根 (Block) のペアを返す。 - std::pair, Block> parse(std::vector content) { - auto [front_yaml, front_yaml_end] = parse_front(content); - - std::vector rest_content(content.begin() + front_yaml_end, content.end()); - - Block ast = parse_rest(rest_content); - - // meta_data を std::map に変換する - std::map meta_data_map; +MarkdownParser::MarkdownParser(const std::vector &lines, std::map &meta_data) : reader(lines, meta_data) {} - // デフォルト値を設定 - meta_data_map["title"] = ""; - meta_data_map["date"] = ""; - meta_data_map["author"] = ""; - meta_data_map["twitter_id"] = ""; - meta_data_map["github_id"] = ""; - meta_data_map["mail"] = ""; - meta_data_map["ogp_url"] = "https://www.abap34.com/almo_logo.jpg"; - meta_data_map["tag"] = ""; - meta_data_map["url"] = ""; - meta_data_map["site_name"] = ""; - meta_data_map["twitter_site"] = ""; +void MarkdownParser::process_block(std::string &str_inline, ASTNode &ast){ + if (str_inline != ""){ + str_inline += ' '; + } + str_inline += reader.get_row(); + reader.move_next_line(); +} - for (auto [key, data] : front_yaml) { - meta_data_map[key] = data; +template +void MarkdownParser::process_block(std::string &str_inline, ASTNode &ast, HeadSyntax&& hsyn, TailSyntax&&... tsyn){ + if (std::invoke(hsyn, reader)){ + if (str_inline != ""){ + InlineParser::process(str_inline, ast); + str_inline = ""; } - - return { meta_data_map, ast }; + std::invoke(hsyn, reader, ast); + return ; } + return process_block(str_inline, ast, tsyn...); +} - - // mdファイルのパスから - // メタデータ (std::map) と - // 抽象構文木の根 (Block) のペアを返す。 - std::pair, Block> parse_md_file(std::string path) { - std::vector content = read_file(path); - return parse(content); +void MarkdownParser::process(ASTNode &ast){ + std::string str_inline = ""; + while (!reader.eof_read_flg){ + process_block( + str_inline, ast, + EOFSyntax{}, + NewLineSyntax{}, + JudgeSyntax{}, + ExecutableCodeBlockSyntax{}, + LoadLibSyntax{}, + DivBlockSyntax{}, + QuoteSyntax{}, + ListBlockSyntax{}, + EnumerateBlockSyntax{}, + HeaderSyntax{}, + CodeBlockSyntax{}, + MathBlockSyntax{}, + HorizontalLineSyntax{}, + TableSyntax{} + ); + } + if (str_inline != ""){ + std::cerr << "Not empty inline string : '" << str_inline << "'" << std::endl; + throw ParseError("Internal Error"); } } + +} // namespace almo \ No newline at end of file diff --git a/src/reader.hpp b/src/reader.hpp new file mode 100644 index 0000000..e4a243a --- /dev/null +++ b/src/reader.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace almo { + +struct Reader { + std::vector lines; + std::map &meta_data; + int row, col; + // already read eof + bool eof_read_flg; + + Reader (const std::vector &_lines, + std::map &_meta_data) + : lines(_lines), meta_data(_meta_data), row(0), col(0), eof_read_flg(false) {} + bool is_line_begin() const { + return col == 0; + } + bool is_line_end() const { + return col == (int)lines[row].size(); + } + bool is_eof() const { + return row == (int)lines.size(); + } + std::string get_row() const { + return lines[row]; + } + std::string get_rest_row() const { + return lines[row].substr(col); + } + void move_next_line(){ + row++; + col = 0; + } + void move_next_char(int n){ + assert(0 <= n && n <= (int)(lines[row].size()) - col); + col += n; + } + void read_eof(){ + assert(!eof_read_flg); + eof_read_flg = true; + } + std::string get_meta_data(std::string key) const { + // if meta_data do not contains key, throw out_of_range exception + return meta_data.at(key); + } + void set_meta_data(std::string key, std::string value){ + // for required_pyodide (stop warning) + if (key == "required_pyodide" && value == "true"){ + assert(meta_data.contains("required_pyodide")); + if (meta_data["required_pyodide"] == "false"){ + meta_data[key] = value; + } + return ; + } + if (meta_data.contains(key)){ + if (meta_data[key] == value) return ; + std::cerr << "Warning : key is duplicating. value is overwritten." << std::endl; + std::cerr << "duplicated key : " << key << std::endl; + std::cerr << "previous value : " << meta_data[key] << std::endl; + std::cerr << " new value : " << value << std::endl; + } + meta_data[key] = value; + } + // get near text from current position(row, col) + std::string near(){ + std::string ret = ""; + if (row > 0){ + ret += lines[row-1] + "\n"; + } + ret += lines[row]; + return ret; + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/render.hpp b/src/render.hpp index ab10c44..8ab0813 100644 --- a/src/render.hpp +++ b/src/render.hpp @@ -7,8 +7,8 @@ #include #include #include -#include "ast.hpp" #include "utils.hpp" +#include "parse.hpp" namespace almo { std::string LIGHT_THEME = { @@ -27,8 +27,10 @@ namespace almo { }; - - std::string load_html_template(std::string html_path, std::string css_setting) { + + std::string load_html_template(std::string html_path, std::string css_setting, bool required_pyodide) { + const std::string pyodide_loader = ""; + std::string html; if (html_path == "__default__") { @@ -58,10 +60,11 @@ namespace almo { std::string runner = ""; - if (loaded_pyodide) { + if (required_pyodide) { // runnner の先頭に  pyodide を挿入 runner = pyodide_loader + runner; - } else { + } + else { runner = ""; } // runner を挿入 @@ -86,14 +89,75 @@ namespace almo { return output_html; } - std::string render(Block ast, std::map meta_data) { - std::string content = ast.render(meta_data); + std::string render(Markdown ast, std::map meta_data) { + std::string content = ast.to_html(); + + std::string html_template = load_html_template(meta_data["template_file"], meta_data["css_setting"], meta_data["required_pyodide"] == "true"); - std::string html_template = load_html_template(meta_data["template_file"], meta_data["css_setting"]); - std::string output_html = replace_template(html_template, meta_data, content); return output_html; } -} + // Call from python to get html from lines without metadata `md_content` and metadata `meta_data`. + // + // - md_to_html + // - md_to_json + // - md_to_dot + // - md_to_ast + // - md_to_summary + // + // note : meta_data may change + + std::string md_to_html(const std::vector& md_content, std::map& meta_data) { + Markdown ast; + MarkdownParser parser(md_content, meta_data); + parser.process(ast); + return render(ast, meta_data); + } + + std::string md_to_json(const std::vector& md_content, std::map& meta_data) { + Markdown ast; + MarkdownParser parser(md_content, meta_data); + parser.process(ast); + return ast.to_json(); + } + + std::string md_to_dot(const std::vector& md_content, std::map& meta_data) { + Markdown ast; + MarkdownParser parser(md_content, meta_data); + parser.process(ast); + std::string dot = ast.to_dot(); + dot = "digraph G {\n graph [labelloc=\"t\"; \n ]\n" + dot + "}"; + return dot; + } + + Markdown md_to_ast(const std::vector& md_content, std::map& meta_data){ + Markdown ast; + MarkdownParser parser(md_content, meta_data); + parser.process(ast); + return ast; + } + + struct ParseSummary { + Markdown ast; + std::string html, json, dot; + bool required_pyodide; + }; + + ParseSummary md_to_summary(const std::vector& md_content, std::map& meta_data){ + Markdown ast; + MarkdownParser parser(md_content, meta_data); + parser.process(ast); + render(ast, meta_data); + ParseSummary summary = { + .ast = ast, + .html = render(ast, meta_data), + .json = ast.to_json(), + .dot = "digraph G {\n graph [labelloc=\"t\"; \n ]\n" + ast.to_dot() + "}", + .required_pyodide = (parser.reader.get_meta_data("required_pyodide") == "true") + }; + return summary; + } + +} // namespace almo diff --git a/src/syntax/CodeBlock.hpp b/src/syntax/CodeBlock.hpp new file mode 100644 index 0000000..dba2535 --- /dev/null +++ b/src/syntax/CodeBlock.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" + +#include"../utils.hpp" + +namespace almo { + +// コードブロックを表すクラス.
    タグで囲まれ、その中に
     タグが入る.
    +struct CodeBlock : public ASTNode {
    +  private:
    +    // コードの中身。
    +    std::string code;
    +
    +    // Highlight.js によって正確にハイライトするために、言語を指定する必要がある。
    +    // ```python -> python を持っておき、 `to_html` する際に  として出力する。
    +    std::string language;
    +
    +  public:
    +    CodeBlock(std::string code, std::string language) : code(code), language(language) {
    +        set_uuid();
    +    }
    +
    +    std::string to_html() const override {
    +        std::string code_class;
    +
    +        if (language == "") {
    +            code_class = "language-plaintext";
    +        } else {
    +            code_class = "language-" + language;
    +        }
    +
    +        return "
    " + escape_for_html(code) + "
    "; + } + + std::map get_properties() const override { + return { + {"code", code}, + {"language", language} + }; + } + + std::string get_classname() const override { + return "CodeBlock"; + } +}; + +struct CodeBlockSyntax : public BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + return read.get_row().starts_with("```"); + } + void operator()(Reader &read, ASTNode &ast) const override { + // BEGIN : '```(.*)' + // END : '```\s*' + + std::string language = rtrim(read.get_row().substr(3)); + read.move_next_line(); + std::string code; + while (!read.is_eof()){ + if (rtrim(read.get_row()) == "```"){ + read.move_next_line(); + break; + } + code += read.get_row() + "\n"; + read.move_next_line(); + } + + CodeBlock node(code, language); + ast.add_child(std::make_shared(node)); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/DivBlock.hpp b/src/syntax/DivBlock.hpp new file mode 100644 index 0000000..3e03ec1 --- /dev/null +++ b/src/syntax/DivBlock.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +#include"Markdown.hpp" + +namespace almo { + +struct DivBlock : public ASTNode { + private: + std::string div_class; + public: + DivBlock(std::string div_class) : div_class(div_class) { + set_uuid(); + } + + std::string to_html() const override { + return "
    " + concatenated_childs_html() + "
    "; + } + + std::map get_properties() const override { + return { + {"div_class", div_class}, + }; + } + std::string get_classname() const override { + return "DivBlock"; + } +}; + +struct DivBlockSyntax : public BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + if (!read.get_row().starts_with(":::")) return false; + if (rtrim(read.get_row()) != ":::") return true; + return false; + } + void operator()(Reader &read, ASTNode &ast) const override { + std::stack> scopes; + std::string div_class = read.get_row().substr(3); + + DivBlock root_div(div_class); + scopes.push(std::make_shared(root_div)); + + read.move_next_line(); + std::vector text; + + while (!read.is_eof()){ + if (rtrim(read.get_row()) == ":::"){ + Markdown inner_md; + MarkdownParser parser(text, read.meta_data); + parser.process(inner_md); + auto inner_md_ptr = std::make_shared(inner_md); + scopes.top()->add_child(inner_md_ptr); + + text = {}; + read.move_next_line(); + + if (scopes.size() == 1u){ + ast.add_child(scopes.top()); + break; + } + scopes.pop(); + continue; + } + if (read.get_row().starts_with(":::")){ + Markdown inner_md; + MarkdownParser parser(text, read.meta_data); + parser.process(inner_md); + auto inner_md_ptr = std::make_shared(inner_md); + scopes.top()->add_child(inner_md_ptr); + + DivBlock new_node(read.get_row().substr(3)); + auto new_node_ptr = std::make_shared(new_node); + scopes.top()->add_child(new_node_ptr); + + scopes.push(new_node_ptr); + + text = {}; + read.move_next_line(); + continue; + } + text.emplace_back(read.get_row()); + read.move_next_line(); + } + while (true){ + Markdown inner_md; + MarkdownParser parser(text, read.meta_data); + parser.process(inner_md); + auto inner_md_ptr = std::make_shared(inner_md); + scopes.top()->add_child(inner_md_ptr); + + text = {}; + read.move_next_line(); + + if (scopes.size() == 1u){ + ast.add_child(scopes.top()); + break; + } + scopes.pop(); + continue; + } + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/EOF.hpp b/src/syntax/EOF.hpp new file mode 100644 index 0000000..4f9890f --- /dev/null +++ b/src/syntax/EOF.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" + +namespace almo { + +struct EOFSyntax : public BlockSyntax { + bool operator()(Reader &read) const override { + return read.is_eof(); + } + void operator()(Reader &read, ASTNode &ast) const override { + read.read_eof(); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/EnumerateBlock.hpp b/src/syntax/EnumerateBlock.hpp new file mode 100644 index 0000000..fb6d9f5 --- /dev/null +++ b/src/syntax/EnumerateBlock.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +#include"Item.hpp" +#include"NewLine.hpp" +#include"EOF.hpp" + +namespace almo { + +// 番号付き箇条書きを表すクラス +struct EnumerateBlock : public ASTNode { + public: + EnumerateBlock() { + set_uuid(); + } + + std::string to_html() const override { + return "
      " + concatenated_childs_html() + "
    "; + } + + std::map get_properties() const override { + return { + }; + } + std::string get_classname() const override { + return "EnumerateBlock"; + } +}; + +struct EnumerateBlockSyntax : public BlockSyntax { + static inline const std::regex rex = std::regex(R"(\d+\. (.*))"); + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + return std::regex_match(read.get_row(), rex); + } + void operator()(Reader &read, ASTNode &ast) const override { + // BEGIN : rex match + // END : NewLine or EOF + + std::stack> scopes; + EnumerateBlock root_enum; + scopes.push(std::make_shared(root_enum)); + + std::string current_match = R"(\d+\. (.*))"; + // width : 3 + const std::string INDENT = " "; + + // if s is considered a enum definition, return its depth" + // otherwise return std::string::npos + auto enum_depth = [](std::string s) -> std::size_t { + std::size_t pos = s.find_first_not_of(" \n\r\t"); + // s is consist of only space + if (pos == std::string::npos) return std::string::npos; + // s is enum definition + if (std::regex_match(s.substr(pos), rex)) return pos; + // otherwise + return std::string::npos; + }; + + std::string text = ""; + + while (true){ + // Invariant : read.is_eof() == false + // scopes.size() >= 1 + text += [&]{ + std::smatch sm; + // smatch manage an iterator of the matched string `line` + // --> the string should not be a temporary object + std::string line = read.get_row(); + std::regex_search(line, sm, std::regex(current_match)); + return sm.format("$1"); + }(); + + // within while loop, read changes only here + read.move_next_line(); + + // END + if (EOFSyntax{}(read) || NewLineSyntax{}(read)){ + Item item; + InlineParser::process(text, item); + scopes.top()->add_child(std::make_shared(item)); + text = ""; + break; + } + // same depth + if (std::regex_match(read.get_row(), std::regex(current_match))){ + Item item; + InlineParser::process(text, item); + scopes.top()->add_child(std::make_shared(item)); + text = ""; + continue; + } + // +3 depth + if (std::regex_match(read.get_row(), std::regex(INDENT + current_match))){ + Item item; + InlineParser::process(text, item); + scopes.top()->add_child(std::make_shared(item)); + text = ""; + + // nested EnumerateBlock + EnumerateBlock new_node; + auto new_node_ptr = std::make_shared(new_node); + scopes.top()->add_child(new_node_ptr); + scopes.push(new_node_ptr); + + // update match + current_match = INDENT + current_match; + continue; + } + // -3n depth (n >= 1) + if ([&]{ + std::size_t depth = enum_depth(read.get_row()); + if (depth == std::string::npos) return false; + std::size_t current = (scopes.size() - 1u) * 3u; + // depth == current, depth == current + 3 is already ditected + // depth has decreased and the difference is multiple of 3 + if (depth < current && (current - depth) % 3 == 0) return true; + // ambiguous enum definition detected + std::cerr << "Warning: ambiguous enum\n ... \n" << read.near() << "\n^^^ parsing line" << std::endl; + std::cerr << "indent width must be 3. this line is considered as raw text." << std::endl; + return false; + }()){ + std::size_t delete_indent = (scopes.size() - 1u) * 3u - enum_depth(read.get_row()); + current_match = current_match.substr(delete_indent); + + Item item; + InlineParser::process(text, item); + scopes.top()->add_child(std::make_shared(item)); + text = ""; + + for (std::size_t del = 0; del < delete_indent; del += 3u){ + scopes.pop(); + } + continue; + } + // Ensure : read.get_row() is considered a continuation of the previous item + } + while (scopes.size() > 1u){ + scopes.pop(); + } + ast.add_child(scopes.top()); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/ExecutableCodeBlock.hpp b/src/syntax/ExecutableCodeBlock.hpp new file mode 100644 index 0000000..b375166 --- /dev/null +++ b/src/syntax/ExecutableCodeBlock.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +namespace almo { + +// 実行可能なコードブロックを表すクラス. +// `Judge` と異なり、こちらはジャッジを提供せず、単に実行のみを行う。 +struct ExecutableCodeBlock : public ASTNode { + private: + std::string code; + std::string editor_theme; + public: + ExecutableCodeBlock(std::string code, std::string editor_theme) : code(code), editor_theme(editor_theme) { + set_uuid(); + } + + std::string to_html() const override { + // コード全体を表示するために、何行のコードかを調べておく + int n_line = std::count(code.begin(), code.end(), '\n'); + + std::string uuid_str = get_uuid_str(); + + // `minLines` と `maxLines` を適切に設定してコード全体を表示するようにする。 + std::string ace_editor = "" + "\n"; + + // 通常の出力に加えて、matplotlib のプロットを表示するための div を作っておく。 + std::string editor_div = "
    \n
    \n"; + std::string out_area = "
    \n";
    +        std::string plot_area = "
    \n"; + + std::string run_button = + "\n"; + + std::string output = editor_div + ace_editor + out_area + plot_area + run_button; + + return output; + } + + std::map get_properties() const override { + return { + {"code", code} + }; + } + std::string get_classname() const override { + return "ExecutableCodeBlock"; + } +}; + +struct ExecutableCodeBlockSyntax : public BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + if (rtrim(read.get_row()) == ":::code") return true; + return false; + } + void operator()(Reader &read, ASTNode &ast) const override { + std::string code = ""; + + // skip :::code + read.move_next_line(); + + while (!read.is_eof()){ + if (rtrim(read.get_row()) == ":::"){ + read.move_next_line(); + break; + } + code += read.get_row() + "\n"; + read.move_next_line(); + } + + ExecutableCodeBlock node(code, read.get_meta_data("editor_theme")); + read.set_meta_data("required_pyodide","true"); + ast.add_child(std::make_shared(node)); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/Header.hpp b/src/syntax/Header.hpp new file mode 100644 index 0000000..27fa21b --- /dev/null +++ b/src/syntax/Header.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" + +namespace almo { + +struct Header : public ASTNode { + private: + int level; + public: + Header (int _level) : level(_level) { + set_uuid(); + } + std::string to_html() const override { + std::string childs_html = concatenated_childs_html(); + std::string contents_push = "" + "\n"; + + return contents_push + "" + childs_html + ""; + } + + std::map get_properties() const override { + return { + {"level", std::to_string(level)} + }; + } + std::string get_classname() const override { + return "Header"; + } +}; + +struct HeaderSyntax : BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + if (read.get_row().starts_with("# ")) return true; + if (read.get_row().starts_with("## ")) return true; + if (read.get_row().starts_with("### ")) return true; + if (read.get_row().starts_with("#### ")) return true; + if (read.get_row().starts_with("##### ")) return true; + if (read.get_row().starts_with("###### ")) return true; + return false; + } + void operator()(Reader &read, ASTNode &ast) const override { + int level = 0; + if (read.get_row().starts_with("# ")) level = 1; + if (read.get_row().starts_with("## ")) level = 2; + if (read.get_row().starts_with("### ")) level = 3; + if (read.get_row().starts_with("#### ")) level = 4; + if (read.get_row().starts_with("##### ")) level = 5; + if (read.get_row().starts_with("###### ")) level = 6; + assert(1 <= level && level <= 6); + Header node(level); + InlineParser::process(read.get_row().substr(level+1), node); + ast.add_child(std::make_shared
    (node)); + read.move_next_line(); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/HorizontalLine.hpp b/src/syntax/HorizontalLine.hpp new file mode 100644 index 0000000..d142ee9 --- /dev/null +++ b/src/syntax/HorizontalLine.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +namespace almo { + +struct HorizontalLine : public ASTNode { + public: + HorizontalLine() { + set_uuid(); + } + + std::string to_html() const override { + return "
    "; + } + + std::map get_properties() const override { + return { + }; + } + std::string get_classname() const override { + return "HorizontalLine"; + } +}; + +struct HorizontalLineSyntax : BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + if (rtrim(read.get_row()) == "---") return true; + if (rtrim(read.get_row()) == "___") return true; + if (rtrim(read.get_row()) == "***") return true; + return false; + } + void operator()(Reader &read, ASTNode &ast) const override { + HorizontalLine node; + ast.add_child(std::make_shared(node)); + read.move_next_line(); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/InlineCodeBlock.hpp b/src/syntax/InlineCodeBlock.hpp new file mode 100644 index 0000000..f0573dd --- /dev/null +++ b/src/syntax/InlineCodeBlock.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + + +namespace almo { + +struct InlineCodeBlock : public ASTNode { + private: + std::string code; + public: + InlineCodeBlock(std::string code) : code(code) { + set_uuid(); + } + + std::string to_html() const override { + return " " + escape_for_html(code) + " "; + } + + std::map get_properties() const override { + return { + {"code", code} + }; + } + std::string get_classname() const override { + return "InlineCodeBlock"; + } +}; + +struct InlineCodeBlockSyntax : public InlineSyntax { + static inline const std::regex rex = std::regex(R"((.*?)\`(.*?)\`(.*))"); + int operator()(const std::string &str) const override { + std::smatch sm; + if (std::regex_search(str, sm, rex)){ + return sm.position(2) - 1; + } + return std::numeric_limits::max(); + } + void operator()(const std::string &str, ASTNode &ast) const override { + std::smatch sm; + std::regex_search(str, sm, rex); + std::string prefix = sm.format("$1"); + std::string code = sm.format("$2"); + std::string suffix = sm.format("$3"); + InlineParser::process(prefix, ast); + InlineCodeBlock node(code); + ast.add_child(std::make_shared(node)); + InlineParser::process(suffix, ast); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/InlineImage.hpp b/src/syntax/InlineImage.hpp new file mode 100644 index 0000000..da7d976 --- /dev/null +++ b/src/syntax/InlineImage.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + + +namespace almo { + +struct InlineImage : public ASTNode { + private: + std::string url; + std::string caption; + + public: + InlineImage(std::string url, std::string caption) : url(url), caption(caption) { + set_uuid(); + } + + // 
    タグを使うことで キャプションなどをつける。 + std::string to_html() const override { + std::string output = ""; + std::string figcaption = "
    " + caption + "
    "; + return "
    " + output + figcaption + "
    "; + } + + std::map get_properties() const override { + return { + {"url", url}, + {"caption", caption} + }; + } + std::string get_classname() const override { + return "InlineImage"; + } +}; + +struct InlineImageSyntax : public InlineSyntax { + static inline const std::regex rex = std::regex(R"((.*?)\!\[(.*?)\]\((.*?)\)(.*))"); + int operator()(const std::string &str) const override { + std::smatch sm; + if (std::regex_search(str, sm, rex)){ + return sm.position(2) - 2; + } + return std::numeric_limits::max(); + } + void operator()(const std::string &str, ASTNode &ast) const override { + std::smatch sm; + std::regex_search(str, sm, rex); + std::string prefix = sm.format("$1"); + std::string caption = sm.format("$2"); + std::string url = sm.format("$3"); + std::string suffix = sm.format("$4"); + InlineParser::process(prefix, ast); + InlineImage node(url, caption); + ast.add_child(std::make_shared(node)); + InlineParser::process(suffix, ast); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/InlineItalic.hpp b/src/syntax/InlineItalic.hpp new file mode 100644 index 0000000..494e974 --- /dev/null +++ b/src/syntax/InlineItalic.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" + +namespace almo { + +struct InlineItalic : public ASTNode { + public: + InlineItalic() { + set_uuid(); + } + + std::string to_html() const override { + return " " + concatenated_childs_html() + " "; + } + + std::map get_properties() const override { + return { + }; + } + std::string get_classname() const override { + return "InlineItalic"; + } +}; + +struct InlineItalicSyntax : public InlineSyntax { + static inline const std::regex rex = std::regex(R"((.*?)\*(.*?)\*(.*))"); + int operator()(const std::string &str) const override { + std::smatch sm; + if (std::regex_search(str, sm, rex)){ + return sm.position(2) - 1; + } + return std::numeric_limits::max(); + } + void operator()(const std::string &str, ASTNode &ast) const override { + std::smatch sm; + std::regex_search(str, sm, rex); + std::string prefix = sm.format("$1"); + std::string content = sm.format("$2"); + std::string suffix = sm.format("$3"); + InlineParser::process(prefix, ast); + InlineItalic node; + InlineParser::process(content, node); + ast.add_child(std::make_shared(node)); + InlineParser::process(suffix, ast); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/InlineMath.hpp b/src/syntax/InlineMath.hpp new file mode 100644 index 0000000..5b0a460 --- /dev/null +++ b/src/syntax/InlineMath.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +namespace almo { + +struct InlineMath : public ASTNode { + private: + std::string expr; + public: + InlineMath (std::string _expr) : expr(_expr) { + set_uuid(); + } + + // mathjax の インライン数式用に \( \) で囲む + std::string to_html() const override { + return " \\( " + expr + " \\) "; + } + + std::map get_properties() const override { + return { + {"expr", expr} + }; + } + std::string get_classname() const override { + return "InlineMath"; + } +}; + +struct InlineMathSyntax : public InlineSyntax { + static inline const std::regex rex = std::regex(R"((.*?)\$(.*?)\$(.*))"); + int operator()(const std::string &str) const override { + std::smatch sm; + if (std::regex_search(str, sm, rex)){ + return sm.position(2) - 1; + } + return std::numeric_limits::max(); + } + void operator()(const std::string &str, ASTNode &ast) const override { + std::smatch sm; + std::regex_search(str, sm, rex); + std::string prefix = sm.format("$1"); + std::string expr = sm.format("$2"); + std::string suffix = sm.format("$3"); + InlineParser::process(prefix, ast); + InlineMath node(expr); + ast.add_child(std::make_shared(node)); + InlineParser::process(suffix, ast); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/InlineOverline.hpp b/src/syntax/InlineOverline.hpp new file mode 100644 index 0000000..02e81b8 --- /dev/null +++ b/src/syntax/InlineOverline.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" + +namespace almo { + +struct InlineOverline : public ASTNode { + public: + InlineOverline() { + set_uuid(); + } + std::string to_html() const override { + return " " + concatenated_childs_html() + " "; + } + + std::map get_properties() const override { + return { + }; + } + std::string get_classname() const override { + return "InlineOverline"; + } +}; + +struct InlineOverlineSyntax : public InlineSyntax { + static inline const std::regex rex = std::regex(R"((.*?)\~\~(.*?)\~\~(.*))"); + int operator()(const std::string &str) const override { + std::smatch sm; + if (std::regex_search(str, sm, rex)){ + return sm.position(2) - 2; + } + return std::numeric_limits::max(); + } + void operator()(const std::string &str, ASTNode &ast) const override { + std::smatch sm; + std::regex_search(str, sm, rex); + std::string prefix = sm.format("$1"); + std::string content = sm.format("$2"); + std::string suffix = sm.format("$3"); + InlineParser::process(prefix, ast); + InlineOverline node; + InlineParser::process(content, node); + ast.add_child(std::make_shared(node)); + InlineParser::process(suffix, ast); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/InlineStrong.hpp b/src/syntax/InlineStrong.hpp new file mode 100644 index 0000000..1225381 --- /dev/null +++ b/src/syntax/InlineStrong.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" + +namespace almo { + +struct InlineStrong : public ASTNode { + public: + InlineStrong() { + set_uuid(); + } + + std::string to_html() const override { + return " " + concatenated_childs_html() + " "; + } + + std::map get_properties() const override { + return { + }; + } + std::string get_classname() const override { + return "InlineStrong"; + } +}; + +struct InlineStrongSyntax : public InlineSyntax { + static inline const std::regex rex = std::regex(R"((.*?)\*\*(.*?)\*\*(.*))"); + int operator()(const std::string &str) const override { + std::smatch sm; + if (std::regex_search(str, sm, rex)){ + return sm.position(2) - 2; + } + return std::numeric_limits::max(); + } + void operator()(const std::string &str, ASTNode &ast) const override { + std::smatch sm; + std::regex_search(str, sm, rex); + std::string prefix = sm.format("$1"); + std::string content = sm.format("$2"); + std::string suffix = sm.format("$3"); + InlineParser::process(prefix, ast); + InlineStrong node; + InlineParser::process(content, node); + ast.add_child(std::make_shared(node)); + InlineParser::process(suffix, ast); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/InlineUrl.hpp b/src/syntax/InlineUrl.hpp new file mode 100644 index 0000000..a3976d3 --- /dev/null +++ b/src/syntax/InlineUrl.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + + +namespace almo { + +struct InlineUrl : public ASTNode { + private: + std::string url; + std::string alt; + public: + InlineUrl(std::string url, std::string alt) : url(url), alt(alt) { + set_uuid(); + } + + std::string to_html() const override { + return " " + alt + " "; + } + + std::map get_properties() const override { + return { + {"url", url}, + {"alt", alt} + }; + } + std::string get_classname() const override { + return "InlineUrl"; + } +}; + +struct InlineUrlSyntax : public InlineSyntax { + static inline const std::regex rex = std::regex(R"((.*?)\[(.*?)\]\((.*?)\)(.*))"); + int operator()(const std::string &str) const override { + std::smatch sm; + if (std::regex_search(str, sm, rex)){ + return sm.position(2) - 1; + } + return std::numeric_limits::max(); + } + void operator()(const std::string &str, ASTNode &ast) const override { + std::smatch sm; + std::regex_search(str, sm, rex); + std::string prefix = sm.format("$1"); + std::string alt = sm.format("$2"); + std::string url = sm.format("$3"); + std::string suffix = sm.format("$4"); + InlineParser::process(prefix, ast); + InlineUrl node(url, alt); + ast.add_child(std::make_shared(node)); + InlineParser::process(suffix, ast); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/Item.hpp b/src/syntax/Item.hpp new file mode 100644 index 0000000..0fe2732 --- /dev/null +++ b/src/syntax/Item.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +namespace almo { + +// 箇条書きの要素を表すクラス +struct Item : public ASTNode { + public: + Item() { + set_uuid(); + } + + std::string to_html() const override { + return "
  • " + concatenated_childs_html() + "
  • "; + } + + std::map get_properties() const override { + return { + }; + } + std::string get_classname() const override { + return "Item"; + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/Judge.hpp b/src/syntax/Judge.hpp new file mode 100644 index 0000000..ccc0ab3 --- /dev/null +++ b/src/syntax/Judge.hpp @@ -0,0 +1,261 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +namespace almo { + +struct Judge : public ASTNode { + private: + // 問題のタイトル + std::string title; + + // サンプル入力(単一ファイルのパス) + std::string sample_in_path; + + // サンプル出力(単一ファイルのパス) + std::string sample_out_path; + + // ジャッジの種類. 誤差ジャッジ (err_{rate}) または 完全一致ジャッジ (equal) が指定される. + // C++側では特に処理は変わらず、 JS側に `judge_type[uuid]` を指定することで伝えれば切り替わる。 + std::string judge_type; + + // エディタにデフォルトで入力されてあるコード。 + std::string source; + + // 入力ファイルを表す glob パターン + std::string in_files_glob; + + // 出力ファイルを表す glob パターン + std::string out_files_glob; + + // エディタのテーマ + std::string editor_theme; + public: + Judge(std::string title, + std::string sample_in_path, + std::string sample_out_path, + std::string in_files_glob, + std::string out_files_glob, + std::string judge_type, + std::string source, + std::string editor_theme) : + title(title), + sample_in_path(sample_in_path), + sample_out_path(sample_out_path), + in_files_glob(in_files_glob), + out_files_glob(out_files_glob), + judge_type(judge_type), + source(source), + editor_theme(editor_theme) { + // ジャッジが`err_{rate}` または `equal` 以外の場合はエラーを出す. + if (judge_type.substr(0, 4) != "err_" && judge_type != "equal") { + throw ParseError("Invalid judge type. Expected `err_{rate}` or `equal`, but `" + judge_type + "` is given."); + } + set_uuid(); + } + + std::string to_html() const override { + std::string uuid_str = get_uuid_str(); + + + // タイトルを作る + std::string title_h3 = + "

    WJ
    " + title + "

    \n"; + + // ここから ace editor を作る. + // まず editor を入れる用の div を作る. + std::string editor_div = "
    \n"; + + // ace editor の設定をする. + // テーマは meta_data から取得する. + + std::string source_code = join(read_file(source), "\n"); + + std::string ace_editor = "" + "\n"; + + // サンプル入力を読み込む. + std::string sample_in = join(read_file(sample_in_path)); + std::string sample_out = join(read_file(sample_out_path)); + + // サンプル入力と出力を表示する用の div を作る. + std::string sample_in_area = + "
    サンプルの入力
    " + "
    " + sample_in + "
    \n"; + + std::string sample_out_area = + "
    出力
    " + "
    \n";
    +
    +        std::string expect_out_area =
    +            "
    サンプルの答え
    " + "
    " + sample_out + "
    \n"; + + // データを定義する。 all_input, all_output, all_sample_input, all_sample_output, problem_status は JS側で使うために定義する. + // また、目次生成のために page_contents にも追加する. + std::string define_data = + " \n"; + + // テスト実行ボタンと提出ボタンを作る. + std::string test_run_button = + "\n"; + + std::string submit_button = + "\n"; + + // ジャッジの種類を JS側に伝えるためのコードを作る. + std::string judge_code = + "\n"; + + + std::string output = title_h3 + editor_div + ace_editor + sample_in_area + sample_out_area + expect_out_area + define_data + test_run_button + submit_button + judge_code; + + return output; + } + + std::map get_properties() const override { + return { + {"title", title}, + {"sample_in_path", sample_in_path}, + {"sample_out_path", sample_out_path}, + {"in_files_glob", in_files_glob}, + {"out_files_glob", out_files_glob}, + {"judge_type", judge_type}, + {"source", source} + }; + } + std::string get_classname() const override { + return "Judge"; + } +}; + +struct JudgeSyntax : BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + if (rtrim(read.get_row()) == ":::judge") return true; + return false; + } + void operator()(Reader &read, ASTNode &ast) const override { + // BEGIN : ':::judge\s*' + // END : ':::\s*' + std::map judge_info; + + const std::vector required_args = { + "title", + "sample_in", + "sample_out", + "in", + "out", + }; + + std::map optional_args = { + { "judge", "equal" }, + { "source", "" }, + }; + + // skip :::judge + read.move_next_line(); + + while (!read.is_eof()){ + if (rtrim(read.get_row()) == ":::"){ + read.move_next_line(); + break; + } + std::size_t mid = read.get_row().find("="); + if (mid == std::string::npos){ + throw SyntaxError("judge option must be separated with '='"); + } + std::string key = rtrim(read.get_row().substr(0u, mid)); + std::string value = rtrim(ltrim(read.get_row().substr(mid+1))); + if (judge_info.contains(key)){ + throw SyntaxError("Duplicate judge options : " + key); + } + judge_info[key] = value; + read.move_next_line(); + } + + // required args check + for (std::string arg : required_args){ + if (judge_info.contains(arg)) continue; + // lost args + // set title + if (judge_info.contains("title")){ + throw SyntaxError("問題" + judge_info["title"] + "の引数 " + arg + " がありません. 引数を追加してください."); + } + else { + throw SyntaxError("問題タイトルがありません. 引数を追加してください."); + } + } + + // optional args check + for (auto [arg, default_value] : optional_args){ + if (judge_info.contains(arg)) continue; + judge_info[arg] = default_value; + } + + Judge node( + judge_info["title"], + judge_info["sample_in"], + judge_info["sample_out"], + judge_info["in"], + judge_info["out"], + judge_info["judge"], + judge_info["source"], + read.get_meta_data("editor_theme") + ); + + ast.add_child(std::make_shared(node)); + + read.set_meta_data("required_pyodide","true"); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/ListBlock.hpp b/src/syntax/ListBlock.hpp new file mode 100644 index 0000000..b889586 --- /dev/null +++ b/src/syntax/ListBlock.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +#include"Item.hpp" +#include"NewLine.hpp" +#include"EOF.hpp" + +namespace almo { + +// 箇条書き(番号なし) を表すクラス +struct ListBlock : public ASTNode { + public: + ListBlock() { + set_uuid(); + } + + std::string to_html() const override { + return "
      " + concatenated_childs_html() + "
    "; + } + + std::map get_properties() const override { + return { + }; + } + std::string get_classname() const override { + return "ListBlock"; + } +}; + +struct ListBlockSyntax : public BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + return read.get_row().starts_with("- "); + } + void operator()(Reader &read, ASTNode &ast) const override { + // BEGIN : '- .*' + // END : NewLine or EOF + + std::stack> scopes; + ListBlock root_list; + scopes.push(std::make_shared(root_list)); + + std::string current_prefix = "- "; + const std::string INDENT = " "; + + // if s is considered a list definition, return its depth" + // otherwise return std::string::npos + auto list_depth = [](std::string s) -> std::size_t { + std::size_t pos = s.find_first_not_of(" \n\r\t"); + // s is consist of only space + if (pos == std::string::npos) return std::string::npos; + // s is list definition + if (s.substr(pos).starts_with("- ")) return pos; + // otherwise + return std::string::npos; + }; + + std::string text = ""; + + while (true){ + // Invariant : read.is_eof() == false + // scopes.size() >= 1 + text += remove_listdef(ltrim(read.get_row())); + + // within while loop, read changes only here + read.move_next_line(); + + // END + if (EOFSyntax{}(read) || NewLineSyntax{}(read)){ + Item item; + InlineParser::process(text, item); + scopes.top()->add_child(std::make_shared(item)); + text = ""; + break; + } + // same depth + if (read.get_row().starts_with(current_prefix)){ + Item item; + InlineParser::process(text, item); + scopes.top()->add_child(std::make_shared(item)); + text = ""; + continue; + } + // +2 depth + if (read.get_row().starts_with(INDENT + current_prefix)){ + Item item; + InlineParser::process(text, item); + scopes.top()->add_child(std::make_shared(item)); + text = ""; + + // nested ListBlock + ListBlock new_node; + auto new_node_ptr = std::make_shared(new_node); + scopes.top()->add_child(new_node_ptr); + scopes.push(new_node_ptr); + + // update prefix + current_prefix = INDENT + current_prefix; + continue; + } + // -2n depth (n >= 1) + if ([&]{ + std::size_t depth = list_depth(read.get_row()); + if (depth == std::string::npos) return false; + std::size_t current = (scopes.size() - 1u) * 2u; + // depth == current, depth == current + 2 is already ditected + // depth has decreased and the difference is even + if (depth < current && (current - depth) % 2 == 0) return true; + // ambiguous list definition detected + std::cerr << "Warning: ambiguous list\n ... \n" << read.near() << "\n^^^ parsing line" << std::endl; + std::cerr << "indent width must be 2. this line is considered as raw text." << std::endl; + return false; + }()){ + std::size_t delete_indent = (scopes.size() - 1u) * 2u - list_depth(read.get_row()); + current_prefix = current_prefix.substr(delete_indent); + + Item item; + InlineParser::process(text, item); + scopes.top()->add_child(std::make_shared(item)); + text = ""; + + for (std::size_t del = 0; del < delete_indent; del += 2u){ + scopes.pop(); + } + continue; + } + // Ensure : read.get_row() is considered a continuation of the previous item + } + while (scopes.size() > 1u){ + scopes.pop(); + } + ast.add_child(scopes.top()); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/LoadLib.hpp b/src/syntax/LoadLib.hpp new file mode 100644 index 0000000..5fb2cb5 --- /dev/null +++ b/src/syntax/LoadLib.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +namespace almo { + +// ライブラリの読み込みをする独自記法. +struct LoadLib : public ASTNode { + private: + // 読み込むライブラリの名前のリスト + std::vector libs; + public: + LoadLib(std::vector libs) : libs(libs) { + set_uuid(); + } + + // use_libs に追加しておくと JS側で読み込み処理を行う。 + std::string to_html() const override { + std::string output = ""; + for (std::string lib : libs) { + output += ""; + } + return output; + }; + + std::map get_properties() const override { + return { + {"libs", join(libs)} + }; + } + std::string get_classname() const override { + return "LoadLib"; + } +}; + +struct LoadLibSyntax : public BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + if (rtrim(read.get_row()) == ":::loadlib") return true; + return false; + } + void operator()(Reader &read, ASTNode &ast) const override { + std::vector libs; + + // skip :::loadlib + read.move_next_line(); + + if (read.is_eof()){ + throw SyntaxError("Empty Library"); + } + + while (!read.is_eof()){ + if (rtrim(read.get_row()) == ":::"){ + read.move_next_line(); + break; + } + libs.push_back(rtrim(read.get_row())); + read.move_next_line(); + } + + LoadLib node(libs); + ast.add_child(std::make_shared(node)); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/Markdown.hpp b/src/syntax/Markdown.hpp new file mode 100644 index 0000000..1f6f50a --- /dev/null +++ b/src/syntax/Markdown.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" + +namespace almo { + +struct Markdown : public ASTNode { + public: + Markdown () { + set_uuid(); + } + + std::string to_html() const override { + return concatenated_childs_html(); + } + + std::map get_properties() const override { + return { + }; + } + std::string get_classname() const override { + return "Markdown"; + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/MathBlock.hpp b/src/syntax/MathBlock.hpp new file mode 100644 index 0000000..482b0a9 --- /dev/null +++ b/src/syntax/MathBlock.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +namespace almo { + +// 数式ブロック. 内容はMathJaxでレンダリングされる. 内容は `math-block` というクラスが付与されたdivタグで囲まれる +struct MathBlock : public ASTNode { + private: + // markdownで渡された式をそのまま string で持つ。 + std::string expression; + + public: + MathBlock(std::string expression) : expression(expression) { + set_uuid(); + } + + // mathjax の 複数行数式用に \[ \] で囲む + std::string to_html() const override { + return "
    \\[ \n" + expression + "\n \\]
    "; + } + + std::map get_properties() const override { + return { + {"expression", expression} + }; + } + std::string get_classname() const override { + return "MathBlock"; + } +}; + +struct MathBlockSyntax : public BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + return read.get_row().starts_with("$$"); + } + void operator()(Reader &read, ASTNode &ast) const override { + // BEGIN : '$$.*' + // END : '.*$$\s*' + // note : The mathematical rendering library is responsible for + // the treatment of newlines and whitespace. + + std::string expression = ""; + + read.move_next_char(2); + + while (!read.is_eof()){ + if (rtrim(read.get_rest_row()).ends_with("$$")){ + std::string tail = rtrim(read.get_rest_row()); + expression += tail.substr(0,tail.size()-2u); + read.move_next_line(); + break; + } + expression += read.get_rest_row() + "\n"; + read.move_next_line(); + } + + MathBlock node(expression); + ast.add_child(std::make_shared(node)); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/NewLine.hpp b/src/syntax/NewLine.hpp new file mode 100644 index 0000000..a2a0b19 --- /dev/null +++ b/src/syntax/NewLine.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +namespace almo { + +struct NewLine : public ASTNode { + public: + NewLine () { + set_uuid(); + } + + std::string to_html() const override { + return "
    "; + } + + std::map get_properties() const override { + return { + }; + } + std::string get_classname() const override { + return "NewLine"; + } +}; + +struct NewLineSyntax : public BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + if (rtrim(read.get_row()) == "") return true; + return false; + } + void operator()(Reader &read, ASTNode &ast) const override { + NewLine node; + ast.add_child(std::make_shared(node)); + read.move_next_line(); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/Quote.hpp b/src/syntax/Quote.hpp new file mode 100644 index 0000000..1e87972 --- /dev/null +++ b/src/syntax/Quote.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +#include"Markdown.hpp" + +namespace almo { + +// 引用を表すクラス +struct Quote : public ASTNode { + public: + Quote() { + set_uuid(); + } + + std::string to_html() const override { + return "
    " + concatenated_childs_html() + "
    "; + } + + std::map get_properties() const override { + return { + }; + } + std::string get_classname() const override { + return "Quote"; + } +}; + +struct QuoteSyntax : public BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + return read.get_row().starts_with("> "); + } + void operator()(Reader &read, ASTNode &ast) const override { + // BEGIN : '> .*' + // END : '(?!> ).*' + + std::vector quote_contents; + while (!read.is_eof()){ + if (read.get_row().starts_with("> ")){ + quote_contents.emplace_back(read.get_row().substr(2)); + read.move_next_line(); + continue; + } + break; + } + + // uuid order is node --> inner_md + Quote node; + Markdown inner_md; + MarkdownParser parser(quote_contents, read.meta_data); + parser.process(inner_md); + node.add_child(std::make_shared(inner_md)); + ast.add_child(std::make_shared(node)); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/RawText.hpp b/src/syntax/RawText.hpp new file mode 100644 index 0000000..7adb41f --- /dev/null +++ b/src/syntax/RawText.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" + +#include + +namespace almo { + +struct RawText : public ASTNode { + private: + std::string content; + public: + RawText(std::string _content) : content(_content) { + set_uuid(); + } + + std::string to_html() const override { + return content; + } + + std::map get_properties() const override { + return { + {"content", content} + }; + } + std::string get_classname() const override { + return "RawText"; + } +}; + +struct RawTextSyntax : InlineSyntax { + // All string matches Rawtext syntax + // but matches infinity position + // because Rawtext syntax is weakest. + // If the string matches other Inline syntax + // RawText does not match it. + int operator()(const std::string &str) const override { + return std::numeric_limits::max(); + } + void operator()(const std::string &str, ASTNode &ast) const override { + RawText node(str); + ast.add_child(std::make_shared(node)); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax/Table.hpp b/src/syntax/Table.hpp new file mode 100644 index 0000000..3a03770 --- /dev/null +++ b/src/syntax/Table.hpp @@ -0,0 +1,190 @@ +#pragma once + +#include"../interfaces/ast.hpp" +#include"../interfaces/parse.hpp" +#include"../interfaces/syntax.hpp" +#include"../utils.hpp" + +#include"Markdown.hpp" +#include"NewLine.hpp" +#include"EOF.hpp" + +namespace almo { + +struct Table : public ASTNode { + private: + std::vector> columns; + int n_row; + int n_col; + + // 各列の名前をインライン記法のパースをしていない素の文字列として持つ. + std::vector col_names; + // 各列について、左寄せ(0), 中央寄せ(1), 右寄せ(2) のいずれかを指定する. + std::vector col_format; + public: + Table(std::vector> columns, int n_row, int n_col, std::vector col_names, std::vector col_format) : + columns(columns), n_row(n_row), n_col(n_col), col_names(col_names), col_format(col_format) { + set_uuid(); + } + + // テーブル用の特別な to_html. テーブルの to_html でしか呼ばれないので引数が違ってもOK. + std::string to_html(std::vector headers_html, std::vector childs_html) const { + std::string output = "
    \n"; + output += "\n"; + output += "\n"; + for (int i = 0; i < n_col; i++) { + std::string align = col_format[i] == 0 ? "left" : col_format[i] == 1 ? "center" : "right"; + output += "\n"; + } + output += "\n"; + output += "\n"; + output += "\n"; + for (int i = 0; i < n_row; i++) { + output += "\n"; + for (int j = 0; j < n_col; j++) { + std::string align = col_format[j] == 0 ? "left" : col_format[j] == 1 ? "center" : "right"; + output += "\n"; + } + output += "\n"; + } + output += "\n"; + output += "
    " + headers_html[i] + "
    " + childs_html[i * n_col + j] + "
    \n"; + return output; + } + + std::string to_html() const override { + std::vector columns_html; + for (auto child : columns) { + columns_html.push_back(child->to_html()); + } + + std::vector contents_html; + + for (auto child : childs) { + contents_html.push_back(child->to_html()); + } + + return to_html(columns_html, contents_html); + } + + std::map get_properties() const override { + std::string col_format_str = ""; + for (int i = 0; i < n_col; i++) { + std::string align = col_format[i] == 0 ? "l" : col_format[i] == 1 ? "c" : "r"; + col_format_str += align; + } + + std::string col_names_str = "["; + + for (int i = 0; i < n_col; i++) { + col_names_str += "\"" + col_names[i] + "\""; + if (i != n_col - 1) { + col_names_str += ", "; + } + } + + col_names_str += "]"; + + return { + {"n_row", std::to_string(n_row)}, + {"n_col", std::to_string(n_col)}, + {"col_format", col_format_str}, + {"col_names", col_names_str} + }; + } + std::string get_classname() const override { + return "Table"; + } +}; + +struct TableSyntax : public BlockSyntax { + bool operator()(Reader &read) const override { + if (!read.is_line_begin()) return false; + if (std::regex_match(rtrim(read.get_row()), std::regex(R"((\|[^\|]+).+\|)"))) return true; + return false; + } + void operator()(Reader &read, ASTNode &ast) const override { + // BEGIN : R"((\|[^\|]+).+\|)" + // END : EOF or NewLine + const std::regex each_col_regex(R"(\|[^\|]+)"); + + int n_col = 0; + std::vector col_names; + std::smatch sm; + + std::string line = read.get_row(); + + while (std::regex_search(line, sm, each_col_regex)){ + col_names.push_back(sm[0].str().substr(1)); + line = sm.suffix(); + n_col++; + } + + // 0 --> left (default), 1 --> center, 2 --> right + std::vector col_format; + std::regex Lrex(R"(\|\s*:-+\s*)"); + std::regex Crex(R"(\|\s*:-+:\s*)"); + std::regex Rrex(R"(\|\s*-+:\s*)"); + + read.move_next_line(); + + line = read.get_row(); + + while (std::regex_search(line, sm, each_col_regex)){ + if (std::regex_match(sm[0].str(), Lrex)){ + col_format.emplace_back(0); + } + else if (std::regex_match(sm[0].str(), Crex)){ + col_format.emplace_back(1); + } + else if (std::regex_match(sm[0].str(), Rrex)){ + col_format.emplace_back(2); + } + else { + col_format.emplace_back(0); + } + + line = sm.suffix(); + } + + if (col_names.size() != col_format.size()){ + throw SyntaxError("Number of columns of Table must be same"); + } + + read.move_next_line(); + + int n_row = 0; + std::vector table; + + while (true){ + if (EOFSyntax{}(read) || NewLineSyntax{}(read)) break; + n_row++; + line = read.get_row(); + while (std::regex_search(line, sm, each_col_regex)){ + table.push_back(sm[0].str().substr(1)); + line = sm.suffix(); + } + read.move_next_line(); + } + + std::vector> columns_markdowns; + + for (int i = 0; i < n_col; i++){ + Markdown inner_md; + InlineParser::process(col_names[i], inner_md); + columns_markdowns.push_back(std::make_shared(inner_md)); + } + + Table node(columns_markdowns, n_row, n_col, col_names, col_format); + + for (auto cell : table){ + Markdown inner_md; + InlineParser::process(cell, inner_md); + node.add_child(std::make_shared(inner_md)); + } + + ast.add_child(std::make_shared(node)); + } +}; + +} // namespace almo \ No newline at end of file diff --git a/src/syntax_all.hpp b/src/syntax_all.hpp new file mode 100644 index 0000000..fca26b3 --- /dev/null +++ b/src/syntax_all.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "syntax/CodeBlock.hpp" +#include "syntax/DivBlock.hpp" +#include "syntax/EOF.hpp" +#include "syntax/EnumerateBlock.hpp" +#include "syntax/ExecutableCodeBlock.hpp" +#include "syntax/Header.hpp" +#include "syntax/HorizontalLine.hpp" +#include "syntax/InlineCodeBlock.hpp" +#include "syntax/InlineImage.hpp" +#include "syntax/InlineItalic.hpp" +#include "syntax/InlineMath.hpp" +#include "syntax/InlineOverline.hpp" +#include "syntax/InlineStrong.hpp" +#include "syntax/InlineUrl.hpp" +#include "syntax/Item.hpp" +#include "syntax/Judge.hpp" +#include "syntax/ListBlock.hpp" +#include "syntax/LoadLib.hpp" +#include "syntax/Markdown.hpp" +#include "syntax/MathBlock.hpp" +#include "syntax/NewLine.hpp" +#include "syntax/Quote.hpp" +#include "syntax/RawText.hpp" +#include "syntax/Table.hpp"