How to write accesors for kind Variants in Nim - variant

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.

Related

embed list in a string and run it with exec on python

I was Trying to Make an python line as String and run it using exec(),
that string was supposed to invoke a module and pass parameters ,here the parameter is a list and when i inject in string and call exec on it ..
i'm getting - TypeError: can only concatenate str (not "list") to str
my code :
def skill_manager(skill_name,parameter_list):
skill_name = skill_name[0]
print(parameter_list)
importline = "import skill."+skill_name+" as skill"
exec(importline)
if not skill_name == None:
runline = "skill." + skill_name + "(" +"'"+ parameter_list + "'" + ")"
print(runline)
exec(runline)
error :
..... line 40, in skill_manager
runline = "skill." + skill_name + "(" +"'"+ parameter_list + "'" + ")"
TypeError: can only concatenate str (not "list") to str
code
error
i tried converting list into string and otherside of code back to list .. but it has to be done in all skills ..
i somehow need to pass list from the skill_manager()

How to add Array with hint and hint_string?

based on this answer it is possible to create export variables via _get_property_list() like this:
var _properties := {
"x": "",
"y": ""
}
func _get_property_list() -> Array:
if not Engine.editor_hint or not is_inside_tree():
return []
var result := []
for property_name in _properties.keys():
result.append(
{
name = property_name,
type = typeof(_properties[property_name]),
usage = PROPERTY_USAGE_DEFAULT
}
)
return result
...
but what if I wanted to add an array with hint and hint_string?
the equivalent of export(Array,float,0, 100,10) var Multiples=[0,0,0,0]
result.append(
{
name = property_name,
type = typeof(Array),
usage = PROPERTY_USAGE_DEFAULT,
hint=???,
hint_string=???
}
Scroll to the end where it says "To put it all together" if you just want the rules.
To begin with, I'll be using code that looks like this:
tool
extends Node
var backing_field
func _set(property:String, value) -> bool:
if property == "property_name":
backing_field = value
return true
return false
func _get(property:String):
if property == "property_name":
return backing_field
return null
Pretend that code is always there. So we will be specifying this property "property_name" in _get_property_list.
For reference, I'll start by showing how _get_property_list works in other cases. For example we can make the property an int:
func _get_property_list() -> Array:
return [
{
name = "property_name",
type = TYPE_INT,
usage = PROPERTY_USAGE_DEFAULT,
}
]
And we can use hint and hint_string to further specify how it will behave in the inspector panel. For instance we can narrow the property to an specific range like this:
func _get_property_list() -> Array:
return [
{
name = "property_name",
type = TYPE_INT,
usage = PROPERTY_USAGE_DEFAULT,
hint = PROPERTY_HINT_RANGE,
hint_string = "0,10"
}
]
And that would give the property a range from 0 to 10.
We can of course specify that the property is an Array instead of an int:
func _get_property_list() -> Array:
return [
{
name = "property_name",
type = TYPE_ARRAY,
usage = PROPERTY_USAGE_DEFAULT,
}
]
And here is where we get to the question: What hint and hint_string can we use with TYPE_ARRAY.
If we have a look at GDScript exports we find out that we can export an array and also specify the type of the elements, like this:
export(Array, int) var my_array = [1, 2, 3]
So, presumably we will be able to do that with _get_property_list instead (Without resourcing to Pool*Array types - we will not be able to specify ranges with those anyway). How do we do that? I'll show you.
From here on, these is all undocumented. Most of this I figured this out by experimentation. By the way, for Godot 4.0 this will be different.
To specify the type of the elements of the array:
The hint must be 26 in Godot 3.5. This is an undocumented constant I found by experimentation, and I
found the name in Godot source: PROPERTY_HINT_TYPE_STRING, however
it is not exposed to GDScript. These are the values for other versions of Godot:
Godot 3.0: PROPERTY_HINT_TYPE_STRING = 23
Godot 3.1 to 3.4: PROPERTY_HINT_TYPE_STRING = 24
Godot 3.5: PROPERTY_HINT_TYPE_STRING = 26
To avoid the issue, I'll declare it as PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS + 2.
The hint_string must be the TYPE_* constant converted to String with ":" appended at the end.
For example, if the type of the elements is int, you put "2:" in the hint_string. If the type of the elements is float, you put "3:" on the hint_string. Like this:
const PROPERTY_HINT_TYPE_STRING := PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS + 2
func _get_property_list() -> Array:
return [
{
name = "property_name",
type = TYPE_ARRAY,
usage = PROPERTY_USAGE_DEFAULT,
hint = PROPERTY_HINT_TYPE_STRING,
hint_string = str(TYPE_INT) + ":"
}
]
For reference str(TYPE_INT) is "2", so str(TYPE_INT) + ":" is "2:".
Ok, but what if we want to specify more about the elements? For example what if we want to say that we have an Array of int in the range from 0 to 10 using _get_property_list?
In that case the hint_string will be:
The the TYPE_* constant converted to String
Followed by "/"
Followed by the PROPERTY_HINT_* constant converted to String
Followed by ":"
Followed by the hint_string you would use with that PROPERTY_HINT_* constant.
Like this:
const PROPERTY_HINT_TYPE_STRING := PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS + 2
func _get_property_list() -> Array:
return [
{
name = "property_name",
type = TYPE_ARRAY,
usage = PROPERTY_USAGE_DEFAULT,
hint = PROPERTY_HINT_TYPE_STRING,
hint_string = str(TYPE_INT) + "/" + str(PROPERTY_HINT_RANGE) + ":0,10"
}
]
Here the hint_string comes up as "2/1:0,10". Notice that the "2" is now followed by "/" instead of ":".
Alright, that begs the question. What if the elements of the Array must also be Arrays? Well, we can go back to having an Array and specifying the type, like this:
const PROPERTY_HINT_TYPE_STRING := PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS + 2
func _get_property_list() -> Array:
return [
{
name = "property_name",
type = TYPE_ARRAY,
usage = PROPERTY_USAGE_DEFAULT,
hint = PROPERTY_HINT_TYPE_STRING,
hint_string = str(TYPE_ARRAY) + ":"
}
]
Here the hint_string comes up as "19:. That the "19" came from str(TYPE_ARRAY), and I'm highlighting that because I'll start using writing "19" instead of str(TYPE_ARRAY) in the code.
Well, Let us say we want to specify the type of the elements of the arrays that are elements of the array. For example, let us say that we want an array of arrays of int. That would be like this:
const PROPERTY_HINT_TYPE_STRING := PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS + 2
func _get_property_list() -> Array:
return [
{
name = "property_name",
type = TYPE_ARRAY,
usage = PROPERTY_USAGE_DEFAULT,
hint = PROPERTY_HINT_TYPE_STRING,
hint_string = "19:" + str(TYPE_INT) + ":"
}
]
Here the hint_string comes up as "19:2:.
And we can put more "19:" to make an Array of Arrays of Arrays of whatever and so on. So this is an Array of Arrays of Arrays of ints:
const PROPERTY_HINT_TYPE_STRING := PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS + 2
func _get_property_list() -> Array:
return [
{
name = "property_name",
type = TYPE_ARRAY,
usage = PROPERTY_USAGE_DEFAULT,
hint = PROPERTY_HINT_TYPE_STRING,
hint_string = "19:19:" + str(TYPE_INT) + ":"
}
]
Here the hint_string comes up as "19:19:2:.
Now, let us say that you want to int in the range from 0 to 1 as before, we would have to do this:
const PROPERTY_HINT_TYPE_STRING := PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS + 2
func _get_property_list() -> Array:
return [
{
name = "property_name",
type = TYPE_ARRAY,
usage = PROPERTY_USAGE_DEFAULT,
hint = PROPERTY_HINT_TYPE_STRING,
hint_string = "19:19:" + str(TYPE_INT) + "/" + str(PROPERTY_HINT_RANGE) + ":0,10"
}
]
Here the hint_string comes up as "19:19:2/1:0,10.
Notice (again) that we don't have "19:19:" + str(TYPE_INT) followed by ":" but by "/".
To put it all together:
The type must be TYPE_ARRAY (Which is 19).
The hint must be 26 for Godot 3.5 (this is an undocumented constant called PROPERTY_HINT_TYPE_STRING, historically it has been PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS + 2).
The hint_string must be:
"19:" for each level of nested Arrays we want. None if the Array is not meant to have other Arrays inside.
Followed by the TYPE_* constant that represents the type of the elements.
Then by either:
To specify what would be the hint_string for the elements:
"/"
Followed by the PROPERTY_HINT_* constant converted to String
Followed by ":"
Followed by the hint_string you would use with that PROPERTY_HINT_* constant.
Otherwise:
":"
The other attributes are not affected by this being an array. You can set them as if you were making a property of the type of the elements of the array.
These are some examples of GDScript exports translated to hint_string (remember to set type to TYPE_ARRAY and hint to 26 for Godot 3.5, or use PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS + 2 for any Godot 3.x up to and including 3.5):
export(Array): ""
export(Array, int): "2:"
export(Array, Array, int): "19:2:"
export(Array, int, 0, 10): "2/1:0,10"
export(Array, Array, int, 0, 10): "19:2/1:0,10"
export(Array, int, "Red", "Green", "Blue"): "2/3:Red,Green,Blue"
export(Array, Array, int, "Red", "Green", "Blue"): "19:2/3:Red,Green,Blue"
export(Array, float): "3:"
export(Array, Array, float): "19:3:"
export(Array, float, 0, 100, 10): "3/1:0,100,10"
export(Array, Array, float, 0, 100, 10): "19:3/1:0,100,10"
export(Array, Texture): "17/17:Texture"
export(Array, Array, Texture): "19:17/17:Texture"
What kind of experimentation I did to find this out? I exported some variables and had a look at what was reported by get_property_list, and then tried different combinations in _get_property_list to see what worked and what didn't, what was necessary and what wasn't. Then I looked at Godot source code as sanity check.
By the way, the last documented hint constant PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS with value 21 in Godot 3.0, 22 in Godot 3.2 to Godot 3.4, and value 24 in Godot 3.5. But there are values beyond it, see the source code.

Concatenate number with string in Swift

I need to concatenate a String and Int as below:
let myVariable: Int = 8
return "first " + myVariable
But it does not compile, with the error:
Binary operator '+' cannot be applied to operands of type 'String' and 'Int'
What is the proper way to concatenate a String + Int?
If you want to put a number inside a string, you can just use String Interpolation:
return "first \(myVariable)"
You have TWO options;
return "first " + String(myVariable)
or
return "first \(myVariable)"
To add an Int to a String you can do:
return "first \(myVariable)"
If you're doing a lot of it, consider an operator to make it more readable:
func concat<T1, T2>(a: T1, b: T2) -> String {
return "\(a)" + "\(b)"
}
let c = concat("Horse ", "cart") // "Horse cart"
let d = concat("Horse ", 17) // "Horse 17"
let e = concat(19.2345, " horses") // "19.2345 horses"
let f = concat([1, 2, 4], " horses") // "[1, 2, 4] horses"
operator infix +++ {}
#infix func +++ <T1, T2>(a: T1, b: T2) -> String {
return concat(a, b)
}
let c1 = "Horse " +++ "cart"
let d1 = "Horse " +++ 17
let e1 = 19.2345 +++ " horses"
let f1 = [1, 2, 4] +++ " horses"
You can, of course, use any valid infix operator, not just +++.
Optional keyword would appear when you have marked variable as optional with ! during declaration.
To avoid Optional keyword in the print output, you have two options:
Mark the optional variable as non-optional. For this, you will have
to give default value.
Use force unwrap (!) symbol, next to variable
In your case, this would work just fine
return "first \(myVariable!)"
var a = 2
var b = "Hello W"
print("\(a) " + b)
will print 2 Hello W
Here is documentation about String and characters
var variableString = "Horse"
variableString += " and carriage"
// variableString is now "Horse and carriage"

