🌊

Typst コードを Mathlog マークアップに変換する(in Rust)

2023/06/13に公開

概要

新進気鋭の組版言語 Typst で Mathlog の記事を執筆するためのツールが最低限のところまで出来たので公開しました.

https://github.com/arakur/typst-to-mathlog/tree/master

前提

  • Typst: 新興の数式組版言語.2023 年 3 月にパブリックベータ版がリリースされた.
  • Mathlog: 数学記事を投稿するブログサービス.HTML を拡張した特有の構文で文章を記述する.
  • Rust: 最強のプログラミング言語.Typst は Rust で記述されている.

Mathlog をはじめブログサービスは大抵ブラウザ上で記事を書くことが出来ますが,独自構文による記述をしなければならないことに加えシンタクスハイライトや補完などのエディタ機能が貧弱な場合が多いため苦労しがちです.
そこで,コンパイルが爆速で記述性の高い Typst から各サービスの構文へと変換するツールが欲しくなります.
Mathlog の構文は Markdown + MathJax + 定義などの形式といった感じなので,Typst の良い感じのサブセットをとってやればある程度の精度のものは作れるはずと思いました.

結果

次のような感じで変換されます.

#import "../style/mathlog_style.typ": *

= Gröbner 基底

== 単項式順序

$K$ を体,$R = K[X_1, ..., X_n]$ を $K$-上 $n$ 変数多項式環とする.
$R$ の単項式全体の集合を $cal(M)_R$ とおく.
$cal(M)_R$ は乗法に関して可換モノイドをなす.

#def(title: "単項式順序")[
    多項式環 $R$ の *単項式順序* (_monomial order_) とは,$cal(M)_R$ 上の全順序 $prec.eq$ であって,任意の $mu, mu', nu in cal(M)_R$ に対して以下を満たすもののことである:
    
    1. $1 prec.eq mu$;
    2. $mu prec.eq mu' ==> mu dot nu prec.eq mu' dot nu$.
]

#prop[
    任意の単項式順序は整礎である.
]

#prf[
    略.
]
<!-- #import "../style/mathlog_style.typ": * -->

# Gröbner 基底

## 単項式順序

$K$を体,$R=K\left[X_{1},…,X_{n}\right]$を$K$-上$n$変数多項式環とする.$R$の単項式全体の集合を$\mathcal{M}_{R}$とおく.$\mathcal{M}_{R}$は乗法に関して可換モノイドをなす.

&&&def 単項式順序
多項式環$R$の**単項式順序**(*monomial order*) とは,$\mathcal{M}_{R}$上の全順序$≼$であって,任意の$\mu,\mu',\nu∈\mathcal{M}_{R}$に対して以下を満たすもののことである:
1. $1≼\mu$;
2. $\mu≼\mu'⟹\mu\cdot\nu≼\mu'\cdot\nu$.
&&&

&&&prop
任意の単項式順序は整礎である.
&&&

&&&prf
略.
&&&

実装の流れ

1. Typst ソースの構文木を取得

手始めに Typst ソースをパースして構文木を手に入れます.
Typst のソースはクレートとして cargo に公開はされていませんが,Cargo.toml に

[dependencies]
typst = {git = "https://github.com/typst/typst.git"}

と書けば使えます.

構文解析に必要なコードは typst::syntax 以下にすべて入っています.

use typst::syntax::*;

let parsed = parse(&input);
let typst_stx = ast::Markup::from_untyped(&parsed);

2. Mathlog の構文木を作る

Mathlog は HTML を拡張した独自構文を持っているので,まずはこれを AST として表現できる形に直します.

#[derive(Debug, Clone)]
pub struct Syntax {
    pub paragraphs: Paragraphs,
}

pub type Paragraphs = Vec<Paragraph>;

#[derive(Debug, Clone)]
pub struct Paragraph {
    pub segments: Segments,
}

#[derive(Debug, Clone, Default)]
pub struct Segments(pub Vec<Segment>);

