next step to get stateful components

This commit is contained in:
Araq 2017-04-17 09:39:13 +02:00
commit f0e2d41fde
4 changed files with 126 additions and 49 deletions

View file

@ -10,7 +10,6 @@ proc carousel*(key: VKey): VNode {.component.} =
proc next(ev: Event; n: VNode) = proc next(ev: Event; n: VNode) =
counter = (counter + 1) mod images.len counter = (counter + 1) mod images.len
markDirty key
result = buildHtml(tdiv(key=key)): result = buildHtml(tdiv(key=key)):
text images[counter] text images[counter]

View file

@ -201,3 +201,51 @@ proc visibleKeys(e: Node; a, b: var VKey; h, count: var int) =
else: else:
for i in 0..<e.len: for i in 0..<e.len:
visibleKeys(e[i], a, b, h, count) visibleKeys(e[i], a, b, h, count)
template toState(x): untyped = "state" & x
proc accessState(sv: string): NimNode {.compileTime.} =
newTree(nnkBracketExpr, newIdentNode(sv), newIdentNode("key"))
proc stateDecl(n: NimNode; names: TableRef[string, bool]; decl, init: NimNode) =
case n.kind
of nnkVarSection, nnkLetSection:
for c in n:
expectKind c, nnkIdentDefs
let typ = c[^2]
let val = c[^1]
let usedType = if typ.kind != nnkEmpty: typ else: val
if usedType.kind == nnkEmpty:
error("cannot determine the variable's type", c)
for i in 0 .. c.len-3:
let v = $c[i]
let sv = toState v
decl.add quote do:
var `sv` = newJDict[VKey, `usedType`]()
if val.kind != nnkEmpty:
init.add newTree(nnkAsgn, accessState(sv), val)
names[v] = true
of nnkStmtList, nnkStmtListExpr:
for x in n: stateDecl(x, names, decl, init)
of nnkDo:
stateDecl(n.body, names, decl, init)
of nnkCommentStmt: discard
else:
error("invalid 'state' declaration", n)
proc doState(n: NimNode; names: TableRef[string, bool];
decl, init: NimNode): NimNode =
result = n
case n.kind
of nnkCallKinds:
# handle 'state' declaration and remove it from the AST:
if n.len == 2 and repr(n[0]) == "state":
stateDecl(n[1], names, decl, init)
result = newTree(nnkEmpty)
of nnkSym, nnkIdent:
let v = $n
if v in names:
let sv = toState v
result = accessState(sv)
else:
for i in 0..<n.len:
result[i] = doState(n[i], names, decl, init)

View file

@ -2,6 +2,32 @@
import macros, jdict, dom, vdom, tables, strutils import macros, jdict, dom, vdom, tables, strutils
type
StateDict*[V] = ref object
proc `[]`*[V](d: StateDict[V], k: VKey): V {.importcpp: "#[#]".}
proc put[V](d: StateDict[V], k: VKey, v: V) {.importcpp: "#[#] = #".}
proc contains*[V](d: StateDict[V], k: VKey): bool {.importcpp: "#.hasOwnProperty(#)".}
proc del*[V](d: StateDict[V], k: VKey) {.importcpp: "delete #[#]".}
proc newStateDict*[V](): StateDict[V] {.importcpp: "{@}".}
var
dirty = newStateDict[bool]()
someDirty*: bool
proc markDirty*(key: VKey) =
dirty.put(key, true)
someDirty = true
proc unmarkDirty*(key: VKey) = dirty.del key
proc isDirty*(key: VKey): bool = dirty.contains(key)
proc `[]=`*[V](d: StateDict[V], k: VKey, v: V) =
d.put(k, v)
markDirty(k)
var var
vcomponents* = newJDict[cstring, proc(args: seq[VNode]): VNode]() vcomponents* = newJDict[cstring, proc(args: seq[VNode]): VNode]()
dcomponents* = newJDict[cstring, proc(args: seq[VNode]): Node]() dcomponents* = newJDict[cstring, proc(args: seq[VNode]): Node]()
@ -30,52 +56,50 @@ proc addTags() {.compileTime.} =
static: static:
addTags() addTags()
template toState(x): untyped = "state" & x proc stateDecl(n: NimNode; names: TableRef[string, bool]) =
proc accessState(sv: string): NimNode {.compileTime.} =
newTree(nnkBracketExpr, newIdentNode(sv), newIdentNode("key"))
proc stateDecl(n: NimNode; names: TableRef[string, bool]; decl, init: NimNode) =
case n.kind case n.kind
of nnkVarSection, nnkLetSection: of nnkVarSection, nnkLetSection:
for c in n: for c in n:
expectKind c, nnkIdentDefs expectKind c, nnkIdentDefs
let typ = c[^2]
let val = c[^1]
let usedType = if typ.kind != nnkEmpty: typ else: val
if usedType.kind == nnkEmpty:
error(c, "cannot determine the variable's type")
for i in 0 .. c.len-3: for i in 0 .. c.len-3:
let v = $c[i] let v = $c[i]
let sv = toState v
decl.add quote do:
var `sv` = newJDict[VKey, `usedType`]()
if val.kind != nnkEmpty:
init.add newTree(nnkAsgn, accessState(sv), val)
names[v] = true names[v] = true
of nnkStmtList, nnkStmtListExpr: of nnkStmtList, nnkStmtListExpr:
for x in n: stateDecl(x, names, decl, init) for x in n: stateDecl(x, names)
of nnkDo: of nnkDo:
stateDecl(n.body, names, decl, init) stateDecl(n.body, names)
of nnkCommentStmt: discard else: discard
else:
error(n, "invalid 'state' declaration")
proc doState(n: NimNode; names: TableRef[string, bool]; decl, init: NimNode): NimNode = proc accessesState(n: NimNode; names: TableRef[string, bool]): bool =
case n.kind
of nnkSym, nnkIdent:
result = $n in names
of nnkBracketExpr, nnkDotExpr:
result = accessesState(n[0], names)
else:
for i in 0..<n.len:
if accessesState(n[i], names): return true
proc doState(n: NimNode; names: TableRef[string, bool];
outer: NimNode): NimNode =
result = n result = n
case n.kind case n.kind
of nnkCallKinds: of nnkCallKinds:
# handle 'state' declaration and remove it from the AST: # handle 'state' declaration and move it to the outer block:
if n.len == 2 and repr(n[0]) == "state": if n.len == 2 and repr(n[0]) == "state":
stateDecl(n[1], names, decl, init) stateDecl(n[1], names)
result = newTree(nnkEmpty) outer.add n[1]
of nnkSym, nnkIdent: result = newEmptyNode()
let v = $n of nnkAsgn, nnkFastAsgn:
if v in names: if accessesState(n[0], names):
let sv = toState v result = newStmtList(n, newCall(bindSym"markDirty", newIdentNode"key"))
result = accessState(sv)
else: else:
for i in 0..<n.len: for i in 0..<n.len:
result[i] = doState(n[i], names, decl, init) result[i] = doState(n[i], names, outer)
proc compBody(body, outer: NimNode): NimNode =
var names = newTable[string, bool]()
result = doState(body, names, outer)
proc unpack(symbolicType: NimNode; index: int): NimNode {.compileTime.} = proc unpack(symbolicType: NimNode; index: int): NimNode {.compileTime.} =
#let t = symbolicType.getTypeImpl #let t = symbolicType.getTypeImpl
@ -114,7 +138,7 @@ macro component*(prc: untyped): untyped =
for i in 0..6: n.add prc[i].copyNimTree for i in 0..6: n.add prc[i].copyNimTree
expectKind(n, nnkProcDef) expectKind(n, nnkProcDef)
if n[0].kind == nnkEmpty: if n[0].kind == nnkEmpty:
error("please pass a non anonymous proc") error("please pass a non anonymous proc", n[0])
let name = n[0] let name = n[0]
let params = params(n) let params = params(n)
let rettype = repr params[0] let rettype = repr params[0]
@ -124,7 +148,7 @@ macro component*(prc: untyped): untyped =
elif rettype == "Node": elif rettype == "Node":
isvirtual = ComponentKind.Node isvirtual = ComponentKind.Node
else: else:
error "component must return VNode or Node" error "component must return VNode or Node", params[0]
let realName = if name.kind == nnkPostfix: name[1] else: name let realName = if name.kind == nnkPostfix: name[1] else: name
let nn = $realName let nn = $realName
n[0] = ident("inner" & nn) n[0] = ident("inner" & nn)
@ -138,6 +162,9 @@ macro component*(prc: untyped): untyped =
unpackCall.add unpack(typ, counter) unpackCall.add unpack(typ, counter)
inc counter inc counter
let outer = newTree(nnkStmtList)
discard compBody(n.body, outer)
template vwrapper(pname, unpackCall) {.dirty.} = template vwrapper(pname, unpackCall) {.dirty.} =
proc pname(args: seq[VNode]): VNode = proc pname(args: seq[VNode]): VNode =
unpackCall unpackCall
@ -154,20 +181,30 @@ macro component*(prc: untyped): untyped =
bind jdict.`[]=` bind jdict.`[]=`
`[]=`(dcomponents, cstring(key), val) `[]=`(dcomponents, cstring(key), val)
result = newTree(nnkStmtList, n) outer.add n
#outer.add body
outer.add unpackCall
result = newTree(nnkStmtList)
if isvirtual == ComponentKind.VNode: if isvirtual == ComponentKind.VNode:
result.add getAst(vwrapper(newname name, unpackCall)) result.add getAst(vwrapper(newname name, outer))
result.add getAst(vregister(newLit(nn), realName)) result.add getAst(vregister(newLit(nn), realName))
else: else:
result.add getAst(dwrapper(newname name, unpackCall)) result.add getAst(dwrapper(newname name, outer))
result.add getAst(dregister(newLit(nn), realName)) result.add getAst(dregister(newLit(nn), realName))
allcomponents[nn] = isvirtual allcomponents[nn] = isvirtual
when defined(debugKaraxDsl): when defined(debugKaraxDsl):
echo repr result echo repr result
when isMainModule: when isMainModule:
proc public*(x, y: int, b: bool; s: cstring): VNode {.component.} = proc public*(key: VKey; x, y: int, b: bool; s: cstring): VNode {.component.} =
discard state:
var foo = 89
proc callback() =
foo = 78
let cc = callback
proc private(x, y: int, b: bool; s: cstring): VNode {.component.} = proc private(x, y: int, b: bool; s: cstring): VNode {.component.} =
discard discard

