Closed itsumura-h closed 4 months ago
import std/json import std/macros import std/strutils import std/strformat import std/tables import ./libs/random_string # ==================== xmlEncode ==================== # extract from `cgi` to be able to run for JavaScript. # https://nim-lang.org/docs/cgi.html#xmlEncode%2Cstring proc addXmlChar(dest: var string, c: char) {.inline.} = case c of '&': add(dest, "&") of '<': add(dest, "<") of '>': add(dest, ">") of '\"': add(dest, """) else: add(dest, c) proc xmlEncode*(s: string): string = ## Encodes a value to be XML safe: ## * `"` is replaced by `"` ## * `<` is replaced by `<` ## * `>` is replaced by `>` ## * `&` is replaced by `&` ## * every other character is carried over. result = newStringOfCap(s.len + s.len shr 2) for i in 0..len(s)-1: addXmlChar(result, s[i]) # ==================== libView ==================== proc toString*(val:JsonNode):string = case val.kind of JString: return val.getStr.xmlEncode of JInt: return $(val.getInt) of JFloat: return $(val.getFloat) of JBool: return $(val.getBool) of JNull: return "" else: raise newException(JsonKindError, "val is array") proc toString*(val:string):string = return val.strip.xmlEncode proc toString*(val:bool | int | float):string = return val.`$`.xmlEncode # ==================== Component ==================== type Component* = ref object value:string id:string proc new*(_:type Component):Component = let id = randStr(10) return Component(value:"", id:id) proc add*(self:Component, value:string) = self.value.add(value) proc toString*(self:Component):string = return self.value.strip() proc `$`*(self:Component):string = return self.toString() proc id*(self:Component):string = self.id # ==================== parse template ==================== type BlockType = enum strBlock ifBlock # $if elifBlock # $elif elseBlock # $else forBlock # $for caseBlock # $case ofBlock # $of whileBlock # $while displayVariableBlock # $() nimCodeBlock # ${} proc identifyBlockType(str:string, point:int):BlockType = if str.substr(point, point+2) == "$if": return ifBlock elif str.substr(point, point+4) == "$elif": return elifBlock elif str.substr(point, point+4) == "$else": return elseBlock elif str.substr(point, point+3) == "$for": return forBlock elif str.substr(point, point+4) == "$case": return caseBlock elif str.substr(point, point+2) == "$of": return ofBlock elif str.substr(point, point+5) == "$while": return whileBlock elif str.substr(point, point+1) == "$(": return displayVariableBlock elif str.substr(point, point+1) == "${": return nimCodeBlock else: return strBlock proc findStrBlock(str:string, point:int):(int, string) = var count = -1 var isDoller = false for s in str[point..^1]: count += 1 if s == '$': isDoller = true break elif s == '}': break let resPoint = point + count let resStr = str.substr(point, resPoint-1) # $、}を含めない if isDoller: return (resPoint, resStr) # $からスタート else: return (resPoint+1, resStr) # 「}」の次からスタート proc findNimVariableBlock(str:string, point:int):(int, string) = let point = point + 2 # 「$(」の分進める var count = -1 for s in str[point..^1]: count += 1 if s == ')': break let resPoint = point + count let resStr = str.substr(point, resPoint-1) # 「)」を含めない return (resPoint+1, resStr) # 「)」の次からスタート proc findNimBlock(str:string, point:var int):(int, string) = let point = point + 1 # 「$」の分進める var count = -1 for s in str[point..^1]: count += 1 if s == '{': break let resPoint = point + count let resStr = str.substr(point, resPoint-1) # 「{」を含めない return (resPoint+1, resStr) # 「{」の次からスタート proc findNimCodeBlock(str:string, point:var int):(int, string) = let point = point + 2 # 「${」の分進める var count = -1 for s in str[point..^1]: count += 1 if s == '}': break let resPoint = point + count let resStr = str.substr(point, resPoint-1) # 「}」を含めない return (resPoint+1, resStr) # 「}」の次からスタート proc reindent(str:string, indentLevel:int):string = let indent = " ".repeat(indentLevel) return indent & str macro tmpl*(html: untyped): untyped = var body = "result = Component.new()\n" var point = 0 var indentLevel = 0 var blockType = strBlock while true: if point == html.repr.len: break blockType = identifyBlockType(html.repr, point) case blockType of strBlock: var (resPoint, resStr) = findStrBlock(html.repr, point) resStr = resStr.strip().replace("\n", "") # 改行コードを消す resStr = &"result.add({resStr.repr})\n" resStr = reindent(resStr, indentLevel) body.add(resStr) point = resPoint # resPointの1つ前が「}」の場合、indentLevelを下げる if html.repr[resPoint-1] == '}': indentLevel -= 1 of ifBlock: var (resPoint, resStr) = findNimBlock(html.repr, point) resStr = resStr.strip() & ":\n" resStr = reindent(resStr, indentLevel) body.add(resStr) indentLevel += 1 point = resPoint of elifBlock: var (resPoint, resStr) = findNimBlock(html.repr, point) resStr = resStr.strip() & ":\n" resStr = reindent(resStr, indentLevel) body.add(resStr) indentLevel += 1 point = resPoint of elseBlock: var (resPoint, resStr) = findNimBlock(html.repr, point) resStr = resStr.strip() & ":\n" resStr = reindent(resStr, indentLevel) body.add(resStr) indentLevel += 1 point = resPoint of forBlock: var (resPoint, resStr) = findNimBlock(html.repr, point) resStr = resStr.strip() & ":\n" resStr = reindent(resStr, indentLevel) body.add(resStr) indentLevel += 1 point = resPoint of caseBlock: var (resPoint, resStr) = findNimBlock(html.repr, point) resStr = resStr.strip() & ":\n" resStr = reindent(resStr, indentLevel) point = resPoint body.add(resStr) of ofBlock: var (resPoint, resStr) = findNimBlock(html.repr, point) resStr = resStr.strip() & ":\n" resStr = reindent(resStr, indentLevel) point = resPoint body.add(resStr) indentLevel += 1 of whileBlock: var (resPoint, resStr) = findNimBlock(html.repr, point) resStr = resStr.strip() & ":\n" resStr = reindent(resStr, indentLevel) point = resPoint body.add(resStr) indentLevel += 1 of displayVariableBlock: var (resPoint, resStr) = findNimVariableBlock(html.repr, point) resStr = resStr.strip() resStr = &"result.add(toString(({resStr})))" resStr = reindent(resStr, indentLevel) body.add(resStr & "\n") point = resPoint of nimCodeBlock: var (resPoint, resStr) = findNimCodeBlock(html.repr, point) resStr = resStr.strip() & "\n" resStr = reindent(resStr, indentLevel) body.add(resStr) point = resPoint if point == html.repr.len: break return body.parseStmt()