VBScript string replace with range instead of string?

Replace() already exists, but that function takes strings as parameters. I need range.
In my string there are two "strings" that are 10 characters long.
Greger with 6 chars and 4 spaces and the other string with 10 characters.
"Greger AASSDDFFGG"
I want to replace "Greger " with "googlioa "
What i'm looking for is basically this:
Replace(MyString,1,10) = "googlioa "
Is there any way to achieve this?
If they're always going to be 10 chars, just pad the names.
strNameFind = "Greger"
strNameReplace = "googlioa"
' Pad the names...
strNameFind = Left(strNameFind & Space(10), 10)
strNameReplace = Left(strNameReplace & Space(10), 10)
MyString = Replace(MyString, strNameFind, strNameReplace)
Alternatively, if you don't want to determine the existing name, just pad your new name appropriately and add the remainder of your string:
' Pad the new name to fit in a 10-char column...
strNameReplace = "googlioa"
strNameReplace = Left(strNameReplace & Space(10), 10)
' Update the record...
MyString = strNameReplace & Mid(MyString, 11)
If you want to replace strictly by position, use concatenation of Left(), new, and Mid(). To get you started:
>> Function replByPos(s, f, l, n)
>> replByPos = Left(s, f-1) & n & Mid(s, f + l - 1)
>> End Function
>> s = "Greger AASSDDFFGG"
>> r = replByPos(s, 1, 10, "googlioa ")
>> WScript.Echo s
>> WScript.Echo r
>>
Greger AASSDDFFGG
googlioa AASSDDFFGG
>>
Further enhancements:
safety: f(rom) - 1 is risky - should be checked
padding of new string wrt l(ength)
perhaps you want to search (Instr()) for old ("Greger ") before the concatenation
On second thought (and stealing Bond's padding):
Maybe I should have interpeted the 10 as a to/till/upto value instead of a length/width specification. So see whether
Option Explicit
Function replByPos(src, from, till, ns)
Dim w : w = till - from
replByPos = Left(src, from - 1) & Left(ns & Space(w), w) & Mid(src, till)
End Function
Dim s : s = "Greger AASSDDFFGG"
Dim ns : ns = "googlioa"
WScript.Echo s
WScript.Echo replByPos(s, 1, 10, ns)
s = "Whatever Greger AASSDDFFGG"
ns = "googlioa"
Dim p : p = Instr(s, "Greger")
WScript.Echo s
WScript.Echo replByPos(s, p, p + 10, ns)
output:
cscript 22811896.vbs
Greger AASSDDFFGG
googlioa AASSDDFFGG
Whatever Greger AASSDDFFGG
Whatever googlioa AASSDDFFGG
matches your specs better.

Convert Number to Corresponding Excel Column [duplicate]

This question already has answers here:
How to convert a column number (e.g. 127) into an Excel column (e.g. AA)
(60 answers)
Closed 8 years ago.
I need some help in doing a logic that would convert a numeric value to corresponding MS Excel header value.
For example:
1 = "A"
2 = "B"
3 = "C"
4 = "D"
5 = "E"
.........
25 = "Y"
26 = "Z"
27 = "AA"
28 = "AB"
29 = "AC"
30 = "AD"
.........
Would appreciate some .NET codes (C# or VB) for this. Thanks.
Here's some VBA (with test code) I strung together in Excel which does the trick. Unless VB.NET has changed drastically, it should work okay. Even if it has, you should be able to translate the idea into workable code.
' num2col - translate Excel column number (1-n) into column string ("A"-"ZZ"). '
Function num2col(num As Integer) As String
' Subtract one to make modulo/divide cleaner. '
num = num - 1
' Select return value based on invalid/one-char/two-char input. '
If num < 0 Or num >= 27 * 26 Then
' Return special sentinel value if out of range. '
num2col = "-"
Else
' Single char, just get the letter. '
If num < 26 Then
num2col = Chr(num + 65)
Else
' Double char, get letters based on integer divide and modulus. '
num2col = Chr(num \ 26 + 64) + Chr(num Mod 26 + 65)
End If
End If
End Function
' Test code in Excel VBA. '
Sub main()
MsgBox ("- should be " & num2col(0))
MsgBox ("A should be " & num2col(1))
MsgBox ("B should be " & num2col(2))
MsgBox ("Z should be " & num2col(26))
MsgBox ("AA should be " & num2col(27))
MsgBox ("AB should be " & num2col(28))
MsgBox ("AY should be " & num2col(51))
MsgBox ("AZ should be " & num2col(52))
MsgBox ("BA should be " & num2col(53))
MsgBox ("ZY should be " & num2col(27 * 26 - 1))
MsgBox ("ZZ should be " & num2col(27 * 26))
MsgBox ("- should be " & num2col(27 * 26 + 1))
End Sub
public string ColumnNumberToLetter(int ColumnNumber)
{
if (ColumnNumber > 26)
{
return ((char) (Math.Floor(((double)ColumnNumber - 1) / 26) + 64)).ToString()
+ ((char) (((ColumnNumber - 1) % 26) + 65)).ToString();
}
return ((char)(ColumnNumber+64)).ToString();
}
Try this:
public static string ToExcelString(int number)
{
if (number > 25)
{
int secondaryCounter = 0;
while (number > 25)
{
secondaryCounter = secondaryCounter + 1;
number = number - 25;
}
return ToExcelChar(number) + ToExcelChar(secondaryCounter);
}
else
{
return ToExcelChar(number)
}
}
private const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static string ToExcelChar(int number)
{
if (number > 25 || number < 0)
{
throw new InvalidArgumentException("the number passed in (" + number + ") must be between the range 0-25");
}
return alphabet[number];
}
Use a number base conversion routine. You want to convert from base 10 to base 26. Add each digit to 'A'
As in: http://www.vbforums.com/showthread.php?t=271359
Just Use the activecell.address then manuipulate the string based on where the $ is to what you need.

Resources