View file

@ -116,8 +116,6 @@ proc same(n: VNode, e: Node): bool =
var var
dorender: proc (): VNode {.closure.} dorender: proc (): VNode {.closure.}
currentTree: VNode currentTree: VNode
dirty = newJDict[cstring, bool]()
dirtyCount: int
proc replaceById(id: cstring; newTree: Node) = proc replaceById(id: cstring; newTree: Node) =
let x = document.getElementById(id) let x = document.getElementById(id)
@ -151,14 +149,9 @@ proc equalsTree(a, b: VNode): bool =
else: else:
result = eq(a, b) result = eq(a, b)
proc markDirty*(key: VKey) =
dirty[&key] = true
inc dirtyCount
proc updateDirtyElements(parent, current: Node, newNode: VNode) = proc updateDirtyElements(parent, current: Node, newNode: VNode) =
if newNode.key >= 0 and dirty.contains(&newNode.key): if newNode.key >= 0 and isDirty(newNode.key):
dirty.del(&newNode.key) unmarkDirty(newNode.key)
dec dirtyCount
let n = vnodeToDom(newNode) let n = vnodeToDom(newNode)
if parent == nil: if parent == nil:
replaceById("ROOT", n) replaceById("ROOT", n)
@ -241,9 +234,9 @@ proc dodraw() =
let olddom = document.getElementById("ROOT") let olddom = document.getElementById("ROOT")
updateElement(nil, olddom, newtree, currentTree) updateElement(nil, olddom, newtree, currentTree)
#assert same(newtree, document.getElementById("ROOT")) #assert same(newtree, document.getElementById("ROOT"))
if dirtyCount > 0: if someDirty:
updateDirtyElements(nil, olddom, newtree) updateDirtyElements(nil, olddom, newtree)
dirtyCount = 0 someDirty = false
currentTree = newtree currentTree = newtree
# now that it's part of the DOM, give it the focus: # now that it's part of the DOM, give it the focus:
if toFocus != nil: if toFocus != nil: