rokucommunity / brighterscript

A superset of Roku's BrightScript language
MIT License
160 stars 46 forks source link

reflection #124

Open georgejecook opened 4 years ago

georgejecook commented 4 years ago

We need to have some of the following facilities:

TwitchBronBron commented 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:

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.

elsassph commented 4 years ago

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?

georgejecook commented 4 years ago

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:

image

it uses maestro compiler's reflection function: MRuntime.getClass(name) to achieve this. That's the mechanism this ticket eludes to.

elsassph commented 4 years ago

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
georgejecook commented 4 years ago

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.

georgejecook commented 4 years ago

I do this in rooibos: image image

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

elsassph commented 3 years ago

I think it's a fine pattern - and a good use case for a plugin.

elsassph commented 3 years ago

You could also have a GetAllClasses which would return an array of the classes names.

TwitchBronBron commented 3 years ago

So, how do we handle cross-project reflection?

elsassph commented 3 years ago

Cross-project?

TwitchBronBron commented 3 years ago

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:

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:

elsassph commented 3 years ago

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.