简体   繁体   中英

How to write accesors for kind Variants in Nim

Trying to write accesors to get a value of an object with a kind member, I get invalid indentation error in a macro I am not sure why

I imagine I might be building macros wrong but if there is a better way to abstract type kinds in objects it would be great to know.

Here is the implementation I am working:

import macros,strutils,tables

type Types = enum Integer,Float,String,Array
type StyleName = enum X,Y,W,H,Left,Margin,Padding,Color,BorderColor

type Value=object
  case kind : Types
  of Integer: ival:int
  of Float: fval:float
  of String: sval:string
  of Array: cval:array[4,int]
proc toValue(x:float):Value=Value(kind:Float,fval:x)
proc toValue(x:int):Value=Value(kind:Integer,ival:x)
proc toValue(x:string):Value=Value(kind:String,sval:x)
proc toValue(x:array[4,int]):Value=Value(kind:Array,cval:x)

#Construct {stylename:value,...} to {stylename:Value(kind,value),...}
macro style(args: varargs[untyped]): Table[StyleName,Value] =
  #echo repr(args), args.treeRepr
  var s:seq[string]
  for arg in args:
    s.add arg[0].repr & ":" & "toValue(" & arg[1].repr & ")"
  result = parseStmt("{" & s.join(",") & "}.toTable")

Macro accesors for the variant object "Value"

macro getWithKind(style:Table[StyleName,Value], prop:StyleName, kind:Types):untyped=
  var accesor:string
  case kind.repr:
  of "Integer": accesor="ival"
  of "Float": accesor="fval"
  of "String": accesor="sval"
  of "Array": accesor="cval"
  result = parseStmt(style.repr & "[" & prop.repr & "]." & accesor)

#Transforms simple get(style,prop) call to getWithKind(style,prop,kind)
macro get(style:Table[StyleName,Value], prop:StyleName):untyped=
  result = parseStmt(style.repr & ".getWithKind(" & prop.repr & ", kind=" & style.repr & "[" & prop.repr & "].kind)")

This is the output I get: Is it not possible to call a macro inside another macro?

let style1 = style(X=1,Y=2.0,Color=[0,0,0,0]) #build a table of (StyleName:Value)
echo style1  #output: {X: (kind: Integer, ival: 1), Y: (kind: Float, fval: 2.0), Color: (kind: Array, cval: [0, 0, 0, 0])}
echo style1[X].ival  # output:1
echo style1[X].kind  # output:Integer
echo style1.getWithKind(X,kind=Integer)  # output:1
echo style1.get(X)  #(should get 1), output:

C:\Users\cravs\Desktop\test.nim(109, 21) getWithKind
C:\nim-1.4.8\lib\core\macros.nim(558, 17) parseStmt
C:\Users\cravs\Desktop\test.nim(119, 12) template/generic instantiation of `get` from here
C:\nim-1.4.8\lib\core\macros.nim(557, 7) template/generic instantiation of `getWithKind` from here
C:\nim-1.4.8\lib\core\macros.nim(558, 17) Error: unhandled exception: C:\nim-1.4.8\lib\core\macros.nim(557, 11) Error: invalid indentation [ValueError]

Edit: Here is another attempt

macro getWithKind(style:Table[StyleName,Value], prop:StyleName, kind:Types):untyped=
  var accesor:string
  case kind.repr
  of "Integer": accesor="ival"
  of "Float": accesor="fval"
  of "String": accesor="sval"
  of "Array": accesor="cval"
  result = newDotExpr(newTree(nnkBracketExpr,style,prop), newIdentNode(accesor))
  #echo parseStmt(style.repr & "[" & prop.repr & "]." & accesor)

macro get(style:Table[StyleName,Value], prop:StyleName):untyped=
  #result = parseStmt(style.repr & ".getWithKind(" & prop.repr & ", kind=" & style.repr & "[" & prop.repr & "].kind)")
  let kind = newDotExpr(newTree(nnkBracketExpr,style,prop), newIdentNode("kind"))
  echo treeRepr kind
  quote do: 
    echo `kind` #prints Integer
    getWithKind(`style`,`prop`,kind=`kind`)


let style1 = style(X=1,Y=2.0,Color=[0,0,0,0]) #build a table of (StyleName:Value)
echo style1  #output: {X: (kind: Integer, ival: 1), Y: (kind: Float, fval: 2.0), Color: (kind: Array, cval: [0, 0, 0, 0])}
echo style1[X].ival  # output:1
echo style1[X].kind  # output:Integer
echo style1.getWithKind(X,kind=Integer)  # output:1
echo style1.get(X)  #(should get 1), output:

C:\Users\...\test.nim(127, 3) Error: undeclared field: '' for type test.Value [declared in C:\Users\...\test.nim(80, 6)]

Here is how you should make macros:

import macros, strutils, tables

type Types = enum Integer, Float, String, Array
type StyleName = enum X, Y, W, H, Left, Margin, Padding, Color, BorderColor

type Value = object
  case kind: Types
  of Integer: ival: int
  of Float: fval: float
  of String: sval: string
  of Array: cval: array[4, int]

proc toValue(x: float): Value = Value(kind: Float, fval: x)
proc toValue(x: int): Value = Value(kind: Integer, ival: x)
proc toValue(x: string): Value = Value(kind: String, sval: x)
proc toValue(x: array[4, int]): Value = Value(kind: Array, cval: x)

macro style(properties: untyped): Table[StyleName, Value] =
  var table = newNimNode(nnkTableConstr)
  
  properties.expectKind nnkStmtList
  
  for property in properties:
    property.expectKind nnkCall
    property.expectLen 2
    
    property[0].expectKind nnkIdent
    property[1].expectKind nnkStmtList
    property[1].expectLen 1

    let
      name = property[0]
      value = property[1][0]

    table.add(newTree(nnkExprColonExpr, name , quote do: `value`.toValue()))

  quote do:
    `table`.toTable()

let table = style:
  X: 10.0
  Y: 20.0
  Color: "ff00ff"
  Margin: [10, 20, 30, 40]

echo table

I had no idea you can use strings in macro but its match better to manipulate AST instead. This way you can also verify what user is inputting.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM