new operator
Hey,
Awesome you pursue this idea! I tried this a while in the past as well, although i tried to make automagic new operator for standard nim objects, allocating non-ref with init() and ref with new(). That was a limited success. I spent a while mucking in nim's ast trees. Since then i no longer use nim for anything. Since you are interested in this featured i figured i will share what i cobbled together. If you find any of this useful - treat this snippet as public domain code. And if it is useless - off to 🗑️ then 👍🏻
import macros
import strutils
# 12:43 <Araq> compiler/semstmts, edit semOverride
# 12:44 <Araq> add a new field finalizer* to the TType in ast.nim
# 12:44 <Araq> look at how deepcopy is implemented
# 12:44 <Araq> where it's used and instantiated
# 12:45 <Araq> cause `=finalize` needs to works with generics too
# 12:45 <Araq> so that's a slight complication
# 12:45 <Araq> but actually just grep for "deepcopy"
# 12:45 <Araq> it's all there for it
# 12:45 <Araq> generic instantation magic
# 12:46 <Araq> codegen type info slot
macro ctor*(none: untyped): auto =
let args = callsite()
var prc: NimNode
var allocator: NimNode
if args.len == 3:
if args[1].kind != nnkBracket:
error("Usage: {.ctor: [allocator: prc]}")
if args[2].kind != nnkProcDef:
error("ctor pragma is used only with procedures")
prc = args[2]
var margs = args[1]
for i in 0 ..< margs.len:
if $margs[i][0] == "allocator":
allocator = margs[i][1]
else:
error("Unknown {.ctor.} paramater " & $margs[i][0])
else:
if args[1].kind != nnkProcDef:
error("ctor pragma is used only with procedures")
prc = args[1]
allocator = new_ident_node("new")
if prc[3][1][1].kind != nnkVarTy:
error("Constructor must have var non-ref type as first parameter")
if prc[3][0].kind != nnkEmpty:
error("Constructor must not have return type")
var proc_name = $prc[0]
if proc_name notin ["init", "init*"]:
error("Constructor name must be `init` but is `" & proc_name & "`")
var export_proc = proc_name.ends_with("*")
var type_identifier = prc[3][1][1][0]
var ctor = quote do:
proc init(_: typedesc[`type_identifier`]): `type_identifier` =
init(result)
ctor = ctor[0]
ctor[2] = prc[2]
if export_proc:
ctor[0] = new_nim_node(nnkPostfix).add(
new_ident_node("*"),
ctor[0]
)
# Extend ctor with parameters of constructor
for i in 2 ..< prc[3].len:
ctor[3].add(prc[3][i])
# Passes ctor params to main init proc
ctor[6][0][1] = new_ident_node("result") # otherwise result is taken from macro context. weird!
for i in 2 ..< prc[3].len:
ctor[6][0].add(prc[3][i][0])
var allc = quote do:
proc new(_: typedesc[`type_identifier`]): ref `type_identifier` =
`allocator`(result)
init(result[])
allc = allc[0]
allc[2] = prc[2]
if export_proc:
allc[0] = new_nim_node(nnkPostfix).add(
new_ident_node("*"),
allc[0]
)
# Extend allc with parameters of constructor
for i in 2 ..< prc[3].len:
allc[3].add(prc[3][i])
# Passes allc params to main init proc
allc[6][0][1] = new_ident_node("result") # otherwise result is taken from macro context. weird!
allc[6][1][1][0] = new_ident_node("result")
for i in 2 ..< prc[3].len:
allc[6][1].add(prc[3][i][0])
result = new_stmt_list(prc, ctor, allc)
when is_main_module:
type
Foo = object
a: int
Bar = object
a: int
proc destroy(self: ref Foo) =
echo "done"
proc custom_new(res: var ref Foo) =
echo "allocating with custom allocator"
var x: ref Foo
new(x)
res = x
# var x: ref Foo
# custom_new(x)
# proc init(self: var Foo, n: int) =
# self.a = n
# proc init(_: typedesc[Foo], n: int): Foo =
# init(result, n)
# proc new(_: typedesc[Foo], n: int): ref Foo =
# custom_new(result, destroy)
# init(result[], n)
proc init(self: var Foo, n: int) {.ctor.} =
self.a = n
# proc init(self: var Bar, n: int) {.ctor: [allocator: custom_new].} =
# self.a = n
var f: Foo
f.init(1)
echo f.repr
#echo Foo.init(2).repr
echo Foo.new(3).repr
Hey, thanks for this 👍 I'll take a look when I can...
There's still so much about Nim I don't understand tbh, like destructors, pragmas, prioritizing overlapping references from multiple libraries (like the js issue on the bottom of the readme) etc