Nimチュヌトリアルパヌト2

翻蚳者からのメモ
最初の郚分は、 「Nimチュヌトリアル郚1」です。

翻蚳は自分のために、぀たり䞍噚甚にそしお急いで行われたした。 いく぀かのフレヌズの蚀葉遣いはひどい苊痛を生む必芁があったため、ロシア語に少しでも䌌おいたした。 より良い曞き方を知っおいる人-個人で曞いお、私は線集したす。


はじめに


「繰り返しは䞍条理を慎重に芋せる。」-ノヌマン・ワむルドバヌガヌ

原文「繰り返しはばかげたこずを合理的にする。」-ノヌマン・ワむルドバヌガヌ

このドキュメントは耇雑なNim蚀語構成物に関するチュヌトリアルです。 このドキュメントはやや時代遅れであり、マニュアルには蚀語の耇雑な機胜に関するより倚くの関連する䟋があるこずに泚意しおください。

プラグマ


プラグマは、新しいキヌワヌドを入力せずにコンパむラに远加情報たたはコマンドを䌝えるNimの受け入れられた方法です。 プラグマは、ドット{. and .}特別な䞭括匧で囲たれおいたす{. and .} {. and .} 。 それらはこのチュヌトリアルではカバヌされおいたせん。 利甚可胜なプラグマのリストに぀いおは、 マニュアルたたはナヌザヌマニュアルを参照しおください。

オブゞェクト指向プログラミング


Nimでのオブゞェクト指向プログラミングOOPのサポヌトは最小限ですが、匷力なOOPテクニックを䜿甚できたす。 OOPは、プログラムを開発する方法の1぀ず芋なされたすが、それだけではありたせん。 手続き型アプロヌチはコヌドを単玔化し、効率を向䞊させるこずがありたす。 たずえば、継承の代わりに構成を䜿甚するず、倚くの堎合、アヌキテクチャが向䞊したす。

オブゞェクト


タプルなどのオブゞェクトは、さたざたな倀を単䞀の構造にパックするように蚭蚈されおいたす。 しかし、オブゞェクトにはタプルにはないいく぀かの機胜がありたす継承ず隠蔜情報。 オブゞェクトはデヌタをカプセル化するため、 T()オブゞェクトのコンストラクタヌは通垞、内郚開発でのみ䜿甚され、初期化のために、プログラマヌは特別な手順 コンストラクタヌず呌ばれたすを提䟛する必芁がありたす。

オブゞェクトは、実行時にその型にアクセスできたす。 の挔算子がof 、これを䜿甚しおオブゞェクトのタむプを確認できたす。

 type Person = ref object of RootObj name*: string #  * ,  `name`      age: int #         Student = ref object of Person # Student   Person id: int #    id var student: Student person: Person assert(student of Student) #  true #  : student = Student(name: "Anton", age: 5, id: 2) echo student[] 

定矩されおいるモゞュヌルの倖郚で衚瀺されるオブゞェクトのフィヌルドには、アスタリスク * が付いおいたす。 タプルずは異なり、さたざたなオブゞェクトタむプが同等になるこずはありたせん。 新しいオブゞェクトタむプは、タむプセクションでのみ定矩できたす。

継承はobject of構文のobject of䜿甚しお行われたす。 珟圚、倚重継承はサポヌトされおいたせん。 オブゞェクトタむプに適した祖先がない堎合は、 RootObj祖先にするこずができたすが、これは単なる慣習です。 祖先を持たないオブゞェクトは、暗黙的にfinalずしお宣蚀されたす。 system.RootObjから継承されない新しいオブゞェクトを導入するには、 inheritableプラグマを䜿甚できたすこれは、たずえばGTKラッパヌで䜿甚されたす。

参照オブゞェクトは、継承に関係なく䜿甚できたす。 これは必ずしも必芁ではありたせんが、たずえば、 let person: Person = Student(id: 123)非参照オブゞェクトが割り圓おられおいる堎合、子クラスのフィヌルドは切り捚おられたす。
泚単玔なコヌドの再利甚の堎合、構成 「含たれる」関係は継承 「ある」関係よりも望たしい堎合がありたす。 。 Nimのオブゞェクトは倀型であるため、構成は継承ず同じくらい効率的です。

盞互再垰型


オブゞェクト、タプル、およびリンクを䜿甚しお、 盞互に䟝存する盞互に再垰的なかなり耇雑なデヌタ構造をモデル化できたす。 Nimでは、そのような型は単䞀の型セクション内でのみ宣蚀できたす。 他の゜リュヌションでは、远加の文字怜玢が必芁になるため、コンパむルが遅くなりたす。

䟋

 type Node = ref NodeObj #    NodeObj NodeObj = object le, ri: Node #     sym: ref Sym # ,    Sym Sym = object #  name: string #   line: int # ,      code: PNode #     

型倉換


Nimは、型キャストず型倉換を区別したす。 キャストはcast挔算子を䜿甚しお行われ、コンパむラに匷制的にバむナリデヌタを指定された型ずしお解釈させたす。

型倉換は、ある型を別の型に倉換するより゚レガントな方法です。型を倉換できるかどうかをチェックしたす。 型倉換が䞍可胜な堎合、コンパむラはこれを報告するか、䟋倖がスロヌされたす。

型倉換の構文は次のずおりです destination_type(expression_to_convert) 通垞の呌び出しに䌌おいたす。

 proc getID(x: Person): int = Student(x).id 

x Studentむンスタンスでxない堎合、 InvalidObjectConversionError䟋倖がスロヌされたす。

バリアントオブゞェクト


オブゞェクト階局が過剰な堎合があり、単玔なバリアント型ですべおを解決できたす。

䟋

 #   ,        #   Nim type NodeKind = enum #     nkInt, #     nkFloat, #       nkString, #     nkAdd, #  nkSub, #  nkIf #  if Node = ref NodeObj NodeObj = object case kind: NodeKind #  ``kind``   of nkInt: intVal: int of nkFloat: floatVal: float of nkString: strVal: string of nkAdd, nkSub: leftOp, rightOp: PNode of nkIf: condition, thenPart, elsePart: PNode var n = PNode(kind: nkFloat, floatVal: 1.0) #     `FieldError`,   # n.kind  : n.strVal = "" 

この䟋からわかるように、オブゞェクト階局ずは察照的に、異なるオブゞェクトタむプ間で倉換を行う必芁はありたせん。 ただし、オブゞェクトの間違ったフィヌルドにアクセスするず䟋倖が発生したす。

方法


通垞のオブゞェクト指向蚀語では、プロシヌゞャ メ゜ッドずも呌ばれたすはクラスにバむンドされたす。 このアプロヌチには、次の欠点がありたす。

Nimは、メ゜ッドをクラスにバむンドしないこずにより、これらの問題を回避したす。 Nimのすべおのメ゜ッドはマルチメ゜ッドです。 埌で芋るように、マルチメ゜ッドは動的バむンディングの手順ずのみ異なりたす。

メ゜ッド呌び出しの構文


Nimでサブルヌチンを呌び出すための特別な構文糖衣がありたす obj.method(args)コンストラクトはmethod(obj, args)ず同じ意味です。 匕数がない堎合は、ブラケットをスキップできたす len(obj)代わりにobj.len 。

このメ゜ッド呌び出し構文はオブゞェクトに限定されず、あらゆるタむプに䜿甚できたす。

 echo("abc".len) #  ,   echo(len("abc")) echo("abc".toUpper()) echo({'a', 'b', 'c'}.card) stdout.writeLine("Hallo") #  ,   writeLine(stdout, "Hallo") 

メ゜ッド呌び出しの構文に関する別の芳点は、欠萜しおいる接尟蟞衚蚘法を実装するこずです。

これにより、「クリヌンなオブゞェクト指向コヌド」を簡単に䜜成できたす。

 import strutils, sequtils stdout.writeLine("Give a list of numbers (separated by spaces): ") stdout.write(stdin.readLine.split.map(parseInt).max.`$`) stdout.writeLine(" is the maximum!") 

プロパティ


䞊蚘の䟋からわかるように、Nimはget-propertiesを必芁ずしたせん。これらは、メ゜ッド呌び出し構文を䜿甚しお呌び出される通垞のget-proceduresに眮き換えられたす。 ただし、倀の割り圓おは別の問題です。このためには、特別な構文が必芁です。

 type Socket* = ref object of RootObj host: int #  ,   proc `host=`*(s: var Socket, value: int) {.inline.} = ##    s.host = value proc host*(s: Socket): int {.inline.} = ##    s.host var s: Socket new s s.host = 34 #  ,   `host=`(s, 34) 

この䟋はinline手順も瀺しおいたす。

配列プロパティを実装するには、配列アクセス挔算子[]オヌバヌロヌドできたす。

 type Vector* = object x, y, z: float proc `[]=`* (v: var Vector, i: int, value: float) = # setter case i of 0: vx = value of 1: vy = value of 2: vz = value else: assert(false) proc `[]`* (v: Vector, i: int): float = # getter case i of 0: result = vx of 1: result = vy of 2: result = vz else: assert(false) 

この䟋は䞍噚甚です。v v[]既にアクセスできるタプルでベクトルをモデル化する方が良いためです。

動的バむンディング


プロシヌゞャは垞に静的バむンディングを䜿甚したす。 動的バむンディングの堎合、 procキヌワヌドをmethod眮き換えたす。

 type PExpr = ref object of RootObj ##      PLiteral = ref object of PExpr x: int PPlusExpr = ref object of PExpr a, b: PExpr #  : 'eval'     method eval(e: PExpr): int = #    quit "to override!" method eval(e: PLiteral): int = ex method eval(e: PPlusExpr): int = eval(ea) + eval(eb) proc newLit(x: int): PLiteral = PLiteral(x: x) proc newPlus(a, b: PExpr): PPlusExpr = PPlusExpr(a: a, b: b) echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4))) 

この䟋では、 newLitおよびnewPlusはプロシヌゞャであるこずに泚意しおください。静的バむンディングを䜿甚する方が適切であり、動的バむンディングを必芁ずするevalすでにメ゜ッドです。

マルチメ゜ッドでは、オブゞェクトタむプを持぀すべおのパラメヌタヌがバむンドに䜿甚されたす。

 type Thing = ref object of RootObj Unit = ref object of Thing x: int method collide(a, b: Thing) {.inline.} = quit "to override!" method collide(a: Thing, b: Unit) {.inline.} = echo "1" method collide(a: Unit, b: Thing) {.inline.} = echo "2" var a, b: Unit new a new b collide(a, b) #  : 2 

この䟋からわかるように、マルチメ゜ッドの呌び出しは曖昧にcollideこずはできたせん。解像床が巊から右に機胜collideため、 collide 1よりcollide 2の方が望たしいです。 したがっお、 Unit 、 Thing 、 Thing 、 UnitよりThing優先されたす。
パフォヌマンスに関する泚意 Nimは仮想メ゜ッドのテヌブルを䜜成したせんが、ディスパッチツリヌを生成したす。 これにより、コストのかかるメ゜ッド呌び出しの間接分岐が回避され、埋め蟌みが可胜になりたす。 ただし、コンパむル段階での蚈算やデッドコヌドの削陀など、他の最適化はメ゜ッドでは機胜したせん。

䟋倖


Nimでは、䟋倖はオブゞェクトです。 慣䟋により、䟋倖タむプは「゚ラヌ」で終わりたす。 systemモゞュヌルは、バむンドできる䟋倖階局を定矩したす。 䟋倖は、共通むンタヌフェヌスを提䟛するsystem.Exceptionから発生したす。

䟋倖は、その存続期間が䞍明であるため、ヒヌプにスロヌする必芁がありたす。 コンパむラヌは、スタックに眮かれた䟋倖をスロヌするこずを蚱可したせん。 スロヌされるすべおの䟋倖は、少なくずもmsgフィヌルドに出珟する理由を瀺す必芁がありたす。

䟋倖は䟋倖的な堎合にスロヌされるこずになっおいたす。たずえば、ファむルを開くこずができない堎合、䟋倖はスロヌされたせんファむルが存圚しない堎合がありたす。

コマンドをraise


raiseコマンドを䜿甚しお䟋倖がスロヌされたす。

 var e: ref OSError new(e) e.msg = "the request to the OS failed" raise e 

匏がraiseキヌワヌドの埌に​​続かない堎合、最埌の䟋倖が再びスロヌされたす。 䞊蚘のコヌドを蚘述しないために、 systemモゞュヌルからnewExceptionテンプレヌトを䜿甚できたす。

 raise newException(OSError, "the request to the OS failed") 

コマンドをtry


tryコマンドは䟋倖を凊理したす。

 #      ,    ,  #    var f: File if open(f, "numbers.txt"): try: let a = readLine(f) let b = readLine(f) echo "sum: ", parseInt(a) + parseInt(b) except OverflowError: echo "overflow!" except ValueError: echo "could not convert string to integer" except IOError: echo "IO error!" except: echo "Unknown exception!" # reraise the unknown exception: raise finally: close(f) 

try埌のコマンドは、䟋倖がスロヌされるたで実行されたす。 この堎合、察応するexceptブランチが実行されたす。

発生した䟋倖が明瀺的にリストされおいない堎合、空のexceptブロックが実行されたす。 これは、 ifコマンドのelseブランチに䌌おいelse 。

finallyブランチが存圚する堎合、䟋倖ハンドラが実行された埌に垞に実行されたす。

exceptブランチで䟋倖がスロヌされたす。 䟋倖が凊理されない堎合、呌び出しスタックに沿っお䌝播したす。 これは、䟋倖が発生した堎合、 finallyブロック内にない残りのプロシヌゞャは実行されないこずを意味したす。

exceptブランチ内で珟圚の䟋倖オブゞェクトたたはそのメッセヌゞを取埗する必芁がある堎合は、 systemモゞュヌルからgetCurrentException()およびgetCurrentExceptionMsg()プロシヌゞャを䜿甚できたす。 䟋

 try: doSomethingHere() except: let e = getCurrentException() msg = getCurrentExceptionMsg() echo "Got exception ", repr(e), " with message ", msg 

陀倖された䟋倖を含むプロシヌゞャに泚釈を付ける


オプションのプラグマ{.raises.}を䜿甚しお、プロシヌゞャが特定の䟋倖を発生させるか、たったく発生させないように指定できたす。 プラグマ{.raises.}䜿甚されおいる堎合、コンパむラはそれが正しいこずを確認したす。 たずえば、プロシヌゞャIOErrorある時点でそれたたは呌び出されたプロシヌゞャの1぀が別の䟋倖をスロヌするこずを瀺す堎合、コンパむラはコンパむルを拒吊したす。 䜿甚䟋

 proc complexProc() {.raises: [IOError, ArithmeticError].} = ... proc simpleProc() {.raises: [].} = ... 

同様のコヌドを䜜成した埌、スロヌされた䟋倖のリストが倉曎されるず、コンパむラは、プラグマの怜蚌を停止したプロシヌゞャ内の行ずリストにない䟋倖を指す゚ラヌで停止したす。 さらに、この䟋倖が発生したファむルず行も瀺されたす。これにより、疑わしいコヌドを芋぀けやすくなり、倉曎が原因ずなっおいたす。

プラグマ{.raises.}を既存のコヌドに远加する堎合は、コンパむラヌも圹立ちたす。 プラグマ{.effects.}をプロシヌゞャに远加するず、コンパむラはその時点で衚瀺されるすべおの効果を出力したす䟋倖远跡はNim効果システムの䞀郚です。 プロシヌゞャによっおスロヌされた䟋倖のリストを取埗する別の回避策は、Nim doc2を䜿甚するこずです。このdoc2 、モゞュヌル党䜓のドキュメントを生成し、スロヌされた䟋倖のリストですべおのプロシヌゞャを装食したす。 ゚フェクトシステムず関連するプラグマの詳现に぀いおは、マニュアルを参照しおください 。

汎化


汎化は、Nimが型パラメヌタヌを䜿甚しおプロシヌゞャ、むテレヌタヌ、たたは型をパラメヌタヌ化できるようにするものです。 これらは、高性胜のタむプセヌフコンテナを䜜成するのに最も䟿利です。

 type BinaryTreeObj[T] = object # BinaryTree      #  ``T`` le, ri: BinaryTree[T] #    ;   nil data: T #     BinaryTree*[T] = ref BinaryTreeObj[T] # ,   proc newNode*[T](data: T): BinaryTree[T] = #   new(result) result.data = data proc add*[T](root: var BinaryTree[T], n: BinaryTree[T]) = #     if root == nil: root = n else: var it = root while it != nil: #   ;    ``cmp`` #     ,   ``==``  ``<`` var c = cmp(it.data, n.data) if c < 0: if it.le == nil: it.le = n return it = it.le else: if it.ri == nil: it.ri = n return it = it.ri proc add*[T](root: var BinaryTree[T], data: T) = #  : add(root, newNode(data)) iterator preorder*[T](root: BinaryTree[T]): T = #     .   #    ,    (    # ): var stack: seq[BinaryTree[T]] = @[root] while stack.len > 0: var n = stack.pop() while n != nil: yield n.data add(stack, n.ri) #      n = n.le #      var root: BinaryTree[string] #  BinaryTree  ``string`` add(root, newNode("hello")) #  ``newNode``    add(root, "world") #     for str in preorder(root): stdout.writeLine(str) 

この䟋は、䞀般化されたバむナリツリヌを瀺しおいたす。 コンテキストに応じお、型パラメヌタヌの入力、たたは䞀般化されたプロシヌゞャ、反埩子、たたは型のむンスタンス化に角括匧が䜿甚されたす。 䟋からわかるように、䞀般化はオヌバヌロヌドでaddたす。最適な䞀臎はaddです。 シヌケンスの組み蟌みaddプロシヌゞャは非衚瀺ではなく、 preorder反埩子で䜿甚されたす。

パタヌン


テンプレヌトは、Nim抜象構文ツリヌASTで動䜜する単玔な眮換メカニズムです。 テンプレヌトは、セマンティックコンパむルパスで凊理されたす。 これらは他の蚀語ずうたく統合されおおり、Cシャむプリプロセッサマクロの通垞の欠点はありたせん。

テンプレヌトを呌び出すには、プロシヌゞャずしお呌び出したす。

䟋

 template `!=` (a, b: expr): expr = #      System not (a == b) assert(5 != 6) #    : assert(not (5 == 6)) 

挔算子!= 、 > 、 >= 、 in 、 isnot 、 isnotは実際にはテンプレヌトです結果ずしお、 ==挔算子をオヌバヌロヌドするず、 !=挔算子は自動的に䜿甚可胜になり、正しく動䜜したすIEEE浮動小数点数を陀くNaNは厳密なブヌル倀を砎りたすロゞック。

a > bはb < a a > bなりたす。 a in b contains(b, a)倉換されcontains(b, a) 。 isnotずisnotは明らかな意味を持ちたせん。

テンプレヌトは、レむゞヌコンピュヌティングに関しお特に有甚です。 ロギングの簡単な手順を怜蚎しおください。

 const debug = true proc log(msg: string) {.inline.} = if debug: stdout.writeLine(msg) var x = 4 log("x has the value: " & $x) 

このコヌドには欠陥がありたす debug䞀床falseに蚭定されるず、かなりコストのかかる操䜜$ず&が実行されたす プロシヌゞャの匕数の蚈算は「貪欲」に行われたす。

log手順をテンプレヌトに倉えるず、この問題が解決したす。

 const debug = true template log(msg: string) = if debug: stdout.writeLine(msg) var x = 4 log("x has the value: " & $x) 

パラメヌタタむプは、通垞のタむプたたはメタタむプexpr  匏の堎合、 stmt  コマンドの堎合、たたはtypedesc  タむプの説明の堎合です。 戻り倀の型がテンプレヌトで明瀺的に指定されおいない堎合、 stmtプロシヌゞャおよびメ゜ッドずの互換性のために䜿甚されたす。

stmtパラメヌタヌがある堎合、それはテンプレヌト宣蚀の最埌でなければなりたせん。 理由は、コマンドが特別なコロン構文 :を䜿甚しおテンプレヌトに枡されるためです

 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.writeLine("line 1") txt.writeLine("line 2") 

この䟋では、2぀のwriteLineコマンドwriteLine bodyパラメヌタヌにバむンドされおいたす。 withFileテンプレヌトにはナヌティリティコヌドが含たれおおり、ファむルを閉じるこずを忘れないずいう䞀般的な問題を回避するのに圹立ちたす。 let fn = filenameは、 filenameが䞀床だけ評䟡されるこずを保蚌するこずに泚意しおください。

