diff --git a/butterfly.nim b/butterfly.nim new file mode 100644 index 0000000..a2d0160 --- /dev/null +++ b/butterfly.nim @@ -0,0 +1,425 @@ +import macros +import tables +import strutils +import typetraits + +proc `$`*[T](ar: openarray[T]): string = + ## Converts an array into a string + result = "[" + if ar.len() > 0: + result &= $ar[0] + for i in 1..ar.len()-1: + result &= ", " & $ar[i] + result &= "]" + +proc typeToStr*[T](some:typedesc[T]): string = name(T) + +template tupleObjToStr(obj): string {.dirty.} = + var res = typeToStr(type(obj)) + template helper(n) {.gensym.} = + res.add("(") + var firstElement = true + for name, value in n.fieldPairs(): + when compiles(value): + if not firstElement: + res.add(", ") + res.add(name) + res.add(": ") + when (value is object or value is tuple): + when (value is tuple): + res.add("tuple " & typeToStr(type(value))) + else: + res.add(typeToStr(type(value))) + helper(value) + elif (value is string): + res.add("\"" & $value & "\"") + else: + res.add($value) + firstElement = false + res.add(")") + helper(obj) + res + +proc `$`*(s: ref object): string = + result = "ref " & tupleObjToStr(s[]).replace(":ObjectType", "") + +proc objToStr*[T: object](obj: var T): string = + tupleObjToStr(obj) + +proc objToStr*[T: tuple](obj: T): string = + result = "tuple " & tupleObjToStr(obj) + +macro toString*(obj: typed): untyped = + ## this macro is to work around not being + ## able to override system.`$` + ## + ## Basically, I want to use my proc to print + ## objects and tuples, but the regular $ for + ## everything else + let kind = obj.getType().typeKind + case kind: + of ntyTuple, ntyObject: + template toStrAst(obj): string = + einheit.objToStr(obj) + result = getAst(toStrAst(obj)) + of ntyString: + template toStrAst(obj): string = + "\"" & $(obj) & "\"" + result = getAst(toStrAst(obj)) + else: + template toStrAst(obj): string = + $(obj) + result = getAst(toStrAst(obj)) + + +type + + LitKind = enum + lkBoolLit, + lkCharLit, + lkIntLit, lkInt8Lit, + lkInt16Lit, lkInt32Lit, lkInt64Lit, + lkUIntLit, lkUInt8Lit, + lkUInt16Lit, lkUInt32Lit, lkUInt64Lit, + lkFloatLit, lkFloat32Lit, lkFloat64Lit, + lkStrLit + + Literal = ref object + case kind: LitKind + of lkBoolLit: + boolVal: bool + of lkCharLit: + charVal: char + of lkIntLit: + intVal: int + of lkInt8Lit: + int8Val: int8 + of lkInt16Lit: + int16Val: int16 + of lkInt32Lit: + int32Val: int32 + of lkInt64Lit: + int64Val: int64 + of lkUIntLit: + uintVal: uint + of lkUInt8Lit: + uint8Val: uint8 + of lkUInt16Lit: + uint16Val: uint16 + of lkUInt32Lit: + uint32Val: uint32 + of lkUInt64Lit: + uint64Val: uint64 + of lkFloatLit: + floatVal: float + of lkFloat32Lit: + float32Val: float32 + of lkFloat64Lit: + float64Val: float64 + of lkStrLit: + strVal: string + + FieldType = enum + ftObject, + ftLiteral + + Field = ref object + case kind: FieldType + of ftObject: + objVal: Object + of ftLiteral: + litVal: Literal + + Object = ref object + name: string + fields: TableRef[string, Field] + + ObjectType = ref object + name: string + fields: TableRef[string, string] + + TypeType = enum + ttObject, + ttLiteral + + Type = ref object + case kind: TypeType + of ttObject: + objType: ObjectType + of ttLiteral: + litType: string + + Context = ref object + locals: TableRef[string, Context] + stmts: seq[string] + types: TableRef[string, Type] + parent: Context + +proc value(lit: Literal): string = + case lit.kind: + of lkBoolLit: + result = $lit.boolVal + of lkCharLit: + result = "'" & $lit.charVal & "'" + of lkIntLit: + result = $lit.intVal + of lkInt8Lit: + result = $lit.int8Val + of lkInt16Lit: + result = $lit.int16Val + of lkInt32Lit: + result = $lit.int32Val + of lkInt64Lit: + result = $lit.int64Val + of lkUIntLit: + result = $lit.uintVal + of lkUInt8Lit: + result = $lit.uint8Val + of lkUInt16Lit: + result = $lit.uint16Val + of lkUInt32Lit: + result = $lit.uint32Val + of lkUInt64Lit: + result = $lit.uint64Val + of lkFloatLit: + result = $lit.floatVal + of lkFloat32Lit: + result = $lit.float32Val + of lkFloat64Lit: + result = $lit.float64Val + of lkStrLit: + result = "\"" & $lit.strVal & "\"" + + +proc type(lit: Literal): string = + case lit.kind: + of lkBoolLit: + result = "bool" + of lkCharLit: + result = "char" + of lkIntLit: + result = "int" + of lkInt8Lit: + result = "int8" + of lkInt16Lit: + result = "int16" + of lkInt32Lit: + result = "int32" + of lkInt64Lit: + result = "int64" + of lkUIntLit: + result = "uint" + of lkUInt8Lit: + result = "uint8" + of lkUInt16Lit: + result = "uint16" + of lkUInt32Lit: + result = "uint32" + of lkUInt64Lit: + result = "uint64" + of lkFloatLit: + result = "float" + of lkFloat32Lit: + result = "float32" + of lkFloat64Lit: + result = "float64" + of lkStrLit: + result = "string" + +proc type*(obj: Object): string +proc value*(obj: Object): string + +proc type*(field: Field): string = + case field.kind: + of ftObject: + result = butterfly.type(field.objVal) + of ftLiteral: + result = butterfly.type(field.litVal) + +proc value*(field: Field): string = + case field.kind: + of ftObject: + result = butterfly.value(field.objVal) + of ftLiteral: + result = butterfly.value(field.litVal) + +proc type*(obj: Object): string = + result = obj.name & "(" + + var fieldData: seq[string] = @[] + + for fname in obj.fields.keys(): + let value = obj.fields[fname] + let ftype = butterfly.type(value) + fieldData.add(fname & ": " & ftype) + + result &= fieldData.join(", ") + result &= ")" + +proc value*(obj: Object): string = + result = obj.name & "(" + + var fieldData: seq[string] = @[] + + for fname in obj.fields.keys(): + let value = obj.fields[fname] + let fval = butterfly.value(value) + fieldData.add(fname & ": " & fval) + + result &= fieldData.join(", ") + result &= ")" + +proc `value=`*[T](lit: var Literal, value: T) = + when T is char: + lit.charVal = value + elif T is bool: + lit.boolVal = value + elif T is int: + lit.intVal = value + elif T is int8: + lit.int8Val = value + elif T is int16: + lit.int16Val = value + elif T is int32: + lit.int32Val = value + elif T is int64: + lit.int64Val = value + elif T is uint: + lit.intVal = value + elif T is uint8: + lit.int8Val = value + elif T is uint16: + lit.int16Val = value + elif T is uint32: + lit.int32Val = value + elif T is uint64: + lit.int64Val = value + elif T is float: + lit.floatVal = value + elif T is float32: + lit.float32Val = value + elif T is float64: + lit.float64Val = value + elif T is string: + lit.strVal = value + +var procTable = newTable[string, Context]() + +proc debugProc() = + echo "Debug" + +proc updateProcTable(procName: string, params: string) = + let + key = procName & params + + if not procTable.hasKey(key): + procTable[key] = new(Context) + +template doNodes(nodeKind: NimNodeKind, node: NimNode, code: untyped) {.dirty.}= + var stackN: seq[NimNode] = @[node] + + while stackN.len() > 0: + let newNode = stackN.pop() + for i in 0 ..< newNode.len(): + let n = newNode[i] + if n.kind == nodeKind: + code + else: + stackN.add(n) + + +iterator getNode(nodeKind: NimNodeKind, node: NimNode): NimNode = + var stack: seq[NimNode] = @[node] + + while stack.len() > 0: + let newNode = stack.pop() + for i in 0 ..< newNode.len(): + let child = newNode[i] + if child.kind == nodeKind: + yield child + else: + stack.add(child) + +template addStmtTree(parentx: NimNode, nodex: NimNode, indexx: int): untyped {.dirty.} = + proc helper(parent: NimNode, node: NimNode, index: int): NimNode = + var newList = newNimNode(nnkStmtList) + + for child in node.children: + + if child.kind == nnkIfStmt: + for subch in child.children: + if subch.kind == nnkElifBranch: + discard helper(subch, subch[1], 1) + else: + discard helper(subch, subch[0], 0) + + if child.kind == nnkBlockStmt: + discard helper(child, child[1], 1) + + + let line = child.lineInfoObj() + echo "Inserting debug on line: " & $line.line + newList.add(newCall(bindSym("debugProc"))) + newList.add(child) + for n in getNode(nnkCall, child): + if n.kind != nnkEmpty: + let + impl = n[0].getImpl() + procName = $impl[0].toStrLit() + params = $impl[3].toStrLit() + key = procName & params + + if not visitedProcs.hasKey(key): + # this can be added once a compiler bug is fixed + #[newList.add( + newCall( + bindSym("updateProcTable"), + newLit(procName), + newLit(params) + ) + )]# + visitedProcs[key] = true + # add the next stmt list onto the stack + # of the newly discovered proc + if impl[6].kind == nnkStmtList: + discard helper(impl, impl[6], 6) + echo impl.toStrLit + + newList.add(newCall(bindSym("debugProc"))) + parent[index] = newList + return parent + helper(parentx, nodex, indexx) + + +macro debug(code: typed): untyped = + var visitedProcs = newTable[string, bool]() + + code[0] = addStmtTree(code[0], code[0][6], 6) + + result = code + echo code.treeRepr + echo code.toStrLit() + +# This proc now has it's stmts replaced! +proc otherProc(stuff: string): string = + let a = "beans" + echo stuff & a + result = stuff & a + +debug: + proc main() = + let x = "balls".otherProc() + let y = otherProc("balls2") + block: + echo "Beans" + echo "Beans" + if x == "ballsbeans": + echo "Yaya" + echo "who" + else: + echo "balls" + echo "multi" + + +when isMainModule: + main() diff --git a/src/butterfly.nim b/src/butterfly.nim new file mode 100644 index 0000000..a2d0160 --- /dev/null +++ b/src/butterfly.nim @@ -0,0 +1,425 @@ +import macros +import tables +import strutils +import typetraits + +proc `$`*[T](ar: openarray[T]): string = + ## Converts an array into a string + result = "[" + if ar.len() > 0: + result &= $ar[0] + for i in 1..ar.len()-1: + result &= ", " & $ar[i] + result &= "]" + +proc typeToStr*[T](some:typedesc[T]): string = name(T) + +template tupleObjToStr(obj): string {.dirty.} = + var res = typeToStr(type(obj)) + template helper(n) {.gensym.} = + res.add("(") + var firstElement = true + for name, value in n.fieldPairs(): + when compiles(value): + if not firstElement: + res.add(", ") + res.add(name) + res.add(": ") + when (value is object or value is tuple): + when (value is tuple): + res.add("tuple " & typeToStr(type(value))) + else: + res.add(typeToStr(type(value))) + helper(value) + elif (value is string): + res.add("\"" & $value & "\"") + else: + res.add($value) + firstElement = false + res.add(")") + helper(obj) + res + +proc `$`*(s: ref object): string = + result = "ref " & tupleObjToStr(s[]).replace(":ObjectType", "") + +proc objToStr*[T: object](obj: var T): string = + tupleObjToStr(obj) + +proc objToStr*[T: tuple](obj: T): string = + result = "tuple " & tupleObjToStr(obj) + +macro toString*(obj: typed): untyped = + ## this macro is to work around not being + ## able to override system.`$` + ## + ## Basically, I want to use my proc to print + ## objects and tuples, but the regular $ for + ## everything else + let kind = obj.getType().typeKind + case kind: + of ntyTuple, ntyObject: + template toStrAst(obj): string = + einheit.objToStr(obj) + result = getAst(toStrAst(obj)) + of ntyString: + template toStrAst(obj): string = + "\"" & $(obj) & "\"" + result = getAst(toStrAst(obj)) + else: + template toStrAst(obj): string = + $(obj) + result = getAst(toStrAst(obj)) + + +type + + LitKind = enum + lkBoolLit, + lkCharLit, + lkIntLit, lkInt8Lit, + lkInt16Lit, lkInt32Lit, lkInt64Lit, + lkUIntLit, lkUInt8Lit, + lkUInt16Lit, lkUInt32Lit, lkUInt64Lit, + lkFloatLit, lkFloat32Lit, lkFloat64Lit, + lkStrLit + + Literal = ref object + case kind: LitKind + of lkBoolLit: + boolVal: bool + of lkCharLit: + charVal: char + of lkIntLit: + intVal: int + of lkInt8Lit: + int8Val: int8 + of lkInt16Lit: + int16Val: int16 + of lkInt32Lit: + int32Val: int32 + of lkInt64Lit: + int64Val: int64 + of lkUIntLit: + uintVal: uint + of lkUInt8Lit: + uint8Val: uint8 + of lkUInt16Lit: + uint16Val: uint16 + of lkUInt32Lit: + uint32Val: uint32 + of lkUInt64Lit: + uint64Val: uint64 + of lkFloatLit: + floatVal: float + of lkFloat32Lit: + float32Val: float32 + of lkFloat64Lit: + float64Val: float64 + of lkStrLit: + strVal: string + + FieldType = enum + ftObject, + ftLiteral + + Field = ref object + case kind: FieldType + of ftObject: + objVal: Object + of ftLiteral: + litVal: Literal + + Object = ref object + name: string + fields: TableRef[string, Field] + + ObjectType = ref object + name: string + fields: TableRef[string, string] + + TypeType = enum + ttObject, + ttLiteral + + Type = ref object + case kind: TypeType + of ttObject: + objType: ObjectType + of ttLiteral: + litType: string + + Context = ref object + locals: TableRef[string, Context] + stmts: seq[string] + types: TableRef[string, Type] + parent: Context + +proc value(lit: Literal): string = + case lit.kind: + of lkBoolLit: + result = $lit.boolVal + of lkCharLit: + result = "'" & $lit.charVal & "'" + of lkIntLit: + result = $lit.intVal + of lkInt8Lit: + result = $lit.int8Val + of lkInt16Lit: + result = $lit.int16Val + of lkInt32Lit: + result = $lit.int32Val + of lkInt64Lit: + result = $lit.int64Val + of lkUIntLit: + result = $lit.uintVal + of lkUInt8Lit: + result = $lit.uint8Val + of lkUInt16Lit: + result = $lit.uint16Val + of lkUInt32Lit: + result = $lit.uint32Val + of lkUInt64Lit: + result = $lit.uint64Val + of lkFloatLit: + result = $lit.floatVal + of lkFloat32Lit: + result = $lit.float32Val + of lkFloat64Lit: + result = $lit.float64Val + of lkStrLit: + result = "\"" & $lit.strVal & "\"" + + +proc type(lit: Literal): string = + case lit.kind: + of lkBoolLit: + result = "bool" + of lkCharLit: + result = "char" + of lkIntLit: + result = "int" + of lkInt8Lit: + result = "int8" + of lkInt16Lit: + result = "int16" + of lkInt32Lit: + result = "int32" + of lkInt64Lit: + result = "int64" + of lkUIntLit: + result = "uint" + of lkUInt8Lit: + result = "uint8" + of lkUInt16Lit: + result = "uint16" + of lkUInt32Lit: + result = "uint32" + of lkUInt64Lit: + result = "uint64" + of lkFloatLit: + result = "float" + of lkFloat32Lit: + result = "float32" + of lkFloat64Lit: + result = "float64" + of lkStrLit: + result = "string" + +proc type*(obj: Object): string +proc value*(obj: Object): string + +proc type*(field: Field): string = + case field.kind: + of ftObject: + result = butterfly.type(field.objVal) + of ftLiteral: + result = butterfly.type(field.litVal) + +proc value*(field: Field): string = + case field.kind: + of ftObject: + result = butterfly.value(field.objVal) + of ftLiteral: + result = butterfly.value(field.litVal) + +proc type*(obj: Object): string = + result = obj.name & "(" + + var fieldData: seq[string] = @[] + + for fname in obj.fields.keys(): + let value = obj.fields[fname] + let ftype = butterfly.type(value) + fieldData.add(fname & ": " & ftype) + + result &= fieldData.join(", ") + result &= ")" + +proc value*(obj: Object): string = + result = obj.name & "(" + + var fieldData: seq[string] = @[] + + for fname in obj.fields.keys(): + let value = obj.fields[fname] + let fval = butterfly.value(value) + fieldData.add(fname & ": " & fval) + + result &= fieldData.join(", ") + result &= ")" + +proc `value=`*[T](lit: var Literal, value: T) = + when T is char: + lit.charVal = value + elif T is bool: + lit.boolVal = value + elif T is int: + lit.intVal = value + elif T is int8: + lit.int8Val = value + elif T is int16: + lit.int16Val = value + elif T is int32: + lit.int32Val = value + elif T is int64: + lit.int64Val = value + elif T is uint: + lit.intVal = value + elif T is uint8: + lit.int8Val = value + elif T is uint16: + lit.int16Val = value + elif T is uint32: + lit.int32Val = value + elif T is uint64: + lit.int64Val = value + elif T is float: + lit.floatVal = value + elif T is float32: + lit.float32Val = value + elif T is float64: + lit.float64Val = value + elif T is string: + lit.strVal = value + +var procTable = newTable[string, Context]() + +proc debugProc() = + echo "Debug" + +proc updateProcTable(procName: string, params: string) = + let + key = procName & params + + if not procTable.hasKey(key): + procTable[key] = new(Context) + +template doNodes(nodeKind: NimNodeKind, node: NimNode, code: untyped) {.dirty.}= + var stackN: seq[NimNode] = @[node] + + while stackN.len() > 0: + let newNode = stackN.pop() + for i in 0 ..< newNode.len(): + let n = newNode[i] + if n.kind == nodeKind: + code + else: + stackN.add(n) + + +iterator getNode(nodeKind: NimNodeKind, node: NimNode): NimNode = + var stack: seq[NimNode] = @[node] + + while stack.len() > 0: + let newNode = stack.pop() + for i in 0 ..< newNode.len(): + let child = newNode[i] + if child.kind == nodeKind: + yield child + else: + stack.add(child) + +template addStmtTree(parentx: NimNode, nodex: NimNode, indexx: int): untyped {.dirty.} = + proc helper(parent: NimNode, node: NimNode, index: int): NimNode = + var newList = newNimNode(nnkStmtList) + + for child in node.children: + + if child.kind == nnkIfStmt: + for subch in child.children: + if subch.kind == nnkElifBranch: + discard helper(subch, subch[1], 1) + else: + discard helper(subch, subch[0], 0) + + if child.kind == nnkBlockStmt: + discard helper(child, child[1], 1) + + + let line = child.lineInfoObj() + echo "Inserting debug on line: " & $line.line + newList.add(newCall(bindSym("debugProc"))) + newList.add(child) + for n in getNode(nnkCall, child): + if n.kind != nnkEmpty: + let + impl = n[0].getImpl() + procName = $impl[0].toStrLit() + params = $impl[3].toStrLit() + key = procName & params + + if not visitedProcs.hasKey(key): + # this can be added once a compiler bug is fixed + #[newList.add( + newCall( + bindSym("updateProcTable"), + newLit(procName), + newLit(params) + ) + )]# + visitedProcs[key] = true + # add the next stmt list onto the stack + # of the newly discovered proc + if impl[6].kind == nnkStmtList: + discard helper(impl, impl[6], 6) + echo impl.toStrLit + + newList.add(newCall(bindSym("debugProc"))) + parent[index] = newList + return parent + helper(parentx, nodex, indexx) + + +macro debug(code: typed): untyped = + var visitedProcs = newTable[string, bool]() + + code[0] = addStmtTree(code[0], code[0][6], 6) + + result = code + echo code.treeRepr + echo code.toStrLit() + +# This proc now has it's stmts replaced! +proc otherProc(stuff: string): string = + let a = "beans" + echo stuff & a + result = stuff & a + +debug: + proc main() = + let x = "balls".otherProc() + let y = otherProc("balls2") + block: + echo "Beans" + echo "Beans" + if x == "ballsbeans": + echo "Yaya" + echo "who" + else: + echo "balls" + echo "multi" + + +when isMainModule: + main()