Nim教程【十五】【完結】

liulun發表於2015-07-08

模版

模版是Nim語言中的抽象語法樹,它是一種簡單的替換機制,在編譯期被處理

這個特性使Nim語言可以和C語言很好的執行在一起

像呼叫一個方法一樣呼叫一個模版

請看如下程式碼:

template `!=` (a, b: expr): expr =
  # this definition exists in the System module
  not (a == b)

assert(5 != 6) # the compiler rewrites that to: assert(not (5 == 6))

類似下面這些符號,其實都是模版

=,>,>=,in,notin

這一個好處,如果你過載==操作符,

!=運算子也就自動提供出來了

並可以做正確的事!

A>B被變換到b<a。 b in a被變換成含有(b,a)。 notin和IsNot運算有明顯的意義。

模板為懶人提供了很大幫助。考慮一個簡單的PROC進行日誌記錄:

const
  debug = true

proc log(msg: string) {.inline.} =
  if debug: stdout.writeln(msg)

var
  x = 4
log("x has the value: " & $x)

這段程式碼有個缺點,如果有一天把debug變數設定為了false

那麼&操作和$操作還是會執行的,而這些操作的資源消耗是非常大的。

(呼叫方法的時候,會先執行方法引數位置處的表示式)

這個時候就可以考慮用模版來解決這個問題:

const
  debug = true

template log(msg: string) =
  if debug: stdout.writeln(msg)

var
  x = 4
log("x has the value: " & $x)

模版的引數型別可以是普通的型別,也可以是表示式;

template withFile(f: expr, filename: string, mode: FileMode,
                  body: stmt): stmt {.immediate.} =
  let fn = filename
  var f: File
  if open(f, fn, mode):
    try:
      body
    finally:
      close(f)
  else:
    quit("cannot open: " & fn)

withFile(txt, "ttempl3.txt", fmWrite):
  txt.writeln("line 1")
  txt.writeln("line 2")

在這個例子中,兩個writeln語句繫結到的是body引數

這段程式碼可以幫助開發人員避免“忘記關閉檔案”的錯誤

 

巨集

Nim語言的巨集提供了一個高階的編譯期的替換功能

Nim語言的巨集不能替換語言本身的語法,

但這並不是什麼缺憾,因為Nim語言本身已經足夠靈活了。

如果外部介面在編譯期不可用,那麼你就必須用純Nim語言寫巨集

(這估計就是在說Nim和C混合程式設計的時候要注意的事情)

你可以使用Nim程式碼編寫任何形式的巨集,編譯器會在編譯期把他們翻譯成真正的Nim程式碼。

 

可以有兩種辦法寫一個巨集

用Nim程式碼編寫巨集,讓編譯器解析它

手動建立抽象語法樹AST,你告訴編譯器

如果你想建立抽象語法樹AST,那麼你一定要知道Nim語言的語法是怎麼轉換為抽象語法樹的

在N關於巨集的幫助說明文件,你可以找到關於AST的幫助說明

你一旦寫了一個巨集,

那麼你有兩種辦法可以使用這個巨集

像呼叫一個方法一樣呼叫一個巨集

通過一種特殊的語法呼叫巨集(macrostmt宣告巨集)

 

表示式巨集

下面的程式碼實現了一個可變引數數量的巨集

# to work with Nim syntax trees, we need an API that is defined in the
# ``macros`` module:
import macros

macro debug(n: varargs[expr]): stmt =
  # `n` is a Nim AST that contains a list of expressions;
  # this macro returns a list of statements:
  result = newNimNode(nnkStmtList, n)
  # iterate over any argument that is passed to this macro:
  for i in 0..n.len-1:
    # add a call to the statement list that writes the expression;
    # `toStrLit` converts an AST to its string representation:
    result.add(newCall("write", newIdentNode("stdout"), toStrLit(n[i])))
    # add a call to the statement list that writes ": "
    result.add(newCall("write", newIdentNode("stdout"), newStrLitNode(": ")))
    # add a call to the statement list that writes the expressions value:
    result.add(newCall("writeln", newIdentNode("stdout"), n[i]))

var
  a: array[0..10, int]
  x = "some string"
a[0] = 42
a[1] = 45

debug(a[0], a[1], x)

編譯完之後,最終展開的程式碼為:

write(stdout, "a[0]")
write(stdout, ": ")
writeln(stdout, a[0])

write(stdout, "a[1]")
write(stdout, ": ")
writeln(stdout, a[1])

write(stdout, "x")
write(stdout, ": ")
writeln(stdout, x)

 

宣告巨集

宣告巨集在某種意義上就是表示式巨集

宣告巨集是用冒號表示式呼叫的

下面的例子展示了正規表示式詞法分析巨集

macro case_token(n: stmt): stmt =
  # creates a lexical analyzer from regular expressions
  # ... (implementation is an exercise for the reader :-)
  discard

case_token: # this colon tells the parser it is a macro statement
of r"[A-Za-z_]+[A-Za-z_0-9]*":
  return tkIdentifier
of r"0-9+":
  return tkInteger
of r"[\+\-\*\?]+":
  return tkOperator
else:
  return tkUnknown

 

後面還有個例子,不翻譯了

至此整個系列寫完了

喜歡的請點推薦

相關文章