Open georgejecook opened 4 years ago
Let's consider the following class for purposes of the discussion:
namespace Humanoids
class Zombie
end class
function DestroyHumanoid()
end function
end namespace
There are two different parts to reflection that we might need to consider:
GetConstructor(Humanoids.Zombie)
)GetConstructor(new Humanoids.Zombie())
Since BrightScript doesn't have global static storage, getting the overall class information might be difficult, whereas reflection on an instance might be much more simple.
On every class that gets created, we add the following:
m.__reflection = {
className: "Humanoid.Zombie"
}
You're also kind of mixing and matching namespace function discovery with reflection. However, I think we could follow a convention for these types of things. We could auto-generate a function for every namespace. At runtime that would look like Humanoids_GetReflectionData()
and it would return something like:
{
"classes": [{
name: "Zombie",
constructor: Humanoids_Zombie
}],
"functions": [{
"name": "DestroyHumanoid"
"constructor": Humanoids_DestroyHumanoid
}]
}]
This would obviously need to be scope-sensitive and do if checks around every function reference in case one of the files was not in scope.
What about m.dialog.observeField("close", @MyNamespace.Function)
(something a bit like the callfunc special syntax) which would be transformed to a string of the transpiled name?
I like that!; but I think that's a separate feature (namespace dereferencing literal, or something like that). the example can be achieved right now, without issue. We're addressing here, the genuine reflection use case, where we don't actually know what the pointer to the method/class is till runtime.
Consider this code from applicaster's general Utils:
it uses maestro compiler's reflection function: MRuntime.getClass(name)
to achieve this. That's the mechanism this ticket eludes to.
Oh yes sorry I lost track of the initial goal here.
With the runtime model of brs I'd think reflection should be done by generating code returning the ref you need instead of returning big structure, e.g.
sub GetClassByName(name as string)
if name = 'AClass' then return AClass
if name = 'BClass' then return BClass
return invalid
end sub
yes, this is how I've been doing it in all my tools, since they got rid of eval. afaik there is no other way.
I do this in rooibos:
which injects the code into the if statement in the function template (I do the same in maestro).. once we get better AST helpers (happy to help with those btw, once I get some time), I'll move away from this pattern; but for now it's quick
I think it's a fine pattern - and a good use case for a plugin.
You could also have a GetAllClasses
which would return an array of the classes names.
So, how do we handle cross-project reflection?
Cross-project?
A major goal for BrighterScript is that any BrighterScript transpiled project should work seamlessly in non-brighterscript projects.
Which means, our reflection implementation needs to work in an unobtrusive way. Consider the following scenario:
HomeMediaApp
is written in plain BrightScript. Logger
and SimpleGrid
are both written in BrighterScript, both use reflection internally, are, transpiled to plain BrightScript, and copied into the HomeMediaApp from GitHub releases manually by the developer of HomeMediaApp
.We can't simply generate a GetClassByName()
function for each BrighterScript project, because that function would cause compile errors if you included both Logger
and SimpleGrid
in the same scope.
Here's how I was thinking of implementing reflection:
__getClassByname_KDJS3472948DS
, which would include the logic for finding a class constructor by its name.
d.bs
files, we can build a full-project-and-dependencies reflection function to support cross-project reflection.Ok avoiding collisions when putting together different transpiled projects is fair. Though maybe one solution is that you calls to, say, GetClassByName
could be replaced by a unique function during transpilation.
We need to have some of the following facilities:
m.dialog.observeField("close", BS.Reflection.getMethodName(MyNamespace.Function))
, which will transpile to something likem.dialog.observeField("close", "MyNamespace_Function")