what if we could use the Euphoria type mechanism to create aggregates with dot-notation - using functions and procedures for getters and setters:
type foo (object o)
return atom(o)
end type
function foo.id (atom this)
return peek4u(this)
end function
procedure foo.id (atom this, atom value)
poke4(this, value)
end procedure
foo ptr = allocate(4)
ptr.id = 3 -- implicitly calls foo.id(ptr, 3)
? ptr.id -- prints 3 -- implicitly calls foo.id(ptr)
This seems to be simple yet flexible and fits the spirit of Euphoria. It's also dang ugly and not as concise as C struct declarations. The same name for the procedure and functions could be confusing, but the interpreter will know to call the function or procedure based on context. Also, the type of the object is static - it doesn't get passed to functions or inside sequences.
What about nested aggregates? And pointers?
-- point { int x, int y }
type point (object this) return atom(this) end type
function point.x (atom this) return peek4s(this) end function
procedure point.x (atom this, atom x) poke4(this, x) end procedure
function point.y (atom this) return peek4s(this+4) end function
procedure point.y (atom this, atom y) poke4(this+4, y) end procedure
procedure point.init (atom this, sequence xy) poke4(this, xy) end procedure
-- line { point p1, point p2 }
type line (object this) return atom(this) end type
function line.p1 (atom this) return this end function
procedure line.p1 (atom this, point p)
if p != this then
memcpy(this, p, 8)
end if
end function
function line.p2 (atom this) return this+8 end function
procedure line.p2 (atom this, point p)
if p != this+8 then
memcpy(this+8, p, 8)
end if
end procedure
procedure line.init (line this, sequence a, sequence b)
this.p1.init(a)
this.p2.init(b)
end procedure
line l1 = allocate(16)
l1.init({10, 15}, {20,25})
? {{l1.p1.x, l1.p1.y}, {l1.p2.x, l1.p2.y}} -- prints {{10, 15}, {20, 25}}
The only knowledge we have the type of line.p1 is the second argument in the setter procedure. I hope that's enough.
I've also introduced method procedures in the above example, and I think it would be fine for functions too.
What about pointers?
-- line2 { pointer-to-point p1, pointer-to-point p2 }
type line2 (object o) return 1 end type
function line2.p1 (atom this) return peek4u(this) end function
procedure line2.p1 (atom this, point p) poke4(this, p) end procedure
function line2.p2 (atom this) return peek4u(this+4) end function
procedure line2.p2 (atom this, point p) poke4(this+4, p) end procedure
line2 l2 = allocate(8)
l2.p1 = l1.p2 -- l2.p1 points to l1.p2
l2.p2 = l1.p1 -- l2.p2 points to l1.p1
? {{l2.p1.x, l2.p1.y}, {l2.p2.x, l2.p2.y}} -- prints {{20, 25}, {10, 15}}
Keeping track of the size of the aggregate gets to be a pain. A sizeof function might be nice.
Would this work for sequences too?
type bar (object o)
return sequence(o)
end type
function bar.first (sequence this)
return this[1]
end function
-- this must have special calling conventions where this is passed by reference
procedure bar.first (sequence this, object o)
this[1] = o
end procedure
function bar.last (sequence this)
return this[length(this)]
end function
-- this must have special calling conventions where this is passed by reference
procedure bar.last (sequence this, object o)
this[length(this)] = o
end procedure
bar seq = {1, 2, 3}
seq.first = 5
? seq -- prints {5, 2, 3}
seq.last = 10
? seq -- prints {5, 2, 10}
Instead of an init procedure, what if we could use C99-like dot-notation in initialization:
foo ptr = { .id = 3 }
Wait, that doesn't make sense: the ptr hasn't been allocated before initialization. What if we were to add constructors and destructors?
function foo._init ()
return allocate(4)
end function
procedure foo._fini (object this)
free(this)
end procedure
foo ptr = { .id = 3 }
The ._init function only gets called when ptr is created without an initializer. The ._fini procedure gets called automatically when ptr refcount goes to zero (when it gets assigned a new value). Hopefully the existing refcount free mechanism can be modified to call the _fini function.