Custom prototype methods Topic is solved

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
User avatar
Onimuru
Posts: 107
Joined: 08 Sep 2018, 18:35
Contact:

Custom prototype methods

14 May 2021, 05:58

Would someone please give me some pointers on how to attach new prototype methods to the existing base classes of Object and Array. In v1 this is as simple as overwriting the constructor functions:

Code: Select all

Array(parameters*) {
	loop, % (parameters.Length(), r := new __Array) {
		r[A_Index - 1] := parameters[A_Index]
	}

	return (r)
}

Object(parameters*) {
	loop, % (parameters.MaxIndex()//2, r := new __Object) {
		i := A_Index*2

		r[parameters[i - 1]] := parameters[i]
	}

	return (r)
}
I'd very much like to retain the the ability to create an array and have direct access to any prototype functions I define such as

Code: Select all

array := [12]
index := array.IndexOf(12)
and also is it possible to switch the base index to 0 as that would make switching to v2 a lot easier.
User avatar
Onimuru
Posts: 107
Joined: 08 Sep 2018, 18:35
Contact:

Re: Custom prototype methods

14 May 2021, 08:20

I see that it's possible to do this:

Code: Select all

Array.Prototype.DefineProp("IndexOf", {Call: (this, needle, start := 0) => IndexOf(this, needle, start)})

IndexOf(this, needle, start) {
	static minIndex := [1][1] == 1

	s := this.Length
		, start := (start >= 0) ? (Min(s, start)) : (Max(s + start, 0))

	loop (s - start) {
		if (this[start + minIndex] == needle) {
			return (start + minIndex)
		}

		start++
	}

	return (-1)
}
Is there a better way such that including a library like this doesn't have to be in the autoexec area?
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Custom prototype methods  Topic is solved

14 May 2021, 17:19

v2 doesnt have an autoexecute section anymore, so u can include it pretty much anywhere it would syntactically make sense to do so(eg not inside a class definition where the instance vars go or in unreachable code paths, eg after an explicit return of the autoexec thread). of course, trying to call the methods before the prototype has been adjusted, would still throw an exception. in any case, ud want to place ur #Include somewhere towards the very top of the script

although, if the /include cmd line switch build makes it to v2, i suppose u could use that instead. it would depend on the implementation, but i assume Array will have been initialized by the time the /include includes get to execute(havent actually tried the test build or checked to see how its supposed to work, so thats just speculation on my part)
and also is it possible to switch the base index to 0 as that would make switching to v2 a lot easier.

Code: Select all

#Requires AutoHotkey v2.0-a134-d3d43350

ApplyZeroBasedArrayIndexPatches()

ApplyZeroBasedArrayIndexPatches() {
	OneBased__Item := Array.Prototype.GetOwnPropDesc('__Item') ; retrieve the default __Item implementation, ie {Get: the default getter, Set: the default setter}
	
	; store in a variable, so it can be captured by closure further down.
	; this is done, so u can retain a reference to the default implementation
	; at all times, no matter what client code tries to do (eg delete/override it),
	; since our functionality depends on it
	OneBasedGet := OneBased__Item.Get  
	OneBasedSet := OneBased__Item.Set ; store in a variable, so it can be captured by closure

	Array.Prototype.DefineProp('__Item', {Get: ZeroBasedGet, Set: ZeroBasedSet}) ; override the default _Item with ours

	ZeroBasedGet(this, zeroBasedIndex) {
		try
		{
			; since were using the default one based implementation to do the work,
			; we have to convert the zero based index back into a one based index, but
			; do it in a way that preserves negative index semantics(ie array lookup from 
			; the end of the array, -1 last element, -2 second to last and so on...)
			oneBasedIndex := (zeroBasedIndex >= 0) ? zeroBasedIndex + 1 : zeroBasedIndex
			return OneBasedGet(this, oneBasedIndex) ; delegate to the default implementation
		}
		catch IndexError as Err ; need to adjust the index in the error msg here, otherwise the default exception would report a wrong index...
			throw IndexError(Err.Message, -2, oneBasedIndex) ; ...eg 1 instead of 0, given an empty Array := [] and indexing with Array[0] into it
		catch TypeError as Err ; this one is triggered when u try to index with strings for example Array['asdfasdf']. only added it cause i dont want the default exception
			throw TypeError(Err.Message, -2, Err.Extra) ; to point to this function's internals instead of the callsite where the exception actually occurred(see -2)
		; catch ???
			; implement more exception handling as u see fit
	}

	ZeroBasedSet(this, value, zeroBasedIndex) {
		try
		{
			oneBasedIndex := (zeroBasedIndex >= 0) ? zeroBasedIndex + 1 : zeroBasedIndex
			return OneBasedSet(this, value, oneBasedIndex)
		}
		catch IndexError as Err
			throw IndexError(Err.Message, -2, oneBasedIndex)
		catch TypeError as Err
			throw TypeError(Err.Message, -2, Err.Extra)
	}

	; store in a variable, so it can be captured by closure further down.
	; same premise as was done for __Item
	OneBasedEnumDispatcher := Array.Prototype.GetOwnPropDesc('__Enum').Call

	Array.Prototype.DefineProp('__Enum', {Call: ZeroBasedEnumDispatcher}) ; override the default dispatcher with ours

	ZeroBasedEnumDispatcher(this, numberOfVars) {
		; have the original dispatcher provide us with the original one based enumerator implementation
		OneBasedEnum := OneBasedEnumDispatcher(this, numberOfVars)

		switch numberOfVars
		{
		case 1: return OneBasedEnum ; no special handling needed, since it enumerates the array's VALUES only, eg for Val in Array
		case 2: return ZeroBasedIndexValueEnum ; for Idx, Val in Array
			ZeroBasedIndexValueEnum(&zeroBasedIndex, &value) {
				if OneBasedEnum(&oneBasedIndex, &value) ; while Array has Items, retrieve one based index and assign Item to for-loop's second OutputVar
				{
					zeroBasedIndex := oneBasedIndex - 1 ; convert one based index back into zero based, and assign to for-loop's first OutputVar
					return true ; continue enumerating, since an Item had been returned
				}
			}
		; case 3-19: ; implement as u see fit... or dont(see below)
		default: throw Error('No matching Array Enumerator found for this many for-loop variables.', -2, numberOfVars)
		}
	}
}
most anything is possible with v2 ;)
User avatar
Onimuru
Posts: 107
Joined: 08 Sep 2018, 18:35
Contact:

