From d3b7711cd1ff947938287ce5663d4c9166f12d5f Mon Sep 17 00:00:00 2001 From: Andrea Ferretti Date: Thu, 2 Jul 2015 21:09:58 +0200 Subject: [PATCH] Added constructors --- README.md | 23 +++++++++++++++++------ patty.nim | 53 ++++++++++++++++++++++++++++++++++++++++++++++------- test.nim | 23 +++++++++++++++++++++-- 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2a37190..e25690e 100644 --- a/README.md +++ b/README.md @@ -86,25 +86,36 @@ and expands to ```nim type - ShapeKind = enum + ShapeE = enum Circle, Rectangle Shape = object - case kind: ShapeKind - of Circle: + case kind: ShapeE + of CircleE: r: float - of Rectangle: + of RectangleE: w: float h: float - of UnitCircle: + of UnitCircleE: nil + +proc Circle(r: float; x: float; y: float): Shape = + Shape(kind: CircleE, r: r) + +proc Rectangle(w: float; h: float): Shape = + Shape(kind: RectangleE, w: w, h: h) + +proc UnitCircle(side: int): Shape = + Shape(kind: UnitCircleE) ``` +Notice that the macro also generates three convenient constructors (`Circle` ,`Rectangle` and `UnitCircle`), and in fact the names in the enum are `CircleE`, `RectangleE` and `UnitCircleE` to avoid a name conflict. + A couple of limitations fo the `adt` macro: * field names must be unique across branches (that is, different variants cannot have two fields with the same name). This is actually a limitation of Nim. * the shortcut that groups field names by type does not seem to work, that is, in the above example one could not write `Rectangle(w, h: float)`. -In the future, Patty will also add a proper definition of equality and generated contructors. +In the future, Patty will also add a proper definition of equality. Things that do not work (yet) ----------------------------- diff --git a/patty.nim b/patty.nim index 4d0809a..0521604 100644 --- a/patty.nim +++ b/patty.nim @@ -1,21 +1,26 @@ import macros +const enumSuffix = "E" + iterator tail(a: NimNode): NimNode = var first = true for x in children(a): if not first: yield x first = false +proc `&`(n: NimNode, s: string): NimNode {. compileTime .} = + n.expectKind(nnkIdent) + result = ident($(n) & s) + proc enumsIn(n: NimNode): seq[NimNode] {. compileTime .} = result = @[] for c in children(n): if c.kind == nnkObjConstr: - # c.expectKind(nnkObjConstr) let id = c[0] id.expectKind(nnkIdent) - result.add(id) + result.add(id & enumSuffix) elif c.kind == nnkIdent: - result.add(c) + result.add(c & enumSuffix) else: error("Invalid ADT case: " & $(toStrLit(c))) @@ -29,7 +34,7 @@ proc newEnum(name: NimNode, idents: seq[NimNode]): NimNode {. compileTime .} = proc makeBranch(n: NimNode): NimNode {. compileTime .} = result = newNimNode(nnkOfBranch) if n.kind == nnkObjConstr: - let id = n[0] + let id = n[0] & enumSuffix var list = newNimNode(nnkRecList) for e in tail(n): e.expectKind(nnkExprColonExpr) @@ -37,7 +42,7 @@ proc makeBranch(n: NimNode): NimNode {. compileTime .} = list.add(newIdentDefs(e[0], e[1])) result.add(id, list) elif n.kind == nnkIdent: - result.add(n, newNimNode(nnkRecList).add(newNilLit())) + result.add(n & enumSuffix, newNimNode(nnkRecList).add(newNilLit())) else: error("Invalid ADT case: " & $(toStrLit(n))) @@ -51,7 +56,7 @@ proc defineTypes(e, body: NimNode): NimNode {. compileTime .} = # # Here we first extract the external identifiers (Circle, Rectangle) # that will be the possible values of the kind enum. - let enumName = ident($(e) & "Enum") + let enumName = ident($(e) & enumSuffix) let enumType = newEnum(enumName, enumsIn(body)) # Then we put the actual type we are defining @@ -73,8 +78,42 @@ proc defineTypes(e, body: NimNode): NimNode {. compileTime .} = result.add(enumType) result.add(definedType) +proc defineConstructor(e, n: NimNode): NimNode {. compileTime .} = + if n.kind == nnkObjConstr: + var params = @[e] + for c in tail(n): + c.expectKind(nnkExprColonExpr) + c.expectMinLen(2) + params.add(newIdentDefs(c[0], c[1])) + + var constr = newNimNode(nnkObjConstr).add( + e, newColonExpr(ident("kind"), n[0] & enumSuffix)) + for c in tail(n): + c.expectKind(nnkExprColonExpr) + c.expectMinLen(2) + constr.add(newColonExpr(c[0], c[0])) + + result = newProc( + name = n[0], + params = params, + body = newStmtList().add(constr) + ) + elif n.kind == nnkIdent: + var constr = newNimNode(nnkObjConstr).add( + e, newColonExpr(ident("kind"), n & enumSuffix)) + result = newProc( + name = n, + params = [e], + body = newStmtList().add(constr) + ) + else: + error("Invalid ADT case: " & $(toStrLit(n))) + macro adt*(e: expr, body: stmt): stmt {. immediate .} = - result = defineTypes(e, body) + result = newStmtList(defineTypes(e, body)) + + for child in children(body): + result.add(defineConstructor(e, child)) when defined(pattydebug): echo toStrLit(result) diff --git a/test.nim b/test.nim index 2c2f076..50abbc0 100644 --- a/test.nim +++ b/test.nim @@ -8,7 +8,7 @@ suite "adt construction": Rectangle(w: float, h: float) Square(side: int) - let c = Shape(kind: Circle, r: 4, x: 2, y: 0) + let c = Shape(kind: CircleE, r: 4, x: 2, y: 0) check c.r == 4.0 test "allowing empty objects": @@ -18,9 +18,28 @@ suite "adt construction": Square(side: int) UnitCircle - let r = Shape(kind: Rectangle, w: 2, h: 5) + let r = Shape(kind: RectangleE, w: 2, h: 5) check r.h == 5.0 + test "constructor creation": + adt Shape: + Circle(r: float, x: float, y: float) + Rectangle(w: float, h: float) + Square(side: int) + + let c = Circle(r = 4, x = 2, y = 0) + check c.kind == CircleE + check c.r == 4.0 + + test "constructor of constant objects": + adt Shape: + Circle(r: float, x: float, y: float) + Rectangle(w: float, h: float) + Square(side: int) + UnitCircle + + let c = UnitCircle() + check c.kind == UnitCircleE suite "pattern matching": type