Using NSScanner in ASOC

In trying to find a way to parse data from an NSLocation object (see discussion in this thread http://macscripter.net/viewtopic.php?id=32763), I found a way to use the NSScanner class in ASOC even though one of the parameters in some of the functions needs to be a pointer to a variable. If you use the word “reference” as that parameter, the method returns a list, where the first item is the BOOL value (1 if it found something, 0 if it didn’t), and the second item is the thing found. Here is a code snippet that parses a typical NSLocation object’s description string:

script ScannersAppDelegate
	property parent : class "NSObject"
	property NSScanner : class "NSScanner"
	property NSString : class "NSString"
	property NSCharacterSet : class "NSCharacterSet"
	
	on applicationWillFinishLaunching_(aNotification)
		set mySet to (NSCharacterSet's decimalDigitCharacterSet())'s invertedSet()
		set aString to NSString's stringWithString_("<+38.23, -122.0001> +/- 0.00m (speed -1.00 mps / course -1.00) @ 2010-04-29 21:54:41 -0700")
		set scanner to NSScanner's scannerWithString_(aString)
		scanner's setCharactersToBeSkipped_(mySet)
		repeat while scanner's isAtEnd() is 0
			log item 2 of scanner's scanDouble_(reference)
		end repeat
	end applicationWillFinishLaunching_
end script

The log you get is:

2010-04-30 12:49:02.451 Scanners[11316:a0f] 38.23
2010-04-30 12:49:02.454 Scanners[11316:a0f] 122.0001
2010-04-30 12:49:02.455 Scanners[11316:a0f] 0
2010-04-30 12:49:02.455 Scanners[11316:a0f] 1
2010-04-30 12:49:02.456 Scanners[11316:a0f] 1
2010-04-30 12:49:02.456 Scanners[11316:a0f] 2010
2010-04-30 12:49:02.456 Scanners[11316:a0f] 4
2010-04-30 12:49:02.457 Scanners[11316:a0f] 29
2010-04-30 12:49:02.457 Scanners[11316:a0f] 21
2010-04-30 12:49:02.458 Scanners[11316:a0f] 54
2010-04-30 12:49:02.459 Scanners[11316:a0f] 41
2010-04-30 12:49:02.460 Scanners[11316:a0f] 700

Unfortunately, it strips the negative signs off any negative numbers, so more logic would be needed to get that part to work correctly.

Well I’m impressed – what a useful discovery! I’ve been wanting to use NSScanner for ages.

Your problem with minus signs is due to your character set – try it with something like:

set mySet to (current application's NSCharacterSet's characterSetWithCharactersInString_("0123456789-"))'s invertedSet()

And using reference works with most of the other NSScanner methods I’ve tried.

I did find that if I accidentally excluded the wrong characters I could get into an infinite loop – isAtEnd() kept returning 0 – so maybe something like this would be a better test for this sort of exercise:

repeat
 set {theResult, theValue} to scanner's scanDouble_(reference)
 if theResult = 0 then exit repeat
 log theValue
 end repeat
return

I wonder whether this reference business is just an NSScanner thing…

Thanks Shane, I was hoping that this would be useful for someone. I also found out that I got into infinite loops when I tried various character sets. I had tried the character set with the minus sign added, and that works fine to get the first 2 numbers – the problem comes when it runs into “+/- 0.0m” because there is a space between the “-” and the “0.0m” – that’s where I got into the infinite loop. I can fix the problem by calling the scanner twice with the set with the minus sign excluded, switching to just digits excluded, do one scan then switch back and do the rest (and that still splits the date apart with minus signs in front of the minutes and seconds, so another method would be better to finish out the string if one wanted to leave the time stamp intact). It just becomes a little less elegant with all the extra logic.

I think I have seen other methods that return values in the parameters like the scanner methods do, but right now I can’t remember which ones. I’ll keep looking and see if using “reference” makes them available in ASOC too.

Shane,

I remembered another method that uses pointers – it’s NSMatrix’s getRow:column:forPoint. This script logs the row and column number when you click in a cell (the nib has a 4x4 matrix of NSImageViews which is an instance of my AS’s class):

script MatrixAppDelegate
	property parent : class "NSMatrix"
	
	on mouseDown_(theEvent)
		set loc to my convertPoint_fromView_(theEvent's locationInWindow(), missing value)
		set {yesNo, aRow, aColumn} to my getRow_column_forPoint_(reference, reference, loc)
		log "Row: " & aRow & "   Column: " & aColum
	end mouseDown_
end script

I tried another one of NSMatrix’s methods, getNumberOfRows:columns:, with this line added to the above program: log my getNumberOfRows_columns_(reference, reference). But that did not work – it gave me a EXC_BAD_ACCESS error. So, I don’t know what’s up with that one.

Regarding the - followed by the space, I suspect what’s happening is that the scanner is trying to make a double with a value of 0 out of it, and because you can’t have such a thing it’s throwing an error, hence the infinite loop.

But the reference discovery is big, really big :slight_smile: There was a post on the ASStudio mailing list ages ago from Hamish Sanderson, when I asked how other bridged languages handle the problem. He said that:

Which is what we’re seeing with reference, and makes me think it’s not just a fluke. The fileExistsAtPath_isDirectory_ method is a good example of where it’s useful.

But it also gives us a way to get at all those NSError **s – for example:

		set fm to current application's NSFileManager's defaultManager()
		set {x, y} to fm's attributesOfItemAtPath_error_("No such path", reference)
		log x
		log y's |class|()
		log y's localizedDescription()

returns in the log:

2010-05-01 17:37:40.116 XML stuff[10971:a0f] (null)
2010-05-01 17:37:40.117 XML stuff[10971:a0f] NSError
2010-05-01 17:37:40.118 XML stuff[10971:a0f] The file "No such path" couldn't be opened because there is no such file.

Why reference? Why did you try it? Whatever, it’s a great find…

I tried it because of the error message I got when I was messing around with NSScanner methods. Even though I didn’t think it would work, I declared a variable as a property and tried it as the argument in scanDouble: and got this error message: 2010-05-01 08:34:56.421 Scanners[2516:a0f] *** -[NSConcreteScanner scanDouble:]: value passed to argument of type ^d must be either missing value' or reference’; assuming `missing value’.

At first, I thought it meant that I need a reference to a variable, so I tried scanDouble_(a reference to myVar), but I got the same error message, so I just took the error message literally, and tried “reference”.

Maybe this is why the getNumberOfRows_columns_(reference, reference) attempt didn’t work – this method doesn’t have an “out”, if by that you mean its return type is void, whereas getRow:column:forPoint: and the scanner methods have BOOL return types.

An “out” in this context means a parameter used to return a value rather than as input. Nonetheless you may have put your finger on the explanation of why some methods work – NSString’s stringWithContentsOfFile:usedEncoding:error: for example – and others don’t – NSString’s getParagraphStart:end:contentsEnd:forRange:.

Please, keep throwing out ideas :slight_smile: