Jump to content


Photo

[AHK_L] richObject


  • Please log in to reply
13 replies to this topic

#1 tinku99

tinku99
  • Members
  • 560 posts

Posted 29 May 2010 - 05:53 PM

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. 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 Lexikos

Lexikos
  • Administrators
  • 8844 posts

Posted 30 May 2010 - 08:22 AM

tinku99,

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 tinku99

tinku99
  • Members
  • 560 posts

Posted 30 May 2010 - 11:36 PM

A few words of introduction would've been nice...
I will split the topic if you agree.

Sorry about that. Yes, feel free to split the topic. I will try to add pretty documentation eventually, for now here is an intro.

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 tinku99

tinku99
  • Members
  • 560 posts

Posted 01 June 2010 - 08:03 AM

While working on this rosettacode task, i fixed up objPrint to try to print noncircular parts of circular objects. I have updated the code in the first post.

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 Lexikos

Lexikos
  • Administrators
  • 8844 posts

Posted 01 June 2010 - 08:31 AM

I wasn't sure if overriding the default base would work

It wouldn't.

(keys aren't allowed to be objects, right ? )

Wrong. :)

#6 tinku99

tinku99
  • Members
  • 560 posts

Posted 01 June 2010 - 08:41 AM

(keys aren't allowed to be objects, right ? )

Wrong. :)

That's great. Because my next method was going to be swapping keys and values... But gotta go to bed now...

#7 tinku99

tinku99
  • Members
  • 560 posts

Posted 12 June 2010 - 07:18 PM

From the docs

Default_Set_AutomaticVarInit(ByRef var, key, value)
{
    if var =
        var := Object(key, value)
}

Along the same lines, I thought a default ._Insert would be helpful to avoid having to initialize objects.
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 fragman

fragman
  • Members
  • 1591 posts

Posted 22 June 2010 - 12:14 PM

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?

#9 tinku99

tinku99
  • Members
  • 560 posts

Posted 23 June 2010 - 04:11 AM

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?

Thanks.
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 fragman

fragman
  • Members
  • 1591 posts

Posted 24 June 2010 - 10:13 AM

One option could be to add a limited ammount of parameters maybe, better than nothing I suppose.

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 tinku99

tinku99
  • Members
  • 560 posts

Posted 25 June 2010 - 07:09 AM

I think ast.base is the correct base to use [in objdeepcopy and obcopy]

You are right, I think.

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?

Based on this post by Lexikos, this is possible now. I have updated the first post.
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 fragman

fragman
  • Members
  • 1591 posts

Posted 26 June 2010 - 02:23 PM

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))

Not sure about the cyclic reference detection, I haven't used this myself.

#13 tinku99

tinku99
  • Members
  • 560 posts

Posted 26 June 2010 - 03:10 PM

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))

Seems reasonable. I am updated richobject.ahk.

Not sure about the cyclic reference detection, I haven't used this myself.

objCopy and objDeepCopy skip cyclic references, so it shouldn't be a problem.
Try running richobject.ahk by itself, it has an example at the top of it.

#14 tinku99

tinku99
  • Members
  • 560 posts

Posted 28 June 2010 - 10:38 PM

updated to work with AutoHotkey(_N/_H/.dll): richObject