Archive: nsArray plug-in


nsArray plug-in
  Wrote this light weight array plug-in which will be used in the nsL Assembler to support native language constructs for arrays. I figure people will also use it in their NSIS projects too.

http://nsis.sourceforge.net/File:NsArray.zip


Stu

Very cool, thank you for the new plugin :)


Thanks for making this.

A couple things I've noticed while using it:

*After deleting an array, the length of the array is still 1.
*Removing items from an array do not shorten the length.

I can provide my code examples if necessary.


Ah thanks I probably forgot to reduce the counters. Will fix soon thanks. I'm thinking of allowing associative arrays (akin to PHP arrays) rather than indexed ones. Think that would be a good idea?

Stu


Personally, I have no preference one way or the other. I've been doing fine with indexes, but if changing to associative arrays works for others I can change my code.


Well it would work like PHP arrays so you can have both index-associated and key-associated elements in the same array. Perhaps I'll fix these small bugs first though.

Stu


Finally fixed those bugs (and a couple more):

1.0.0.1 - 2nd June 2011
* Fixed decrementing of element and array counters on element and array
deletion.
* Fixed memory leak of array name on array deletion.
* Fixed bad pointers remaining after last element and array deletion.
This will be the last version (hopefully) to only support indexed arrays. The next version I plan to have associative and indexed arrays in one, akin to PHP. The changes I had in mind to allow for this are:

* "New" will no longer initialise the values of an array. One can just use "Insert" (will reduce some of the code size).
* "Insert" will take an optional prefix argument for each element in the format "/key=val" where val can be an index or a string. Omitting /key will result in the element being inserted at its natural index (which is the last highest index + 1).
* "Remove" and "Get" will no longer take index ranges, but instead will take a list of keys/indices followed by /end.

Stu

New plug-in version which supports associative arrays (as well as indexed). The plug-in usage and functionality has changed quite a lot since the previous version but the DLL is still the same size.

http://nsis.sourceforge.net/File:NsArray.zip

Stu


Nice updating spree you got going there, Stu. :)


Error with ToString
  If I follow the example on the DLL archive, I get that ouput :


MyArray = {0}
MyArray[0] is 11
MyArray[1] is 14
MyArray[4] is blah
MyArray[5] is /key=jajaj
MyArray[jajaj] is
Set MyArray[jajaj] = 22
MyArray[jajaj] is
MyArray = {0}
MyArray length: 1
MyArray length: 1
MyArray = {0}
MyArray = {1}
MyArray length: 1
MyArray2 values sorted numerically = {1}
MyArray2 sorted by keys = {1}
MyArray2 = {1}
MyArray2[0] is
MyArray2[1] is 14
MyArray2 = {1}
MyArray2 length: 1
MyArray2Keys = {0}
MyArray2 = {0}
MyArray2 length: 7
Completed


I think every ToString call return something wrong.
Quote from documentation :

nsArray::ToString my_array
Pop $var

Returns a string representation of `my_array` which includes keys and
values in the format key1 => value1, key2 => value2, etc.. The error
flag is set if the array does not exist or if the result cannot
completely fit into the output buffer (NSIS_MAX_STRLEN) although as
much as can fit into the output buffer will still be returned.


Will take a look sometime today.

Stu


I have no problems with the example script. Make sure you are using the correct plug-in (i.e. Unicode or ANSI).

Edit: If there is ever a problem, the error flag is set.

Stu


Great !
I was using the Unicode version but my NSIS is no longer compiled with Unicode.

Thanks.


New plug-in version:

1.1.0.2 - 12th October 2011
* Added GetAt function.
* Added addition sort flag (16) to retain keys/indices order when sorting values.
* Removed New function. Set function now creates the array if it doesn't already exist.
Stu

New plug-in version:

1.1.1.0 - 12th February 2012
* Added Split function.
* Added /noempty switch to Join function.
* Sort function now supports ORing (|) for its sort flags.
* Replaced Delete function with Clear function.
* Replace GetAt function with /at=position switch on Get function.
* Added Get /at=-1 for array element iteration.
* Added /at=position and /val=value switches for Remove function.
* Implemented script header replacements for Copy, CopyKeys and ToString functions.
* Added script header ForEachIn LogicLib loop.
The ForEachIn loop can be used like so:
${ForEachIn} my_array $key $value
DetailPrint "my_array[$key] => $value"
${Next}


Stu

New plug-in version:

1.1.1.1 - 12th February 2012
* Optimised Get /at=-1 by using an pointer instead of a counter.
* Added Get /at=-2 to iterate in reverse order.
* Added script header ForEachInReverse LogicLib loop.
Stu

New plug-in version (hopefully the last for a while):

1.1.1.2 - 13th February 2012
* Replaced /at=-1 with /next for Get function.
* Replaced /at=-2 with /prev for Get function.
* Added /reset switch to Get function to reset /next or /prev iteration.
* The /at=position switch for Get and Remove functions now support negative numbers.
* Added call to Get /reset before entering ForEachIn loop.
* Fixed incorrect usage of Split function in the readme.
Stu

When set a array outside of section, this init with some empty elements

For example:

Page custom CreateMyPage LeaveMyPage


Function CreateMyPage

InstallOptions
FunctionEnd

Function LeaveMyPage

FunctionEnd


#When set a array outside any section, this aray have some empty elements,
por example in the function of a custom page.#

Page custom MyPage LeaveMyPage
Page instfiles

Function MyPage
Push $R0

InstallOptions::dialog $PLUGINSDIR\someIni.ini
Pop $R0
FunctionEnd

Function LeaveMyPage

nsArray::Set MyArray 'one'
nsArray::Set MyArray 'two'
nsArray::Set MyArray 'three'
nsArray::Length MyArray
Pop $1 ; $1 = 4 instead 3
NsArray::Get MyArray 0
Pop $0 ; $0 = ´´ ( Empty String)

#This only occur into a custom page function#
Function

Section MySection
Call LeaveMyPage ; When the call is from here, no there problems with the array
SectionEnd


nsArray::Set takes multiple values so you need to put /end on the end, i.e.

nsArray::Set MyArray one two three /end
If you don't add /end, values left on the NSIS stack will get added.

Edit: Maybe I should add a first argument of /list to add multiple elements in a single call to Set (i.e. /end can be omitted without /list).

Stu

I must /end when I put a single elment?

eg.: nsArray::Set MyArray ´Single´ /end


At the moment yes because of what I said. NSIS passes arguments to plug-ins using the stack and because Set can have multiple arguments there is no way of knowing if the argument was on the stack already or passed as a plug-in argument in the call. This is why /end is necessary and not optional. Some other plug-ins require the use of /end too.

Stu


New version uploaded. Set and Remove calls no longer need the /end suffix when adding/removing a single element. If you want to specify multiple elements, start the list with /list and end it with /end. The readme has examples.

http://nsis.sourceforge.net/File:NsArray.zip

Stu


feature requests:
-unique (removes duplicate values)
-merge (two arrays)


You can do both of those with the existing functions plus some loops. Perhaps I will add them to the script header when I have time.

Stu


just a suggestion, after all the same could be said about sorting


Actually you could only do sorting with the existing functions if the array is indexed only (i.e. how would you sort if the array is associative?) Also the internal Sort function simply moves the pointers around rather than the actual data. This is far more efficient and does not require any new memory allocation or deallocation or data copying. Also writing the quick sort algorithm in NSIS code would not be very straight forward (bubble sort would be easier but is less efficient).

Removing duplicates and merging arrays both require memory deallocation and allocation so writing an internal function would serve little benefit over an NSIS script equivalent and would only add additional size to the DLL.

Stu


I checked the plugin code in order to maybe get a hint about what was causing the memory corruption problems I got, and there's some potential issue with the Set command and its key words.

According to my interpretation of the code, there's no accurate distinguishing between a value and a key word. Therefore the function is not able to recognize if a string value accidently has the same text as a known keyword like "/list", "/end" or "/key".

For example, if just one value is added and this value accidently starts with "/key=", it will set the error flag and abort the installation in my case.
If a list of parameters is added and one of the values is accidently "/end", like...
nsArray::Set array /list "Bla" "/end" "BlaBla" /end
... the last two parameters are not interpreted anymore, and might pollute the NSIS stack, because there's not condition for "/end" to be the last available parameter.

It would be better to have different function names for each type since every keyword reduces the set of valid values.
Since I have to add values read from various registry values, there's pretty much any value possible including the key words.
I will now check the values to add for the keywords first and use a workaround it that happens. Fore example, it's possible to add the "/list" value with the following statement:
nsArray::Set array /list "/list" /end


Yes that is indeed a problem which I was well aware of when I wrote the functions. I suppose a SetList function would not hurt and I am perhaps over-obsessed with making the plug-in as small as possible.

I have run both the ANSI and Unicode builds through the debugger and have had no memory access violations. There could still be some but I would have a look at the other plug-ins you use.

Stu


