Override string conversion for custom classes in Nim - tostring

Is it possible to override whatever method is used when an object of a custom class is converted to a string for display?
For example, this code currently prints (x: 4, y: 5), but I want it to print just (4,5)
type Point = object
x: int
y: int
let p = Point(x:4, y:5)
echo p
What proc/method/whatever do I implement to change the default Point->string conversion used by echo?

Figured it out; echo's docs says you just gotta overload the $ operator:
from strformat import fmt
type Point = object
x: int
y: int
proc `$`(point: Point): string = fmt"({point.x}, {point.y})"
let p = Point(x:4, y:5)
echo p # prints "(4, 5)"

Related

Problems with mutable sets in Kotlin

I've recently started learning to program in Kotlin. As I tried to add a sequence of numbers to a mutable set using the for loop, the corresponding ASCII value was being added instead.
fun main(args: Array<String>) {
var userInput = readLine() //The input was 12345
var set = mutableSetOf<Int>()
for(n in userInput.orEmpty()){
var numN = n.toInt()
set.add(numN)
}
println(set)
}
// The output is [49, 50, 51, 52, 53], whereas I expected [1, 2, 3, 4, 5]
At first I speculated the problem to be with the .toInt() function, so I tried this:
fun main(args: Array<String>) {
var x = "2"
println(x.toInt())
}
//The output is 2
But the output came as expected, the number itself, not the corresponding ASCII version.
Now how am I supposed to fix this, so that I get my expected result in the first code?
I'm yet a novice in this programming language, so there could be any hilarious mistakes in my code as well.
Thanks in advance!
This happens because n is a Char and the method Char.toInt() returns its ASCII value.
To avoid that, you should convert the Char into a String and then use String.toInt() (like you have done in the sample with the value "2").
fun main(args: Array<String>) {
var userInput = readLine() //The input was 12345
var set = mutableSetOf<Int>()
for(n in userInput.orEmpty()){
var numN = n.toString().toInt()
set.add(numN)
}
println(set)
}
By the way, you obtain the same result with way less code:
fun main(args: Array<String>) {
var userInput = readLine() //The input was 12345
// 1. Converts the string to an array of char
// 2. Transforms each char into its number value
// 3. Converts the result list in a set
val set = userInput.orEmpty().toCharArray().map(Character::getNumericValue).toSet()
println(set)
}

Using custom class in XPC protocol