Re: Custom prototype methods

15 May 2021, 05:05

swagfag wrote:
14 May 2021, 17:19
v2 doesnt have an autoexecute section anymore, so u can include it pretty much anywhere it would syntactically make sense to do so(eg not inside a class definition where the instance vars go or in unreachable code paths, eg after an explicit return of the autoexec thread). of course, trying to call the methods before the prototype has been adjusted, would still throw an exception. in any case, ud want to place ur #Include somewhere towards the very top of the script

although, if the /include cmd line switch build makes it to v2, i suppose u could use that instead. it would depend on the implementation, but i assume Array will have been initialized by the time the /include includes get to execute(havent actually tried the test build or checked to see how its supposed to work, so thats just speculation on my part)
and also is it possible to switch the base index to 0 as that would make switching to v2 a lot easier.

Code: Select all

#Requires AutoHotkey v2.0-a134-d3d43350

ApplyZeroBasedArrayIndexPatches()

ApplyZeroBasedArrayIndexPatches() {
	OneBased__Item := Array.Prototype.GetOwnPropDesc('__Item') ; retrieve the default __Item implementation, ie {Get: the default getter, Set: the default setter}
	
	; store in a variable, so it can be captured by closure further down.
	; this is done, so u can retain a reference to the default implementation
	; at all times, no matter what client code tries to do (eg delete/override it),
	; since our functionality depends on it
	OneBasedGet := OneBased__Item.Get  
	OneBasedSet := OneBased__Item.Set ; store in a variable, so it can be captured by closure

	Array.Prototype.DefineProp('__Item', {Get: ZeroBasedGet, Set: ZeroBasedSet}) ; override the default _Item with ours

	ZeroBasedGet(this, zeroBasedIndex) {
		try
		{
			; since were using the default one based implementation to do the work,
			; we have to convert the zero based index back into a one based index, but
			; do it in a way that preserves negative index semantics(ie array lookup from 
			; the end of the array, -1 last element, -2 second to last and so on...)
			oneBasedIndex := (zeroBasedIndex >= 0) ? zeroBasedIndex + 1 : zeroBasedIndex
			return OneBasedGet(this, oneBasedIndex) ; delegate to the default implementation
		}
		catch IndexError as Err ; need to adjust the index in the error msg here, otherwise the default exception would report a wrong index...
			throw IndexError(Err.Message, -2, oneBasedIndex) ; ...eg 1 instead of 0, given an empty Array := [] and indexing with Array[0] into it
		catch TypeError as Err ; this one is triggered when u try to index with strings for example Array['asdfasdf']. only added it cause i dont want the default exception
			throw TypeError(Err.Message, -2, Err.Extra) ; to point to this function's internals instead of the callsite where the exception actually occurred(see -2)
		; catch ???
			; implement more exception handling as u see fit
	}

	ZeroBasedSet(this, value, zeroBasedIndex) {
		try
		{
			oneBasedIndex := (zeroBasedIndex >= 0) ? zeroBasedIndex + 1 : zeroBasedIndex
			return OneBasedSet(this, value, oneBasedIndex)
		}
		catch IndexError as Err
			throw IndexError(Err.Message, -2, oneBasedIndex)
		catch TypeError as Err
			throw TypeError(Err.Message, -2, Err.Extra)
	}

	; store in a variable, so it can be captured by closure further down.
	; same premise as was done for __Item
	OneBasedEnumDispatcher := Array.Prototype.GetOwnPropDesc('__Enum').Call

	Array.Prototype.DefineProp('__Enum', {Call: ZeroBasedEnumDispatcher}) ; override the default dispatcher with ours

	ZeroBasedEnumDispatcher(this, numberOfVars) {
		; have the original dispatcher provide us with the original one based enumerator implementation
		OneBasedEnum := OneBasedEnumDispatcher(this, numberOfVars)

		switch numberOfVars
		{
		case 1: return OneBasedEnum ; no special handling needed, since it enumerates the array's VALUES only, eg for Val in Array
		case 2: return ZeroBasedIndexValueEnum ; for Idx, Val in Array
			ZeroBasedIndexValueEnum(&zeroBasedIndex, &value) {
				if OneBasedEnum(&oneBasedIndex, &value) ; while Array has Items, retrieve one based index and assign Item to for-loop's second OutputVar
				{
					zeroBasedIndex := oneBasedIndex - 1 ; convert one based index back into zero based, and assign to for-loop's first OutputVar
					return true ; continue enumerating, since an Item had been returned
				}
			}
		; case 3-19: ; implement as u see fit... or dont(see below)
		default: throw Error('No matching Array Enumerator found for this many for-loop variables.', -2, numberOfVars)
		}
	}
}
most anything is possible with v2 ;)
That is very cool sir, thanks a lot!

Thank you for all the comments, I'm just a casual so it helps a lot :)

Return to “Ask for Help (v2)”

Who is online

Users browsing this forum: CraigM, niCode, redrum, sofista and 36 guests