diff options
author | Araq <rumpf_a@web.de> | 2015-08-02 01:16:47 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2015-08-02 02:55:59 +0200 |
commit | 5e325bc9f9be68841ce1aef6b64aada2aeb52918 (patch) | |
tree | 1f81cd8688a716cf75b305fc407041b6e26f007c | |
parent | bd786812e7fd1cbd1b7a8f038000ac06ad08505e (diff) | |
download | Nim-5e325bc9f9be68841ce1aef6b64aada2aeb52918.tar.gz |
made test green again
-rw-r--r-- | tests/template/t_otemplates.nim | 678 |
1 files changed, 339 insertions, 339 deletions
diff --git a/tests/template/t_otemplates.nim b/tests/template/t_otemplates.nim index db535d818..6c419f72f 100644 --- a/tests/template/t_otemplates.nim +++ b/tests/template/t_otemplates.nim @@ -2,344 +2,344 @@ discard """ output: "Success" """ -# Ref: -# http://nim-lang.org/macros.html -# http://nim-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: NimNode, 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): NimNode {.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): NimNode {.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: NimNode, 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: NimNode, value: string) = - ## Parses through entire template, outputing valid - ## Nim 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: +# Ref: +# http://nim-lang.org/macros.html +# http://nim-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: NimNode, 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): NimNode {.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 = result[resultIndex].len-1 + 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): NimNode {.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 = result.len-1 + result[stmtIndex] = body + + # Parse through EOL again + inc(index, value.parse_thru_eol(index)) + + +proc parse_until_symbol(node: NimNode, 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: NimNode, value: string) = + ## Parses through entire template, outputing valid + ## Nim 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" |