discard """ output: '''heavy_calc_impl is called sub_calc1_impl is called sub_calc2_impl is called ** no changes recompute effectively ** change one input and recompute effectively heavy_calc_impl is called sub_calc2_impl is called''' """ # sample incremental import tables import macros var inputs = initTable[string, float]() var cache = initTable[string, float]() var dep_tree {.compileTime.} = initTable[string, string]() macro symHash(s: typed{nkSym}): string = result = newStrLitNode(symBodyHash(s)) ####################################################################################### template graph_node(key: string) {.pragma.} proc tag(n: NimNode): NimNode = ## returns graph node unique name of a function or nil if it is not a graph node expectKind(n, {nnkProcDef, nnkFuncDef}) for p in n.pragma: if p.len > 0 and p[0] == bindSym"graph_node": return p[1] return nil macro graph_node_key(n: typed{nkSym}): untyped = result = newStrLitNode(n.symBodyHash) macro graph_discovery(n: typed{nkSym}): untyped = # discovers graph dependency tree and updated dep_tree global var let mytag = newStrLitNode(n.symBodyHash) var visited: seq[NimNode] proc discover(n: NimNode) = case n.kind: of nnkNone..pred(nnkSym), succ(nnkSym)..nnkNilLit: discard of nnkSym: if n.symKind in {nskFunc, nskProc}: if n notin visited: visited.add n let tag = n.getImpl.tag if tag != nil: dep_tree[tag.strVal] = mytag.strVal else: discover(n.getImpl.body) else: for child in n: discover(child) discover(n.getImpl.body) result = newEmptyNode() ####################################################################################### macro incremental_input(key: static[string], n: untyped{nkFuncDef}): untyped = # mark leaf nodes of the graph template getInput(key) {.dirty.} = {.noSideEffect.}: inputs[key] result = n result.pragma = nnkPragma.newTree(nnkCall.newTree(bindSym"graph_node", newStrLitNode(key))) result.body = getAst(getInput(key)) macro incremental(n: untyped{nkFuncDef}): untyped = ## incrementalize side effect free computation ## wraps function into caching layer, mark caching function as a graph_node ## injects dependency discovery between graph nodes template cache_func_body(func_name, func_name_str, func_call) {.dirty.} = {.noSideEffect.}: graph_discovery(func_name) let key = graph_node_key(func_name) if key in cache: result = cache[key] else: echo func_name_str & " is called" result = func_call cache[key] = result let func_name = n.name.strVal & "_impl" let func_call = nnkCall.newTree(ident func_name) for i in 1..