I checked the basic functions of the plugin and the code is rather clean.
I highly doubt the memory corruptions are caused by your plugin as well.

I also validated our own plugin code, but althouh I found memeory leaks but I couldn't find any memory corruptions.
However, I replaced some unsave functions like sprintf with their safer variant with array size limitation.

However, our installer still has some sporadic stability problems which might be caused by another plugin.
We also upgraded to the newest NSIS version but that didn't help either.


New version:

1.1.1.4 - 3rd September 2012
* Replaced Set /list with SetList.
* Replaced Remove /list with RemoveList.
Please update your code accordingly if you switch to the new version.

Stu

What is correct usage of nsArray::Get? I think nsArray corrupts my stack but I cannot find the problem.

This is my code - Is this usage correct?
1)

ClearErrors

nsArray
::Get UrlTree $CompleteGroup
>${If} ${Errors}
${DebugMsg} "UrlTree($CompleteGroup) is not set!"
>${Else}
Pop $CompleteTotal
; There are some files
>${EndIf}

In documentation I saw this usage:
2)
  ClearErrors

nsArray
::Get MyArray 2
Pop $R0
${If} ${Errors}
DetailPrint `Error!`
${EndIf}

So in my example (1): I do POP only if ${Errors} is fine.

But the docs say (2): do the POP, then check ${Errors}.

I have many values in stack, so I think POP in (2) will remove element from stack even in case the nsArray does not contain required element.

Is this assumption correct?

Yes that is an error in the example script. It will not push anything onto the stack if an error occurs (element not found).

Stu


Is there a way to sort an array with version numbers using the versioncompare logic? I'm using "numeric" now, and it doesn't give the right result:

* 4.2.6_32
* 5.1.10.2_32
* 5.1.8.2_32
* 5.1.9.0_32

should be:

* 4.2.6_32
* 5.1.8.2_32
* 5.1.9.0_32
* 5.1.10.2_32

fretje


See attached.

Stu


Thank you very much for the very fast reply, Stu.
This is exactly what I needed!


btw, the readme still mentions the old syntax for nsArray_ToString


Couldn't find this documented anywhere and I think it is worth mentioning:
/at= does not work with the Set command.
Further more /key= only works on keynames, not indexes, as follows:


nsArray::Set "myArray" "0"
nsArray::Set "myArray" "1"
nsArray::Set "myArray" "2"
nsArray::Set "myArray" "3"
;array now = {"0", "1", "2", "3"}
nsArray::Remove "myArray" /at=1
;array now = {"0", "2", "3"}
nsArray::Set "myArray" /key=2 "three"
;array now = {"0", "three", "3"}

Because elements with no specified keyname are presumably given a keyNAME of their initial index.
The above is correct behaviour as you might have keynames that consist only of numbers.

If you want to have /at= functionality with the Set command then you can add it yourself using the following macro

!macro ArraySetAt arrayname index value
Push $R0
ClearErrors
nsArray::Get "${arrayname}" "/at=${index}"
${IfNot} ${Errors}
Pop $R0 ;key
Exch $R0 ;discard value
Pop $R0 ;discard value
nsArray::Set "${arrayname}" "/key=$R0" "${value}"
${EndIf}
Pop $R0
!macroend

Originally posted by Marshallx7
/at= does not work with the Set command.
That is correct - the readme does not show any uses of /at= with the Set instruction, but I could add it.
Originally posted by Marshallx7
Further more /key= only works on keynames, not indexes
This is not true. Your array after the Remove call is:
myArray = {0 => 0, 2 => 2, 3 => 3}

After your Set call, it becomes:
myArray = {0 => 0, 2 => three, 3 => 3}

There is no distinction between numeric keys and string keys. It is just that numeric keys are assigned when no key is specified when adding elements.

Edit: You probably assumed that removing element with index 1 would result in the indexes after it being decreased by 1 also. This does not happen in hashed arrays because then the association is lost. I suppose the plug-in needs a reindex function. For example in PHP you'd use array_values().

Stu

Sorry I wasn't clear before, yes - what you say the arrays would be is what I concur with, I just didn't mention the keynames.

I fully accept that this is intended behaviour, and I did run my own tests to be sure, because I need Set /at functionality.

To make my position clear to anyone reading, there are NO bugs in nsArray (that I can see) - just Set /at functionality is not (presently) a feature (but you can emulate it using my macro above).

A reindex method might be useful, but I imagine it is more resource intensive than a Set /at command. Using Get /at, Remove /at and Set /at would mean a reindex is not necessary, because you can then choose to either use associative keynames (without /at) or positional indexes (with /at)