janino-compiler / jsh

jsh - The "Java Shell"
37 stars 6 forks source link

Extensibility: Adding new commands, new functions #1

Open buko opened 5 years ago

buko commented 5 years ago

Not sure this project is still under active development but it's certainly piqued some interest. (Nobody likes javascript but that's the alternative.) One question is how to implement extensibility. We'd like to give the user the ability to load packages which would introduce new commands and functions into the shell. Logically, this would involve dynamically defining a new base class in which to evaluate commands and functions. The question is what happens when the user loads another package. Lets say you have two classes that both extend JshBase:

class ShellPackageA extends JshBase { }

class ShellPackageB extends JshBase { }

When the user loads package A we want to change the base class to Package A. But when the user loads Package B we don't want to simply switch over to Package B and lose Package A functionality. Really what we want to do is change ShellPackageB to extend ShelllPackageA. Does this make sense? Is something like this possible?

aunkrig commented 5 years ago

That should be no problem at all. Just do

IClassBodyEvaluator cbe = new ClassBodyEvaluator(); cbe.setExtendedClass(JshBase.class); cbe.setClassName(„pkg.ShellPackageA“); cbe.cook(shellPackageACode); Class shellPackageAClass = cbe.getClazz();

cbe.setExtendedClass(shellPackageAClass); cbe.setClassName(„pkg.ShellPackageB“); cbe.cook(shellPackageBCode); Class shellPackageBClass = cbe.getClazz();

CU Arno

Am 15.06.2019 um 10:56 schrieb buko notifications@github.com:

Not sure this project is still under active development but it's certainly piqued some interest. (Nobody likes javascript but that's the alternative.) One question is how to implement extensibility. We'd like to give the user the ability to load packages which would introduce new commands and functions into the shell. Logically, this would involve dynamically defining a new base class in which to evaluate commands and functions. The question is what happens when the user loads another package. Lets say you have two classes that both extend JshBase:

class ShellPackageA extends JshBase { }

class ShellPackageB extends JshBase { }

When the user loads package A we want to change the base class to Package A. But when the user loads Package B we don't want to simply switch over to Package B and lose Package A functionality. Really what we want to do is change ShellPackageB to extend ShelllPackageA. Does this make sense? Is something like this possible?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

buko commented 5 years ago

In this example, shellPackageACode and shelPackageBCode are the sources for the actual PackageA and PackageB classes that both extend JshBase? So this code actually does change the base class for the existing classes?

So we can start off by writing:

class PackageA extends JshBase { } class PackageB extends JshBase { }

And the CBE code will produce:

class ShellPackageA extends JshBase { } class ShellPackageB extends ShellPackageA { }

This is very interesting and exactly what we wanted to do!

The other question that comes up are variables. My understanding from playing with the shell is that variables are basically fields. You can refer to variables like 'wd' as long as the generated class has a field called 'wd'. Is this correct?

I suppose if we then wanted to give the user the ability to assign variables we'd need to generate a new class with that protected field. I'm assuming Janino also has a mechanism for introducing such fields? Can it be done without introducing a whole new class but simply redefining the existing class?

aunkrig commented 5 years ago

Hey there,

I believe I mistook your issue a bit because I missed the fact that it relates to JSH, not JANINO. After all, the first JSH ever, congrats!

Currently, the InteractiveShell hardcodes that the extended class is JshBase. I could either make that configurable, or you could put

import static my.package.ShellPackageA.*;
Import static my.package.ShellPackageB.*;

in your .jshrc file. The only limitation then is that you can access only static members of your ShellPackageA/B classes.

Does that help? Let me know which solution you choose and why.

CU Arno

buko commented 5 years ago

I think basically I need to do two things:

1) Change the base class used by InteractiveShell at runtime.

So if the user types ‘load com.example.ShellPackageA’ then InteractiveShell will stop using JshBase as the shell’s base class and instead use ShellPackageA. This should be easy since ShellPackageA extends JshBase:

public class ShellPackageA extends JshBase { }

2) Merge ShellPackage classes together

This is the tricky part. If the user then types ‘load com.example.ShellPackageB’ we don’t really want to lose all the functionality associated with ShellPackageB. What we sort of want to do is create a Mixin class that includes ShellPackageA and ShellPackageB.

The only limitation then is that you can access only static members of your ShellPackageA/B classes.

This is a pretty harsh limitation. What I was thinking was perhaps using a framework like cglib or bytebuddy to dynamically generate a new mixed implementation class. The new class would copy over all the fields and all the methods from ShellPackageA and ShellPackageB and would extend JshBase.

I’ll do some more research and see if I can get it to work.

From: Arno Unkrig Sent: Wednesday, June 19, 2019 5:36 PM To: janino-compiler/jsh Cc: buko; Author Subject: Re: [janino-compiler/jsh] Extensibility: Adding new commands, newfunctions (#1)

Hey there, I believe I mistook your issue a bit because I missed the fact that it relates to JSH, not JANINO. After all, the first JSH ever, congrats! Currently, the InteractiveShell hardcodes that the extended class is JshBase. I could either make that configurable, or you could put import static my.package.ShellPackageA.; Import static my.package.ShellPackageB.; in your .jshrc file. The only limitation then is that you can access only static members of your ShellPackageA/B classes. Does that help? Let me know which solution you choose and why. CU Arno — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

aunkrig commented 5 years ago

I just updated JSH so that it uses more up-to-date versions of de.unkrig.commons and org.codehaus.janino.

My feeling is that it is not ideal to change the "extended class" from JshBase to some other class (which probably extends JshBase) because it is difficult to combine multiple such "packages".

Again, I'd recomment that you use IMPORT STATIC, like so:

$ emptyList();
Line 1, Column 1: A method named "emptyList" is not declared in any enclosing class nor any supertype, nor through a static import
$ import static java.util.Collections.*;
$ emptyList();
$