This PR adds the following class stubs:
* 'flash.display.Loader'
* 'flash.net.URLRequest'
* 'flash.ui.Keyboard'
* 'flash.utils.Timer'
These are needed for 'This is the Only Level TOO' (though we'll need
actual implementations to get this game past the loading screen).
* avm2: Implement JSON.parse
* avm2: Add AvmSerializer for serializing AVM values to JSON
* avm2: Add support for replacer objects
* avm2: use *const ObjectPtr for object stack
* avm2: Add support for space parameter is JSON.stringify
* avm2: Refactor AvmSerializer design
* avm2: Restrict spaces to a maximum of 10
* avm2: Refactor map_value
* tests: Add JSON.parse test
* chore: Appease clippy
* avm2: Check if value is undefined before inserting
* tests: Add test for JSON.stringify
* tests: Improve JSON.stringify test
* chore: Replace map_or with explicit match statements
* chore: Use QName::dynamic_name
* avm2: Use Object<'gc> instead of ObjectPtr
* chore: Use explicit match in deserialize_value
* Rebase fixes
Co-authored-by: Adrian Wielgosik <adrian.wielgosik@gmail.com>
* avm2: Properly make all classes an instance of `Class`.
Also, does this technically mean that `Class` is a metaclass?
* avm2: Remove `Function::from_method_and_proto` as it will no longer be needed
* avm2: Ensure builtin classes are also instances of `Class`.
This requires tying a veritable gordian knot of classes; everything needs to be allocated up-front, linked together, and then properly initialized later on. This necessitated splitting the whole class construction process up into three steps:
1. Allocation via `from_class_partial`, which does everything that can be done without any other classes
2. Weaving via `link_prototype` and `link_type`, which links all of the allocated parts together correctly. This also includes initializing `SystemClasses` and `SystemPrototypes`.
3. Initialization via `into_finished_class`, which must be done *after* the weave has finished.
Once complete you have core classes that are all instances of `Class`, along with prototypes that have their usual legacy quirks.
Note that this does *not* make prototypes instances of their class. We do need to do that, but doing so breaks ES3 legacy support. This is because we currently only work with bound methods, but need to be able to call unbound methods in `callproperty`.
* tests: Add a test for all core classes' instance-of relationships
Instead, the following terms are used:
* Static classes, to refer to `Class<'gc>`. Shortened to "class" in contexts where this is not ambiguous.
* Class objects, to refer to objects that represent a particular class. Also shortened to "class" in non-ambiguous contexts.
Downstream of this, the `base_constr` (referring to the class that a currently called trait has been pulled from) is now called `subclass_object` and several `TObject` methods have also been renamed.
This is limited by the fact that we currently cannot store type metadata in static tables. I don't think it's necessary to do so as of yet as pretty much every actual parameter type I *could* shove in here turned out to be optional and broke tests if it wasn't. Still, it's probably useful enough for new classes to include.
This is very wrong: Strictly speaking, we should not be instantiating anything that needs a scope when we install the trait. We just create a slot for it to go into. Script initializers are responsible for providing a scope stack to instantiate traits into.
Because we have stuff running in early globals, we stlil need a more elaborate version of this function that *does* take `fn_proto`. We also can't pull `scope` from the activation since this gets called to install traits.
This also makes it more difficult to accidentally build a class without calling it's initializer. Native/builtin class initializers should also be running now, too.
The only minor bit of jank is that we need a class initialized bit to flag classes we've already run, because our current lazy-init design for traits causes classes to be constructed twice. This is temporary and I intend to remove it along with lazy-init traits.
This also incurred a large number of ancillary changes, as it turns out nearly every native object is currently pulling a prototype and sticking it into an object. Right now, I have it instead pulling the constructor out of the prototype, but a future PR will also remove `system_prototypes` as well.
Other ancillary changes include:
* `Domain` now supports partial initialization to avoid an order-of-events issue. Accessing domain memory on a partially-initialized `Domain` will panic.
* `Domain` construction requires a full `activation` now, except for `global_scope` which needs to be initialized later with valid domain memory before user code runs.
* Pretty much every native object constructor now takes a proto/constr pair
* Trait lookup was rewritten to handle this. It's still buggy - seven tests don't work
* `TObject.construct` now actually does the full object construction dance. This allows `ClassObject` to implement the ES4 object construction pathway directly while `FunctionObject` maintains ES3 compatibility.
This is a tentative commit; there are still seven failing tests that I need to fix.
This entirely abolishes the "global scope object" in AVM2. I even had to redefine several global object functions to work with the bottom of the scope stack, which seems to be where ASC likes to stick the script scope.
This code also ensures that the prototypes of each system object are created in the appropriate `TObject` impl. This ensures that, for example, `new Array` hands you back an actual array.
This has some particularly annoying consequences for initialization order: notably, we can't actually create any ES4 classes using the standard machinery until after the three objects I just mentioned get created. Ergo, we have to create them through lower-level means, handing prototypes around, and then initialize AVM2's system prototypes list for it.
When we start adding more system prototypes, we'll also have to fill the extras with blank objects and then slot them in as we create them.
This was surprisingly tricky - due to the need to look up superclasses, class trait instantiation requires an active `Activation` and `UpdateContext`. We can't get those during VM instance creation, since the player needs the VM first before it can give it a context to work with. Ergo, we have to tear the global scope initialization in two. At the first possible moment, the player calls a new `load_player_globals` method that initializes all class traits in global scope.
This code is slightly over/under-precise compared to AVM2. This is because we handle precision limiting in binary floats rather than as part of the float printing process. Flash Player may also be rounding differently than us. However, I'm pretty sure ECMA-262 allows us to slightly differ here.