diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index ba1b33e77f..186d27ae45 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -969,7 +969,7 @@ const
proc fitsRegister*(t: PType): bool =
t.skipTypes(abstractInst-{tyTypeDesc}).kind in {
- tyRange, tyEnum, tyBool, tyInt..tyUInt64}
+ tyRange, tyEnum, tyBool, tyInt..tyUInt64, tyChar}
proc requiresCopy(n: PNode): bool =
if n.typ.skipTypes(abstractInst-{tyTypeDesc}).kind in atomicTypes:
@@ -1191,6 +1191,8 @@ proc genArrAccess2(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode;
c.gABC(n, opcNodeToReg, dest, cc)
c.freeTemp(cc)
else:
+ #message(n.info, warnUser, "argh")
+ #echo "FLAGS ", flags, " ", fitsRegister(n.typ), " ", typeToString(n.typ)
c.gABC(n, opc, dest, a, b)
c.freeTemp(a)
c.freeTemp(b)
@@ -1623,7 +1625,7 @@ proc genProc(c: PCtx; s: PSym): int =
c.gABC(body, opcEof, eofInstr.regA)
c.optimizeJumps(result)
s.offset = c.prc.maxSlots
- #if s.name.s == "parse_until_symbol":
+ #if s.name.s == "find":
# echo renderTree(body)
# c.echoCode(result)
c.prc = oldPrc
diff --git a/tests/template/annotate.nim b/tests/template/annotate.nim
new file mode 100644
index 0000000000..fa58030dc1
--- /dev/null
+++ b/tests/template/annotate.nim
@@ -0,0 +1,113 @@
+import macros, parseutils
+
+# Generate tags
+macro make(names: openarray[expr]): stmt {.immediate.} =
+ result = newStmtList()
+
+ for i in 0 .. names.len-1:
+ result.add newProc(
+ name = ident($names[i]).postfix("*"),
+ params = [
+ ident("string"),
+ newIdentDefs(
+ ident("content"),
+ ident("string")
+ )
+ ],
+ body = newStmtList(
+ parseStmt("reindent(content)")
+ )
+ )
+
+
+iterator lines(value: string): string =
+ var i = 0
+ while i < value.len:
+ var line: string
+ inc(i, value.parseUntil(line, 0x0A.char, i) + 1)
+ yield line
+
+
+proc reindent*(value: string, preset_indent = 0): string =
+ var indent = -1
+
+ # Detect indentation!
+ for ln in lines(value):
+ var read = ln.skipWhitespace()
+
+ # If the line is empty, ignore it for indentation
+ if read == ln.len: continue
+
+ indent = if indent < 0: read
+ else: min(indent, read)
+
+ # Create a precursor indent as-needed
+ var precursor = newString(0)
+ for i in 1 .. preset_indent:
+ precursor.add(' ')
+
+ # Re-indent
+ result = newString(0)
+
+ for ln in lines(value):
+ var value = ln.substr(indent)
+
+ result.add(precursor)
+
+ if value.len > 0:
+ result.add(value)
+ result.add(0x0A.char)
+
+ return result
+
+
+#Define tags
+make([ html, xml, glsl, js, css, rst ])
+
+
+when isMainModule:
+ ## Test tags
+
+ const script = js"""
+ var x = 5;
+ console.log(x.toString());
+ """
+
+ const styles = css"""
+ .someRule {
+ width: 500px;
+ }
+ """
+
+ const body = html"""
+
+ """
+
+ const info = xml"""
+ -
+ 1
+ 2
+
+ """
+
+ const shader = glsl"""
+ void main()
+ {
+ gl_Position = gl_ProjectionMatrix
+ * gl_ModelViewMatrix
+ * gl_Vertex;
+ }
+ """
+
+
+ echo script
+ echo styles
+ echo body
+ echo info
+ echo shader
\ No newline at end of file
diff --git a/tests/template/otests.nim b/tests/template/otests.nim
new file mode 100644
index 0000000000..c885e23df5
--- /dev/null
+++ b/tests/template/otests.nim
@@ -0,0 +1,219 @@
+# Fields
+const x = 5
+
+
+# Test substring
+static:
+ assert "test".substring(3) == "t"
+ assert "test".substring(2,1) == "s"
+ assert "test".substring(3,2) == "t"
+ assert "test".substring(1,2) == "es"
+
+
+# Various parsing tests
+when true:
+
+ block: #no_substitution
+ proc actual: string = tmpli html"""
+ Test!
+ """
+ const expected = html"""
+ Test!
+ """
+ doAssert actual() == expected
+
+ block: #basic
+ proc actual: string = tmpli html"""
+ Test $$x
+ $x
+ """
+ const expected = html"""
+ Test $x
+ 5
+ """
+ doAssert actual() == expected
+
+ block: #expression
+ proc actual: string = tmpli html"""
+ Test $$(x * 5)
+ $(x * 5)
+ """
+ const expected = html"""
+ Test $(x * 5)
+ 25
+ """
+ doAssert actual() == expected
+
+ block: #escape
+ proc actual: string = tmpli js"""
+ [{
+ "hello world"
+ }]
+ """
+ const expected = js"""
+ [{
+ "hello world"
+ }]
+ """
+ doAssert actual() == expected
+
+ block: #forIn
+ proc actual: string = tmpli html"""
+ Test for
+
+ $for y in 0..2 {
+ - $y
+ }
+
+ """
+ const expected = html"""
+ Test for
+
+ """
+ doAssert actual() == expected
+
+ block: #while
+ proc actual: string = tmpli html"""
+ Test while/stmt
+
+ ${ var y = 0 }
+ $while y < 4 {
+ - $y
+ ${ inc(y) }
+ }
+
+ """
+ const expected = html"""
+ Test while/stmt
+
+ """
+ doAssert actual() == expected
+
+ block: #ifElifElse
+ proc actual: string = tmpli html"""
+ Test if/elif/else
+ $if x == 8 {
+ x is 8!
+ }
+ $elif x == 7 {
+ x is 7!
+ }
+ $else {
+ x is neither!
+ }
+ """
+ const expected = html"""
+ Test if/elif/else
+ x is neither!
+ """
+ doAssert actual() == expected
+
+ block: #multiLineStatements
+ proc actual: string = tmpli html"""
+ Test multiline statements
+ ${
+ var x = 5
+ var y = 7
+ }
+ $x$y
+ """
+ const expected = html"""
+ Test multiline statements
+ 57
+ """
+ doAssert actual() == expected
+
+ block: #caseOfElse
+ proc actual: string = tmpli html"""
+ Test case
+ $case x
+ $of 5 {
+ x == 5
+ }
+ $of 6 {
+ x == 6
+ }
+ $else {}
+ """
+ const expected = html"""
+ Test case
+ x == 5
+ """
+ doAssert actual() == expected
+
+
+
+when true: #embeddingTest
+ proc no_substitution: string = tmpli html"""
+ Template test!
+ """
+
+ # # Single variable substitution
+ proc substitution(who = "nobody"): string = tmpli html"""
+ hello $who!
+ """
+
+ # Expression template
+ proc test_expression(nums: openarray[int] = []): string =
+ var i = 2
+ tmpli html"""
+ $(no_substitution())
+ $(substitution("Billy"))
+ Age: $($nums[i] & "!!")
+ """
+
+ proc test_statements(nums: openarray[int] = []): string =
+ tmpli html"""
+ $(test_expression(nums))
+ $if true {
+
+ $for i in nums {
+ - $(i * 2)
+ }
+
+ }
+ """
+
+ var actual = test_statements([0,1,2])
+ const expected = html"""
+ Template test!
+ hello Billy!
+ Age: 2!!
+
+ """
+ doAssert actual == expected
+
+
+when defined(future):
+ block: #tryCatch
+ proc actual: string = tmpli html"""
+ Test try/catch
+
+ $try {
+
Lets try this!
+ }
+ $except {
+
Uh oh!
+ }
+
+ """
+ const expected = html"""
+ Test try/catch
+
+ """
+ doAssert actual() == expected
\ No newline at end of file
diff --git a/tests/template/t_otemplates.nim b/tests/template/t_otemplates.nim
new file mode 100644
index 0000000000..7de728ab25
--- /dev/null
+++ b/tests/template/t_otemplates.nim
@@ -0,0 +1,345 @@
+discard """
+ output: "Success"
+"""
+
+# Ref:
+# http://nimrod-lang.org/macros.html
+# http://nimrod-lang.org/parseutils.html
+
+
+# Imports
+import tables, parseutils, macros, strutils
+import annotate
+export annotate
+
+
+# Fields
+const identChars = {'a'..'z', 'A'..'Z', '0'..'9', '_'}
+
+
+# Procedure Declarations
+proc parse_template(node: PNimrodNode, value: string) {.compiletime.}
+
+
+# Procedure Definitions
+proc substring(value: string, index: int, length = -1): string {.compiletime.} =
+ ## Returns a string at most `length` characters long, starting at `index`.
+ return if length < 0: value.substr(index)
+ elif length == 0: ""
+ else: value.substr(index, index + length-1)
+
+
+proc parse_thru_eol(value: string, index: int): int {.compiletime.} =
+ ## Reads until and past the end of the current line, unless
+ ## a non-whitespace character is encountered first
+ var remainder: string
+ var read = value.parseUntil(remainder, {0x0A.char}, index)
+ if remainder.skipWhitespace() == read:
+ return read + 1
+
+
+proc trim_after_eol(value: var string) {.compiletime.} =
+ ## Trims any whitespace at end after \n
+ var toTrim = 0
+ for i in countdown(value.len-1, 0):
+ # If \n, return
+ if value[i] in [' ', '\t']: inc(toTrim)
+ else: break
+
+ if toTrim > 0:
+ value = value.substring(0, value.len - toTrim)
+
+
+proc trim_eol(value: var string) {.compiletime.} =
+ ## Removes everything after the last line if it contains nothing but whitespace
+ for i in countdown(value.len - 1, 0):
+ # If \n, trim and return
+ if value[i] == 0x0A.char:
+ value = value.substr(0, i)
+ break
+
+ # This is the first character
+ if i == 0:
+ value = ""
+ break
+
+ # Skip change
+ if not (value[i] in [' ', '\t']): break
+
+
+proc detect_indent(value: string, index: int): int {.compiletime.} =
+ ## Detects how indented the line at `index` is.
+ # Seek to the beginning of the line.
+ var lastChar = index
+ for i in countdown(index, 0):
+ if value[i] == 0x0A.char:
+ # if \n, return the indentation level
+ return lastChar - i
+ elif not (value[i] in [' ', '\t']):
+ # if non-whitespace char, decrement lastChar
+ dec(lastChar)
+
+
+proc parse_thru_string(value: string, i: var int, strType = '"') {.compiletime.} =
+ ## Parses until ending " or ' is reached.
+ inc(i)
+ if i < value.len-1:
+ inc(i, value.skipUntil({'\\', strType}, i))
+
+
+proc parse_to_close(value: string, index: int, open='(', close=')', opened=0): int {.compiletime.} =
+ ## Reads until all opened braces are closed
+ ## ignoring any strings "" or ''
+ var remainder = value.substring(index)
+ var open_braces = opened
+ result = 0
+
+ while result < remainder.len:
+ var c = remainder[result]
+
+ if c == open: inc(open_braces)
+ elif c == close: dec(open_braces)
+ elif c == '"': remainder.parse_thru_string(result)
+ elif c == '\'': remainder.parse_thru_string(result, '\'')
+
+ if open_braces == 0: break
+ else: inc(result)
+
+
+iterator parse_stmt_list(value: string, index: var int): string =
+ ## Parses unguided ${..} block
+ var read = value.parse_to_close(index, open='{', close='}')
+ var expressions = value.substring(index + 1, read - 1).split({ ';', 0x0A.char })
+
+ for expression in expressions:
+ let value = expression.strip
+ if value.len > 0:
+ yield value
+
+ #Increment index & parse thru EOL
+ inc(index, read + 1)
+ inc(index, value.parse_thru_eol(index))
+
+
+iterator parse_compound_statements(value, identifier: string, index: int): string =
+ ## Parses through several statements, i.e. if {} elif {} else {}
+ ## and returns the initialization of each as an empty statement
+ ## i.e. if x == 5 { ... } becomes if x == 5: nil.
+
+ template get_next_ident(expected): stmt =
+ var nextIdent: string
+ discard value.parseWhile(nextIdent, {'$'} + identChars, i)
+
+ var next: string
+ var read: int
+
+ if nextIdent == "case":
+ # We have to handle case a bit differently
+ read = value.parseUntil(next, '$', i)
+ inc(i, read)
+ yield next.strip(leading=false) & "\n"
+
+ else:
+ read = value.parseUntil(next, '{', i)
+
+ if nextIdent in expected:
+ inc(i, read)
+ # Parse until closing }, then skip whitespace afterwards
+ read = value.parse_to_close(i, open='{', close='}')
+ inc(i, read + 1)
+ inc(i, value.skipWhitespace(i))
+ yield next & ": nil\n"
+
+ else: break
+
+
+ var i = index
+ while true:
+ # Check if next statement would be valid, given the identifier
+ if identifier in ["if", "when"]:
+ get_next_ident([identifier, "$elif", "$else"])
+
+ elif identifier == "case":
+ get_next_ident(["case", "$of", "$elif", "$else"])
+
+ elif identifier == "try":
+ get_next_ident(["try", "$except", "$finally"])
+
+
+proc parse_complex_stmt(value, identifier: string, index: var int): PNimrodNode {.compiletime.} =
+ ## Parses if/when/try /elif /else /except /finally statements
+
+ # Build up complex statement string
+ var stmtString = newString(0)
+ var numStatements = 0
+ for statement in value.parse_compound_statements(identifier, index):
+ if statement[0] == '$': stmtString.add(statement.substr(1))
+ else: stmtString.add(statement)
+ inc(numStatements)
+
+ # Parse stmt string
+ result = parseExpr(stmtString)
+
+ var resultIndex = 0
+
+ # Fast forward a bit if this is a case statement
+ if identifier == "case":
+ inc(resultIndex)
+
+ while resultIndex < numStatements:
+
+ # Detect indentation
+ let indent = detect_indent(value, index)
+
+ # Parse until an open brace `{`
+ var read = value.skipUntil('{', index)
+ inc(index, read + 1)
+
+ # Parse through EOL
+ inc(index, value.parse_thru_eol(index))
+
+ # Parse through { .. }
+ read = value.parse_to_close(index, open='{', close='}', opened=1)
+
+ # Add parsed sub-expression into body
+ var body = newStmtList()
+ var stmtString = value.substring(index, read)
+ trim_after_eol(stmtString)
+ stmtString = reindent(stmtString, indent)
+ parse_template(body, stmtString)
+ inc(index, read + 1)
+
+ # Insert body into result
+ var stmtIndex = macros.high(result[resultIndex])
+ result[resultIndex][stmtIndex] = body
+
+ # Parse through EOL again & increment result index
+ inc(index, value.parse_thru_eol(index))
+ inc(resultIndex)
+
+
+proc parse_simple_statement(value: string, index: var int): PNimrodNode {.compiletime.} =
+ ## Parses for/while
+
+ # Detect indentation
+ let indent = detect_indent(value, index)
+
+ # Parse until an open brace `{`
+ var splitValue: string
+ var read = value.parseUntil(splitValue, '{', index)
+ result = parseExpr(splitValue & ":nil")
+ inc(index, read + 1)
+
+ # Parse through EOL
+ inc(index, value.parse_thru_eol(index))
+
+ # Parse through { .. }
+ read = value.parse_to_close(index, open='{', close='}', opened=1)
+
+ # Add parsed sub-expression into body
+ var body = newStmtList()
+ var stmtString = value.substring(index, read)
+ trim_after_eol(stmtString)
+ stmtString = reindent(stmtString, indent)
+ parse_template(body, stmtString)
+ inc(index, read + 1)
+
+ # Insert body into result
+ var stmtIndex = macros.high(result)
+ result[stmtIndex] = body
+
+ # Parse through EOL again
+ inc(index, value.parse_thru_eol(index))
+
+
+proc parse_until_symbol(node: PNimrodNode, value: string, index: var int): bool {.compiletime.} =
+ ## Parses a string until a $ symbol is encountered, if
+ ## two $$'s are encountered in a row, a split will happen
+ ## removing one of the $'s from the resulting output
+ var splitValue: string
+ var read = value.parseUntil(splitValue, '$', index)
+ var insertionPoint = node.len
+
+ inc(index, read + 1)
+ if index < value.len:
+
+ case value[index]
+ of '$':
+ # Check for duplicate `$`, meaning this is an escaped $
+ node.add newCall("add", ident("result"), newStrLitNode("$"))
+ inc(index)
+
+ of '(':
+ # Check for open `(`, which means parse as simple single-line expression.
+ trim_eol(splitValue)
+ read = value.parse_to_close(index) + 1
+ node.add newCall("add", ident("result"),
+ newCall(bindSym"strip", parseExpr("$" & value.substring(index, read)))
+ )
+ inc(index, read)
+
+ of '{':
+ # Check for open `{`, which means open statement list
+ trim_eol(splitValue)
+ for s in value.parse_stmt_list(index):
+ node.add parseExpr(s)
+
+ else:
+ # Otherwise parse while valid `identChars` and make expression w/ $
+ var identifier: string
+ read = value.parseWhile(identifier, identChars, index)
+
+ if identifier in ["for", "while"]:
+ ## for/while means open simple statement
+ trim_eol(splitValue)
+ node.add value.parse_simple_statement(index)
+
+ elif identifier in ["if", "when", "case", "try"]:
+ ## if/when/case/try means complex statement
+ trim_eol(splitValue)
+ node.add value.parse_complex_stmt(identifier, index)
+
+ elif identifier.len > 0:
+ ## Treat as simple variable
+ node.add newCall("add", ident("result"), newCall("$", ident(identifier)))
+ inc(index, read)
+
+ result = true
+
+ # Insert
+ if splitValue.len > 0:
+ node.insert insertionPoint, newCall("add", ident("result"), newStrLitNode(splitValue))
+
+
+proc parse_template(node: PNimrodNode, value: string) =
+ ## Parses through entire template, outputing valid
+ ## Nimrod code into the input `node` AST.
+ var index = 0
+ while index < value.len and
+ parse_until_symbol(node, value, index): nil
+
+
+macro tmpli*(body: expr): stmt =
+ result = newStmtList()
+
+ result.add parseExpr("result = \"\"")
+
+ var value = if body.kind in nnkStrLit..nnkTripleStrLit: body.strVal
+ else: body[1].strVal
+
+ parse_template(result, reindent(value))
+
+
+macro tmpl*(body: expr): stmt =
+ result = newStmtList()
+
+ var value = if body.kind in nnkStrLit..nnkTripleStrLit: body.strVal
+ else: body[1].strVal
+
+ parse_template(result, reindent(value))
+
+
+# Run tests
+when isMainModule:
+ include otests
+ echo "Success"