マクロ


マクロを䜿甚するず、コンパむル段階でコヌドを集䞭的に倉換できたすが、Nimの構文を倉曎するこずはできたせん。 ただし、Nim構文は非垞に柔軟であるため、これはそれほど深刻な制限ではありたせん。 マクロは玔粋なNimに実装する必芁がありたす。倖郚関数むンタヌフェむスFFIがコンパむラで蚱可されおいないためです。

マクロを蚘述する方法は2぀ありたす。Nim゜ヌスコヌドを生成し、解析のためにコンパむラに枡すか、コンパむラにフィヌドされる抜象構文ツリヌASTを手動で䜜成したす。ASTを構築するには、特定のNim構文が抜象構文ツリヌにどのように倉換されるかを知る必芁がありたす。ASTはモゞュヌルに文曞化されおいたすmacros。

マクロの準備ができたら、呌び出す方法が2぀ありたす。
  1. マクロをプロシヌゞャずしお呌び出す匏マクロ
  2. 特別な構文を䜿甚しおマクロを呌び出すmacrostmtマクロコマンド

匏マクロ


次の䟋は、debug任意の数の匕数を取る匷力なコマンドを実装しおいたす。

 #      Nim   API,  #   ``macros``: import macros macro debug(n: varargs[expr]): stmt = # `n`  AST Nim,   ; #     : result = newNimNode(nnkStmtList, n) #  ,   : for i in 0..n.len-1: #     ,   ; # `toStrLit`  AST    : result.add(newCall("write", newIdentNode("stdout"), toStrLit(n[i]))) #     ,   ": " result.add(newCall("write", newIdentNode("stdout"), newStrLitNode(": "))) #     ,    : result.add(newCall("writeLine", 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, ": ") writeLine(stdout, a[0]) write(stdout, "a[1]") write(stdout, ": ") writeLine(stdout, a[1]) write(stdout, "x") write(stdout, ": ") writeLine(stdout, x) 

コマンドマクロ


コマンドマクロは、匏マクロず同じ方法で定矩されたす。ただし、コロンで終わる匏を介しお呌び出されたす。

次の䟋は、正芏衚珟の字句解析ツヌルを生成するマクロを瀺しおいたす。

 macro case_token(n: stmt): stmt = #       # ... ( --    :-) discard case_token: #    ,     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 

最初のマクロを䜜成する


マクロの䜜成をガむドするために、兞型的な動的コヌドを静的にコンパむルできるものに倉える方法を瀺したす。たずえば、次のコヌドフラグメントを開始点ずしお䜿甚したす。

 import strutils, tables proc readCfgAtRuntime(cfgFilename: string): Table[string, string] = let inputString = readFile(cfgFilename) var source = "" result = initTable[string, string]() for line in inputString.splitLines: #    if line.len < 1: continue var chunks = split(line, ',') if chunks.len != 2: quit("Input needs comma split values, got: " & line) result[chunks[0]] = chunks[1] if result.len < 1: quit("Input file empty!") let info = readCfgAtRuntime("data.cfg") when isMainModule: echo info["licenseOwner"] echo info["licenseKey"] echo info["version"] 

おそらく、このコヌドフラグメントを商甚プログラムで䜿甚しお、構成ファむルを読み取り、プログラムの賌入者に関する情報を衚瀺できたす。この倖郚ファむルは、ラむセンス情報をプログラムに含めるために賌入時に生成できたす。

 version,1.1 licenseOwner,Hyori Lee licenseKey,M1Tl3PjBWO2CC48m 

プロシヌゞャreadCfgAtRuntimeは、指定されたファむル名を開きTable、モゞュヌルから戻りたすtables。splitLinesモゞュヌルからの手順を䜿甚しお、ファむル解析が行われたす゚ラヌ凊理たたは境界ケヌスなしstrutils。うたくいかないこずがたくさんありたす。これはコンパむル時にコヌドを実行する方法を説明するものであり、コピヌ防止を適切に実装する方法を説明するものではないこずに泚意しおください。

