この記事は、TypeNovelのコンパイラをアプリケーションから利用したい開発者向けのものです。
導入
npm install --save typenovel
コンパイラの呼び出し
ソーステキストからコンパイルするときは、Tnc.fromString
です。
import { Tnc } from 'typenovel'; const result = Tnc.fromString('@scene(){ foo }', { format: 'html', // もしくは 'text' minify: false // trueだと圧縮表示 }); console.error(result.errors); // エラー console.log(result.output); // コンパイル結果
ソースファイルからコンパイルするときは、Tnc.fromFile
です。
import { Tnc } from 'typenovel'; const result = Tnc.fromFile('sample.tn', { format: 'html', minify: false }); console.error(result.errors); // エラー console.log(result.output); // コンパイル結果
コンパイラを拡張する
Compile
クラスを使うと、コンパイルの各段階におけるVisitorを自前のものに置き換えることができます。
例えば、TypeNovelのノードツリー(TnNode
)から出力文字列に変換するVisitor(NodeFormatter
)を自前のものにするときは、こんな風にします。
import { Compile } from 'typenovel'; const myFormatter = new MyNodeFormatter(); // 自前で実装したFormatter const result = Compile.fromString('@scene(){ foo }', { nodeFormatter: myFormatter // 自前のFormatterを設定 }); console.log(result.output); // コンパイル結果
ここで、MyNodeFormatter
は、NodeFormatter
interfaceを実装したクラスです。
NodeFormatter
interfaceはnode-formatter.ts
にて、次のように定義されています。
export interface NodeFormatter { visitTextNode: (args: { content: string; isWhiteSpacePre: boolean; isFirstChild: boolean; isLastChild: boolean; prev?: TnNode; next?: TnNode; indent: number; }) => string; visitAnnotNode: (args: { name: string; tagName: string; id: string; className: string; attrs: any; content: string; selfClosing: boolean; prev?: TnNode; next?: TnNode; indent: number; }) => string; visitBlockNode: (args: { name: string; tagName: string; id: string; className: string; attrs: any; content?: string; children: TnNode[]; prev?: TnNode; next?: TnNode; indent: number; }) => string; }
それぞれの関数の引数に、各ノードの情報が入っています。
ようするに、これらの情報を元にして、テキストノード(TextNode
)、注釈タグ(AnnotNode
)、制約ブロック(BlockNode
)のそれぞれを、なんらかのテキストに変換して返せばよいわけです。
例えば注釈タグではクラス属性やid属性など無視して、タグ名だけあれば十分!などと思うなら、次のようにvisitAnnotNode
を実装すればいいわけです。
class MyNodeFormatter implements NodeFormatter { (省略) visitAnnotNode: (args: { name: string; tagName: string; id: string; className: string; attrs: any; content: string; selfClosing: boolean; prev?: TnNode; next?: TnNode; indent: number; }){ return `<${args.tagName}>${args.content}</${args.tagName}>`; } }
拡張できるinterfaceはノードから出力テキストの変換処理だけではありません。
次の部分をユーザーが自由に拡張できます。
TypeNovelParser(String -> Ast)
TypeNovelParser
は、TypeNovelのソースからAstを作成するインターフェイスです。
AstMapper(Ast -> Ast')
AstMapper
は、Ast
を別のAst
に入れ替えるインターフェイスです。
AstConverter(Ast -> TnNode)
AstConverter
は、Ast
をTnNode
に変換するインターフェイスです。
NodeMapper(TnNode -> TnNode')
NodeMapper
は、TnNode
を別のTnNode
に入れ替えるインターフェイスです。
NodeValidator(TnNode -> ValidationError[])
NodeValidator
は、TnNode
を検査してValidationError
を出力するインターフェイスです。
NodeFormatter(TnNode -> String)
NodeFormatter
は、TnNode
から出力テキストを生成するインターフェイスです。
拡張例
例えばTypeNovelにおいて、別のTypeNovelソースを展開する構文である
$include('別ソース')
などは、Ast -> Ast'
な処理なので、IncludeExpander
クラスとしてAstMapper
インターフェイスで実装されています。
あるいは、ノードから無駄なホワイトスペースを削除するNodeWhitespaceCleaner
クラスは、TnNode -> TnNode'
な処理なので、NodeMapper
インターフェイスで実装されています。
こうやって、コンパイラの各段階を自分好みのものに置き換えることで、各自でコンパイラが拡張できるような作りになっています。