anotherTypesetter/site/defineASTandGrammar/index.html
2023-12-02 03:46:41 +08:00

312 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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> &raquo;</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.8BNF風格的表達法為<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)=&gt;{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) | &quot;(&quot; Expr &quot;)&quot;
Letting ::= &quot;let&quot; Var &quot;=&quot; Expr &quot;in&quot; Expr // let foo = 12 in ...
Setting ::= Var &quot;:=&quot; Expr &quot;in&quot; Expr // foo := a in ...
Lambda ::= &quot;fn&quot; Var &quot;-&gt;&quot; Expr // fn x -&gt; 12
Apply ::= Expr Expr // foo 3 即foo(3)
Var ::= ID
Const ::= String | Float | Int
Int ::= [0-9]+
Float ::= [0-9]+ &quot;.&quot; [0-9]+
String ::= '&quot;' (不是「&quot;」的任一字元|('\' '&quot;')) '&quot;'
</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: &quot;OK&quot;, rest : rest, matched : matched};
}
else{
return {type : &quot;Nothing&quot;};
}
}
</code></pre>
<p>假設我們要將字串的前兩個字的match 0~9呢如果會高階函數的話引入一個<code>then</code>函數,然後把<code>match0to9</code>傳進去,這樣寫起來比較簡潔:</p>
<pre><code>function thenDo(input, fun){
if (input.type != &quot;Nothing&quot;{
middle = fun(input.rest);
if (middle.type != &quot;Nothing&quot;){
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 &quot;createPDF&quot; '()) ;新增PDF
(func &quot;createPage&quot; '()) ;新增頁面
(func &quot;writePdf&quot; '(str)) ;寫入PDF頁面String是PATH
(func &quot;putchar&quot; '(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(&amp;str),
Style{font_path : Measure, size : Measure},
Func(&amp;str, Vec&lt;Expr&gt;),
Void // return nothing
}
</code></pre>
<p>然後我們可以這樣定義一個處理輸入輸出的interpreter於<code>interp</code>,並修改<code>main.rs</code>如下,縱使我們準時:</p>
<pre><code>fn interp(exp : Expr)-&gt;(){
// the function will be extended.
match exp {
Expr::Mea(Measure::Pt(x)) =&gt; println!(&quot;{:?} pt&quot;, x),
Expr::Mea(Measure::Px(x)) =&gt; println!(&quot;{:?} px&quot;, x),
_ =&gt; println!(&quot;not found expression&quot;),
};
}
// 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;">&laquo; 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>