#[derive(Debug, Clone)]
pub enum Segment {
    Linebreak,
    Heading(Heading),
    Text(Text),
    CodeInline(CodeInline),
    Strong(Strong),
    Emph(Emph),
    MathInline(MathInline),
    MathDisplay(MathDisplay),
    ListItem(ListItem),
    MathDelimited(MathDelimited),
    MathAttach(MathAttach),
    MathAlignPoint,
    Command(Command),
    RawCommand(RawCommand),
    Env(Env),
    ExportComment(String),
}

#[derive(Debug, Clone)]
pub struct Heading {
    pub level: usize,
    pub content: Segments,
}

// ... 以下略

通常の markdown + MathJax と大きく違うのは定義や定理を記述する 環境

&&&thm (定理名)
ソクラテスは死ぬ.
&&&

の存在です.

次にそのパーサを書いて,Mathlog 構文木を文字列に変換します.
この出力されたコードを Mathlog にコピペすればよいというわけです.

3. Typst の構文木を Mathlog の構文木に変換

最後に Typst の構文木を Mathlog の構文木に変換するのですが,これは大きく分けて

  1. Typst のマークアップを Mathlog のマークアップに変換する過程
  2. Typst の数式を MathJax の数式に変換する過程

の2段階に分けられます.

1 については,Typst のマークアップの構文要素を一つずつ Mathlog のものに変換する(かエラーを吐く)ことで網羅することが可能ですが,2 には Typst のコマンドを LaTeX の数式コマンドに変換するテーブルを作成するというそれができるだけでリポジトリ一つ建てられそうな作業が待っているため,これを網羅するのは現実的ではありません.
一方,Typst のコマンドと Unicode 文字の対応関係であればこちらから取得できるので,これを適当に整形してコマンド対応表.json を作成しました.

{
    "idents": {
        "AA": "𝔸",
        "Alpha": "Α",
        "BB": "𝔹",
        "Beta": "Β",
        "CC": "ℂ",
        "Chi": "Χ",
        "DD": "𝔻",
        "Delta": "Δ",
        "EE": "𝔼",
        "Epsilon": "Ε",
        "Eta": "Η",
        "FF": "𝔽",
        "GG": "𝔾",
        "Gamma": "Γ",
        "HH": "ℍ",
        "II": "𝕀",
        "Im": "ℑ",
        "Iota": "Ι",
        "JJ": "𝕁",
        "KK": "𝕂",
        "Kai": "Ϗ",
        "Kappa": "Κ",
        "LL": "𝕃",
        "Lambda": "Λ",
        "MM": "𝕄",
        "Mu": "Μ",
        "NN": "ℕ",
        "Nu": "Ν",
        "OO": "𝕆",
        "Omega": "Ω",
...

その後,対応関係を追加するファイルを別途手動で作成し,先ほどのものに上書きします.
すべてを網羅するのは難しいですが,少なくとも LaTeX コマンドが出せなくとも unicode 文字は出せるので,最終的な数式出力は問題なく得られるようになります.

{
  "idents": {
    "quad": "\\quad",
    "AA": "\\bb{A}",
    "Alpha": "A",
    "BB": "\\bb{B}",
    "Beta": "B",
    "CC": "\\bb{C}",
    "Chi": "\\Chi",
    "DD": "\\bb{D}",
    "Delta": "\\Delta",
    "EE": "\\bb{E}",
    "Epsilon": "E",
    "Eta": "H",
    "FF": "\\bb{F}",
    "GG": "\\bb{G}",
    "Gamma": "\\Gamma",
    "HH": "\\bb{H}",
    "II": "\\bb{I}",
    "Im": "\\Im",
    "Iota": "I",
    "JJ": "\\bb{J}",
    "KK": "\\bb{K}",
    "Kappa": "K",
    "LL": "\\bb{L}",
    "Lambda": "\\Lambda",
    "MM": "\\bb{M}",
    "Mu": "M",
    "NN": "\\bb{N}",
    "Nu": "N",
    "OO": "\\bb{O}",
    "Omega": "\\Omega",
...

以上によって得られた辞書を元に,Typst の構文木を Mathlog の構文木に変換するコードを書きました.

結論

基本的に自分用に作ったものなので,自分が使わない機能の実装を後回しにしてあります(そのうち埋めます).
個人的にはかなり要領が掴めてきたので,暇があれば他のブログサービスも変換ツールを作ってみたいと思います.

Discussion