1+ package org .fiji .jython .autocompletion ;
2+
3+ import java .util .ArrayList ;
4+ import java .util .HashMap ;
5+ import java .util .List ;
6+ import java .util .Map ;
7+
8+ import org .python .antlr .PythonTree ;
9+ import org .python .antlr .ast .Assign ;
10+ import org .python .antlr .ast .Call ;
11+ import org .python .antlr .ast .ClassDef ;
12+ import org .python .antlr .ast .FunctionDef ;
13+ import org .python .antlr .ast .ImportFrom ;
14+ import org .python .antlr .ast .Name ;
15+ import org .python .antlr .ast .Tuple ;
16+ import org .python .antlr .base .mod ;
17+ import org .python .core .CompileMode ;
18+ import org .python .core .CompilerFlags ;
19+ import org .python .core .ParserFacade ;
20+
21+ public class AutoCompletionUtil {
22+
23+ static public String testCode = String .join ("\n " ,
24+ "from ij import IJ, ImageJ as IJA, VirtualStack" ,
25+ "from ij.process import ByteProcessor" ,
26+ "imp = IJ.getImage()" ,
27+ "width, height = imp.getWidth(), imp.getHeight()" ,
28+ "imp2 = imp" ,
29+ "class Volume(VirtualStack):" ,
30+ " def getProcessor(self, index):" ,
31+ " return ByteProcessor(512, 512)" ,
32+ " def getSize(self):" ,
33+ " return 10" ,
34+ "def setRoi(an_imp):" ,
35+ " ip = an_imp.getStack().getProcessor(3)" ,
36+ " pixels = ip." );
37+
38+ static public class Scope {
39+ final Scope parent ;
40+ final List <Scope > children = new ArrayList <>();
41+ final HashMap <String , String > imports = new HashMap <>();
42+ final HashMap <String , String > vars = new HashMap <>();
43+
44+ public Scope (final Scope parent ) {
45+ this .parent = parent ;
46+ if (null != parent ) {
47+ this .imports .putAll (parent .imports );
48+ this .vars .putAll (parent .vars );
49+ }
50+ }
51+
52+ public Scope getLast () {
53+ if (children .isEmpty ()) return this ;
54+ return children .get (children .size () -1 ).getLast ();
55+ }
56+
57+ public void print (final String indent ) {
58+ if ("" == indent ) {
59+ for (final Map .Entry <String , String > e : imports .entrySet ())
60+ System .out .println (indent + "import :: " + e .getKey () + " --> " + e .getValue ());
61+ System .out .println ("scope global:" );
62+ }
63+ for (final Map .Entry <String , String > e : vars .entrySet ()) {
64+ if (null != parent && parent .vars .containsKey (e .getKey ())) continue ; // to print only the newly added ones
65+ System .out .println (indent + "var :: " + e .getKey () + " = " + e .getValue ());
66+ }
67+
68+ int i = 0 ;
69+ for (final Scope child : children ) {
70+ System .out .println (indent + "scope[" + (i ++) + "]:" );
71+ child .print (indent + " " );
72+ }
73+ }
74+ }
75+
76+ /**
77+ * Returns the top-level Scope.
78+ */
79+ static public Scope parseAST (final String code ) {
80+ // The code includes from beginning of the file until the point at which an autocompletion is requested.
81+ // Therefore, remove the last line, which would fail to parse because it is incomplete
82+ final int lastLineBreak = code .lastIndexOf ("\n " );
83+ final String codeToParse = code .substring (0 , lastLineBreak );
84+ final mod m = ParserFacade .parse (codeToParse , CompileMode .exec , "<none>" , new CompilerFlags ());
85+
86+ return parseNode (m .getChildren (), null );
87+ }
88+
89+ static public Scope parseNode (final List <PythonTree > children , final Scope parent ) {
90+
91+ final Scope scope = new Scope (parent );
92+ if (null != parent )
93+ parent .children .add (scope );
94+
95+ for (final PythonTree child : children ) {
96+ print (child .getClass ());
97+
98+ if (child instanceof ImportFrom )
99+ scope .imports .putAll (parseImportStatement ( (ImportFrom )child ));
100+ else if (child instanceof Assign )
101+ scope .vars .putAll (parseAssignStatement ( (Assign )child , scope ));
102+ else if (child instanceof FunctionDef )
103+ scope .vars .put (parseFunctionDef ( (FunctionDef )child , scope ), null ); // no value: no class. TODO return the list of arguments, for autocompletion.
104+ else if (child instanceof ClassDef )
105+ scope .vars .put (parseClassDef ( (ClassDef )child , scope ), null ); // TODO no value, but should have one, too look into its methods and implemented interfaces or superclasses
106+ }
107+
108+ return scope ;
109+ // Prints the top code blocks
110+ // class org.python.antlr.ast.ImportFrom
111+ // class org.python.antlr.ast.ImportFrom
112+ // class org.python.antlr.ast.Assign
113+ // class org.python.antlr.ast.Assign
114+ // class org.python.antlr.ast.ClassDef
115+ // class org.python.antlr.ast.FunctionDef
116+ }
117+
118+ /**
119+ * Parse import statements, considering aliases.
120+ * @param im
121+ * @return
122+ */
123+ static public Map <String , String > parseImportStatement (final ImportFrom im ) {
124+ final Map <String , String > classes = new HashMap <>();
125+ final String module = im .getModule ().toString ();
126+ for (int i =0 ; i <im .getNames ().__len__ (); ++i ) {
127+ final String alias = im .getInternalNames ().get (i ).getAsname ().toString (); // alias: as name
128+ final String simpleClassName = im .getInternalNames ().get (i ).getInternalName (); // class name
129+ classes .put ("None" == alias ? simpleClassName : alias , module + "." + simpleClassName );
130+ }
131+ return classes ;
132+ }
133+
134+ static public Map <String , String > parseAssignStatement (final Assign assign , final Scope scope ) {
135+ final Map <String , String > assigns = new HashMap <>();
136+ //final expr right = assign.getInternalValue(); // strangely this works
137+ final PythonTree right = assign .getChildren ().get (1 );
138+ if (right instanceof Tuple || right instanceof org .python .antlr .ast .List ) { // TODO are there any other possible?
139+ final PythonTree left = assign .getChildren ().get (0 );
140+ for (int i =0 ; i <right .getChildren ().size (); ++i ) {
141+ final String name = left .getChildren ().get (i ).getNode ().toString ();
142+ final String val = parseRight (right .getChildren ().get (i ), scope );
143+ if (null != val ) assigns .put (name , val ); // scope.vars.put(name, val);
144+ }
145+ } else {
146+ final String name = assign .getInternalTargets ().get (0 ).getNode ().toString ();
147+ final String val = parseRight (right , scope );
148+ assigns .put (name , val );
149+ }
150+
151+ return assigns ;
152+ }
153+
154+ /**
155+ * Adds a child Scope to the given parent Scope, and also a variable to the parent scope
156+ * with no class (just for the name). Then populates the child scope.
157+ */
158+ static public String parseFunctionDef (final FunctionDef fn , final Scope parent ) {
159+ final String name = fn .getInternalName ();
160+ print ("function name: " + name );
161+ parseNode (fn .getChildren (), parent );
162+ return name ;
163+ }
164+
165+ static public String parseClassDef (final ClassDef c , final Scope parent ) {
166+ final String name = c .getInternalName ();
167+ parseNode (c .getChildren (), parent );
168+ return name ;
169+ }
170+
171+ /** Discover the class of the right statement in an assignment.
172+ *
173+ * @param right
174+ */
175+ static public String parseRight (final Object right , final Scope scope ) {
176+ if (right instanceof Name ) {
177+ return scope .vars .getOrDefault ( ((Name )right ).toString (), null );
178+ }
179+ if (right instanceof Call ) {
180+ // TODO recursive, to parse e.g. imp.getProcessor().getPixels()
181+ // to figure out what class is imp (if known), what class getProcessor returns,
182+ // and then what class getPixels returns.
183+ // And to handle also IJ.getImage()
184+ }
185+ return null ;
186+ }
187+
188+ static public final void print (Object s ) {
189+ System .out .println (s );
190+ }
191+
192+ static public final void main (String [] args ) {
193+ try {
194+ parseAST (testCode ).print ("" );
195+ } catch (Exception e ) {
196+ e .printStackTrace ();
197+ if (null != e .getCause ())
198+ e .getCause ().printStackTrace ();
199+ }
200+ }
201+ }
0 commit comments