Skip to content

Commit 1e3dca7

Browse files
committed
feat(pysugar/class): suport define cls.new -> cls.__new__; ref #4
1 parent 95ecb11 commit 1e3dca7

File tree

2 files changed

+150
-44
lines changed

2 files changed

+150
-44
lines changed

src/pylib/pysugar/class.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
import ./stmt/class
3+
export class.new
34
import ./parserWithCfg
45

56
macro class*(obj, body: untyped): untyped =

src/pylib/pysugar/stmt/class.nim

Lines changed: 149 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ proc parseDeclWithType(def: NimNode): tuple[name, typ, val: NimNode] =
2727

2828
#proc mapSuper(nCall: NimNode): NimNode = discard
2929

30-
proc replaceSuperCall(n: var NimNode, defSupCls: NimNode): bool =
30+
type
31+
MethKind = enum
32+
mkNorm
33+
mkCls
34+
mkStatic
35+
36+
proc replaceSuperCall(n: var NimNode, defSupCls: NimNode, methKind: MethKind): bool =
3137
template nret(cond): untyped =
3238
if not cond:
3339
return
@@ -37,24 +43,51 @@ proc replaceSuperCall(n: var NimNode, defSupCls: NimNode): bool =
3743
nret callSup.kind == nnkCall
3844
nret callSup[0].eqIdent "super"
3945

40-
var supCls = defSupCls
41-
let cLen = callSup.len
42-
if cLen > 1:
43-
expectIdent callSup[2], "self"
44-
if callSup.len > 2:
45-
if cLen > 3: error "super(<SupCls>, self) expected, but got too many args"
46-
supCls = callSup[1]
46+
var cLen = callSup.len
47+
let supCls =
48+
if cLen > 2:
49+
if cLen > 3: error "super([<cls>[, self]]) expected, but got too many args"
50+
callSup[1]
51+
else:
52+
if cLen == 1:
53+
callSup.add defSupCls
54+
cLen.inc
55+
defSupCls
56+
57+
if methKind == mkCls:
58+
if cLen == 3:
59+
expectIdent callSup[2], "cls"
60+
else:
61+
callSup.add ident"cls"
62+
cLen.inc
63+
n = newDotExpr(supCls, n[0][1]).newCall(callSup[2])
64+
elif methKind == mkNorm:
65+
if cLen > 1:
66+
expectIdent callSup[2], "self"
67+
n[0][0] = newCall(supCls, ident"self")
4768

48-
n[0][0] = newCall(supCls, ident"self")
69+
#let meth = n[0][1]
70+
#let args = n[1..^1]
4971

50-
#let meth = n[0][1]
51-
#let args = n[1..^1]
72+
n = newCall(ident"procCall", n)
5273

53-
n = newCall(ident"procCall", n)
5474
result = true
5575

56-
proc recReplaceSuperCall*(n: NimNode, defSupCls: NimNode, start=0): NimNode =
57-
##[ Recursively maps `super(...).f(...)`
76+
template new*[T; R: RootRef](_: typedesc[R], cls: typedesc[T],
77+
_: varargs[untyped]): typedesc[T] =
78+
## py's `object.__new__`
79+
cls
80+
81+
proc init_subclass*[T](cls: typedesc[ref T]): ref T =
82+
## py's `object.__init_subclass__`
83+
discard
84+
85+
proc recReplaceSuperCall*(n: NimNode, defSupCls: NimNode, start=0, methKind=mkNorm): NimNode =
86+
##[ Recursively maps
87+
88+
### 1.
89+
90+
`super(...).f(...)`
5891
to `procCall(<SuperClass>(self).f(...))`
5992
6093
The AST map:
@@ -66,7 +99,7 @@ The AST map:
6699
[<SuperClass>]
67100
[Ident "self"]
68101
Ident "f"
69-
<args>
102+
<args>
70103
71104
|
72105
|
@@ -80,7 +113,13 @@ The AST map:
80113
<SuperClass>
81114
Ident "self"
82115
Ident "f"
83-
``` ]##
116+
<args>
117+
```
118+
119+
### 2
120+
`super(...).new/init_subclass(...)`
121+
to `<SuperClass>.new/init_subclass(...)`
122+
]##
84123
runnableExamples:
85124
import std/[macros, strutils]
86125
macro checkSupSub(resStr: static string; b) =
@@ -104,9 +143,9 @@ The AST map:
104143
result = n.copy()
105144
var i = start
106145
while i<result.len:
107-
discard replaceSuperCall(result, defSupCls)
146+
discard replaceSuperCall(result, defSupCls, methKind=methKind)
108147
if result[i].len != 0:
109-
result[i] = recReplaceSuperCall(result[i], defSupCls, 0)
148+
result[i] = recReplaceSuperCall(result[i], defSupCls, 0, methKind=methKind)
110149
i.inc
111150

