digitalmars.D - Shouldn't __traits return Tuples?
- Trass3r (16/16) Mar 30 2009 Shouldn't __traits return Tuples instead of arrays (esp. allMembers) to
- Max Samukha (21/37) Mar 30 2009 It's possible to statically unroll the foreach's with a template
- Jarrett Billingsley (3/10) Mar 30 2009 It has in LDC ;)
- Tomas Lindquist Olsen (3/14) Mar 30 2009 I think we need D2 support first ;)
- Jarrett Billingsley (3/8) Mar 30 2009 I was talking about the RTTI getMembers function :)
- Tomas Lindquist Olsen (4/20) Mar 30 2009 Maybe you're thinking of OffsetTypeInfo (classinfo.offTi) ? Yes that
- Trass3r (30/49) Mar 30 2009 Well this is interesting. That code yields:
- Max Samukha (23/72) Mar 31 2009 Yeah, __traits returns expressions of various types depending on the
- Max Samukha (3/86) Mar 31 2009 SpinLock, you are out of place here.
- Trass3r (4/11) Mar 31 2009 So the docs are incorrect:
- Max Samukha (3/14) Mar 31 2009 I've got a feeling that the compile-time introspection thing is going
- Trass3r (5/30) Mar 31 2009 Yeah extreeemely buggy. dmd also crashes if that enum members is changed...
- Christopher Wright (4/6) Mar 31 2009 Buggy __traits is the major reason I'm not on d2. If __traits were not
- Trass3r (2/6) Mar 31 2009 Any chance traits get fixed anytime soon?
- Christopher Wright (7/14) Mar 31 2009 I was using __traits + CTFE around 2.006 through 2.011. Every release,
- Trass3r (19/34) Apr 01 2009 Ok, narrowed that down to:
- Max Samukha (10/15) Apr 01 2009 There was ellipsis to suggest that you should terminate the recursion
- Trass3r (5/23) Apr 01 2009 OMG, I knew the recursion base case was missing, but I couldn't imagine
- Max Samukha (6/10) Apr 01 2009 I think it's not a bug because "const" storage class doesn't always
- Trass3r (7/20) Apr 01 2009 Man, static is one of the things that are totally confusing. It has
- Max Samukha (8/28) Apr 01 2009 Agree, there are too many meanings of 'static' - global, compile-time,
- Trass3r (4/12) Apr 01 2009 omg, those bugs are known for nearly 2 years now.
- Trass3r (52/52) Apr 01 2009 I came across another strange bug(?):
- Daniel Keep (3/20) Apr 01 2009 Wow, I think you just made the executable puke its guts! :S
- Don (3/21) Apr 02 2009 ...
- Gide Nwawudu (3/19) Apr 03 2009 Logged as http://d.puremagic.com/issues/show_bug.cgi?id=2792
Shouldn't __traits return Tuples instead of arrays (esp. allMembers) to allow sophisticated compile time reflection? Currently it seems impossible to do something along the lines of foreach (member; __traits (allMembers, Class)) { foreach (overload; __traits (getVirtualFunctions, Class, member)) { // do stuff } } Maybe it would be possible with Tuples using template recursion. Also the following doesn't work with dmd, returns 0 for all members: Base base = new Base; auto members = __traits(allMembers, typeof(base)); foreach(m; members) writefln(base.classinfo.getMembers(m).length);
Mar 30 2009
On Mon, 30 Mar 2009 15:06:09 +0200, Trass3r <mrmocool gmx.de> wrote:Shouldn't __traits return Tuples instead of arrays (esp. allMembers) to allow sophisticated compile time reflection? Currently it seems impossible to do something along the lines of foreach (member; __traits (allMembers, Class)) { foreach (overload; __traits (getVirtualFunctions, Class, member)) { // do stuff } }It's possible to statically unroll the foreach's with a template generating a tuple of consequtive integers (not tested): template Sequence(size_t count, size_t index = 0) { static if (index < count) alias Tuple!(index, Sequence!(count, index + 1)) Sequence; ... } enum members = __traits (allMembers, Class); foreach (i; Sequence!(members.length)) { enum funcs = __traits (getVirtualFunctions, Class, members[i]); foreach (j; Sequence!(funcs.length)) { // do stuff } } I'm not sure __traits should return tuples because arrays can be used with CTFE without generating an extra symbol, meaning less code bloat.Maybe it would be possible with Tuples using template recursion. Also the following doesn't work with dmd, returns 0 for all members: Base base = new Base; auto members = __traits(allMembers, typeof(base)); foreach(m; members) writefln(base.classinfo.getMembers(m).length);getMembers has not been implemented, AFAIK
Mar 30 2009
On Mon, Mar 30, 2009 at 10:57 AM, Max Samukha <samukha voliacable.com.removethis> wrote:It has in LDC ;)Also the following doesn't work with dmd, returns 0 for all members: Base base =3D new Base; auto members =3D __traits(allMembers, typeof(base)); foreach(m; members) =A0 =A0 writefln(base.classinfo.getMembers(m).length);getMembers has not been implemented, AFAIK
Mar 30 2009
On Mon, Mar 30, 2009 at 5:11 PM, Jarrett Billingsley <jarrett.billingsley gmail.com> wrote:On Mon, Mar 30, 2009 at 10:57 AM, Max Samukha <samukha voliacable.com.removethis> wrote:I think we need D2 support first ;)It has in LDC ;)Also the following doesn't work with dmd, returns 0 for all members: Base base =3D new Base; auto members =3D __traits(allMembers, typeof(base)); foreach(m; members) =C2=A0 =C2=A0 writefln(base.classinfo.getMembers(m).length);getMembers has not been implemented, AFAIK
Mar 30 2009
On Mon, Mar 30, 2009 at 11:13 AM, Tomas Lindquist Olsen <tomas.l.olsen gmail.com> wrote:I was talking about the RTTI getMembers function :)I think we need D2 support first ;)getMembers has not been implemented, AFAIKIt has in LDC ;)
Mar 30 2009
On Mon, Mar 30, 2009 at 5:13 PM, Tomas Lindquist Olsen <tomas.l.olsen gmail.com> wrote:On Mon, Mar 30, 2009 at 5:11 PM, Jarrett Billingsley <jarrett.billingsley gmail.com> wrote:Maybe you're thinking of OffsetTypeInfo (classinfo.offTi) ? Yes that can be enabled when configuring LDC, before building the compiler.On Mon, Mar 30, 2009 at 10:57 AM, Max Samukha <samukha voliacable.com.removethis> wrote:I think we need D2 support first ;)It has in LDC ;)Also the following doesn't work with dmd, returns 0 for all members: Base base =3D new Base; auto members =3D __traits(allMembers, typeof(base)); foreach(m; members) =C2=A0 =C2=A0 writefln(base.classinfo.getMembers(m).length);getMembers has not been implemented, AFAIK
Mar 30 2009
Max Samukha schrieb:It's possible to statically unroll the foreach's with a template generating a tuple of consequtive integers (not tested): template Sequence(size_t count, size_t index = 0) { static if (index < count) alias Tuple!(index, Sequence!(count, index + 1)) Sequence; ... } enum members = __traits (allMembers, Class); foreach (i; Sequence!(members.length)) { enum funcs = __traits (getVirtualFunctions, Class, members[i]); foreach (j; Sequence!(funcs.length)) { // do stuff } }Well this is interesting. That code yields: variable main.main._funcs_field_0 cannot be declared to be a function. class Class { int foo(int i) { return 1; } } [...] auto funcs = __traits(getVirtualFunctions, Class, members[i])(1); yields Error: function expected before (), not tuple(foo) of type (int(int i)) So it is a tuple although the docs tell us it's an array? Strangely this doesn't work either: enum members = __traits (allMembers, Class); foreach (i; Sequence!(members.length)) { foreach (j; __traits(getVirtualFunctions, Class, members[i])) { writefln(typeid(typeof(j))); // do stuff } } Though it correctly gets foo() (leaving out typeof above yields "function main.Class.foo is used as a type") it doesn't compile when adding typeof: "variable main.main.i voids have no value"
Mar 30 2009
On Mon, 30 Mar 2009 22:04:57 +0200, Trass3r <mrmocool gmx.de> wrote:Max Samukha schrieb:Yeah, __traits returns expressions of various types depending on the Id. In case of getVirtualFunctions, it's a tuple expression. You may want to look through traits.c in dmd sources to see what's going on.It's possible to statically unroll the foreach's with a template generating a tuple of consequtive integers (not tested): template Sequence(size_t count, size_t index = 0) { static if (index < count) alias Tuple!(index, Sequence!(count, index + 1)) Sequence; ... } enum members = __traits (allMembers, Class); foreach (i; Sequence!(members.length)) { enum funcs = __traits (getVirtualFunctions, Class, members[i]); foreach (j; Sequence!(funcs.length)) { // do stuff } }Well this is interesting. That code yields: variable main.main._funcs_field_0 cannot be declared to be a function. class Class { int foo(int i) { return 1; } } [...] auto funcs = __traits(getVirtualFunctions, Class, members[i])(1); yields Error: function expected before (), not tuple(foo) of type (int(int i)) So it is a tuple although the docs tell us it's an array?Strangely this doesn't work either: enum members = __traits (allMembers, Class); foreach (i; Sequence!(members.length)) { foreach (j; __traits(getVirtualFunctions, Class, members[i])) { writefln(typeid(typeof(j))); // do stuff } } Though it correctly gets foo() (leaving out typeof above yields "function main.Class.foo is used as a type") it doesn't compile when adding typeof: "variable main.main.i voids have no value"__traits is buggy. You could try to do what you want like this: template isFunction(C, string member) { enum isFunction = is(typeof(mixin("C." ~ member)) == function); } void main() { //SpinLock lock; enum members = __traits(allMembers, Class); foreach (i; Sequence!(members.length)) { static if (isFunction!(Class, members[i])) { foreach (j; __traits(getVirtualFunctions, Class, members[i])) writefln(members[i], ": ", typeid(typeof(j))); } } }
Mar 31 2009
On Tue, 31 Mar 2009 10:55:22 +0200, Max Samukha <samukha voliacable.com.removethis> wrote:On Mon, 30 Mar 2009 22:04:57 +0200, Trass3r <mrmocool gmx.de> wrote:SpinLock, you are out of place here.Max Samukha schrieb:Yeah, __traits returns expressions of various types depending on the Id. In case of getVirtualFunctions, it's a tuple expression. You may want to look through traits.c in dmd sources to see what's going on.It's possible to statically unroll the foreach's with a template generating a tuple of consequtive integers (not tested): template Sequence(size_t count, size_t index = 0) { static if (index < count) alias Tuple!(index, Sequence!(count, index + 1)) Sequence; ... } enum members = __traits (allMembers, Class); foreach (i; Sequence!(members.length)) { enum funcs = __traits (getVirtualFunctions, Class, members[i]); foreach (j; Sequence!(funcs.length)) { // do stuff } }Well this is interesting. That code yields: variable main.main._funcs_field_0 cannot be declared to be a function. class Class { int foo(int i) { return 1; } } [...] auto funcs = __traits(getVirtualFunctions, Class, members[i])(1); yields Error: function expected before (), not tuple(foo) of type (int(int i)) So it is a tuple although the docs tell us it's an array?Strangely this doesn't work either: enum members = __traits (allMembers, Class); foreach (i; Sequence!(members.length)) { foreach (j; __traits(getVirtualFunctions, Class, members[i])) { writefln(typeid(typeof(j))); // do stuff } } Though it correctly gets foo() (leaving out typeof above yields "function main.Class.foo is used as a type") it doesn't compile when adding typeof: "variable main.main.i voids have no value"__traits is buggy. You could try to do what you want like this: template isFunction(C, string member) { enum isFunction = is(typeof(mixin("C." ~ member)) == function); } void main() { //SpinLock lock; enum members = __traits(allMembers, Class); foreach (i; Sequence!(members.length)) { static if (isFunction!(Class, members[i])) { foreach (j; __traits(getVirtualFunctions, Class, members[i])) writefln(members[i], ": ", typeid(typeof(j))); } } }
Mar 31 2009
Max Samukha schrieb:So the docs are incorrect: "getVirtualFunctions The result is an array of the virtual overloads of that function."So it is a tuple although the docs tell us it's an array?Yeah, __traits returns expressions of various types depending on the Id. In case of getVirtualFunctions, it's a tuple expression. You may want to look through traits.c in dmd sources to see what's going on.
Mar 31 2009
On Tue, 31 Mar 2009 13:53:02 +0200, Trass3r <mrmocool gmx.de> wrote:Max Samukha schrieb:I've got a feeling that the compile-time introspection thing is going to change, so relying on its current crippled implementation is risky.So the docs are incorrect: "getVirtualFunctions The result is an array of the virtual overloads of that function."So it is a tuple although the docs tell us it's an array?Yeah, __traits returns expressions of various types depending on the Id. In case of getVirtualFunctions, it's a tuple expression. You may want to look through traits.c in dmd sources to see what's going on.
Mar 31 2009
Max Samukha schrieb:Yeah extreeemely buggy. dmd also crashes if that enum members is changed to const members or invariant members. In fact, what I'm trying to do is getting all the functions of a given class and checking their parameters for correctness."variable main.main.i voids have no value"__traits is buggy. template isFunction(C, string member) { enum isFunction = is(typeof(mixin("C." ~ member)) == function); } void main() { //SpinLock lock; enum members = __traits(allMembers, Class); foreach (i; Sequence!(members.length)) { static if (isFunction!(Class, members[i])) { foreach (j; __traits(getVirtualFunctions, Class, members[i])) writefln(members[i], ": ", typeid(typeof(j))); } } }
Mar 31 2009
Trass3r wrote:Yeah extreeemely buggy. dmd also crashes if that enum members is changed to const members or invariant members.Buggy __traits is the major reason I'm not on d2. If __traits were not buggy, I could do, god, so many things! Though it would be even better to have the runtime equivalent.
Mar 31 2009
Christopher Wright schrieb:Buggy __traits is the major reason I'm not on d2. If __traits were not buggy, I could do, god, so many things! Though it would be even better to have the runtime equivalent.Any chance traits get fixed anytime soon?
Mar 31 2009
Trass3r wrote:Christopher Wright schrieb:I was using __traits + CTFE around 2.006 through 2.011. Every release, things were broken in new and incompatible ways. I could never narrow it down sufficiently to get a good bug report (well, usually not), but I could usually make it work by bashing my code in a few times. The one consistent bug I found was fixed. So I could get back to it, but I don't want to keep chasing the CTFE engine.Buggy __traits is the major reason I'm not on d2. If __traits were not buggy, I could do, god, so many things! Though it would be even better to have the runtime equivalent.Any chance traits get fixed anytime soon?
Mar 31 2009
Christopher Wright wrote:Trass3r wrote:That situation should be much better now that we have all of the source code. If something changes, it'll be really easy to find exactly what caused the change.Christopher Wright schrieb:I was using __traits + CTFE around 2.006 through 2.011. Every release, things were broken in new and incompatible ways. I could never narrow it down sufficiently to get a good bug report (well, usually not), but I could usually make it work by bashing my code in a few times. The one consistent bug I found was fixed. So I could get back to it, but I don't want to keep chasing the CTFE engine.Buggy __traits is the major reason I'm not on d2. If __traits were not buggy, I could do, god, so many things! Though it would be even better to have the runtime equivalent.Any chance traits get fixed anytime soon?
Apr 01 2009
Don schrieb:That situation should be much better now that we have all of the source code. If something changes, it'll be really easy to find exactly what caused the change.Well, if dmd was available in a source control repository we could really easily track the changes.
Apr 01 2009
Trass3r wrote:Don schrieb:Agreed, but we effectively have a repository with a once-per-month commit, which isn't too bad. It's more of a problem for people submitting bugfixes -- there's been a couple of cases where someone's submitted a patch, only to be told by Walter that he'd already fixed it. Since there are about 1000 bugs in bugzilla, it's a shame to waste effort that way.That situation should be much better now that we have all of the source code. If something changes, it'll be really easy to find exactly what caused the change.Well, if dmd was available in a source control repository we could really easily track the changes.
Apr 01 2009
Trass3r schrieb:Ok, narrowed that down to: template Sequence(size_t count, size_t index = 0) { static if (index < count) alias Tuple!(index, Sequence!(count, index + 1)) Sequence; } void main() { const members = ["foo", "bar"]; foreach (i; Sequence!(members.length)) { writefln(members[i]); } } Also crashes. Changing to auto or even enum (i mean enum is also constant, so ???) yields: Error: variable main2.main.i voids have no valueenum members = __traits(allMembers, Class); foreach (i; Sequence!(members.length)) { static if (isFunction!(Class, members[i])) { foreach (j; __traits(getVirtualFunctions, Class, members[i])) writefln(members[i], ": ", typeid(typeof(j))); } } }Yeah extreeemely buggy. dmd also crashes if that enum members is changed to const members or invariant members.
Apr 01 2009
On Wed, 01 Apr 2009 20:54:12 +0200, Trass3r <mrmocool gmx.de> wrote:template Sequence(size_t count, size_t index = 0) { static if (index < count) alias Tuple!(index, Sequence!(count, index + 1)) Sequence; }There was ellipsis to suggest that you should terminate the recursion properly :) Sorry template Sequence(size_t count, size_t index = 0) { static if (index < count) alias Tuple!(index, Sequence!(count, index + 1)) Sequence; else alias Tuple!() Sequence; }
Apr 01 2009
Max Samukha schrieb:On Wed, 01 Apr 2009 20:54:12 +0200, Trass3r <mrmocool gmx.de> wrote:OMG, I knew the recursion base case was missing, but I couldn't imagine how to represent "nothing". Of course, an empty tuple. But the reason I posted is cause that bug(?) is still present. dmd doesn't crash anymore, but using const still doesn't work.template Sequence(size_t count, size_t index = 0) { static if (index < count) alias Tuple!(index, Sequence!(count, index + 1)) Sequence; }There was ellipsis to suggest that you should terminate the recursion properly :) Sorry template Sequence(size_t count, size_t index = 0) { static if (index < count) alias Tuple!(index, Sequence!(count, index + 1)) Sequence; else alias Tuple!() Sequence; }
Apr 01 2009
On Wed, 01 Apr 2009 23:22:40 +0200, Trass3r <mrmocool gmx.de> wrote:OMG, I knew the recursion base case was missing, but I couldn't imagine how to represent "nothing". Of course, an empty tuple. But the reason I posted is cause that bug(?) is still present. dmd doesn't crash anymore, but using const still doesn't work.I think it's not a bug because "const" storage class doesn't always mean "compile time". It's rather a variable (we should find a better name) which is initialized once and cannot be changed later. To ensure compiletimeness, you can declare the variable as "static const", "static invariant" or "enum"
Apr 01 2009
Max Samukha schrieb:On Wed, 01 Apr 2009 23:22:40 +0200, Trass3r <mrmocool gmx.de> wrote:Man, static is one of the things that are totally confusing. It has various meanings depending on where you use it and moreover it's hard to find documentation about the one you need cause it's spread all over the docs. But what you are talking about is in fact what "final" does (once initialized it can't be changed).OMG, I knew the recursion base case was missing, but I couldn't imagine how to represent "nothing". Of course, an empty tuple. But the reason I posted is cause that bug(?) is still present. dmd doesn't crash anymore, but using const still doesn't work.I think it's not a bug because "const" storage class doesn't always mean "compile time". It's rather a variable (we should find a better name) which is initialized once and cannot be changed later. To ensure compiletimeness, you can declare the variable as "static const", "static invariant" or "enum"
Apr 01 2009
On Thu, 02 Apr 2009 00:07:56 +0200, Trass3r <mrmocool gmx.de> wrote:Max Samukha schrieb:Agree, there are too many meanings of 'static' - global, compile-time, fixed-size, non-instance. The good thing is that these meanings are interrelated. Probably, 'static' in module constructor definitions is redundant. On the other hand, module constructors are not many and could be otherwise easily confused with class instance constructors.On Wed, 01 Apr 2009 23:22:40 +0200, Trass3r <mrmocool gmx.de> wrote:Man, static is one of the things that are totally confusing. It has various meanings depending on where you use it and moreover it's hard to find documentation about the one you need cause it's spread all over the docs.OMG, I knew the recursion base case was missing, but I couldn't imagine how to represent "nothing". Of course, an empty tuple. But the reason I posted is cause that bug(?) is still present. dmd doesn't crash anymore, but using const still doesn't work.I think it's not a bug because "const" storage class doesn't always mean "compile time". It's rather a variable (we should find a better name) which is initialized once and cannot be changed later. To ensure compiletimeness, you can declare the variable as "static const", "static invariant" or "enum"But what you are talking about is in fact what "final" does (once initialized it can't be changed).There is no 'final' storage class in D2. Or you are talking about the concept?
Apr 01 2009
Trass3r schrieb:foreach (member; __traits (allMembers, Class)) { foreach (overload; __traits (getVirtualFunctions, Class, member)) { // do stuff } }omg, those bugs are known for nearly 2 years now. http://d.puremagic.com/issues/show_bug.cgi?id=1386 http://d.puremagic.com/issues/show_bug.cgi?id=1499
Apr 01 2009
I came across another strange bug(?): Using const, static const or auto instead of enum makes it work. Using the foreach way also works with enum. void main() { enum members = ["foo", "bar"]; for (uint i=0; i<members.length; i++) // foreach (i; Sequence!(members.length)) { writefln(members[i]); } } But enum with for yields: object.Error: Access Violation std.encoding.EncodingSchemeASCII à‘B ANSI_X3.4-1968 ’B ANSI_X3.4-1986 (’B ASCII ’B IBM367 P’B ISO646-US `’B ISO_646.irv:1991 x’B US-ASCII ˜’B cp367 °’B csASCIIiso-ir-6 À’B us Ø’B ? è’B €“B std.encoding.EncodingSchemeASCII TB ø’B “B €“B ”B 0“B GA ƒ ƒ pƒ tFA GA $GA 4GA LGA œGA ìGA ÔHA IA `IA KA 8KA dKA encoding.1270 pOB È“B ”B ”B øGA pkB 0“B Unable to create class ”B Unrecognized Encoding: ”B à”B std.encoding.EncodingScheme TB `”B h”B à”B 0QB ”B ÔHA IA `IA KA 8KA dKA ЕB std.encoding.UnrecognizedEncodingException TB $ 0•B * T•B ЕB –B €•B €ƒ ƒ ƒ pƒ –B std.encoding.EncodingException TB $ ð•B –B –B ðYB –B €ƒ ƒ ƒ pƒ P—B core.exception.UnicodeException TB ( °–B Ø–B P—B ðYB —B €ƒ ƒ ƒ pƒ No appropriate switch clause found " p—B 0˜B core.exception.SwitchError TB $ —B Ä—B 0˜B pSB à—B €ƒ ƒ ƒ pƒ Hidden method called for P˜B ™B core.exception.HiddenFuncError TB $ €˜B ¤˜B ™B pSB ИB €ƒ ƒ ƒ pƒ Finalization error ™B An exception was thrown while finalizing an instance of class > `™B PšB core.exception.FinalizeError TB ( °™B Ø™B PšB pSB šB LA ƒ ƒ pƒ qB std.dateparse pOB €šB ÈšB КB ðWB qB À©B unrecognized attribute äšB redundant attribute ›B unmatched ')' (›B q ›B P›B printProgram() 10: REchar '
Apr 01 2009
Trass3r wrote:I came across another strange bug(?): Using const, static const or auto instead of enum makes it work. Using the foreach way also works with enum. void main() { enum members = ["foo", "bar"]; for (uint i=0; i<members.length; i++) // foreach (i; Sequence!(members.length)) { writefln(members[i]); } } But enum with for yields: ...Wow, I think you just made the executable puke its guts! :S -- Daniel
Apr 01 2009
Trass3r wrote:I came across another strange bug(?): Using const, static const or auto instead of enum makes it work. Using the foreach way also works with enum. void main() { enum members = ["foo", "bar"]; for (uint i=0; i<members.length; i++) // foreach (i; Sequence!(members.length)) { writefln(members[i]); } } But enum with for yields: object.Error: Access Violation std.encoding.EncodingSchemeASCII à‘B ANSI_X3.4-1968 ’B... Confirmed. Congratulations, you've discovered the Bug of the Year!
Apr 02 2009
On Thu, 02 Apr 2009 03:56:35 +0200, Trass3r <mrmocool gmx.de> wrote:I came across another strange bug(?): Using const, static const or auto instead of enum makes it work. Using the foreach way also works with enum. void main() { enum members = ["foo", "bar"]; for (uint i=0; i<members.length; i++) // foreach (i; Sequence!(members.length)) { writefln(members[i]); } } But enum with for yields: object.Error: Access Violation std.encoding.EncodingSchemeASCII B ANSI_X3.4-1968 B ANSI_X3.4-1986 (B ASCII B IBM367 PB ISO646-USLogged as http://d.puremagic.com/issues/show_bug.cgi?id=2792 Gide
Apr 03 2009