網路城邦
上一篇 回創作列表 下一篇   字體:
MetaPost語言﹣宏(Macros)
2013/07/14 13:01:47瀏覽3473|回應0|推薦0

Macros 翻譯中文是什麼,查字典就是「宏」。有的書也寫為「巨集」。這二者好像含意搭不起來,至少我無法從中文的意義和Macro的意義聯接不上。照Marco的字義來看,是「大」的意思,所以「宏」和「大」的意義相關,但在程式設計上,也不是這個意義。而「巨集」本身在中文詞彙中本來就沒有意義,也很難理解為什麼是用這個詞,「巨」和「大」相關,但「集」呢?我想是如果能看到其真正編譯時的程式碼,可能是數萬行的程式碼,原因是所看到定義的Marco其實不是單數,而是Marco"s", 一個看起來簡單的定義,背後必包含先前的定義,像是洋䓤一樣,用一層一層的定義包含起來。由這個觀點來看,「巨大的集合」的「巨集」就有了道理了。

有些詞的翻譯就比較精準,如copy 翻為「拷貝」,其實「拷備」可能更精準一點,但「貝」「備」同音,為了和原音接近,也還可以接受。當然,「複制」是最準確的。

在程式上,定義Macros的意思比較接近「置換」、「替換」、「轉換」的意思。例如 「機車」=「討人厭」而言,說某某很「機車」的意思,就是某某很「討人厭」。如果寫成「機車很漂亮」,則電腦會轉換成「討人厭很漂亮」。在MetaPost中,可以這樣定義「機車」:

def 機車 = 討人厭 enddef; 後面的程式碼就會把所有機車的詞都替換成討人厭的詞。

MetaPost是由「宏」所組成的語言,它的許多內容,都是由各種「宏」組成的。例如upto就是一個宏,它的內容是:

def upto = step 1 until enddef;

所以宏的定義,就是:

def 宏名稱 = 替換的詞句 enddef;

所以 for i=1 upto 10: 就等於是 for i=1 step 1 until 10:

宏本身也可以傳值,而值的種類,有expr, text, suffix三種,例如

def rotatedaround (expr z, d) = %在z點週圍旋轉

  shifted -z rotated d shifted z

enddef;

這樣一來,z 和 d就會被傳入的值取代,

rotatedaround(p+q, a+b); 就會變成

shifted -(p+q) rotated (a+b) shifted (p+q);

其中替換詞的的z會被p+q取代,而d被a+b取代。

def 宏名稱(變值類型 變值詞)=替換詞句內含變值詞句 enddef;

變值類型有expr, text, suffix三,expr是傳入變數的值,所以上例的rotatedaround(a,b)例子中,它僅傳入a, b分別代表的值,如a=(0,1); b=2;等等。

text是內容是什麼,則傳入什麼,所以是算式也好,變數也好,寫入什麼就把全部都傳入宏中。通常是單獨使用。

suffix則是傳入變數名稱,因為變數名稱使用前必進行宣告變數型態,所以使用它可以確保傳入的變數形態正確。

這個例子可以看出expr和suffix的不同:

def f(expr a)=a:=2;

def g(suffix a)=a:=3;

x:=1; show x;

f(x); show x;

g(x); show x;

其中 f(x)就會出現錯誤,因為1:=2;不可能成立。

群組與區域變數(Grouping and Local Variables)

MetaPost所有的變數都是全域變數,只要一宣告使用,之後的所有程式碼都可以使用。而數個基本語句組合起來,如果最後能形成一個運算結果,這些語句可以形成一個群組(group)。形成群組(grouping)的語法是begingroup ... endgroup,在群組內的變數,如果沒有用save宣告,它們就會是全域變數,而使用save宣告的變數,就會是區域變數,其有效範圍在群組內。例如

a:=1; b:=2;

begingroup

c:=3;

save a;

a:=4; b:=a+1;

show (a,b,c);

endgroup;

show (a,b,c);

end;

結果會是(4,5,3)和(1,5,3)。a在群組中使用save,等於是在群組中宣告了a變數,所以群組裡面的a值是4,在群組外的a值仍是1。b, c二變數在群組中被改變,它們是全域變數,所以在群組外面也受到改變。

MetaPost裡面定義了hide這個宏,內容是:

def hide(text t)=exitif numeric begingroup t; endgroup; enddef;

