next step to get stateful components
This commit is contained in:
parent
0255f58964
commit
f0e2d41fde
4 changed files with 126 additions and 49 deletions
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue