312 lines
14 KiB
HTML
312 lines
14 KiB
HTML
<!DOCTYPE html>
|
||
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
|
||
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
||
|
||
<link rel="canonical" href="https://blog.kianting.info/pages/docs/anotherTypeSetter/defineASTandGrammar/">
|
||
<link rel="shortcut icon" href="../img/favicon.ico">
|
||
<title>Ch 1 - Another Typesetter 另一個排版器</title>
|
||
<link rel="stylesheet" href="../css/theme.css" />
|
||
<link rel="stylesheet" href="../css/theme_extra.css" />
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css" />
|
||
|
||
<script>
|
||
// Current page data
|
||
var mkdocs_page_name = "Ch 1";
|
||
var mkdocs_page_input_path = "defineASTandGrammar.md";
|
||
var mkdocs_page_url = "/pages/docs/anotherTypeSetter/defineASTandGrammar/";
|
||
</script>
|
||
|
||
<script src="../js/jquery-2.1.1.min.js" defer></script>
|
||
<script src="../js/modernizr-2.8.3.min.js" defer></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||
<script>hljs.initHighlightingOnLoad();</script>
|
||
|
||
</head>
|
||
|
||
<body class="wy-body-for-nav" role="document">
|
||
|
||
<div class="wy-grid-for-nav">
|
||
|
||
|
||
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
||
<div class="wy-side-scroll">
|
||
<div class="wy-side-nav-search">
|
||
<a href=".." class="icon icon-home"> Another Typesetter 另一個排版器</a>
|
||
<div role="search">
|
||
<form id ="rtd-search-form" class="wy-form" action="../search.html" method="get">
|
||
<input type="text" name="q" placeholder="Search docs" title="Type search term here" />
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
|
||
<ul>
|
||
<li class="toctree-l1"><a class="reference internal" href="..">Home</a>
|
||
</li>
|
||
</ul>
|
||
<ul class="current">
|
||
<li class="toctree-l1 current"><a class="reference internal current" href="./">Ch 1</a>
|
||
<ul class="current">
|
||
<li class="toctree-l2"><a class="reference internal" href="#_1">抽象語法樹</a>
|
||
</li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#_2">決定語法</a>
|
||
</li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#parsercombinatortokenize">用ParserCombinator進行tokenize</a>
|
||
</li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#_3">平面操作</a>
|
||
<ul>
|
||
<li class="toctree-l3"><a class="reference internal" href="#_4">基本函數與直譯器</a>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<ul>
|
||
<li class="toctree-l1"><a class="" href="../2DManipulating.md">Ch 2</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||
|
||
|
||
<nav class="wy-nav-top" role="navigation" aria-label="top navigation">
|
||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||
<a href="..">Another Typesetter 另一個排版器</a>
|
||
</nav>
|
||
|
||
|
||
<div class="wy-nav-content">
|
||
<div class="rst-content">
|
||
<div role="navigation" aria-label="breadcrumbs navigation">
|
||
<ul class="wy-breadcrumbs">
|
||
<li><a href="..">Docs</a> »</li>
|
||
|
||
|
||
|
||
<li>Ch 1</li>
|
||
<li class="wy-breadcrumbs-aside">
|
||
|
||
</li>
|
||
</ul>
|
||
|
||
<hr/>
|
||
</div>
|
||
<div role="main">
|
||
<div class="section">
|
||
|
||
<h1 id="ch1">Ch1 定義抽象語法樹和語法</h1>
|
||
<h2 id="_1">抽象語法樹</h2>
|
||
<p>C語言、Python語言就算有許多的關鍵字、操作符、符號或是常數變數,在編譯器分析語法以後,最後會轉成編譯器可以操作的樹結構,然後再轉成我們想要的另一個語言的樹,最後輸出另一個語言的程式碼。</p>
|
||
<p>但是什麼叫做抽象語法樹呢?我們先從一點句法知識來談。</p>
|
||
<p>學過中學國文文法的課程,會背一堆類似「主詞+動詞+受詞」、「主詞+(有/無)+受詞」的結構。可以換個說法,是句子=「主詞+動詞+受詞」或是「主詞+(有/無)+賓詞」的形式。我們將「=」寫成「::=」,「/」(或是)寫成「|」,「動詞」擴充變成「動詞片語」,就變成:</p>
|
||
<pre><code> 句子 ::= (主詞 動詞片語 受詞) | (主詞 (有 | 無) 受詞)...
|
||
|
||
</code></pre>
|
||
<p>為了易讀所以寫成:</p>
|
||
<pre><code>句子 ::= 主詞 動詞片語 受詞
|
||
| 主詞 (有 | 無) 受詞
|
||
| ...
|
||
|
||
</code></pre>
|
||
<p>用這種形式表示的語言句法,叫做「BNF文法」。這種句法看起來很語言學,但是我們想:受詞和主詞可以為名詞、專有名詞或是「形容詞+名詞」;動詞片語可以為動詞或是「副詞+動詞」。因此這樣之規則,就可以生成許多句子,比如「我有筆」、「張三養貓」、「小芳慢慢移動檯燈」等等的句子。然後句子可以用上述規則,分析成語法的樹狀結構,如下圖把「我曾旅居新竹」寫成語法樹。</p>
|
||
<figure>
|
||
<p><img alt="「我曾旅居新竹」的語法樹" src="../syntaxtree.svg" title="" />
|
||
</p>
|
||
<figcaption>「我曾旅居新竹」的語法樹</figcaption>
|
||
</figure>
|
||
<p>同理,程式語言通常也有更嚴謹的這樣生成文法,可以用幾個簡單規則生出繁多的程式碼,而且合乎語法規定。這種生成文法也可檢查輸入的程式碼有沒有符合句法的規定。而這種語法生成的程式碼,去掉不需要的逗號等等符號,當然也可以做成語法樹,就是抽象語法樹 (abstract syntax tree, AST),如下圖所示。</p>
|
||
<figure>
|
||
<p><img alt="「(2+2) == 4」的語法樹。注意括號已經刪除。" src="../syntaxtree2.svg" title="" />
|
||
</p>
|
||
<figcaption>「(2+2) == 4」的語法樹。注意括號已經刪除。</figcaption>
|
||
</figure>
|
||
<p>而上文的抽象語法樹,可以是我們把程式經過編譯器分析之後,用「樹」儲存的資料結構。而樹形結構我們可以使用Lisp語言的S表達式(S-expressiom; S-exp)來表示,本文採用這樣的表示方法。所以上文的<code>(2+2)==4</code>即<code>(== (+ 2 2) 4)</code>;<code>let baz = foo("bar")</code>,若是把foo("bar")這種函數套用(apply)寫成<code>(APPLY foo "bar")</code>,則其S-exp語法樹可寫為<code>(let baz(APPLY foo "bar"))</code>。</p>
|
||
<h2 id="_2">決定語法</h2>
|
||
<p>那我們要如何制定這個語言的語法,這樣我們才能夠寫出符合這個語法的函數,然後再用tokenizer和parser轉成AST樹。</p>
|
||
<p>不考慮<code>+ - * /</code>這種運算子,以及向量的表示子,函數可以用<code>ID(arg1, arg2, ...)</code>這種方式來表示,其中<code>arg_x</code>是引數,<code>ID</code>是識別子(identifier,可以把它想成變函數的名字)。</p>
|
||
<p>變數可以是<code>ID</code>,<code>arg_n</code>可以是<code>ID</code>或常數(量)。</p>
|
||
<p>常數(量)的表示法可以是下列任一:</p>
|
||
<ul>
|
||
<li>
|
||
<p>浮點數如0.0, 36.8,BNF風格的表達法為:<code>[0-9]+ '.' [0-9]+</code>。<code>'c'</code>指c這個文字,<code>+</code>表示前面的重複1次以上;<code>[0-9]</code>表示數字0到9。</p>
|
||
</li>
|
||
<li>
|
||
<p>整數如22、0:<code>[0-9]+</code></p>
|
||
</li>
|
||
<li>
|
||
<p>字串:<code>'"' (不是「"」的任一字元|('\' '"')) '"'</code>(<code>.</code>表示任何一個字元)</p>
|
||
</li>
|
||
</ul>
|
||
<p>然而我們還是需要綁定變數<code>let x = var in boby</code>(在<code>body</code>裡面,<code>x</code>指代<code>var</code>)、<code>set x = var</code>(改變變數值)、lambda<code>lambda (x)=>{body}</code>。另外為了要區別要在PDF印上去的一般字元,在這個檔案的常數、變數、函數、關鍵字等前後需要加@表示(但是函數、lambda裡面的變數不用)。比如<code>@foo(a, b)@</code>、<code>@lambda(x)@</code>、<code>@"IAmAString"@</code>、<code>@2.2@</code>、<code>@3@</code>(後三者應該很少用到)可是若需在PDF印<code>@</code>時怎辦?那就用<code>\@</code>。比如<code>foo\@example.com</code>。</p>
|
||
<p>所以我們可以定義以下的BNF風文法:</p>
|
||
<pre><code>Language ::= PrintTxt | Exprs
|
||
|
||
PrintTxt ::= (('\' '@')| 非@字元)+ //「我是一隻貓」或是「www\@example.com」
|
||
|
||
Exprs ::= @ Expr* @ // *表示前面的重複0次以上(包含不出現)
|
||
|
||
Expr ::= (Letting | Setting | Lambda | Apply | Var| Const) | "(" Expr ")"
|
||
|
||
Letting ::= "let" Var "=" Expr "in" Expr // let foo = 12 in ...
|
||
|
||
Setting ::= Var ":=" Expr "in" Expr // foo := a in ...
|
||
|
||
Lambda ::= "fn" Var "->" Expr // fn x -> 12
|
||
|
||
Apply ::= Expr Expr // foo 3 即foo(3)
|
||
|
||
Var ::= ID
|
||
|
||
Const ::= String | Float | Int
|
||
|
||
Int ::= [0-9]+
|
||
|
||
Float ::= [0-9]+ "." [0-9]+
|
||
|
||
String ::= '"' (不是「"」的任一字元|('\' '"')) '"'
|
||
</code></pre>
|
||
<h2 id="parsercombinatortokenize">用ParserCombinator進行tokenize</h2>
|
||
<p>Parser combinator(分析器組合子)是一種利用高階函數來簡化分析器撰寫的辦法。講一個簡單的案例吧:</p>
|
||
<p>假設我們想要將字串的開頭match 0~9 之中的其中一個,我們可以寫一個函數match0to9如下:</p>
|
||
<pre><code>function match0to9(string){
|
||
if (string[0] in 0,1,..,9){
|
||
let rest = string[1:];
|
||
let matched = string[0];
|
||
return {type: "OK", rest : rest, matched : matched};
|
||
}
|
||
else{
|
||
return {type : "Nothing"};
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>假設我們要將字串的前兩個字的match 0~9呢?如果會高階函數的話,引入一個<code>then</code>函數,然後把<code>match0to9</code>傳進去,這樣寫起來比較簡潔:</p>
|
||
<pre><code>function thenDo(input, fun){
|
||
if (input.type != "Nothing"{
|
||
|
||
middle = fun(input.rest);
|
||
if (middle.type != "Nothing"){
|
||
middle.matched = input.matched + middle.matched
|
||
return middle;
|
||
}else{
|
||
return middle; // return nothing
|
||
}
|
||
}else{
|
||
input; // return nothing
|
||
}
|
||
|
||
}
|
||
|
||
|
||
</code></pre>
|
||
<h2 id="_3">平面操作</h2>
|
||
<h3 id="_4">基本函數與直譯器</h3>
|
||
<p>我們藉由以上的概念,可以定義一個將文字、線條等形狀排列到2D平面的語法,畢竟不論輸出PDF、SVG等等,粗略而言,就是一種2D平面安放文字的語言。另外PDF的格式相當晦澀,就算_PDF Explained_的PDF教學,也還是要輔助使用其他的工具,沒辦法看了就自己手刻PDF,所以還是用<code>printpdf</code>來教學吧。</p>
|
||
<p>現在我們初始化一個專案目錄,然後將需要的S-exp函式庫和pdf函數庫指定為相依函式庫:</p>
|
||
<pre><code>cargo init;
|
||
|
||
cargo add rsexp printpdf;
|
||
</code></pre>
|
||
<p>我們可以定義一些表達式(包含函數、資料結構,S-exp形式)的說明如下。<code>'()</code>表示空列表(empty list),因為都要表達是函數的引用,所有的函數寫成形式<code>(Func "函數名稱" (引數1 引數2 ....))</code>。Float指64位元浮點數:</p>
|
||
<pre><code>(px Float) ; px表達pixel單位,儲存浮點數
|
||
(pt Float) ; pt表達point單位,儲存浮點數
|
||
(style (str pt)) ; 文字樣式。String表示字型的路徑[fontPath],Float表示字型大小(in Pt) (fontSize)
|
||
(str String) ; 儲存字串
|
||
(func "createPDF" '()) ;新增PDF
|
||
(func "createPage" '()) ;新增頁面
|
||
(func "writePdf" '(str)) ;寫入PDF頁面,String是PATH
|
||
|
||
(func "putchar" '(str style x y)) ; x 軸向右,y 軸向下,str 表示字元(char),style 表示文字樣式
|
||
</code></pre>
|
||
<p><code>main.rs</code>先引用函式庫:
|
||
<code>use printpdf::*;</code></p>
|
||
<p>其中 <code>px</code>、<code>pt</code>是單位,所以可以在<code>main.rs</code>這樣定義:</p>
|
||
<pre><code>enum Measure{
|
||
Pt(f64),
|
||
Px(f64)
|
||
}
|
||
</code></pre>
|
||
<p>最後一次定義expression:</p>
|
||
<pre><code>enum Expr{
|
||
Mea(Measure), // wrapper for measure
|
||
Str(&str),
|
||
Style{font_path : Measure, size : Measure},
|
||
Func(&str, Vec<Expr>),
|
||
Void // return nothing
|
||
}
|
||
</code></pre>
|
||
<p>然後我們可以這樣定義一個處理輸入輸出的interpreter於<code>interp</code>,並修改<code>main.rs</code>如下,縱使我們準時:</p>
|
||
<pre><code>fn interp(exp : Expr)->(){
|
||
// the function will be extended.
|
||
match exp {
|
||
Expr::Mea(Measure::Pt(x)) => println!("{:?} pt", x),
|
||
Expr::Mea(Measure::Px(x)) => println!("{:?} px", x),
|
||
|
||
_ => println!("not found expression"),
|
||
};
|
||
}
|
||
|
||
// exexute interpreter
|
||
fn main() {
|
||
interp(Expr::Mea(Measure::Pt(2.2)));
|
||
interp(Expr::Flo(2.2));
|
||
}
|
||
</code></pre>
|
||
|
||
</div>
|
||
</div>
|
||
<footer>
|
||
|
||
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
|
||
|
||
|
||
<a href=".." class="btn btn-neutral" title="Home"><span class="icon icon-circle-arrow-left"></span> Previous</a>
|
||
|
||
</div>
|
||
|
||
|
||
<hr/>
|
||
|
||
<div role="contentinfo">
|
||
<!-- Copyright etc -->
|
||
|
||
</div>
|
||
|
||
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||
</footer>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<div class="rst-versions" role="note" aria-label="versions">
|
||
<span class="rst-current-version" data-toggle="rst-current-version">
|
||
|
||
|
||
<span><a href=".." style="color: #fcfcfc;">« Previous</a></span>
|
||
|
||
|
||
</span>
|
||
</div>
|
||
<script>var base_url = '..';</script>
|
||
<script src="../js/theme.js" defer></script>
|
||
<script src="../search/main.js" defer></script>
|
||
<script defer>
|
||
window.onload = function () {
|
||
SphinxRtdTheme.Navigation.enable(true);
|
||
};
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|