[AHK_L] richObject
#1
Posted 29 May 2010 - 05:53 PM
I sensed the need to print, copy, and compare objects.
So I implemented them as a base object: richObject. documentation.
Similar to the the builtin: _Insert, _Remove etc... These can be called with objCopy, objEqual, objDeepCopy, objPrint, objShow.
richObjects can additionaly use object syntax: richobj.copy, richobj.print, richobj.deepcopy, richobj.equal.
Requires: latest AutoHotkey_L, latest LowLevel, and...: richobject.ahk.
If you are using AutoHotkey(_N/_H/.dll), get the compatible lowlevel.ahk and richObject.
Edit1: fixed objPrint to print noncircular parts of circular objects
Edit2: added objFlatten().
obj.copy() does a shallow copy by the way. Sorry no lazy copy yet...
Edit3: while keys may be objects, these functions will not recurse into them.
Edit4: fixed copy and deepcopy to use the copied object's base.
Added default ""._insert
object_copy on wikipedia
[Moderator's note: Topic split from AutoHotkey_L. I've also copied in the intro. Feel free to remove this note in a future edit.]
Edit5: variadic initializers for custom objects: example
#2
Posted 30 May 2010 - 08:22 AM
A few words of introduction would've been nice before posting random code. Subject lines in replies don't count - they rarely contain anything useful, so people like me learn to overlook them. Assuming your intention was to share some functions you find useful, it would've been more appropriate to start a new thread. (I will split the topic if you agree.)
copyObject() doesn't exist. I presume you meant to call deepCopyObject().
equalObjects() will be inaccurate if the last key of x is not the last key of y. For instance,
x := Object(1,"a",2,"b") y := shallowCopyObject(x), y.3 := "c" MsgBox % printObject(x) printObject(y) equalObjects(x, y)I would also suggest changing the first few lines to avoid some unnecessary recursions.
equalObjects(x, y)
{
if !(x != y) ; equal non-object values or exact same object
return 1 ; note != obeys StringCaseSense, unlike = and ==
if !isobject(x)
return 0 ; unequal non-object value
; recursively compare contents of both objects:
enumx := x._newenum()
enumy := y._newenum()
while enumx[xkey, xvalue] && enumy[ykey, yvalue]
{
if !equalObjects(xkey, ykey)
return 0
if !equalObjects(xvalue, yvalue)
return 0
}
; finally, check that there are no excess key-value pairs in y:
return ! enumy[ykey]
}
#3
Posted 30 May 2010 - 11:36 PM
Sorry about that. Yes, feel free to split the topic. I will try to add pretty documentation eventually, for now here is an intro.A few words of introduction would've been nice...
I will split the topic if you agree.
Introduction:
While working on a recursive descent parsing script for a rosettacode task: arithmetic evaluation,
I sensed the need to print, copy, and compare objects.
So I implemented them as a base object: richObject.
Similar to the the builtin: _Insert, _Remove etc... These can be called with objCopy, objEqual, objDeepCopy, objPrint.
richObjects can additionaly use object syntax: richobj.copy, richobj.print, richobj.deepcopy, richobj.equal.
I wasn't sure if overriding the default base would work (documentation says default base is for non-objects... ).
So i created the richObject, which I am open to another name...
@Lexikos: I have taken your fixes of objEqual.
I removed recursing into keys (keys aren't allowed to be objects, right ? )
I also added some code to support circular objects in objDeepCopy and objEqual.
I report an error for trying to print a circular object, although I guess I could just insert a note about circularity in the printed representation and continue...
test:
obj := richObject()
obj._insert("red", 0xFF0000)
obj._insert("blue", 0x0000FF)
obj._insert("green", 0x00FF00)
fruits := richObject()
fruits._insert("grapes", "green")
fruits._insert("apple", "red")
obj._insert("zfruits", fruits)
copy := obj.DeepCopy()
msgbox % "colors: " obj.print() "`ncopy: " copy.print() "`nare they equal ?:" obj.equal(copy)
obj._remove("zfruits")
msgbox % "removed zfruits from original`n`noriginal: " obj.print() . "`ncopy: " copy.print() "`n`nstill equal ?:" obj.equal(copy)
obj._insert(obj)
msgbox % "print circular object ? "
obj.print()
return
objDeepCopy(ast, reserved=0)
{
if !reserved
reserved := object("copied" . &ast, 1) ; to keep track of unique objects within top object
if !isobject(ast)
return ast
copy := richObject()
enum := ast._newenum()
while enum[key, value]
{
if reserved["copied" . &value]
continue ; don't copy repeat objects (circular references)
copy._Insert(key, objDeepCopy(value, reserved))
}
return copy
}
objPrint(ast, reserved=0)
{
if !reserved
{
reserved := objIsCircular(ast)
if reserved
return
}
if !isobject(ast)
return " " ast " "
enum := ast._newenum()
while enum[key, value]
string .= key . ": " . objPrint(value)
return "(" string ")`n"
}
objEqual(x, y, reserved=0)
{
if !reserved
reserved := object("seen" . &x, 1) ; to keep track of unique objects within top object
if !(x != y) ; equal non-object values or exact same object
return 1 ; note != obeys StringCaseSense, unlike = and ==
if !isobject(x)
return 0 ; unequal non-object value
; recursively compare contents of both objects:
enumx := x._newenum()
enumy := y._newenum()
while enumx[xkey, xvalue] && enumy[ykey, yvalue]
{
if (xkey != ykey)
return 0
if reserved["seen" . &value]
continue ; don't compare repeat objects (circular references)
if !objEqual(xvalue, yvalue)
return 0
}
; finally, check that there are no excess key-value pairs in y:
return ! enumy[ykey]
}
objCopy(ast)
{
if !isobject(ast)
return ast
copy := richObject()
enum := ast._newenum()
while enum[key, value]
copy._Insert(key, value)
return copy
}
!r::reload
!q::exitapp
richObject(){
static richObject
If !richObject
richObject := Object("base", Object("print", "objPrint", "copy", "objCopy"
, "deepCopy", "objDeepCopy", "equal", "objEqual") )
return Object("base", richObject)
}
objIsCircular(ast, reserved=0)
{
if !reserved
reserved := object("seen" . &ast, 1) ; to keep track of unique objects within top object
if !isobject(ast)
return " " ast " "
enum := ast._newenum()
while enum[key, value]
{
if reserved["seen" . &value]
{
msgbox error: circular references not supported
return 1
}
objIsCircular(value, reserved)
}
return 0
}
#4
Posted 01 June 2010 - 08:03 AM
Here is a second objPrint(), this one doesn't print the keys. Useful when you're using objects as sets / lists rather than associative arrays. I am not sure when you'd want to flatten a tree, but here it is...
list := object(1, object(1, 1), 2, 2, 3, object(1, object(1, 3, 2, 4)
, 2, 5), 4, object(1, object(1, object(1, object()))), 5
, object(1, object(1, 6)), 6, 7, 7, 8, 9, object())
msgbox % objPrint(list) ; (( 1 ) 2 (( 3 4 ) 5 )(((())))(( 6 )) 7 8 ())
msgbox % objPrint(objFlatten(list)) ; ( 1 2 3 4 5 6 7 8 )
return
!r::reload
!q::exitapp
objPrint(ast, reserved=0)
{
if !isobject(ast)
return " " ast " "
if !reserved
reserved := object("seen" . &ast, 1) ; to keep track of unique objects within top object
enum := ast._newenum()
while enum[key, value]
{
if reserved["seen" . &value]
continue ; don't copy repeat objects (circular references)
string .= objPrint(value, reserved)
}
return "(" string ")"
}
objFlatten(ast)
{
if !isobject(ast)
return ast
flat := object() ; flat object
enum := ast._newenum()
while enum[key, value]
{
if !isobject(value)
flat._Insert(value)
else
{
next := objFlatten(value)
loop % next._MaxIndex()
flat._Insert(next[A_Index])
}
}
return flat
}There is a copy function in ahkl_arrays. So sorry if that function is duplicated here...
#5
Posted 01 June 2010 - 08:31 AM
It wouldn't.I wasn't sure if overriding the default base would work
Wrong.(keys aren't allowed to be objects, right ? )
#6
Posted 01 June 2010 - 08:41 AM
That's great. Because my next method was going to be swapping keys and values... But gotta go to bed now...Wrong.(keys aren't allowed to be objects, right ? )
#7
Posted 12 June 2010 - 07:18 PM
Along the same lines, I thought a default ._Insert would be helpful to avoid having to initialize objects.From the docs
Default_Set_AutomaticVarInit(ByRef var, key, value) { if var = var := Object(key, value) }
Default_Insert(ByRef var, key, value=0)
{
if var =
{
var := Object()
if value
objInsert(var, key, value)
else
objInsert(var, key)
}
}
"".base._Insert := "Default_Insert"
x._insert(1, "value1x")
y._insert("valuey")
msgbox % x[1] . "`n" y[1]
#8
Posted 22 June 2010 - 12:14 PM
#9
Posted 23 June 2010 - 04:11 AM
Thanks.Nice set of functions, would it be possible to add parameter support to RichObject(), so it can directly be initialized with variables/functions assigned to an object like Object() allows?
if you want unlimited parameters like object() and objinsert(), I don't know how to do it.
Unfortunately, user defined functions can not be variadic yet.
You don't want to just call object() with a "base" ?
y := object("base", richobject(), "red", 0xFF0000, "blue", 0x0000FF
, "green", 0x00FF00)
#10
Posted 24 June 2010 - 10:13 AM
I believe there is a bug in objDeepCopy, it should be changed to this:
objDeepCopy(ast, reserved=0)
{
if !reserved
reserved := object("copied" . &ast, 1) ; to keep track of unique objects within top object
if !isobject(ast)
return ast
copy := object("base", ast.base)
enum := ast._newenum()
while enum[key, value]
{
if reserved["copied" . &value]
continue ; don't copy repeat objects (circular references)
copy._Insert(key, objDeepCopy(value, reserved))
}
return copy
}I think ast.base is the correct base to use. Correct me if I'm wrong.
#11
Posted 25 June 2010 - 07:09 AM
You are right, I think.I think ast.base is the correct base to use [in objdeepcopy and obcopy]
Based on this post by Lexikos, this is possible now. I have updated the first post.would it be possible to add parameter support to RichObject(), so it can directly be initialized with variables/functions assigned to an object like Object() allows?
Requires latest version of LowLevel and AutoHotkey_L, __setResultToken.ahk and code below.
example.ahk
r := richobject()
foo := r.new("map", "red", 0xFF0000, "blue", 0x0000FF, "green", 0x00FF00)
bar := r.new("array", "red", "blue", "green")
; msgbox % objprint(foo)
foo.show()
bar.show()
return
#Include richObject.ahk__setparamtoken.ahk__setParamToken(rt, r)
{
if IsObject(r)
{
DllCall(NumGet(NumGet(&r)+4), "uint", &r) ; r.AddRef()
NumPut(&r, rt+0) ; rt.object := r
NumPut(5, rt+0, 8, "int") ; rt.symbol := SYM_OBJECT
}
else if r is number
{
NumPut(1, rt+0, 8, "int") ; rt.symbol := SYM_INTEGER
NumPut(r, rt+0, 0, "Int64") ; rt.symbol := SYM_INTEGER
}
else {
; See TokenSetResult() in script2.cpp for how this works.
if StrLen(r) > 255 {
p := __malloc((StrLen(r) + 1) * (A_IsUnicode ? 2:1))
; NumPut(p, rt+12) ; rt.circuit_token := p
; NumPut(StrLen(r), rt+4) ; rt.buf := StrLen(r)
} else
p := __malloc(256 * (A_IsUnicode ? 2:1))
StrPut(r, p)
NumPut(p, rt+0) ; rt.marker := p
NumPut(0, rt+0, 8, "int") ; rt.symbol := SYM_STRING
}
}
excerpt from richObject.ahk;; new
newStub(NameOfBaseObject, PositionToInsert, valueList)
{
; The body of this function is never executed.
}
new(aResultToken=0, aParam=0, aParamCount=0)
{
static pObjInsert, pObject
if !pObjInsert
{
LowLevel_init()
if !(vf := __findFunc("newStub"))
|| !(cb := RegisterCallback("new", "C F", 3))
|| !(ppObjInsert := __findFunc("objInsert") + 4)
|| !(ppObject := __findFunc("object") + 4)
{
MsgBox FAILED initializing new
ExitApp
}
NumPut(true, vf + 49, 0, "char") ; vf.IsBuiltIn := true
NumPut(cb, vf + 4) ; vf.BIF := cb
pObjInsert := numget(ppObjInsert+0)
pObject := numget(ppObject+0)
return
}
type := __getTokenValue(NumGet(aParam + A_PtrSize))
obj := %type%()
basetype := obj.type
if (obj.type == "array"){
__setParamToken(NumGet(aParam + 0), obj) ; replace 0 with a new object
__setParamToken(NumGet(aParam + A_PtrSize), 1) ; insert at index 1
dllcall(pObjInsert + 0, "ptr", aResultToken, "ptr", aParam, "ptr"
, aParamCount, "cdecl")
__setResultToken(aResultToken, obj)
}
if (obj.type == "map"){
dllcall(pObject + 0, "ptr", aResultToken, "ptr", aParam + (2 * A_PtrSize), "ptr"
, aParamCount - 2, "cdecl")
map := __getTokenValue(aResultToken + 0)
; objshow(map)
map.base := obj.base
__setResultToken(aResultToken, map)
}
return
}
;; array(){
array(){
static array
If !array {
array := Object("base", richObject(), "type", "array")
}
return Object("base", array)
}
;; map(){
map(){
static map
If !map {
map := Object("base", richObject(), "type", "map")
}
return Object("base", map)
}
Edit: newer versions of this code in the first post.
#12
Posted 26 June 2010 - 02:23 PM
copy := object("base", objDeepCopy(ast.base))Not sure about the cyclic reference detection, I haven't used this myself.
#13
Posted 26 June 2010 - 03:10 PM
Seems reasonable. I am updated richobject.ahk.I noticed another error in DeepCopy, the line I suggested wasn't correct either, because the base object wasn't DeepCopied. This is how it should be:
copy := object("base", objDeepCopy(ast.base))
objCopy and objDeepCopy skip cyclic references, so it shouldn't be a problem.Not sure about the cyclic reference detection, I haven't used this myself.
Try running richobject.ahk by itself, it has an example at the top of it.




