1# 2# 3# Nim's Runtime Library 4# (c) Copyright 2016 Andreas Rumpf 5# 6# See the file "copying.txt", included in this 7# distribution, for details about the copyright. 8# 9 10##[ 11This module contains a `scanf`:idx: macro that can be used for extracting 12substrings from an input string. This is often easier than regular expressions. 13Some examples as an appetizer: 14 15.. code-block:: nim 16 # check if input string matches a triple of integers: 17 const input = "(1,2,4)" 18 var x, y, z: int 19 if scanf(input, "($i,$i,$i)", x, y, z): 20 echo "matches and x is ", x, " y is ", y, " z is ", z 21 22 # check if input string matches an ISO date followed by an identifier followed 23 # by whitespace and a floating point number: 24 var year, month, day: int 25 var identifier: string 26 var myfloat: float 27 if scanf(input, "$i-$i-$i $w$s$f", year, month, day, identifier, myfloat): 28 echo "yes, we have a match!" 29 30As can be seen from the examples, strings are matched verbatim except for 31substrings starting with ``$``. These constructions are available: 32 33================= ======================================================== 34``$b`` Matches a binary integer. This uses ``parseutils.parseBin``. 35``$o`` Matches an octal integer. This uses ``parseutils.parseOct``. 36``$i`` Matches a decimal integer. This uses ``parseutils.parseInt``. 37``$h`` Matches a hex integer. This uses ``parseutils.parseHex``. 38``$f`` Matches a floating-point number. Uses ``parseFloat``. 39``$w`` Matches an ASCII identifier: ``[A-Za-z_][A-Za-z_0-9]*``. 40``$c`` Matches a single ASCII character. 41``$s`` Skips optional whitespace. 42``$$`` Matches a single dollar sign. 43``$.`` Matches if the end of the input string has been reached. 44``$*`` Matches until the token following the ``$*`` was found. 45 The match is allowed to be of 0 length. 46``$+`` Matches until the token following the ``$+`` was found. 47 The match must consist of at least one char. 48``${foo}`` User defined matcher. Uses the proc ``foo`` to perform 49 the match. See below for more details. 50``$[foo]`` Call user defined proc ``foo`` to **skip** some optional 51 parts in the input string. See below for more details. 52================= ======================================================== 53 54Even though ``$*`` and ``$+`` look similar to the regular expressions ``.*`` 55and ``.+``, they work quite differently. There is no non-deterministic 56state machine involved and the matches are non-greedy. ``[$*]`` 57matches ``[xyz]`` via ``parseutils.parseUntil``. 58 59Furthermore no backtracking is performed, if parsing fails after a value 60has already been bound to a matched subexpression this value is not restored 61to its original value. This rarely causes problems in practice and if it does 62for you, it's easy enough to bind to a temporary variable first. 63 64 65Startswith vs full match 66======================== 67 68``scanf`` returns true if the input string **starts with** the specified 69pattern. If instead it should only return true if there is also nothing 70left in the input, append ``$.`` to your pattern. 71 72 73User definable matchers 74======================= 75 76One very nice advantage over regular expressions is that ``scanf`` is 77extensible with ordinary Nim procs. The proc is either enclosed in ``${}`` 78or in ``$[]``. ``${}`` matches and binds the result 79to a variable (that was passed to the ``scanf`` macro) while ``$[]`` merely 80matches optional tokens without any result binding. 81 82 83In this example, we define a helper proc ``someSep`` that skips some separators 84which we then use in our scanf pattern to help us in the matching process: 85 86.. code-block:: nim 87 88 proc someSep(input: string; start: int; seps: set[char] = {':','-','.'}): int = 89 # Note: The parameters and return value must match to what ``scanf`` requires 90 result = 0 91 while start+result < input.len and input[start+result] in seps: inc result 92 93 if scanf(input, "$w$[someSep]$w", key, value): 94 ... 95 96It also possible to pass arguments to a user definable matcher: 97 98.. code-block:: nim 99 100 proc ndigits(input: string; intVal: var int; start: int; n: int): int = 101 # matches exactly ``n`` digits. Matchers need to return 0 if nothing 102 # matched or otherwise the number of processed chars. 103 var x = 0 104 var i = 0 105 while i < n and i+start < input.len and input[i+start] in {'0'..'9'}: 106 x = x * 10 + input[i+start].ord - '0'.ord 107 inc i 108 # only overwrite if we had a match 109 if i == n: 110 result = n 111 intVal = x 112 113 # match an ISO date extracting year, month, day at the same time. 114 # Also ensure the input ends after the ISO date: 115 var year, month, day: int 116 if scanf("2013-01-03", "${ndigits(4)}-${ndigits(2)}-${ndigits(2)}$.", year, month, day): 117 ... 118 119 120The scanp macro 121=============== 122 123This module also implements a ``scanp`` macro, which syntax somewhat resembles 124an EBNF or PEG grammar, except that it uses Nim's expression syntax and so has 125to use prefix instead of postfix operators. 126 127============== =============================================================== 128``(E)`` Grouping 129``*E`` Zero or more 130``+E`` One or more 131``?E`` Zero or One 132``E{n,m}`` From ``n`` up to ``m`` times ``E`` 133``~E`` Not predicate 134``a ^* b`` Shortcut for ``?(a *(b a))``. Usually used for separators. 135``a ^+ b`` Shortcut for ``?(a +(b a))``. Usually used for separators. 136``'a'`` Matches a single character 137``{'a'..'b'}`` Matches a character set 138``"s"`` Matches a string 139``E -> a`` Bind matching to some action 140``$_`` Access the currently matched character 141============== =============================================================== 142 143Note that unordered or ordered choice operators (``/``, ``|``) are 144not implemented. 145 146Simple example that parses the ``/etc/passwd`` file line by line: 147 148.. code-block:: nim 149 150 const 151 etc_passwd = """root:x:0:0:root:/root:/bin/bash 152 daemon:x:1:1:daemon:/usr/sbin:/bin/sh 153 bin:x:2:2:bin:/bin:/bin/sh 154 sys:x:3:3:sys:/dev:/bin/sh 155 nobody:x:65534:65534:nobody:/nonexistent:/bin/sh 156 messagebus:x:103:107::/var/run/dbus:/bin/false 157 """ 158 159 proc parsePasswd(content: string): seq[string] = 160 result = @[] 161 var idx = 0 162 while true: 163 var entry = "" 164 if scanp(content, idx, +(~{'\L', '\0'} -> entry.add($_)), '\L'): 165 result.add entry 166 else: 167 break 168 169The ``scanp`` maps the grammar code into Nim code that performs the parsing. 170The parsing is performed with the help of 3 helper templates that that can be 171implemented for a custom type. 172 173These templates need to be named ``atom`` and ``nxt``. ``atom`` should be 174overloaded to handle both single characters and sets of character. 175 176.. code-block:: nim 177 178 import std/streams 179 180 template atom(input: Stream; idx: int; c: char): bool = 181 ## Used in scanp for the matching of atoms (usually chars). 182 peekChar(input) == c 183 184 template atom(input: Stream; idx: int; s: set[char]): bool = 185 peekChar(input) in s 186 187 template nxt(input: Stream; idx, step: int = 1) = 188 inc(idx, step) 189 setPosition(input, idx) 190 191 if scanp(content, idx, +( ~{'\L', '\0'} -> entry.add(peekChar($input))), '\L'): 192 result.add entry 193 194Calling ordinary Nim procs inside the macro is possible: 195 196.. code-block:: nim 197 198 proc digits(s: string; intVal: var int; start: int): int = 199 var x = 0 200 while result+start < s.len and s[result+start] in {'0'..'9'} and s[result+start] != ':': 201 x = x * 10 + s[result+start].ord - '0'.ord 202 inc result 203 intVal = x 204 205 proc extractUsers(content: string): seq[string] = 206 # Extracts the username and home directory 207 # of each entry (with UID greater than 1000) 208 const 209 digits = {'0'..'9'} 210 result = @[] 211 var idx = 0 212 while true: 213 var login = "" 214 var uid = 0 215 var homedir = "" 216 if scanp(content, idx, *(~ {':', '\0'}) -> login.add($_), ':', * ~ ':', ':', 217 digits($input, uid, $index), ':', *`digits`, ':', * ~ ':', ':', 218 *('/', * ~{':', '/'}) -> homedir.add($_), ':', *('/', * ~{'\L', '/'}), '\L'): 219 if uid >= 1000: 220 result.add login & " " & homedir 221 else: 222 break 223 224When used for matching, keep in mind that likewise scanf, no backtracking 225is performed. 226 227.. code-block:: nim 228 229 proc skipUntil(s: string; until: string; unless = '\0'; start: int): int = 230 # Skips all characters until the string `until` is found. Returns 0 231 # if the char `unless` is found first or the end is reached. 232 var i = start 233 var u = 0 234 while true: 235 if i >= s.len or s[i] == unless: 236 return 0 237 elif s[i] == until[0]: 238 u = 1 239 while i+u < s.len and u < until.len and s[i+u] == until[u]: 240 inc u 241 if u >= until.len: break 242 inc(i) 243 result = i+u-start 244 245 iterator collectLinks(s: string): string = 246 const quote = {'\'', '"'} 247 var idx, old = 0 248 var res = "" 249 while idx < s.len: 250 old = idx 251 if scanp(s, idx, "<a", skipUntil($input, "href=", '>', $index), 252 `quote`, *( ~`quote`) -> res.add($_)): 253 yield res 254 res = "" 255 idx = old + 1 256 257 for r in collectLinks(body): 258 echo r 259 260In this example both macros are combined seamlessly in order to maximise 261efficiency and perform different checks. 262 263.. code-block:: nim 264 265 iterator parseIps*(soup: string): string = 266 ## ipv4 only! 267 const digits = {'0'..'9'} 268 var a, b, c, d: int 269 var buf = "" 270 var idx = 0 271 while idx < soup.len: 272 if scanp(soup, idx, (`digits`{1,3}, '.', `digits`{1,3}, '.', 273 `digits`{1,3}, '.', `digits`{1,3}) -> buf.add($_)): 274 discard buf.scanf("$i.$i.$i.$i", a, b, c, d) 275 if (a >= 0 and a <= 254) and 276 (b >= 0 and b <= 254) and 277 (c >= 0 and c <= 254) and 278 (d >= 0 and d <= 254): 279 yield buf 280 buf.setLen(0) # need to clear `buf` each time, cause it might contain garbage 281 idx.inc 282 283]## 284 285 286import macros, parseutils 287import std/private/since 288 289proc conditionsToIfChain(n, idx, res: NimNode; start: int): NimNode = 290 assert n.kind == nnkStmtList 291 if start >= n.len: return newAssignment(res, newLit true) 292 var ifs: NimNode = nil 293 if n[start+1].kind == nnkEmpty: 294 ifs = conditionsToIfChain(n, idx, res, start+3) 295 else: 296 ifs = newIfStmt((n[start+1], 297 newTree(nnkStmtList, newCall(bindSym"inc", idx, n[start+2]), 298 conditionsToIfChain(n, idx, res, start+3)))) 299 result = newTree(nnkStmtList, n[start], ifs) 300 301proc notZero(x: NimNode): NimNode = newCall(bindSym"!=", x, newLit 0) 302 303proc buildUserCall(x: string; args: varargs[NimNode]): NimNode = 304 let y = parseExpr(x) 305 result = newTree(nnkCall) 306 if y.kind in nnkCallKinds: result.add y[0] 307 else: result.add y 308 for a in args: result.add a 309 if y.kind in nnkCallKinds: 310 for i in 1..<y.len: result.add y[i] 311 312macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): bool = 313 ## See top level documentation of this module about how ``scanf`` works. 314 template matchBind(parser) {.dirty.} = 315 var resLen = genSym(nskLet, "resLen") 316 conds.add newLetStmt(resLen, newCall(bindSym(parser), inp, results[i], idx)) 317 conds.add resLen.notZero 318 conds.add resLen 319 320 template at(s: string; i: int): char = (if i < s.len: s[i] else: '\0') 321 template matchError() = 322 error("type mismatch between pattern '$" & pattern[p] & "' (position: " & $p & 323 ") and " & $getTypeInst(results[i]) & " var '" & repr(results[i]) & "'") 324 325 var i = 0 326 var p = 0 327 var idx = genSym(nskVar, "idx") 328 var res = genSym(nskVar, "res") 329 let inp = genSym(nskLet, "inp") 330 result = newTree(nnkStmtListExpr, newLetStmt(inp, input), 331 newVarStmt(idx, newLit 0), newVarStmt(res, newLit false)) 332 var conds = newTree(nnkStmtList) 333 var fullMatch = false 334 while p < pattern.len: 335 if pattern[p] == '$': 336 inc p 337 case pattern[p] 338 of '$': 339 var resLen = genSym(nskLet, "resLen") 340 conds.add newLetStmt(resLen, newCall(bindSym"skip", inp, 341 newLit($pattern[p]), idx)) 342 conds.add resLen.notZero 343 conds.add resLen 344 of 'w': 345 if i < results.len and getType(results[i]).typeKind == ntyString: 346 matchBind "parseIdent" 347 else: 348 matchError 349 inc i 350 of 'c': 351 if i < results.len and getType(results[i]).typeKind == ntyChar: 352 matchBind "parseChar" 353 else: 354 matchError 355 inc i 356 of 'b': 357 if i < results.len and getType(results[i]).typeKind == ntyInt: 358 matchBind "parseBin" 359 else: 360 matchError 361 inc i 362 of 'o': 363 if i < results.len and getType(results[i]).typeKind == ntyInt: 364 matchBind "parseOct" 365 else: 366 matchError 367 inc i 368 of 'i': 369 if i < results.len and getType(results[i]).typeKind == ntyInt: 370 matchBind "parseInt" 371 else: 372 matchError 373 inc i 374 of 'h': 375 if i < results.len and getType(results[i]).typeKind == ntyInt: 376 matchBind "parseHex" 377 else: 378 matchError 379 inc i 380 of 'f': 381 if i < results.len and getType(results[i]).typeKind == ntyFloat: 382 matchBind "parseFloat" 383 else: 384 matchError 385 inc i 386 of 's': 387 conds.add newCall(bindSym"inc", idx, 388 newCall(bindSym"skipWhitespace", inp, idx)) 389 conds.add newEmptyNode() 390 conds.add newEmptyNode() 391 of '.': 392 if p == pattern.len-1: 393 fullMatch = true 394 else: 395 error("invalid format string") 396 of '*', '+': 397 if i < results.len and getType(results[i]).typeKind == ntyString: 398 var min = ord(pattern[p] == '+') 399 var q = p+1 400 var token = "" 401 while q < pattern.len and pattern[q] != '$': 402 token.add pattern[q] 403 inc q 404 var resLen = genSym(nskLet, "resLen") 405 conds.add newLetStmt(resLen, newCall(bindSym"parseUntil", inp, 406 results[i], newLit(token), idx)) 407 conds.add newCall(bindSym">=", resLen, newLit min) 408 conds.add resLen 409 else: 410 matchError 411 inc i 412 of '{': 413 inc p 414 var nesting = 0 415 let start = p 416 while true: 417 case pattern.at(p) 418 of '{': inc nesting 419 of '}': 420 if nesting == 0: break 421 dec nesting 422 of '\0': error("expected closing '}'") 423 else: discard 424 inc p 425 let expr = pattern.substr(start, p-1) 426 if i < results.len: 427 var resLen = genSym(nskLet, "resLen") 428 conds.add newLetStmt(resLen, buildUserCall(expr, inp, results[i], idx)) 429 conds.add newCall(bindSym"!=", resLen, newLit 0) 430 conds.add resLen 431 else: 432 error("no var given for $" & expr & " (position: " & $p & ")") 433 inc i 434 of '[': 435 inc p 436 var nesting = 0 437 let start = p 438 while true: 439 case pattern.at(p) 440 of '[': inc nesting 441 of ']': 442 if nesting == 0: break 443 dec nesting 444 of '\0': error("expected closing ']'") 445 else: discard 446 inc p 447 let expr = pattern.substr(start, p-1) 448 conds.add newCall(bindSym"inc", idx, buildUserCall(expr, inp, idx)) 449 conds.add newEmptyNode() 450 conds.add newEmptyNode() 451 else: error("invalid format string") 452 inc p 453 else: 454 var token = "" 455 while p < pattern.len and pattern[p] != '$': 456 token.add pattern[p] 457 inc p 458 var resLen = genSym(nskLet, "resLen") 459 conds.add newLetStmt(resLen, newCall(bindSym"skip", inp, newLit(token), idx)) 460 conds.add resLen.notZero 461 conds.add resLen 462 result.add conditionsToIfChain(conds, idx, res, 0) 463 if fullMatch: 464 result.add newCall(bindSym"and", res, 465 newCall(bindSym">=", idx, newCall(bindSym"len", inp))) 466 else: 467 result.add res 468 469macro scanTuple*(input: untyped; pattern: static[string]; matcherTypes: varargs[untyped]): untyped {.since: (1, 5).}= 470 ## Works identically as scanf, but instead of predeclaring variables it returns a tuple. 471 ## Tuple is started with a bool which indicates if the scan was successful 472 ## followed by the requested data. 473 ## If using a user defined matcher, provide the types in order they appear after pattern: 474 ## `line.scanTuple("${yourMatcher()}", int)` 475 runnableExamples: 476 let (success, year, month, day, time) = scanTuple("1000-01-01 00:00:00", "$i-$i-$i$s$+") 477 if success: 478 assert year == 1000 479 assert month == 1 480 assert day == 1 481 assert time == "00:00:00" 482 var 483 p = 0 484 userMatches = 0 485 arguments: seq[NimNode] 486 result = newStmtList() 487 template addVar(typ: string) = 488 let varIdent = ident("temp" & $arguments.len) 489 result.add(newNimNode(nnkVarSection).add(newIdentDefs(varIdent, ident(typ), newEmptyNode()))) 490 arguments.add(varIdent) 491 while p < pattern.len: 492 if pattern[p] == '$': 493 inc p 494 case pattern[p] 495 of 'w', '*', '+': 496 addVar("string") 497 of 'c': 498 addVar("char") 499 of 'b', 'o', 'i', 'h': 500 addVar("int") 501 of 'f': 502 addVar("float") 503 of '{': 504 if userMatches < matcherTypes.len: 505 let varIdent = ident("temp" & $arguments.len) 506 result.add(newNimNode(nnkVarSection).add(newIdentDefs(varIdent, matcherTypes[userMatches], newEmptyNode()))) 507 arguments.add(varIdent) 508 inc userMatches 509 else: discard 510 inc p 511 result.add newPar(newCall(ident("scanf"), input, newStrLitNode(pattern))) 512 for arg in arguments: 513 result[^1][0].add arg 514 result[^1].add arg 515 result = newBlockStmt(result) 516 517template atom*(input: string; idx: int; c: char): bool = 518 ## Used in scanp for the matching of atoms (usually chars). 519 ## EOF is matched as ``'\0'``. 520 (idx < input.len and input[idx] == c) or (idx == input.len and c == '\0') 521 522template atom*(input: string; idx: int; s: set[char]): bool = 523 (idx < input.len and input[idx] in s) or (idx == input.len and '\0' in s) 524 525template hasNxt*(input: string; idx: int): bool = idx < input.len 526 527#template prepare*(input: string): int = 0 528template success*(x: int): bool = x != 0 529 530template nxt*(input: string; idx, step: int = 1) = inc(idx, step) 531 532macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = 533 ## See top level documentation of this module about how ``scanp`` works. 534 type StmtTriple = tuple[init, cond, action: NimNode] 535 536 template interf(x): untyped = bindSym(x, brForceOpen) 537 538 proc toIfChain(n: seq[StmtTriple]; idx, res: NimNode; start: int): NimNode = 539 if start >= n.len: return newAssignment(res, newLit true) 540 var ifs: NimNode = nil 541 if n[start].cond.kind == nnkEmpty: 542 ifs = toIfChain(n, idx, res, start+1) 543 else: 544 ifs = newIfStmt((n[start].cond, 545 newTree(nnkStmtList, n[start].action, 546 toIfChain(n, idx, res, start+1)))) 547 result = newTree(nnkStmtList, n[start].init, ifs) 548 549 proc attach(x, attached: NimNode): NimNode = 550 if attached == nil: x 551 else: newStmtList(attached, x) 552 553 proc placeholder(n, x, j: NimNode): NimNode = 554 if n.kind == nnkPrefix and n[0].eqIdent("$"): 555 let n1 = n[1] 556 if n1.eqIdent"_" or n1.eqIdent"current": 557 result = newTree(nnkBracketExpr, x, j) 558 elif n1.eqIdent"input": 559 result = x 560 elif n1.eqIdent"i" or n1.eqIdent"index": 561 result = j 562 else: 563 error("unknown pattern " & repr(n)) 564 else: 565 result = copyNimNode(n) 566 for i in 0 ..< n.len: 567 result.add placeholder(n[i], x, j) 568 569 proc atm(it, input, idx, attached: NimNode): StmtTriple = 570 template `!!`(x): untyped = attach(x, attached) 571 case it.kind 572 of nnkIdent: 573 var resLen = genSym(nskLet, "resLen") 574 result = (newLetStmt(resLen, newCall(it, input, idx)), 575 newCall(interf"success", resLen), 576 !!newCall(interf"nxt", input, idx, resLen)) 577 of nnkCallKinds: 578 # *{'A'..'Z'} !! s.add(!_) 579 template buildWhile(input, idx, init, cond, action): untyped = 580 mixin hasNxt 581 while hasNxt(input, idx): 582 init 583 if not cond: break 584 action 585 586 # (x) a # bind action a to (x) 587 if it[0].kind == nnkPar and it.len == 2: 588 result = atm(it[0], input, idx, placeholder(it[1], input, idx)) 589 elif it.kind == nnkInfix and it[0].eqIdent"->": 590 # bind matching to some action: 591 result = atm(it[1], input, idx, placeholder(it[2], input, idx)) 592 elif it.kind == nnkInfix and it[0].eqIdent"as": 593 let cond = if it[1].kind in nnkCallKinds: placeholder(it[1], input, idx) 594 else: newCall(it[1], input, idx) 595 result = (newLetStmt(it[2], cond), 596 newCall(interf"success", it[2]), 597 !!newCall(interf"nxt", input, idx, it[2])) 598 elif it.kind == nnkPrefix and it[0].eqIdent"*": 599 let (init, cond, action) = atm(it[1], input, idx, attached) 600 result = (getAst(buildWhile(input, idx, init, cond, action)), 601 newEmptyNode(), newEmptyNode()) 602 elif it.kind == nnkPrefix and it[0].eqIdent"+": 603 # x+ is the same as xx* 604 result = atm(newTree(nnkTupleConstr, it[1], newTree(nnkPrefix, ident"*", it[1])), 605 input, idx, attached) 606 elif it.kind == nnkPrefix and it[0].eqIdent"?": 607 # optional. 608 let (init, cond, action) = atm(it[1], input, idx, attached) 609 if cond.kind == nnkEmpty: 610 error("'?' operator applied to a non-condition") 611 else: 612 result = (newTree(nnkStmtList, init, newIfStmt((cond, action))), 613 newEmptyNode(), newEmptyNode()) 614 elif it.kind == nnkPrefix and it[0].eqIdent"~": 615 # not operator 616 let (init, cond, action) = atm(it[1], input, idx, attached) 617 if cond.kind == nnkEmpty: 618 error("'~' operator applied to a non-condition") 619 else: 620 result = (init, newCall(bindSym"not", cond), action) 621 elif it.kind == nnkInfix and it[0].eqIdent"|": 622 let a = atm(it[1], input, idx, attached) 623 let b = atm(it[2], input, idx, attached) 624 if a.cond.kind == nnkEmpty or b.cond.kind == nnkEmpty: 625 error("'|' operator applied to a non-condition") 626 else: 627 result = (newStmtList(a.init, newIfStmt((a.cond, a.action), 628 (newTree(nnkStmtListExpr, b.init, b.cond), b.action))), 629 newEmptyNode(), newEmptyNode()) 630 elif it.kind == nnkInfix and it[0].eqIdent"^*": 631 # a ^* b is rewritten to: (a *(b a))? 632 #exprList = expr ^+ comma 633 template tmp(a, b): untyped = ?(a, *(b, a)) 634 result = atm(getAst(tmp(it[1], it[2])), input, idx, attached) 635 636 elif it.kind == nnkInfix and it[0].eqIdent"^+": 637 # a ^* b is rewritten to: (a +(b a))? 638 template tmp(a, b): untyped = (a, *(b, a)) 639 result = atm(getAst(tmp(it[1], it[2])), input, idx, attached) 640 elif it.kind == nnkCommand and it.len == 2 and it[0].eqIdent"pred": 641 # enforce that the wrapped call is interpreted as a predicate, not a non-terminal: 642 result = (newEmptyNode(), placeholder(it[1], input, idx), newEmptyNode()) 643 else: 644 var resLen = genSym(nskLet, "resLen") 645 result = (newLetStmt(resLen, placeholder(it, input, idx)), 646 newCall(interf"success", resLen), 647 !!newCall(interf"nxt", input, idx, resLen)) 648 of nnkStrLit..nnkTripleStrLit: 649 var resLen = genSym(nskLet, "resLen") 650 result = (newLetStmt(resLen, newCall(interf"skip", input, it, idx)), 651 newCall(interf"success", resLen), 652 !!newCall(interf"nxt", input, idx, resLen)) 653 of nnkCurly, nnkAccQuoted, nnkCharLit: 654 result = (newEmptyNode(), newCall(interf"atom", input, idx, it), 655 !!newCall(interf"nxt", input, idx)) 656 of nnkCurlyExpr: 657 if it.len == 3 and it[1].kind == nnkIntLit and it[2].kind == nnkIntLit: 658 var h = newTree(nnkTupleConstr, it[0]) 659 for count in 2i64 .. it[1].intVal: h.add(it[0]) 660 for count in it[1].intVal .. it[2].intVal-1: 661 h.add(newTree(nnkPrefix, ident"?", it[0])) 662 result = atm(h, input, idx, attached) 663 elif it.len == 2 and it[1].kind == nnkIntLit: 664 var h = newTree(nnkTupleConstr, it[0]) 665 for count in 2i64 .. it[1].intVal: h.add(it[0]) 666 result = atm(h, input, idx, attached) 667 else: 668 error("invalid pattern") 669 of nnkPar, nnkTupleConstr: 670 if it.len == 1 and it.kind == nnkPar: 671 result = atm(it[0], input, idx, attached) 672 else: 673 # concatenation: 674 var conds: seq[StmtTriple] = @[] 675 for x in it: conds.add atm(x, input, idx, attached) 676 var res = genSym(nskVar, "res") 677 result = (newStmtList(newVarStmt(res, newLit false), 678 toIfChain(conds, idx, res, 0)), res, newEmptyNode()) 679 else: 680 error("invalid pattern") 681 682 #var idx = genSym(nskVar, "idx") 683 var res = genSym(nskVar, "res") 684 result = newTree(nnkStmtListExpr, #newVarStmt(idx, newCall(interf"prepare", input)), 685 newVarStmt(res, newLit false)) 686 var conds: seq[StmtTriple] = @[] 687 for it in pattern: 688 conds.add atm(it, input, idx, nil) 689 result.add toIfChain(conds, idx, res, 0) 690 result.add res 691 when defined(debugScanp): 692 echo repr result 693