Skip to content

追加したい機能とそのイメージ

Takashi Suwa edited this page Aug 20, 2022 · 9 revisions

将来的に追加したいと思っている機能を具体的なコード例のイメージと共にこのページにひとまず書き留めておきます.

doc comment(2020-03-22作成,2022-08-20更新)

パッケージ中にいわゆるdoc commentを書けるようにする.

具体的なコードのイメージ

基本的にはシグネチャの typeval などの宣言の直後にその説明を書く:

module List :> sig

  #[doc {
    `List.map @f@ @xs@` でリスト `@xs@` の各要素に
     函数 `@f@` を適用してできる各値を元の順序のまま並べたリストを返す.
  }]
  val map : ('a -> 'b) -> 'a list -> 'b list

  ...
end = struct
  ...
end

ドキュメントを組むのに用いるために #[doc-require ...] によって別のパッケージを読み込める:

% bnf.satyh
require Math

#[doc-require Code]
#[doc-require Math]
#[doc-require Bnf] % ドキュメント生成時には自身を通常のパッケージとして読み込む

module Bnf :> sig

  #[doc '<
    +p{
      BNFによる構文定義を組むコマンド.
      第1引数が定義されるメタ変数,第2引数の各要素が並列して組まれる各行となる.
    }
    +p{
      例として,以下のように記述すると,

      \d-code(```
        \Bnf(${M})[
          ${| c | x | M\ M | \lambda x.\ M |};
          ${| \text!{let}\ x = M\ \text!{in}\ M |};
        ];
      ```);

      以下のように組まれる:

      \Bnf(${M})[
        ${| c | x | M\ M | \lambda x.\ M |};
        ${| \text!{let}\ x = M\ \text!{in}\ M |};
      ];
    }
  >]
  val \Bnf : inline [math; list (list math)]

  ...
end = struct
  ...
end

コマンド

以下を実行することでパッケージ 〈Pkg〉 のドキュメントを組む:

$ satysfi gendoc 〈Cls: ドキュメント生成用クラスファイル〉 〈Pkg: ドキュメントを生成したいパッケージ〉

クラスファイル 〈Cls〉 はドキュメント生成のために以下のようなコマンド等を提供する必要がある:

  • +val-entry : block [string, list (string * kind-representation), type-representation; option block-text]
    • val に対するdoc commentを組む.
    • doc commentのない公開されている函数については第4引数に None が渡される.
    • type-representation は型の構文を表現するデータ型.
  • +type-entry : [string, list (string * kind-representation), option type-representation, option block-text] block-cmd
    • type に対するdoc commentを組む.
    • 第1引数は型パラメータの列.
    • 第2引数は,opaque typeの場合は None,transparent typeの場合は Some(_) が渡される.
    • doc commentのない公開されている函数については第3引数に None が渡される,
  • +p : block [inline-text]
    • 通常の段落.
  • document : block-text -> document