112151
func remove1[T](s: var seq[T], x: T) =
@@ -122,7 +161,7 @@ func remove1[T](s: var seq[T], x: T) =
122161

123162
proc tryPreClsBltinDecorater(mparser: var PyAsgnRewriter,
124163
args: var seq[NimNode], procType: var NimNodeKind,
125-
pragmas: var seq[NimNode],
164+
pragmas: var seq[NimNode], methKind: var MethKind
126165
): bool =
127166
#[
128167
@staticmethod
@@ -177,9 +216,11 @@ then you will find it compile but `O.f()` gives `0` instead of `3`
177216
return false
178217
case $decor.name
179218
of "staticmethod":
219+
methKind = mkStatic
180220
purgeBase()
181221
args.insert(newIdentDefs(ident"_", clsType), 1)
182222
of "classmethod":
223+
methKind = mkCls
183224
purgeBase()
184225
args[1][1] = clsType
185226
else:
@@ -191,6 +232,31 @@ template mkPragma(pragmas: seq[NimNode]): NimNode =
191232
if pragmas.len == 0: emptyn
192233
else: nnkPragma.newNimNode.add pragmas
193234

235+
proc genNewCls(classId, generics: NimNode, initArgs, initPragmas: seq[NimNode]): NimNode =
236+
## returns decl of `newXxx`
237+
let classNewName = ident("new" & classId.strVal)
238+
var newArgs = @[classId]
239+
var body = newStmtList()
240+
let resId = ident"result"
241+
var callNew = newCall(newDotExpr(classId, ident"new"), newCall("typeof", classId))
242+
let defInit = initArgs.len > 0
243+
var callInit: NimNode
244+
if defInit:
245+
#var init = newCall(ident"init", resId)
246+
callInit = newCall(resId.newDotExpr(ident"init"))
247+
for i in 2..<initArgs.len: # skip `resType` and `self: Cls`
248+
let argDef = initArgs[i].copyNimTree # we cannot use the old, as it's a symbol
249+
# or there will be a error: positional param was already given as named param
250+
newArgs.add argDef
251+
callNew.add argDef[0]
252+
callInit.add argDef[0]
253+
body.add newAssignment(resId, nnkObjConstr.newTree(callNew))
254+
if defInit:
255+
body.add callInit
256+
var pragmas = initPragmas
257+
pragmas.remove1 ident"base"
258+
newProc(classNewName, generics, newArgs, body, pragmas=pragmas.mkPragma)
259+
194260
proc classImpl*(parser: var PySyntaxProcesser; obj, body: NimNode): NimNode =
195261
##[ minic Python's `class`.
196262
@@ -292,8 +358,25 @@ so if wantting the attr inherited from SupCls, just write it as-is (e.g. `self.a
292358
template addAttr(name; typ=emptyn, defVal=emptyn) =
293359
typDefLs.add nnkIdentDefs.newTree(name, typ, defVal)
294360
var defs = newStmtList()
361+
var decls = newStmtList()
362+
template addMeth(def: NimNode) = defs.add def
363+
#[ return type 'auto' cannot be used in forward declarations
364+
template addMethWithDecl(def: NimNode) =
365+
defs.add def
366+
var decl = def.copyNimNode
367+
let lastI = def.len - 1
368+
for i in 0..<lastI:
369+
decl.add def[i].copyNimTree
370+
decl.add newEmptyNode()
371+
decls.add decl
372+
]#
373+
var noNew = true
374+
var
375+
initArgs: seq[NimNode]
376+
initGenerics = newNimNode nnkGenericParams
377+
initPragmas: seq[NimNode]
295378
for def in items(body):
296-
var pragmas = defPragmas # will be set as empty if `isConstruct` or not base
379+
var pragmas = defPragmas # will be set as empty if `isConstructor` or not base
297380
case def.kind
298381
of nnkCall:
299382
# - attr define, e.g. a: int / a: int = 1
@@ -317,51 +400,68 @@ so if wantting the attr inherited from SupCls, just write it as-is (e.g. `self.a
317400
let tup = parser.parseSignatureMayGenerics(generics_cpy, signature, deftype)
318401
var procName = tup.name
319402
dunderDirVal.add newLit procName.strVal
320-
let isConstructor = procName.eqIdent "init"
321-
if isConstructor:
322-
procName = newIdentNode("new" & className)
403+
let
404+
isConstructor = procName.eqIdent "init"
405+
isNew = procName.eqIdent "new"
406+
var methKind: MethKind
407+
if isNew or procName.eqIdent "init_subclass":
408+
methKind = mkCls
409+
if isNew:
410+
noNew = false
323411
pragmas = @[]
324412
# First argument is the return type of the procedure
325413
var args = tup.params
326414
# push a new stack frame
327415
parser.push()
328-
# Statements which will occur before proc body
329-
var beforeBody = newStmtList()
416+
var body = newStmtList()
417+
template markSelfType =
418+
args[1][1] = genericsClassId
419+
template chk1ArgCls =
420+
if not args[1][0].eqIdent "cls":
421+
warning "the 1st arg of __new__ is not cls, but " & args[1][0].repr,
422+
args[1]
423+
args[1][1] = classId
330424
if isConstructor:
425+
if args[0].strVal in ["None", "auto"]:
426+
args[0] = newEmptyNode()
331427
expectIdent args[1][0], "self"
332-
args.delete 1
333-
args[0] = genericsClassId
334-
template construct(): untyped {.dirty.} =
335-
var self: type(result)
336-
new(self)
337-
beforeBody.add getAst(construct())
428+
markSelfType
429+
initArgs = args
430+
elif methKind == mkCls:
431+
if isNew:
432+
args[0] = nnkBracketExpr.newTree(bindSym"typedesc", genericsClassId)
433+
chk1ArgCls
434+
elif methKind == mkStatic:
435+
discard
338436
else:
339437
if args.len > 1 and args[1][0].eqIdent "self":
340-
args[1][1] = genericsClassId
438+
markSelfType
341439
# Function body
342440
var docNode: NimNode
343-
var parsedbody = recReplaceSuperCall(parser.parsePyBodyWithDoc(def[2], docNode), supCls)
344-
# If we're generating a constructor proc - we need to return self
345-
# after we've created it
346-
if isConstructor:
347-
parsedbody.add nnkReturnStmt.newTree ident"self"
348-
# Add statement which will occur before function body
349-
beforeBody.add parsedBody
441+
var parsedbody = recReplaceSuperCall(
442+
parser.parsePyBodyWithDoc(def[2], docNode), supCls,
443+
methKind=methKind)
350444
if docNode.len != 0:
351-
beforeBody.insert(0, docNode)
445+
body.insert(0, docNode)
446+
# Add statement which will occur before function body
447+
body.add parsedBody
352448
parser.pop()
353449

354450
# Finally create a procedure and add it to result!
355451
var procType: NimNodeKind
356452
discard parser.tryPreClsBltinDecorater(
357-
args, procType, pragmas=pragmas
453+
args, procType, pragmas=pragmas, methKind=methKind
358454
)
359-
if isConstructor: procType = nnkProcDef
455+
if methKind != mkNorm:
456+
procType = nnkProcDef
457+
elif isConstructor:
458+
initPragmas = pragmas
459+
initGenerics = generics_cpy
360460
let nDef = parser.consumeDecorator(
361-
newProc(procName, generics_cpy, args, beforeBody,
461+
newProc(procName, generics_cpy, args, body,
362462
procType, pragmas=pragmas.mkPragma)
363463
)
364-
defs.add nDef
464+
addMeth nDef
365465

366466
of nnkStrLit, nnkRStrLit, nnkTripleStrLit:
367467
result.add newCommentStmtNode $def
@@ -370,6 +470,9 @@ so if wantting the attr inherited from SupCls, just write it as-is (e.g. `self.a
370470
result.add def
371471
else:
372472
result.add def # AS-IS
473+
474+
addMeth genNewCls(classId, initGenerics, initArgs, initPragmas)
475+
373476
let ty = nnkRefTy.newTree nnkObjectTy.newTree(emptyn, supClsNode, typDefLs)
374477
let typDef = nnkTypeSection.newTree nnkTypeDef.newTree(classId, generics, ty)
375478
result.add:
@@ -383,6 +486,8 @@ so if wantting the attr inherited from SupCls, just write it as-is (e.g. `self.a
383486
# result.add quote do:
384487
# when not declaredInScope `classId`:
385488
# `typDef`
489+
490+
result.add decls
386491
result.add defs
387492

388493
result.add newConstStmt(

0 commit comments

Comments
 (0)