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