2828 */
2929package org .scijava .plugins .scripting .jython .completion ;
3030
31+ import java .lang .reflect .Modifier ;
3132import java .math .BigInteger ;
3233import java .util .ArrayList ;
34+ import java .util .Arrays ;
3335import java .util .Collection ;
36+ import java .util .HashSet ;
3437import java .util .List ;
3538import java .util .Map ;
3639import java .util .Set ;
3740import java .util .TreeSet ;
3841
42+ import org .python .core .PyFunction ;
43+ import org .python .core .PyJavaType ;
44+ import org .python .core .PyModule ;
45+ import org .python .core .PyObject ;
3946import org .scijava .code .lsp .ScriptDialect ;
4047import org .scijava .plugin .Plugin ;
4148import org .scijava .plugins .scripting .jython .JythonScriptLanguage ;
5360 * from stubs (see scijava-code-lsp-jython) read: e.g.
5461 * {@code import net.imglib2.img; image = net.imglib2.img.Img(); name = str()}.
5562 * </p>
63+ * <p>
64+ * A live interpreter's variables are declared by their values, where those
65+ * say more: its imported Java classes as the classes themselves, its modules
66+ * imported again, its functions as functions, and its other Python values as
67+ * instances of their builtin types.
68+ * </p>
5669 *
5770 * @author Gabriel Selzer
5871 */
5972@ Plugin (type = ScriptDialect .class , name = "Jython" )
6073public class JythonDialect implements ScriptDialect {
6174
75+ /** Names Python reserves, which cannot be declared. */
76+ private static final Set <String > KEYWORDS = new HashSet <>(Arrays .asList (
77+ "False" , "None" , "True" , "and" , "as" , "assert" , "async" , "await" , "break" ,
78+ "class" , "continue" , "def" , "del" , "elif" , "else" , "except" , "exec" ,
79+ "finally" , "for" , "from" , "global" , "if" , "import" , "in" , "is" , "lambda" ,
80+ "nonlocal" , "not" , "or" , "pass" , "print" , "raise" , "return" , "try" ,
81+ "while" , "with" , "yield" ));
82+
6283 @ Override
6384 public boolean supports (final ScriptLanguage language ) {
6485 return language instanceof JythonScriptLanguage ;
@@ -70,14 +91,14 @@ public String languageId() {
7091 }
7192
7293 @ Override
73- public Header header (final Map <String , Class <?>> parameters ) {
74- // Declare each parameter as an instance of its type , for type reasoning
94+ public Header header (final Map <String , Variable > variables ) {
95+ // Declare each variable , for type reasoning
7596 final List <String > types = new ArrayList <>();
7697 final List <String > declarations = new ArrayList <>();
77- parameters .forEach ((name , type ) -> {
78- final String t = parameterType ( type );
79- types . add ( t );
80- declarations . add ( name + " = " + t . replace ( '$' , '.' ) + "()" );
98+ variables .forEach ((name , v ) -> {
99+ // NB: Names Python reserves cannot be declared.
100+ if (! KEYWORDS . contains ( name )) declarations . add ( declaration ( name , v ,
101+ types ) );
81102 });
82103 // And import the packages of those types, before the declarations
83104 final List <String > code = new ArrayList <>();
@@ -92,6 +113,93 @@ public Header header(final Map<String, Class<?>> parameters) {
92113 return new Header (String .join ("; " , code ), hidden );
93114 }
94115
116+ /**
117+ * Declares a variable: by its value, if known (e.g. a live interpreter's
118+ * imported Java class, as the class itself), or else as an instance of its
119+ * type.
120+ *
121+ * @param types Where to note the types the declaration refers to (see
122+ * {@link #packages}).
123+ */
124+ static String declaration (final String name , final Variable v ,
125+ final List <String > types )
126+ {
127+ final Object value = v .value ().orElse (null );
128+ // A Java class (e.g. after "from java.util import ArrayList"): itself
129+ final Class <?> c = value instanceof PyJavaType ? ((PyJavaType ) value )
130+ .getProxyType () : value instanceof Class ? (Class <?>) value : null ;
131+ if (c != null ) {
132+ if (!isNamed (c )) return name + " = " + any (types );
133+ types .add (c .getName ());
134+ return name + " = " + c .getName ().replace ('$' , '.' );
135+ }
136+ // A Python module (e.g. after "import os"): imported again
137+ if (value instanceof PyModule ) {
138+ final PyObject module = ((PyModule ) value ).__findattr__ ("__name__" );
139+ if (module != null && module .toString ().matches ("[\\ w.]+" )) {
140+ return "import " + module + " as " + name ;
141+ }
142+ }
143+ // A function (e.g. after "def f(): ...")
144+ if (value instanceof PyFunction ) {
145+ return name + " = lambda *args, **kwargs: None" ;
146+ }
147+ // Another Python value (e.g. a dict): an instance of its builtin type
148+ if (value instanceof PyObject ) {
149+ final String builtin = pythonType (((PyObject ) value ).getType ()
150+ .getName ());
151+ return name + " = " + (builtin != null ? builtin + "()" : any (types ));
152+ }
153+ // A Java value, or a script parameter: an instance of its type
154+ final String t = parameterType (v .type ());
155+ types .add (t );
156+ return name + " = " + t .replace ('$' , '.' ) + "()" ;
157+ }
158+
159+ /**
160+ * A value of any type (noting the module that says so), for variables
161+ * without a type to declare.
162+ */
163+ private static String any (final List <String > types ) {
164+ types .add ("typing.Any" );
165+ return "typing.cast(typing.Any, None)" ;
166+ }
167+
168+ /**
169+ * The Python 3 type of a Python 2 builtin one (as the Python server knows
170+ * Python 3's), e.g. {@code str} for {@code unicode}; or null if not a
171+ * builtin one.
172+ */
173+ private static String pythonType (final String type ) {
174+ switch (type ) {
175+ case "int" :
176+ case "long" :
177+ return "int" ;
178+ case "unicode" :
179+ return "str" ;
180+ case "bool" :
181+ case "float" :
182+ case "str" :
183+ case "list" :
184+ case "dict" :
185+ case "tuple" :
186+ case "set" :
187+ return type ;
188+ default :
189+ return null ;
190+ }
191+ }
192+
193+ /** Whether code can refer to a class by its name. */
194+ private static boolean isNamed (final Class <?> c ) {
195+ if (c .isPrimitive () || c .isArray () || c .isAnonymousClass () || c
196+ .isLocalClass () || c .getName ().indexOf ('.' ) < 0 ) return false ;
197+ for (Class <?> t = c ; t != null ; t = t .getEnclosingClass ()) {
198+ if (!Modifier .isPublic (t .getModifiers ())) return false ;
199+ }
200+ return true ;
201+ }
202+
95203 /**
96204 * @param type the Java type (of a SciJava Script parameter) declared in the
97205 * script
0 commit comments