Open ylazy opened 3 days ago
Hi
I suspect that the failure of getDefinitionByName
is, as you say, because the initialization hasn't finished: specifically, setting the class definition as a property of the global script object hasn't been completed. Whereas, when you reference the class name directly, it's looking at the scope stack instead.
Which might mean that we can adjust getDefinitionByName
to also search the scope stack in case it doesn't find the definition in the global script...
For that stack trace, I'm not sure what line number you would expect to see there? It's the 'global' initialisation for that file, not actually the class initialiser .. and it would be the compiler that's at fault really, it hasn't generated a debugline
instruction but I guess there's a debugfile
already.
Which might mean that we can adjust getDefinitionByName to also search the scope stack in case it doesn't find the definition in the global script...
I'm happy to hear that it's possible.
For that stack trace, I'm not sure what line number you would expect to see there?
If possible, it would be nice to see:
Error
at SuperClass$/get test()[Y:\Development\ActionScript\SDK\Ylazy\Core\Libs\as3\src\ylazy\projects\core\x\topLevel\dataTypes\SuperClass.as:20]
at SubClass$cinit()[Y:\Development\ActionScript\SDK\Ylazy\Core\Libs\as3\src\ylazy\projects\core\x\topLevel\dataTypes\SubClass.as:6]
at global$init()
at Test()[Y:\Development\ActionScript\Projects\VSCode\src\Test.as:11]
Thank you!
Re the line number: when I decompile:
function Main():* /* disp_id=-1 method_id=1 nameIndex = 1 */
{
// local_count=1 max_scope=1 max_stack=1 code_len=12
// method position=903 code position=1049
0 getlocal0
1 pushscope
2 debugfile "I:\Adobe\AIR\bugs\Github3334\src;;Main.as" //stringIndex = 10
4 getlocal0
5 constructsuper (0)
7 getlex SubClass //nameIndex = 3
9 debugline 14
11 returnvoid
}
So, the debugline
is coming after the getlex
call which is triggering all this...
Similar thing if we just have a constructor with line trace("Test");
:
7 getlex trace //nameIndex = 3
9 getglobalscope
10 debugline 13
12 pushstring "Test" //stringIndex = 53
14 call (1)
Will have to look further at the compiler here, to try to ensure the debugline
instruction gets pushed out earlier.
Regarding the getDefinitionByName
failure:
11 newclass SubClass
13 popscope
14 popscope
15 initproperty SubClass //nameIndex = 3
The subclass is being created on line 11, but not set into the script slot until line 15. Which is why, if we try finding it by name before that point (i.e. within the static initializers that are called within that newclass
function) we won't then find it.
The other usage of it is done within its own class initializer:
static function SubClass$cinit():* /* disp_id=0 method_id=9 nameIndex = 0 */
{ ...
0 getlocal0
1 pushscope
16 getlex SuperClass::test //nameIndex = 9
...
25 getlocal0
28 callpropvoid SuperClass::test2 (1) //nameIndex = 10
}
in other words, the call to test2(SubClass)
isn't finding the SubClass
class definition using its name, it just knows that it's the same as this
i.e. local0
and so it passing that in for the argument.
I still think that we should be able to pick up the scope that contains this SubClass
class definition, within that test
function, but currently we're struggling to find how to access/traverse down to the appropriate scope...!
Okay -> a bit more investigation, and it sounds like we wouldn't be able to pick this up after all, without some additional hacks / caching of values within the runtime (i.e. within the newclass
handler, we would need to store a reference to the class definition, so that we can check for it specifically and return it from the getDefinitionByName
call..)
But .. I'm not so sure this is a good idea. If you look at what "works", i.e. that use of test2(SubClass)
, it doesn't actually pick up the class when it's initialised. So you run the risk of odd behaviour.
For example:
If your SubClass
file contained:
static private var _i : uint;
static public function get i() : uint { return _i };
// calling static method test at static constructor
{
trace("SubClass static initializer");
test;
test2(SubClass);
_i = 5;
}
Then in your SuperClass
method, you had:
protected static function test2(input:Class):*
{
trace("input");
trace(input); // [class SubClass]
trace("i = " + input.i);
}
it gives you '0', because that _i
value has not yet been initialised. That's just a trivial example, but it would be possible to get into a real problem with this.
You mentioned earlier that this capability would allow you to create enums via:
package
{
public class PhoneOS extends Enum
{
{enum}
public static var ANDROID:PhoneOS, IOS:PhoneOS;
}
}
I guess it depends a little on what exactly you're doing/how, but I would have considered enums to be better as a set of integer values rather than object instances.. I need to go back and review all the info / requests about enum
though, but it's something we could perhaps look at implementing a different way?
Sorry for late response.
I guess it depends a little on what exactly you're doing/how, but I would have considered enums to be better as a set of integer values rather than object instances.. I need to go back and review all the info / requests about enum though, but it's something we could perhaps look at implementing a different way?
I think an Enum should be a class instance, not a Number/Int value, because in real uses, we need Type Safety, and we need Enum instances that can expanding to work as a normal class instance.
For example:
class ControlSize.as
package x.controls.base
{
public class ControlSize extends Enum
{
public static const FREE :ControlSize = new ControlSize();
/**
* eXtra eXtra Small
*/
public static const XXS :ControlSize = new ControlSize();
/**
* eXtra Small
*/
public static const XS :ControlSize = new ControlSize();
/**
* Small
*/
public static const S :ControlSize = new ControlSize();
/**
* Medium - default
*/
public static const M :ControlSize = new ControlSize();
/**
* Large
*/
public static const L :ControlSize = new ControlSize();
/**
* eXtra Large
*/
public static const XL :ControlSize = new ControlSize();
/**
* eXtra eXtra Large
*/
public static const XXL :ControlSize = new ControlSize();
/**
* eXtra eXtra eXtra Large
*/
public static const XXXL :ControlSize = new ControlSize();
}
}
class ControlSizeRange.as
package x.controls.base
{
public class ControlSizeRange extends Enum
{
{enum(ControlSizeRange)}
public static const S2L :ControlSizeRange = new ControlSizeRange(ControlSize.S, ControlSize.L);
public static const S2XL :ControlSizeRange = new ControlSizeRange(ControlSize.S, ControlSize.XL);
public static const XS2XL :ControlSizeRange = new ControlSizeRange(ControlSize.XS, ControlSize.XL);
public static const M :ControlSizeRange = new ControlSizeRange(ControlSize.M, ControlSize.M);
public static const FREE :ControlSizeRange = new ControlSizeRange(ControlSize.FREE, ControlSize.FREE);
private var _minSize :ControlSize;
private var _maxSize :ControlSize;
public function ControlSizeRange(minSize:ControlSize, maxSize:ControlSize):void
{
_minSize = minSize;
_maxSize = maxSize;
super();
}
public function get minSize():ControlSize
{
return _minSize;
}
public function get maxSize():ControlSize
{
return _maxSize;
}
public function has(size:ControlSize = null):Boolean
{
if (size == null) return false;
if (this == FREE) return true;
return _minSize <= size && size <= _maxSize;
}
}
}
Usage:
class Component.as
package x.controls
{
public class Component extends Container implements IComponent, IOptions
{
public function Component(size:ControlSize = null, supportedSizes:ControlSizeRange = null, visible:Boolean = true):void
{
initializeControlSize(size, supportedSizes);
super(visible);
}
protected function initializeControlSize(size:ControlSize = null, supportedSizes:ControlSizeRange = null):void
{
if (size == null) size = ControlSize.M;
if (supportedSizes == null) supportedSizes = ControlSizeRange.FREE;
if (!supportedSizes.has(size))
{
throw new Error("Unsupported size: " + size.name + " (" + size.index + ")" + ". Supported sizes: " + supportedSizes.name + "[" + supportedSizes.minSize.index + ", " + supportedSizes.maxSize.index + "]");
}
_controlSize = size;
}
}
}
class Control.as
package x.controls
{
public class Control extends Component implements IControl
{
public function Control(size:ControlSize = null, supportedSizes:ControlSizeRange = null, visible:Boolean = true):void
{
super(size, supportedSizes, visible);
}
}
}
class ActionButton.as
package x.controls
{
public class ActionButton extends Control
{
public function ActionButton(size:ControlSize = null):void
{
super(size, ControlSizeRange.XS2XL);
}
}
}
var button:ActionButton = new ActionButton(ControlSize.M);
with the Enum class that I've built, each enum can be instantiated automatically:
class ControlTheme.as
package x.controls.base
{
public class ControlTheme extends Enum
{
{enum(ControlTheme)}
public static var LIGHT :ControlTheme;
public static var DARK :ControlTheme;
public static var DARKEST :ControlTheme;
public static function fromName(name:String):ControlTheme
{
return Enum.fromName(name, ControlTheme);
}
}
}
Usage:
trace(ControlTheme.LIGHT); // LIGHT
trace(ControlTheme.DARK); // DARK
trace(ControlTheme.DARKEST); // DARKEST
trace(ControlTheme.LIGHT.index); // 0
trace(ControlTheme.DARK.index); // 1
trace(ControlTheme.DARKEST.index); // 2
trace(ControlTheme.LIGHT.name); // LIGHT
trace(ControlTheme.DARK.name); // DARK
trace(ControlTheme.DARKEST.name); // DARKEST
var theme:ControlTheme = ControlTheme.fromName("DARK");
You can customize the name:
package x.net
{
public class WebHyperlinkTargetWindow extends Enum
{
{enum(WebHyperlinkTargetWindow)}
public static const SELF :WebHyperlinkTargetWindow = new WebHyperlinkTargetWindow("_self");
public static const BLANK :WebHyperlinkTargetWindow = new WebHyperlinkTargetWindow("_blank");
public static const PARENT :WebHyperlinkTargetWindow = new WebHyperlinkTargetWindow("_parent");
public static const TOP :WebHyperlinkTargetWindow = new WebHyperlinkTargetWindow("_top");
public function WebHyperlinkTargetWindow(customName:String = null):void
{
super(NaN, customName);
}
}
}
trace(WebHyperlinkTargetWindow.BLANK); // _blank
trace(WebHyperlinkTargetWindow.BLANK.toString()); // _blank
trace(WebHyperlinkTargetWindow.BLANK.name); // _blank
customizing the index:
package
{
public class Comparison extends Enum
{
{enum(Comparison)}
public static const LESS_THAN :Comparison = new Comparison(-1);
public static const EQUAL :Comparison = new Comparison(0);
public static const GREATER_THAN :Comparison = new Comparison(1);
public function Comparison(index:Number = NaN):void
{
super(index);
}
public static function fromIndex(index:Number):Comparison
{
return Enum.fromIndex(index, Comparison);
}
public static function fromName(name:String):Comparison
{
return Enum.fromName(name, Comparison);
}
}
}
usage:
public function compare(str1:String, str2:String):Comparison
{
if (str1 == str2)
{
return Comparison.EQUAL;
}
else if (str1 > str2)
{
return Comparison.GREATER_THAN;
}
else
{
return Comparison.LESS_THAN;
}
}
trace(Comparison.GREATER_THAN.index); // 1
trace(Comparison.GREATER_THAN.valueOf()); // 1
trace(["c", "a", "b"].sort(compare)); // a,b,c
In fact I've built a full-featured Enum class and I'm using it everyday in official products. If this issue can be resolved, I can shorten the initialization:
package
{
public class PhoneOS extends Enum
{
{enum(PhoneOS)}
public static var ANDROID:PhoneOS, IOS:PhoneOS;
}
}
will be
package
{
public class PhoneOS extends Enum
{
{enum}
public static var ANDROID:PhoneOS, IOS:PhoneOS;
}
}
Another Example:
class UserState.as
package x.controls.states
{
public class UserState extends Enum
{
{enum(UserState)}
public static var NO_FOCUS_OUT :UserState;
public static var NO_FOCUS_OVER :UserState;
public static var NO_FOCUS_DOWN :UserState;
public static var KEY_FOCUS_OVER :UserState;
public static var KEY_FOCUS_DOWN :UserState;
public static var PRESS_FOCUS_DOWN :UserState;
public static var PRESS_FOCUS_OVER :UserState;
public static var PRESS_FOCUS_OUT :UserState;
public function toPointerState():PointerState
{
if (this == NO_FOCUS_OUT || this == PRESS_FOCUS_OUT)
{
return PointerState.OUT;
}
else if (this == NO_FOCUS_OVER || this == PRESS_FOCUS_OVER || this == KEY_FOCUS_OVER)
{
return PointerState.OVER;
}
else if (this == NO_FOCUS_DOWN || this == PRESS_FOCUS_DOWN || this == KEY_FOCUS_DOWN)
{
return PointerState.DOWN;
}
return null;
}
public function toFocusState():FocusState
{
if (this == NO_FOCUS_OUT || this == NO_FOCUS_OVER || this == NO_FOCUS_DOWN)
{
return FocusState.NO_FOCUS;
}
else if (this == PRESS_FOCUS_DOWN || this == PRESS_FOCUS_OVER || this == PRESS_FOCUS_OUT)
{
return FocusState.PRESS_FOCUS;
}
else if (this == KEY_FOCUS_OVER || this == KEY_FOCUS_DOWN)
{
return FocusState.KEY_FOCUS;
}
return null;
}
}
}
class PointerState.as
package x.controls.states
{
public class PointerState extends Enum
{
{enum(PointerState)}
public static var OUT :PointerState;
public static var OVER :PointerState;
public static var DOWN :PointerState;
}
}
class FocusState.as
package x.controls.states
{
public class FocusState extends Enum
{
{enum(FocusState)}
public static var NO_FOCUS :FocusState;
public static var PRESS_FOCUS :FocusState;
public static var KEY_FOCUS :FocusState;
}
}
Usage:
var userState :UserState = UserState.PRESS_FOCUS_DOWN;
var pointerState :PointerState = userState.toPointerState();
var focusState :FocusState = userState.toFocusState();
trace(pointerState); // DOWN
trace(focusState); // PRESS_FOCUS
And an enum.index in my enum class is a Number value (not Integer) because I need to check whether an enum use a custom index. For example:
package
{
public class Days extends Enum
{
{enum(Days)}
public static const MONDAY :Days = new Days(2);
public static const TUESDAY :Days = new Days();
public static const WEDNESDAY :Days = new Days();
public static const THURSDAY :Days = new Days();
public static const FRIDAY :Days = new Days();
public static const SATURDAY :Days = new Days();
public static const SUNDAY :Days = new Days();
public function Days(index:Number = NaN):void
{
super(index);
}
}
}
The indexes of the days will be auto-incremented. So it must be a Number to have a default value (NaN
).
// 2 3 4 5 6 7 8
trace(Days.MONDAY.index, Days.TUESDAY.index, Days.WEDNESDAY.index, Days.THURSDAY.index, Days.FRIDAY.index, Days.SATURDAY.index, Days.SUNDAY.index);
And we can add utility functions:
package
{
public class Days extends Enum
{
{enum(Days)}
public static const MONDAY :Days = new Days(2);
public static const TUESDAY :Days = new Days();
public static const WEDNESDAY :Days = new Days();
public static const THURSDAY :Days = new Days();
public static const FRIDAY :Days = new Days();
public static const SATURDAY :Days = new Days();
public static const SUNDAY :Days = new Days();
public function Days(index:Number = NaN):void
{
super(index);
}
public static function fromName(name:String):Days
{
return Enum.fromName(name, Days);
}
public static function fromIndex(index:Number):Days
{
return Enum.fromIndex(index, Days);
}
// return ES6 Set that contains all enums
public static function get values():Set
{
return Enum.getValues(Days);
}
}
}
Usage:
trace(Days.fromIndex(4)); // WEDNESDAY
trace(Days.fromName("FRIDAY").index); // 6
trace(Days.values); // [Set MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
As you can see in above examples, my Enum class have all capabilities like Java Enums.
Enums are used to create our own data type like classes. The enum data type (also known as Enumerated Data Type) is used to define an enum in Java. Unlike C/C++, enum in Java is more powerful.
Enum improves type safety Enum can be easily used in switch Enum can be traversed Enum can have fields, constructors and methods Enum may implement many interfaces but cannot extend any class because it internally extends Enum class
Looks good .. are you using describeType
to do some of the initialisation? And just wondering if you can use those Enum members in a switch
statement?
Looking at the Java documentation, it does seem like they are using the compiler to create the actual (internal) class definition that it then uses for type safety / code completion etc. The shorthand mechanism for defining an enum is quite nice, for the simple case it just makes it trivial.. but although it's a bit more effort in setting up, the functionality you've created here seems very useful!
thanks
are you using describeType to do some of the initialisation? And just wondering if you can use those Enum members in a switch statement?
Sure, I must use describeType to get the list of variables/constants. It bases on this Enum class with improvements. I'm not sure if I understand clearly about what "can be used in switch" mean. If it's just something like bellow:
var focusState:FocusState = getFocusState();
switch (focusState)
{
case FocusState.PRESS_FOCUS:
doSth1();
break;
case FocusState.KEY_FOCUS:
doSth2();
break;
case FocusState.NO_FOCUS:
doSth3();
break;
default: break;
}
then it surely can.
But it can't:
var focusState:FocusState = getFocusState();
switch (focusState)
{
case PRESS_FOCUS:
doSth1();
break;
case KEY_FOCUS:
doSth2();
break;
case NO_FOCUS:
doSth3();
break;
default: break;
}
because the import static
is not available. And we can't inline enums in our code. We can't also use the shorthand mechanism as you mentioned. But I think it's not a problem (at least for me). Each enum should be placed inside a class for better management, reusing, or expanding... With the help of snippets (like VSCode Snippets), defining an enum is very quick. If some enums are internal for a class, I will place them at the bottom of the class as internal classes. I also like to write the data type of each enum:
public static var NO_FOCUS:FocusState, PRESS_FOCUS:FocusState, KEY_FOCUS:FocusState;
It reminds me that each variable/constant is a FocusState
.
But yes, if the compiler can help, we can make it more perfect and suitable for everyone.
I'm refactoring some code & will release my lib asap.
But .. I'm not so sure this is a good idea. If you look at what "works", i.e. that use of test2(SubClass), it doesn't actually pick up the class when it's initialised. So you run the risk of odd behaviour.
So what about a new method for the Error class, like bellow:
/*
[
[class SuperClass, "get test", "Y:\Development\ActionScript\SDK\Ylazy\Core\Libs\as3\src\ylazy\projects\core\x\topLevel\dataTypes\SuperClass.as", 20],
[class SubClass, "cinit", "Y:\Development\ActionScript\SDK\Ylazy\Core\Libs\as3\src\ylazy\projects\core\x\topLevel\dataTypes\SubClass.as", 0],
["global$", "init"],
[class Test, "Y:\Development\ActionScript\Projects\VSCode\src\Test.as", 0],
["runtime::ContentPlayer", "loadInitialContent"],
["runtime::ContentPlayer", "playRawContent"],
["runtime::ContentPlayer", "playContent"],
["runtime::ContentPlayer", "run"],
["ADLAppEntry", "run"]
]
*/
var stacks:Array = new Error().getStacks();
var subClassReference:Class = stacks[1][0];
Main class
Test.as
class
SubClass.as
class
SuperClass.as
The expected result of
classReference
shoud be[class SubClass]
. The actual result isnull
.I don't know whether this's a bug or not. Maybe I got the
null
result because of theSubClass
was not initialized (because the static constructor was not complete, so the static class referenceSubClass
was not available to use). But, if so, then theinput
must also benull
(but I got[class SubClass]
). So... maybe Harman can make some changes to makegetDefinitionByName()
work for this case?I really need this feature. If this issue can be resolved, I can make
Enum
feature available for AS3, just simple like bellow:and I can also make more
{tag}
to expanding AS3 features.Btw I found a minor bug in the stack traces:
Thanks!