Skip to content

Instantly share code, notes, and snippets.

@zah
Created August 28, 2011 10:43
Show Gist options
  • Save zah/1176523 to your computer and use it in GitHub Desktop.
Save zah/1176523 to your computer and use it in GitHub Desktop.
String interpolation macro
import macros, parseutils
proc isEscaped(s: string, pos: int) : bool =
var
backslashes = 0
j = pos - 1
while j >= 0:
if s[j] == '\\':
inc backslashes
dec j
else:
break
return backslashes mod 2 != 0
type
TInterpStrFragment = tuple[interpStart, interpEnd, exprStart, exprEnd: int]
iterator interpolatedFragments(s: string): TInterpStrFragment =
var i = 0
while i < s.len:
# the $ sign marks the start of an interpolation.
# it's followed either by an identifier or an opening bracket (so it should be before the end of the string)
# if the dollar sign is escaped, don't trigger interpolation
if s[i] == '$' and i < (s.len - 1) and not isEscaped(s, i):
var next = s[i+1]
if next == '{':
inc i
var
brackets = {'{', '}'}
nestingCount = 1
start = i + 1
# find closing braket, while respecting any nested brackets
while i < s.len:
inc i, skipUntil(s, brackets, i+1) + 1
if not isEscaped(s, i):
if s[i] == '}':
dec nestingCount
if nestingCount == 0: break
else:
inc nestingCount
var t : TInterpStrFragment
t.interpStart = start - 2
t.interpEnd = i
t.exprStart = start
t.exprEnd = i - 1
yield t
# FIXME: Nimrod refused to evaluate tuple literals in macro code
# yield (s: start, e: i-1)
else:
var
ident: string
start = i + 1
var identLen = parseIdent(s, ident, i+1)
if identLen != 0:
inc i, identLen
var t : TInterpStrFragment
t.interpStart = start - 1
t.interpEnd = i
t.exprStart = start
t.exprEnd = i
yield t
# FIXME: Nimrod refused to evaluate tuple literals in macro code
# yield (s: start, e: i)
else:
macros.error "Unable to parse a variable name at: " & s[i..s.len]
break
inc i
proc concat(strings: openarray[string]) : string =
result = newString(0)
for s in items(strings): result.add(s)
macro i(e: expr) : expr =
var
s = e[1].strVal
stringStart = 0
args : seq[PNimrodNode]
newSeq(args, 0)
for i in interpolatedFragments(s):
var leadingString = s[stringStart..i.interpStart-1]
var interpolatedExpr = s[i.exprStart..i.exprEnd]
echo " leading string: |" & leadingString & "|"
echo " interpolated e: |" & interpolatedExpr & "|"
args.add(newStrLitNode(leadingString))
args.add(newStrLitNode(interpolatedExpr))
stringStart = i.interpEnd + 1
if stringStart != s.len:
var endingString = s[stringStart..s.len]
echo " ending string : |" & endingString & "|"
args.add(newStrLitNode(endingString))
result = newCall("concat", args)
var x = i"hello ${user} \$ ${call {a, b, c}}"
echo x
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment