import std/macros macro `<-`*(lhs: untyped, rhs: untyped): untyped = # ensure that the left-hand side is a valid bracket expression e.g. [a, b, c] let errmsg = "Invalid syntax for unpack operator: " & lhs.toStrLit().strVal() if lhs.kind != nnkBracket: error(errmsg, lhs) for name in lhs: if name.kind != nnkIdent: error(errmsg, lhs) # the result will be a check to ensure no index defects, followed by assignment statements result = newNimNode(nnkStmtList, lineInfoFrom = lhs) # first add a check to ensure that the container being unpacked has enough values # this has to be done at runtime, since it's potentially unknown at compile time let numIdents = lhs.len let strRepr = lhs.toStrLit().strVal() & " <- " & rhs.toStrLit().strVal() let lenCheck = quote do: if `rhs`.len < `numIdents`: raise newException(ValueError, "Not enough values to unpack: \"" & `strRepr` & "\"") result.add(lenCheck) var assign_from = rhs # if the right-hand side is an expression, it might # be expensive, so we save it to a temp variable if rhs.kind != nnkIdent: # repoint assign_src so that we end up using a temp variable # instead of repeating the original expression a bunch of times assign_from = ident("unpack_tmp_src") # e.g. "let unpack_tmp_src = somestring.split()" let declare_assign_src = newLetStmt(assign_from, rhs) result.add(declare_assign_src) # finally, inject the actual assignments for i, name in lhs: let assignment = quote do: # expanded to e.g. "let a = someseq[0]" let `name` = `assign_from`[`i`] result.add(assignment) proc showAddr*[T](x: T) = echo cast[uint64](unsafeAddr x)