追加する構文

  • ドキュメント生成の際に読み込むパッケージの指定

    #[doc-require 〈パッケージ名〉]
    
    • 読み込むパッケージは自身でもよい.
  • 値に対するdoc comment

    #[doc {〈It: X について説明する文のインラインテキスト〉}]
    val 〈X: 名前〉 〈Q: 型変数の量化〉 : 〈T: 型〉
    
    • +val-entry(`〈X〉`)(〈Q を表現するデータ〉)(〈T を表現するデータ〉)(Some '<+p{〈It〉}>); というコードに変換される.
  • 値に対するdoc comment(複数段落にしたい場合)

    #[doc '<〈Bt: X について説明する文のブロックテキスト〉>]
    val 〈X: 名前〉 : 〈T: 型〉
    
    • +val-entry(`〈X〉`)(〈Q を表現するデータ〉)(〈T を表現するデータ〉)(Some '<〈Bt〉>); と書いたものして扱われる.
  • 型に対するdoc comment

    #[doc {〈It〉}]
    type 〈X: 名前〉 〈Params: 型パラメータの列〉 = 〈T: 型〉
    
    • +type-entry(〈Params を表現するデータ〉)(`〈X〉`)(Some '<+p{〈It〉}>); と書いたものとして扱われる.
    • #[doc '<〈Bt〉>]` の場合も同様.
  • 一般の段落

    #[doc-p '<〈Bt〉>]
    
    • 〈Bt〉 を直接書いたものとして扱われる.

数式のテキスト出力対応(2020-04-02作成)

数式をテキスト出力モードに対応させたいが,PDF出力向けに整備された既存の仕組みをそのまま使い回すのではかなりぎこちない.そのぎこちない方法も含めて何通りかの実現方法の候補があるので,以下に掲げておく.かなり雑然とした説明になってしまうが,いつまでも何も書かないよりは遥かに有意義なので書く,という程度の記述であることを了承されたい.

1 数式を代数的データ型で扱う

まず,通常の数式である math 型は以下に示すような定義をもつ組み込みの型であり,${ … } などによる記法は math 型の式を書くための糖衣構文であるとする:

type math-class =
  | MathOrd
  | MathBin
  | …

type math =
  math-element list

type math-element =
  | MathSuperscript     of math * math
  | MathSubscript       of math * math
  | MathFraction        of math * math
  | MathRadical         of math
  | MathUpperLimit      of math * math
  | MathLowerLimit      of math * math
  | MathGroup           of math-class * math-class * math

  | MathChar            of math-class * math-char
  | MathParen           of math-paren * math-paren * math
  | MathParenWithMiddle of math-paren * math-paren * math-paren * math list
  | MathGeneralInline   of inline-result

math-class数式クラス という(スペーシング等に関連する)数式アトムの種別を列挙した型.

ひとまず math-element 型のコンストラクタのうち手前の7種類をみる.MathSuperscript(m1, m2)m1 に対して指数 m2 をつけてつくる数式のデータである.同様に MathSubscript(m1, m2)MathFraction(m1, m2)MathRadical(m)MathUpperLimit(m1, m2)MathLowerLimit(m1, m2) はそれぞれ添字,分数,根号,上積み,下積みの構造によって構成される数式のデータである.MathGroup(mclass1, mclass2, m) は数式 m を(内的に決まる数式クラスに従わずに)左側に対して数式クラス mclass1,右側に対して数式クラス mclass2 でスペーシング上扱うものと指定する.

最後の4つがPDFとテキストの共通化に関して重要である.MathChar(mclass, mchar) は数式記号 mchar を数式クラス mclass として組む数式の指定である.また MathParen(paren1, paren2, m) は数式 m を左の括弧 paren1 と右の括弧 paren2 で囲って出力する数式の指定であり,MathParenWithMiddle(paren1, paren2, paren, ms)paren を “セパレータ” として数式のリスト ms : math list の各要素を 左の括弧 paren1 と右の括弧 paren2 の間に並べて組む指定である.最後のものはわかりづらいが,集合の内包表記や量子力学によく登場するブラケット記法など,左右の括弧に高さを合わせて伸びるグリフが括弧内にもある場合に使う.MathGeneralInline(inline) は “通常のテキストの処理結果” inline を数式中に埋め込む.

そして,これらの引数には以下のような型が使われる:

  • math-char 型: 数式中の(ローマン,イタリックといった数式文字クラスに依存しない)記号を表す型である.すぐ後で述べるが,PDF出力モードとテキスト出力モードで定義が変わりうる.PDF出力の場合は,添字や指数がついたときのカーニングの入れ方の指定なども含む.
  • math-paren 型: 括弧のグリフ等を指定するための型.これも後述するがPDF出力モードとテキスト出力モードで定義が変わりうる.
  • inline-result 型: これは単純で,PDF出力モードでは inline-boxes,テキスト出力モードでは string

ところで「通常のラテン文字やギリシア文字のように “ローマンとかイタリックといった指定に依存して変化する文字” はどうやって扱うのだろう?」と疑問に思う必要はない.これらは “よりフロントエンド側の部分” で吸収すればよい問題だからである.これについては 1.3 で後述する.

1.1 math-char の実体

さて,math-char 型の “実体” はどう定めればよいだろうか? 既に述べたように,これはPDFモードとテキストモードで別々に提供すべき部分である.というのも “PDF出力に向けては に,LaTeXコードの出力に向けては \subseteq に,MathMLの出力に向けてはやはり にする” といった文字単独に起因する差異をここに集約するためである.

1.1.1 テキスト出力モードの場合

まずテキスト出力モードの場合は基本的には単に string 型にするだけでよい.これにより,例えば \subseteq コマンドは(出力も単に \subseteq␣ という文字列にしたいので)以下のようにLaTeX出力向けに定義できる:

let-math tinfo \subseteq =
  MathChar(MathRel, `\subseteq `#)

ここで let-math によるコマンド定義は(let-inlinelet-block と同様に)外から文脈が渡されてくるようになっているものとする.

しかし,実際には文字色など “構造から自然に決まるわけではない追加の体裁情報” も出力に含められるように定式化したいので,このような追加の体裁情報を文脈 tinfo から取り出して埋め込んでおく必要がある.そこで以下のように math-char を定める:

type display-spec = (|
  color : color;
  …
|)

type math-char = (|
  main : string;
  spec : display-spec;
|)

要するに spec というフィールドに文字色など追加の体裁情報をとれるようにしたもの.これに基づくと,上に掲げた \subseteq の定義は以下のように変わる:

let-math tinfo \subseteq =
  let spec = get-math-char-spec tinfo in
  MathChar(MathRel, (|
    main = `\subseteq `#;
    spec = spec;
  |))

ただし get-math-char-spec : text-info -> display-spec は別の箇所で定義されていて,文脈から必要な体裁情報を抽出する補助函数であるとする.

1.1.2 PDF出力モードの場合

PDFモードの場合も,文字色などの体裁情報を扱う必要はあるものの基本的には単に string 型の延長にすれば事足りる.実際,現行のv0.0.4のSATySFiでは

val math-char : math-class -> string -> math

というプリミティヴが提供されており,構築方法自体はここで考えた MathChar とほぼ等価になっている.

ただし,PDFモードでは(別のテキスト形式への変換ではなく自前で組版処理を行なう場合に特有な指定として)カーニング方法の指定が入ることがある.実際,現行のSATySFi v0.0.4では以下のようなプリミティヴが提供されている(これはv0.0.1当初からある):

type math-kern = length -> length -> length

val math-char-with-kern : math-class -> string -> math-kern -> math-kern -> math

math-kern は組み込みの型名ではないがここで便宜的に使っている.この第3・第4引数は “この文字アトムに添字や指数がついたときに高さに応じてどれだけカーニングを施すべきか” を指定した函数である.これらも扱うためには,math-char は以下のような型へと拡張されねばならない:

type display-spec = (|
  kern  : (math-kern * math-kern) option;
  color : color;
  …
|)

type math-char = (|
  main : string;
  spec : display-spec;
|)

そして従来の math-charmath-char-with-kern に相当する函数を以下のように定義する:

let math-char : context -> math-class -> string -> math
  | ctx mcls s =
      let me =
        MathChar(mcls, (|
          main = s;
          spec = (| kern = None; color = get-text-color ctx; … |);
        |))
      in
      [me]

let math-char-with-kern : context -> math-class -> string -> math-kern -> math-kern -> math
  | ctx mcls s kernL kernR =
      let me =
        MathChar(mcls, (|
          main = s;
          spec = (| kern = Some(kernL, kernR); color = get-text-color ctx; … |);
        |))
      in
      [me]

この新しい math-char を用いると,\subseteq は次のような定義になる:

let-math ctx \subseteq =
  math-char ctx MathRel `⊆`

1.2 math-paren の実体

(要加筆)

1.3 数式出力コマンドの定義

PDFを出力する場合は基本的には math-element 型を用いて構築される数式のデータをあらためて分解したりする必要はなく,(現行のSATySFi v0.0.4の embed-math : context -> math -> inline-boxes のような)math 型のデータを渡されたらそれを組む数式エンジン的な機構がプリミティヴとして備わっていればよい.

一方でテキストを出力する場合は一度組み上げた数式を文字列に落とし込む処理が必要である.\math コマンドの実装は以下のような具合になる:

let make-math-class-command mcls =
  match mcls with
  | MathOrd -> `\mathord`
  | MathBin -> `\mathbin`
  | MathRel -> `\mathrel`
  …

let make-color-spec color =
  match color with
  | RGB(r, g, b) -> `[rgb]{` ^ show-float (r /. 255.) ^ `,` show-float (g /. 255.) ^ `,` ^ show-float (b /. 255.) ^ `}`
  …

let-rec output-math-element tinfo me =
  match me with
  | MathSuperscript(m1, m2) -> output-math tinfo m1 ^ `_{` ^ output-math tinfo m2 ^ `}`
  | MathSubscript(m1, m2)   -> output-math tinfo m1 ^ `^{` ^ output-math tinfo m2 ^ `}`
  | MathFraction(m1, m2)    -> `\frac{` ^ output-math tinfo m1 ^ `}{` ^ output-math tinfo m2 ^ `}`
  …
  | MathChar(mcls, mchar) ->
      let clscmd = make-math-class-command mcls in
      let str = mchar#main in
      let color-spec = make-color-spec mchar#spec#color in
      clscmd ^ `{\textcolor` ^ color-spec ^ `{` ^ str ^ `}}`


and output-math tinfo m =
  m |> List.map (output-math-element tinfo) |> List.fold-left (^) ` `


let-inline tinfo \math m =
  `\(` ^ output-math tinfo m ^ `\)`

1.4 フロントエンド側

前述のように,ラテン文字やギリシア文字のような “ローマンとかイタリックといった指定に依存して変化する文字” は math 型よりもフロントエンド側での処理により扱える.例えば \Gamma は次のように扱える:

let-math ctx \Gamma =
  let str =
    match get-math-class ctx with
    | MathItalic     -> `𝛤` % U+1D6E4
    | MathBoldItalic -> `𝜞` % U+1D71E
    | MathRoman      -> `Γ` % U+0393
    | MathBoldRoman  -> `𝚪` % U+1D6AA
    | _              -> `Γ` % U+0393 (dummy)
  in
  let me =
    MathChar(MathOrd, (|
      main = str;
      spec = (| color = get-text-color ctx; kern = None; … |);
    |))
  in
  [me]

ラテン文字はこれに相当する定義を文脈に適切なプリミティヴで突っ込んでおき,数式を走査した際にそのときの文脈に基づいて変換する仕組みにするとよい.

2 現行の数式の取り扱い方の延長上にテキスト出力機能を追加する

(要加筆)

Clone this wiki locally