I’m attempting to write an XPC service using my own type on the withReply signature. The type/class has Xcode’s “target membership” of both the main app and the XPC service. However I am getting incompatible reply block signature in the debug output even though the same class is being used in the withReply signature however the Xcode target differs as I will explain below.
Note: This is being done in Swift using this project to get me started. Except there they use NSData instead of a custom type.
Details
For the purposes of this question I’ll use the following as an example
Custom class - Tweet - This class conforms to the NSSecureCoding protocol so that it can be passed between the main app and the XPC service
XPC Protocol - TweetTransfer with one method required func take(_ count: Int, withReply: ((Tweet) -> Void))
and then all the usual XPC boilerplate where I export an object conforming to TweetTransfer. The XPC service appears to launch but then transfer between it and the main app fails with
XPCWorker[11069:402719] <NSXPCConnection: 0x61800010e220> connection from pid 11066 received an undecodable message
The full message is below[1] but the only difference between the “wire” and “local” is that argument one is
wire - _TtC17MainApp5Tweet
local - _TtC23XPCWorker5Tweet
Where the Xcode target is different. Is that enough to throw it off? How then do I share code between an app and it's XPC service?
[1] Full error text
<NSXPCConnection: 0x61800010e220> connection from pid 11066 received an undecodable message (incompatible reply block signature (wire: <NSMethodSignature: 0x618000074ec0>
number of arguments = 2
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (#) '#?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (#) '#"_TtC17MainApp5Tweet"'
flags {isObject}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
class '_TtC17MainApp5Tweet'
vs local: <NSMethodSignature: 0x610000074740>
number of arguments = 2
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (#) '#?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (#) '#"_TtC23XPCWorker5Tweet"'
flags {isObject}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
class '_TtC23XPCWorker5Tweet'
)
Update
Some more info regarding the protocol, remoteObjectProxy connection and Tweet object. This is the protocol used for the XPC calls:
#objc(TweetTransfer)
protocol TweetTransfer {
func take(_ count: Int, withReply: replyType)
}
typealias replyType = ((Tweet) -> Void)
I'm using a type alias for convenience. And then the Tweet object is very simple and just for testing (although somewhat complicated by supporting NSSecureCoding):
final class Tweet: NSObject, NSSecureCoding {
var name: String
var text: String
static var supportsSecureCoding = true
init(name: String, text: String) {
self.name = name
self.text = text
}
init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String else {
fatalError("Could not deserialise name!")
}
guard let text = aDecoder.decodeObject(forKey: "text") as? String else {
fatalError("Could not deseralise text!")
}
self.name = name
self.text = text
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(text, forKey: "text")
}
}
and finally the point at which we call the remoteObjectProxy
guard let loader = workerConnection.remoteObjectProxyWithErrorHandler(handler) as? TweetTransfer else {
fatalError("Could not map worker to TweetTransfer protocol!")
}
var tweets = [Tweet]()
loader.take(1) { tweet in
tweets.append(tweet)
}
The full message is below but the only difference between the “wire” and “local” is that argument one is
wire - _TtC17MainApp5Tweet
local - _TtC23XPCWorker5Tweet
Where the Xcode target is different. Is that enough to throw it off? How then do I share code between an app and it's XPC service?
That is indeed enough to throw it off. Swift's namespacing makes the archived object appear as a different class. You can disable name spacing by declaring your Tweet object with;
#objc(Tweet) class Tweet: NSObject, NSSecureCoding { ... }
The name in #objc(name) is often presented as a way to present a different name in objc vs Swift, but it also has the effect of disabling Swift's name spacing.
From Using Swift with Cocoa and Objective-C
When you use the #objc(name) attribute on a Swift class, the class is made available in Objective-C without any namespacing. As a result, this attribute can also be useful when migrating an archivable Objective-C class to Swift. Because archived objects store the name of their class in the archive, you should use the #objc(name) attribute to specify the same name as your Objective-C class so that older archives can be unarchived by your new Swift class.
Another alternative is to move your custom object to a Framework. That framework target then becomes the namespace, and both the XPC and App would refer to the same namespace/class in the framework.

Declare an lldb summary-string for a sized string type

I would like to have a formatter for the buildin string type of the nim language, but somehow I fail at providing it. Nim compilis to c, and the c representation of the string type you see here:
#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER)
# define SEQ_DECL_SIZE /* empty is correct! */
#else
# define SEQ_DECL_SIZE 1000000
#endif
typedef char NIM_CHAR;
typedef long long int NI64;
typedef NI64 NI;
struct TGenericSeq {NI len; NI reserved; };
struct NimStringDesc {TGenericSeq Sup; NIM_CHAR data[SEQ_DECL_SIZE]; };
and here is the output of what I have tried in the lldb session:
(lldb) frame variable *longstring
(NimStringDesc) *longstring = {
Sup = (len = 9, reserved = 15)
data = {}
}
(lldb) frame variable longstring->data
(NIM_CHAR []) longstring->data = {}
(lldb) type summary add --summary-string "${&var[0]%s}" "NIM_CHAR []"
(lldb) frame variable longstring->data
(NIM_CHAR []) longstring->data = {}
(lldb) type summary add --summary-string "${var%s}" "NIM_CHAR *"
(lldb) frame variable longstring->data
(NIM_CHAR []) longstring->data = {}
(lldb) frame variable &longstring->data[0]
(NIM_CHAR *) &[0] = 0x00007ffff7f3a060 "9 - 3 - 2"
(lldb) frame variable *longstring
(lldb) type summary add --summary-string "${var.data%s}" "NimStringDesc"
(lldb) frame variable *longstring
(NimStringDesc) *longstring = NIM_CHAR [] # 0x7ffff7f3a060
(lldb) type summary add --summary-string "${&var.data[0]%s}" "NimStringDesc"
(lldb) frame variable *longstring
(NimStringDesc) *longstring = {
Sup = (len = 9, reserved = 15)
data = {}
}
(lldb)
I simply can't manage, that the output will just be data interpreted as a '\0' terminated c-string
The summary string syntax you've tried is (by design) not as syntax rich as C.
And since you're using a zero-sized array, I don't think we have any magic provision to treat that as a pointer-to string. You might want to file a bug about it, but in this case, it's arguable whether it would help you. Since your string is length-encoded it doesn't really need to be zero-terminated, and that is the only hint LLDB would understand out of the box to know when to stop reading out of a pointer-to characters.
In your case, you're going to have to resort to Python formatters
The things you need are:
the memory location of the string buffer
the length of the string buffer
a process to read memory out of
This is a very small Python snippet that does it - I'll give you enhancement suggestions as well, but let's start with the basics:
def NimStringSummary(valobj,stuff):
l = valobj.GetChildMemberWithName('Sup').GetChildMemberWithName('len').GetValueAsUnsigned(0)
s = valobj.GetChildMemberWithName('data').AddressOf()
return '"%s"'% valobj.process.ReadMemory(s.GetValueAsUnsigned(0),l,lldb.SBError())
As you can see, first of all it reads the value of the length field;
then it reads the address-of the data buffer; then it uses the process that the value comes from to read the string content, and returns it in quotes
Now, this is a proof of concept. If you used it in production, you'd quickly run into a few issues:
What if your string buffer hasn't been initialized yet, and it says the size of the buffer is 20 gigabytes? You're going to have to limit the size of the data you're willing to read. For string-like types it has builtin knowledge of (char*, std::string, Swift.String, ...) LLDB prints out the truncated buffer followed by ..., e.g.
(const char*) buffer = "myBufferIsVeryLong"...
What if the pointer to the data is invalid? You should check that s.GetValueAsUnsigned(0) isn't actually zero - if it is you might want to print an error message like "null buffer".
Also, here I just passed an SBError that I then ignore - it would be better to pass one and then check it
All in all, you'd end up with something like:
import lldb
import os
def NimStringSummary(valobj,stuff):
l = valobj.GetChildMemberWithName('Sup').GetChildMemberWithName('len').GetValueAsUnsigned(0)
if l == 0: return '""'
if l > 1024: l = 1024
s = valobj.GetChildMemberWithName('data').AddressOf()
addr = s.GetValueAsUnsigned(0)
if addr == 0: return '<null buffer>'
err = lldb.SBError()
buf = valobj.process.ReadMemory(s.GetValueAsUnsigned(0),l,err)
if err.Fail(): return '<error: %s>' % str(err)
return '"%s"' % buf
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand("type summary add NimStringDesc -F %s.NimStringSummary" % os.path.splitext(os.path.basename(__file__))[0])
The one extra trick is the __lldb_init_module function - this function is automatically called by LLDB whenever you 'command script import' a python file. This will allow you to add the 'command script import ' to your ~/.lldbinit file and automatically get the formatter to be picked up in all debug sessions
Hope this helps!

Swift '(int, int) -> $T4' is not identical to 'Point'

I'm fairly new to swift and get this error. All I want the declaration to do is assign the 4 values to create 2 Point objects for a Line object. So Line(10, 20, 20, 40) should create two points at (10,20) and (20,40). But I get the error '(Int, Int) -> $T4' is not identical to 'Point'
class Point {
var x: Int = 0
var y: Int = 0
init(){
x = 0
y = 0
}
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
class Line: Point {
var pointA: Point
var pointB: Point
init(p1x: Int, p1y: Int, p2x: Int, p2y: Int) {
self.pointA(p1x, p1y)
self.pointB(p2x, p2y)
}
}
var lineA = Line(p1x:10, p1y:20 , p2x:20 , p2y:40)
Your code in Line's init is meaningless. This is not valid Swift:
self.pointA(p1x, p1y)
self.pointB(p2x, p2y)
You can't just stick parentheses after the name of a variable like that! Plus you are not writing your init correctly. Here's a possible fix:
class Line: Point {
var pointA: Point
var pointB: Point
init(p1x: Int, p1y: Int, p2x: Int, p2y: Int) {
self.pointA = Point(x:p1x, y:p1y)
self.pointB = Point(x:p2x, y:p2y)
super.init()
}
}
But as Antonio has already said, it all depends on what you're trying to do. My code above will cause your code to compile correctly, but I have no idea whether it does what you want, because what you're trying to do is utterly impossible to guess.
In the initializer you are missing to create instances - you are just trying to reference 2 properties as if they were methods. The correct way is:
init(p1x: Int, p1y: Int, p2x: Int, p2y: Int) {
self.pointA = Point(x: p1x, y: p1y)
self.pointB = Point(x: p2x, y: p2y)
}
There is an architectural mistake though: Line inherits from Point, but I see no relationship between the 2 classes - you should at least call the superclass initializer, but what to pass?
I would instead define only 1 new property in Line, and reuse the property already available in Point:
class Line: Point {
var pointB: Point
init(p1x: Int, p1y: Int, p2x: Int, p2y: Int) {
self.pointB = Point(x: p2x, y:p2y)
super.init(x: p1x, y: p1y)
}
}
I don't know if that makes sense (depending on what you want to achieve) - if not, then I would just make Line not inherit from Point. If you need a common interface, but with different implementations, then you can use a protocol adopted by the 2 classes.

Extending Seq.sortBy in Scala

Say I have a list of names.
case class Name(val first: String, val last: String)
val names = Name("c", "B") :: Name("b", "a") :: Name("a", "B") :: Nil
If I now want to sort that list by last name (and if that is not enough, by first name), it is easily done.
names.sortBy(n => (n.last, n.first))
// List[Name] = List(Name(a,B), Name(c,B), Name(b,a))
But what, if I‘d like to sort this list based on some other collation for strings?
Unfortunately, the following does not work:
val o = new Ordering[String]{ def compare(x: String, y: String) = collator.compare(x, y) }
names.sortBy(n => (n.last, n.first))(o)
// error: type mismatch;
// found : java.lang.Object with Ordering[String]
// required: Ordering[(String, String)]
// names.sortBy(n => (n.last, n.first))(o)
is there any way that allow me to change the ordering without having to write an explicit sortWith method with multiple if–else branches in order to deal with all cases?
Well, this almost does the trick:
names.sorted(o.on((n: Name) => n.last + n.first))
On the other hand, you can do this as well:
implicit val o = new Ordering[String]{ def compare(x: String, y: String) = collator.compare(x, y) }
names.sortBy(n => (n.last, n.first))
This locally defined implicit will take precedence over the one defined on the Ordering object.
One solution is to extend the otherwise implicitly used Tuple2 ordering. Unfortunately, this means writing out Tuple2 in the code.
names.sortBy(n => (n.second, n.first))(Ordering.Tuple2(o, o))
I'm not 100% sure what methods you think collator should have.
But you have the most flexibility if you define the ordering on the case class:
val o = new Ordering[Name]{
def compare(a: Name, b: Name) =
3*math.signum(collator.compare(a.last,b.last)) +
math.signum(collator.compare(a.first,b.first))
}
names.sorted(o)
but you can also provide an implicit conversion from a string ordering to a name ordering:
def ostring2oname(os: Ordering[String]) = new Ordering[Name] {
def compare(a: Name, b: Name) =
3*math.signum(os.compare(a.last,b.last)) + math.signum(os.compare(a.first,b.first))
}
and then you can use any String ordering to sort Names:
def oo = new Ordering[String] {
def compare(x: String, y: String) = x.length compare y.length
}
val morenames = List("rat","fish","octopus")
scala> morenames.sorted(oo)
res1: List[java.lang.String] = List(rat, fish, octopus)
Edit: A handy trick, in case it wasn't apparent, is that if you want to order by N things and you're already using compare, you can just multiply each thing by 3^k (with the first-to-order being multiplied by the largest power of 3) and add.
If your comparisons are very time-consuming, you can easily add a cascading compare:
class CascadeCompare(i: Int) {
def tiebreak(j: => Int) = if (i!=0) i else j
}
implicit def break_ties(i: Int) = new CascadeCompare(i)
and then
def ostring2oname(os: Ordering[String]) = new Ordering[Name] {
def compare(a: Name, b: Name) =
os.compare(a.last,b.last) tiebreak os.compare(a.first,b.first)
}
(just be careful to nest them x tiebreak ( y tiebreak ( z tiebreak w ) ) ) so you don't do the implicit conversion a bunch of times in a row).
(If you really need fast compares, then you should write it all out by hand, or pack the orderings in an array and use a while loop. I'll assume you're not that desperate for performance.)

Resources