このコヌドをコンパむル手順の手順ずしお実装するず、ファむルを取り陀くこずができたすdata.cfg。そうでなければ、バむナリずずもに配垃する必芁がありたす。さらに、情報が本圓に䞀定である堎合、ロゞックの芳点からは、情報を可倉に保぀こずは意味がありたせんグロヌバル倉数、それが定数であればより良いです。最埌に、最も䟡倀のある機胜の1぀は、コンパむル段階でいく぀かのチェックを実装できるこずです。これは改善された単䜓テストずしお認識できたす。これにより、䜕かが機胜しないバむナリを取埗するこずはできたせん。これにより、1぀の小さな重芁なファむルの障害のために起動しない壊れたプログラムのナヌザヌぞの配信が防止されたす。

゜ヌスコヌド生成


プログラムを倉曎しお、コンパむル段階で生成された゜ヌスコヌドを含む行を䜜成parseStmtし、moduleからprocedureに枡すようにしたすmacros。以䞋は、マクロを実装する倉曎された゜ヌスコヌドです。

  1 import macros, strutils 2 3 macro readCfgAndBuildSource(cfgFilename: string): stmt = 4 let 5 inputString = slurp(cfgFilename.strVal) 6 var 7 source = "" 8 9 for line in inputString.splitLines: 10 # Ignore empty lines 11 if line.len < 1: continue 12 var chunks = split(line, ',') 13 if chunks.len != 2: 14 error("Input needs comma split values, got: " & line) 15 source &= "const cfg" & chunks[0] & "= \"" & chunks[1] & "\"\n" 16 17 if source.len < 1: error("Input file empty!") 18 result = parseStmt(source) 19 20 readCfgAndBuildSource("data.cfg") 21 22 when isMainModule: 23 echo cfglicenseOwner 24 echo cfglicenseKey 25 echo cfgversion 

良いこずは、ほずんど䜕も倉わっおいないこずですたず、入力パラメヌタヌの凊理が倉曎されたした3行目。動的バヌゞョンでは、プロシヌゞャreadCfgAtRuntimeは文字列パラメヌタを受け取りたす。ただし、マクロバヌゞョンでは、文字列ずしお宣蚀されおいたすが、マクロの倖郚むンタヌフェむスのみです。マクロが実行されるず、実際にはPNimNode文字列ではなくオブゞェクトが取埗され、マクロに枡された文字列を取埗するためにstrValモゞュヌルからプロシヌゞャを呌び出す必芁がありたすmacros5行目。

第二に、readFileモゞュヌルからプロシヌゞャを䜿甚するこずはできたせんsystem - FFI . ( , FFI), , , , . , slurp system , ( gorge , ).

, Table。代わりに、゜ヌス倉数にNim゜ヌスコヌドを生成したす。構成ファむルの各行に察しお、定数倉数が生成されたす15行目。競合を避けるために、これらの倉数のプレフィックスを付けたしたcfg。䞀般に、コンパむラは、マクロ呌び出し行を次のコヌドフラグメントに眮き換えるだけです。

 const cfgversion= "1.1" const cfglicenseOwner= "Hyori Lee" const cfglicenseKey= "M1Tl3PjBWO2CC48m" 

これを確認するには、゜ヌスコヌドの出力を含む行をマクロの最埌の画面に远加し、プログラムをコンパむルしたす。もう1぀の違いは、通垞のプロシヌゞャquitを呌び出しお終了する呌び出すこずができる代わりに、このバヌゞョンがプロシヌゞャを呌び出すこずですerror14行目。手順errorは同じですquitが、さらに、゚ラヌが発生したファむルの゜ヌスコヌドず行番号も衚瀺したす。これは、プログラマがコンパむル䞭に゚ラヌを芋぀けるのに圹立ちたす。この状況では、凊理䞭の行ではなく、マクロを呌び出す行を指すdata.cfgこずになりたす。これを自分で制埡する必芁がありたす。

手動AST生成


ASTを生成するには、理論䞊、Nimコンパむラが䜿甚する構造を完党に知る必芁がありたすmacros。これらの構造はmoduleに瀺されおいたす。䞀芋、これは困難な䜜業のように芋えたす。ただしdumpTree、匏のマクロではなく、コマンドのマクロずしお䜿甚するこずにより、マクロを䜿甚できたす。文字の䞀郚を生成したいこずがわかっおいるのでconst、次の゜ヌスファむルを䜜成しおコンパむルし、コンパむラが期埅するものを確認できたす。

 import macros dumpTree: const cfgversion: string = "1.1" const cfglicenseOwner= "Hyori Lee" const cfglicenseKey= "M1Tl3PjBWO2CC48m" 

゜ヌスコヌドのコンパむルプロセスでは、次の行の出力が衚瀺されたすこれはマクロなので、コンパむルで十分なので、バむナリを実行する必芁はありたせん。

 StmtList ConstSection ConstDef Ident !"cfgversion" Ident !"string" StrLit 1.1 ConstSection ConstDef Ident !"cfglicenseOwner" Empty StrLit Hyori Lee ConstSection ConstDef Ident !"cfglicenseKey" Empty StrLit M1Tl3PjBWO2CC48m 

この情報により、コンパむラが必芁ずするデヌタをすでによりよく理解できたす。コマンドのリストを生成する必芁がありたす。各゜ヌスコヌド定数に぀いお、ConstSectionおよびが生成されConstDefたす。これらすべおの定数を単䞀のブロックに転送した堎合、3぀の子孫を持぀const1぀だけが衚瀺されConstSectionたす。

気付いおいないかもしれたせんが、dumpTree最初の定数を䜿甚した䟋では、定数のタむプを明瀺的に決定しおいたす。これが、最埌の2぀の定数に出力ツリヌに2番目の子がEmptyあり、最初の定数に文字列識別子がある理由です。したがっお、䞀般に、定矩constは識別子、オプションのタむプ空のノヌドの堎合もありたす、および倀で構成されたす。この知識を歊噚に、ASTビルドマクロの完成バヌゞョンを芋おみたしょう。

  1 import macros, strutils 2 3 macro readCfgAndBuildAST(cfgFilename: string): stmt = 4 let 5 inputString = slurp(cfgFilename.strVal) 6 7 result = newNimNode(nnkStmtList) 8 for line in inputString.splitLines: 9 #    10 if line.len < 1: continue 11 var chunks = split(line, ',') 12 if chunks.len != 2: 13 error("Input needs comma split values, got: " & line) 14 var 15 section = newNimNode(nnkConstSection) 16 constDef = newNimNode(nnkConstDef) 17 constDef.add(newIdentNode("cfg" & chunks[0])) 18 constDef.add(newEmptyNode()) 19 constDef.add(newStrLitNode(chunks[1])) 20 section.add(constDef) 21 result.add(section) 22 23 if result.len < 1: error("Input file empty!") 24 25 readCfgAndBuildAST("data.cfg") 26 27 when isMainModule: 28 echo cfglicenseOwner 29 echo cfglicenseKey 30 echo cfgversion 

゜ヌスコヌド生成の前の䟋から始めたので、それずの違いのみに泚目したす。䞀時的な型倉数を䜜成し、手動でstring蚘述されたように゜ヌスコヌドを曞き蟌む代わりに、倉数を盎接䜿甚しお、子孫を含むコマンドリストノヌドを䜜成したす行7。入力行ごずに、定数定矩を䜜成し、定数セクションでラップしたすresultnnkStmtList

nnkConstDefnnkConstSectionこれらの倉数が䜜成されるず、前のASTツリヌダンプに瀺されおいるように、階局的にそれらを埋めたす17行目定数定矩はセクション定矩の子孫であり、識別子ノヌド、空のノヌドコンパむラヌがここにある型を掚枬できるようにするおよび文字列リテラルを含みたす倀。

マクロ䜜成の最埌のヒント䜜成したASTが正垞に芋えるかどうかわからない堎合は、マクロを䜿甚しおみおくださいdumpTree。ただし、䜜成たたはデバッグするマクロ内では䜿甚できたせん。代わりに、生成された行を衚瀺したすtreeRepr。この䟋の最埌に远加するずecho treeRepr(result)、マクロを䜿甚したずきず同じ出力が衚瀺されたすdumpTree。最埌に呌び出したす オプションで、問題が発生しおいるマクロ内の任意の時点で呌び出すこずができたす。

Source: https://habr.com/ru/post/J271361/


All Articles