hide的內容是一個判斷式(exitif),就是檢查t是否為數值,由於它是以text方式傳入,幾乎不可能成立,判斷式運算結果必為否,所程式不會中斷。但它的內容不影響其他的程式碼,而對全域變數做了運算使其改變。這用在程式中,用來計算不能直接賦值的變數型態如pair很有用。如本例是一種繪圖玩具,用二種圓所組成,小圓在大圓內圈滾動,小圓上的某一點所形成的軌跡。path內的各點用--連結中間不能有終止(;),所以使用hide來運算全域變數,而不影響path的運算。

宏也可以像是函數(founction)和子程式(sobroutine)一樣,可以寫成

def 宏名稱 = 替換詞;回傳詞 enddef;

vardef是和def很像,通常如同def的寫法,如

vardef (macroname) ... = ... enddef;其內容相似於

def (macroname)... = begingroup ... endgroup enddef;

但它主要是以變數名稱為主的宏,變數名稱組成是:

variable 由tag和suffix組成;

suffix可以empty or suffix subscript or suffix tag 組成

subscript 由 number or [numeric expression] 組成

其中除了宏名稱 如 draw 或著運算元符號 + 等不能用在變數名稱上面,其他如 alpha, ==>, @&#$&, ~~ 都是合法的變數名稱。沒有特別意義的字母、符號,稱為(tags)。 而a[]. a.bcde這種[], .bcde的部分稱為suffix。

vardef可以對suffix的部分進行設計運算,形成新的宏。但變數名的後綴(suffix),有數字或文字。數字的部分,如同陣列,a[],意思為a[1],...a[n]或a1...a(n);文字的部分就是a.bot這類的組成。在vardef的設定中,有符號#@,@來表示陣列suffix的區別:

vardef a[]b(expr p) = p shifted (#@, b) enddef;

vardef ab[](expr p) = p shifted (#@, b) enddef;

二者的#@, @是有差別的。可以從這個例子來看其差異。

vardef a[]b(text t)= show (#@,@); enddef;

vardef a.b[](text t)= show (#@,@); enddef;

a3b();

a.b4();

end;

執行結果是

(a3,b)

(a.b,4)

變數為a[]b的例子中,#@的意思是[]前面的所有的東西,包括[]在內,而@則表示[]之後的東西,本例中就是b了。[]如果在最後方,如a.b[], 則#@是[]前方所有的東西a.b,不包括[],而@則是[]的部分。如果是a.b[4],那麼@的意思為[4]而不是4。@+1是不合法的。

變數suffix是文字的部分,則使用@#來表示其suffix的部分,如MetaPost中,z的定義:

vardef z@# = (x@#, y@#) enddef;

其中如果是z.a1,@#的值就是a1。

宏與運算元的差異

一元運算元

它們的差別在使用的方式。以 round為例,round a; 是運算元;而round(a)是宏。實驗一下不同的使用方式:

def f(expr t) = t +1 enddef;

def g expr t = t+1 enddef;

x:=1;

show f(x); show f x; show g(x); show g x;

end;

其中,f x會出現錯誤,f不能當成運算元使用,而g(x), g x 都可以執行。但這樣的運算結果正確嗎?是我們要的結果嗎?再看另一個實驗:

def f expr t = t =t*2 endfor;

def g primary t = t*2 endfor;

x:=2;

show f x +3;

show g x +3;

end;

二者結果不同,f x +3 結果是 10, 而g x +3 是 7。原因在MetaPost解析文件時,其運算元的先後順序是後atom, primary, secondary, tertiary, expression的層級進行,在f中,它是屬於expression級的運算元,而+是 secondary級的運算元。所以 f x +3,x+3先做運算,然後才算f 5,而得到10的結果;而g是 primary的運算元,所以它先算g x,然後才算 4+3而得到7。

一元運算元的例子:

vardef incr suffix $ = $:=$+1; $ enddef;

univector 是傳回長度為1的pair值:

vardef univector primary z = z/abs z enddef;

二元運算元,則由primarydef, secondarydef, tertiarydef來定義其運算層級,這裡沒有 def 或 vardef的版本。如二向量的點積:

primarydef a dotprod b = xpart a * xpart b + ypart a* ypart b enddef;

最後是of 運算元的例子,如

direction of 的定義是:

vardef direction expr t of p = postcontrol t of p - precontrol t of p enddef;

MetaPost 的  宏和運算元都可以重新定義,消除原先的含義,提供給使用者很大的彈性。

( 興趣嗜好電腦3C )
回應 推薦文章 列印 加入我的文摘
上一篇 回創作列表 下一篇

引用
引用網址:https://classic-blog.udn.com/article/trackback.jsp?uid=paraquat&aid=7848738