-
Notifications
You must be signed in to change notification settings - Fork 183
/
Interpreter.java
1391 lines (1218 loc) · 50.6 KB
/
Interpreter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*****************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
* *
* *
* This file is part of the BeanShell Java Scripting distribution. *
* Documentation and updates may be found at http://www.beanshell.org/ *
* Patrick Niemeyer (pat@pat.net) *
* Author of Learning Java, O'Reilly & Associates *
* *
*****************************************************************************/
package bsh;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ResourceBundle;
/**
The BeanShell script interpreter.
An instance of Interpreter can be used to source scripts and evaluate
statements or expressions.
<p>
Here are some examples:
<p><blockquote><pre>
Interpeter bsh = new Interpreter();
// Evaluate statements and expressions
bsh.eval("foo=Math.sin(0.5)");
bsh.eval("bar=foo*5; bar=Math.cos(bar);");
bsh.eval("for(i=0; i<10; i++) { print(\"hello\"); }");
// same as above using java syntax and apis only
bsh.eval("for(int i=0; i<10; i++) { System.out.println(\"hello\"); }");
// Source from files or streams
bsh.source("myscript.bsh"); // or bsh.eval("source(\"myscript.bsh\")");
// Use set() and get() to pass objects in and out of variables
bsh.set( "date", new Date() );
Date date = (Date)bsh.get( "date" );
// This would also work:
Date date = (Date)bsh.eval( "date" );
bsh.eval("year = date.getYear()");
Integer year = (Integer)bsh.get("year"); // primitives use wrappers
// With Java1.3+ scripts can implement arbitrary interfaces...
// Script an awt event handler (or source it from a file, more likely)
bsh.eval( "actionPerformed( e ) { print( e ); }");
// Get a reference to the script object (implementing the interface)
ActionListener scriptedHandler =
(ActionListener)bsh.eval("return (ActionListener)this");
// Use the scripted event handler normally...
new JButton.addActionListener( script );
</pre></blockquote>
<p>
In the above examples we showed a single interpreter instance, however
you may wish to use many instances, depending on the application and how
you structure your scripts. Interpreter instances are very light weight
to create, however if you are going to execute the same script repeatedly
and require maximum performance you should consider scripting the code as
a method and invoking the scripted method each time on the same interpreter
instance (using eval()).
<p>
See the BeanShell User's Manual for more information.
*/
public class Interpreter
implements Runnable, Serializable, BshClassManager.Listener
{
/* --- Begin static members --- */
private static final long serialVersionUID = 1L;
/*
Debug utils are static so that they are reachable by code that doesn't
necessarily have an interpreter reference (e.g. tracing in utils).
The DEBUG flag is thread local and would allow for enabling debug for
any Interpreter specifically assuming different Interpreters are isolated
on separate threads.
*/
public static final ThreadLocal<Boolean> DEBUG = ThreadLocal.withInitial(()->Boolean.FALSE);
private boolean EOF;
public static boolean TRACE;
public static boolean COMPATIBIILTY;
public static final String VERSION;
static {
ResourceBundle b = ResourceBundle.getBundle("version");
VERSION = b.getString("release") + "." + b.getString("build");
staticInit();
}
/** Shared system object visible under bsh.system */
private static final This SYSTEM_OBJECT = This.getThis(new NameSpace(null, null, "bsh.system"), null);
/** Shared system object visible under bsh.system */
public static void setShutdownOnExit(final boolean value) {
try {
SYSTEM_OBJECT.getNameSpace().setVariable("shutdownOnExit", Boolean.valueOf(value), false);
} catch (final UtilEvalError utilEvalError) {
throw new IllegalStateException(utilEvalError);
}
}
/**
Strict Java mode
@see #setStrictJava( boolean )
*/
private boolean strictJava = false;
/* --- End static members --- */
/* --- Instance data --- */
transient Parser parser;
NameSpace globalNameSpace;
ConsoleAssignable console;
/** If this interpeter is a child of another, the parent */
Interpreter parent;
/** The name of the file or other source that this interpreter is reading */
String sourceFileInfo;
/** thread yield time in milliseconds */
private int yield_for = -1;
/** by default in interactive mode System.exit() on EOF */
private boolean exitOnEOF = true;
protected boolean
evalOnly, // Interpreter has no input stream, use eval() only
interactive; // Interpreter has a user, print prompts, etc.
/** Control the verbose printing of results for the show() command. */
private boolean showResults = true;
/**
* Compatibility mode. When {@code true} missing classes are tried to create from corresponding java source files.
* Default value is {@code false}, could be changed to {@code true} by setting the system property
* "bsh.compatibility" to "true".
*/
private boolean compatibility = COMPATIBIILTY;
/* --- End instance data --- */
/** The main constructor, all other constructors should pass through here.
* If namespace is not null it becomes this interpreter's global namespace
* otherwise a new instance is created. A parent interpreter allows values
* to be inherited like a shared class manager.
* @param console assignable collection of input output streams.
* @param interactive whether attached to user input.
* @param namespace global name space or null.
* @param parent interpreter or null.
* @param sourceFileInfo source file info for debugging or null. */
public Interpreter( ConsoleAssignable console, boolean interactive,
NameSpace namespace, Interpreter parent, String sourceFileInfo ) {
long t1 = 0;
if (Interpreter.DEBUG.get())
t1=System.nanoTime();
this.interactive = interactive;
this.parent = parent;
if ( parent != null ) {
setStrictJava( parent.strictJava );
this.parser = parent.parser;
this.evalOnly = parent.evalOnly;
}
this.sourceFileInfo = sourceFileInfo;
if ( namespace == null ) {
BshClassManager bcm = BshClassManager.createClassManager( this );
namespace = new NameSpace(namespace, bcm, "global");
}
this.setConsole(console);
this.setNameSpace(namespace);
this.getClassManager().addListener(this);
if ( Interpreter.DEBUG.get() )
Interpreter.debug("Time to initialize interpreter: interactive=",
interactive, " ", (System.nanoTime() - t1), " nanoseconds.");
}
/** Wraps individual input output streams as a console collection.
* @param in input stream.
* @param out standard output stream.
* @param err error output stream.
* @param interactive whether attached to user input.
* @param namespace global name space or null.
* @param parent interpreter or null.
* @param sourceFileInfo source file info for debugging or null. */
public Interpreter(
Reader in, PrintStream out, PrintStream err,
boolean interactive, NameSpace namespace,
Interpreter parent, String sourceFileInfo ) {
this(new Console(in , out, err), interactive, namespace,
parent, sourceFileInfo);
}
/** Interpreter instance without a parent interpreter and source file.
* @param in input stream.
* @param out standard output stream.
* @param err error output stream.
* @param interactive whether attached to user input.
* @param namespace global name space or null. */
public Interpreter(
Reader in, PrintStream out, PrintStream err,
boolean interactive, NameSpace namespace) {
this(in, out, err, interactive, namespace, null, null);
}
/** Interpreter instance without a namespace, parent and source file.
* @param in input stream.
* @param out standard output stream.
* @param err error output stream.
* @param interactive whether attached to user input. */
public Interpreter(
Reader in, PrintStream out, PrintStream err, boolean interactive)
{
this(in, out, err, interactive, null);
}
/** An interactive interpreter attached to the specified console.
* @param console read only collection of input output streams.
* @param namespace global name space or null.
* @param parent interpreter or null. */
public Interpreter(ConsoleInterface console, NameSpace namespace, Interpreter parent) {
this(new Console(console), true, namespace, parent,
null == parent ? null : parent.sourceFileInfo);
}
/** An interactive interpreter attached to a console using parent namesepace.
* @param console read only collection of input output streams.
* @param parent interpreter or null. */
public Interpreter(ConsoleInterface console, Interpreter parent) {
this(console, parent.globalNameSpace, parent);
}
/** An interactive interpreter attached to a console with supplied namespace.
* @param console read only collection of input output streams.
* @param namespace global name space or null. */
public Interpreter(ConsoleInterface console, NameSpace globalNameSpace) {
this(console, globalNameSpace, null);
}
/** An interactive interpreter attached to a console, no namespace or parent.
* @param console read only collection of input output streams. */
public Interpreter(ConsoleInterface console) {
this(console, null, null);
}
/** A non interactive interpreter for evaluation purposes only.
* Uses system standard output and error with input deferred to eval. */
public Interpreter() {
this(null, null, "");
this.sourceFileInfo = null;
}
/** A non interactive interpreter for namespace.
* Uses system standard output and error with input deferred to eval.
* @param namespace global name space. */
public Interpreter(NameSpace namespace) {
this(namespace, null, null);
}
/** A non interactive interpreter for namespace and source file.
* Uses system standard output and error with input deferred to eval.
* @param namespace global name space.
* @param sourceFileInfo source file info for debugging. */
public Interpreter(NameSpace namespace, String sourceFileInfo) {
this(namespace, null, sourceFileInfo);
}
/** A non interactive interpreter for namespace and parent.
* Uses system standard output and error with input deferred to eval.
* @param namespace global name space.
* @param parent interpreter. */
public Interpreter(NameSpace namespace, Interpreter parent) {
this(namespace, parent, null);
}
/** A non interactive interpreter for namespace, parent and source file.
* Uses system standard output and error with input deferred to eval.
* @param namespace global name space.
* @param parent interpreter.
* @param sourceFileInfo source file info for debugging. */
public Interpreter(NameSpace namespace, Interpreter parent, String sourceFileInfo) {
this(null, System.out, System.err, false, namespace, parent, sourceFileInfo);
this.evalOnly = true;
setu( "bsh.evalOnly", Primitive.TRUE );
}
/** Extend existing interpreter.
* @param parent interpreter. */
public Interpreter(Interpreter parent) {
this(parent.console, parent.interactive, parent.globalNameSpace,
parent, parent.sourceFileInfo);
}
// End constructors
/** Attach an assignable console and initialize a new parser.
* @param console assignable collection of input output streams. */
public void setConsole( ConsoleAssignable console ) {
this.console = console;
if ( null == this.parser || get_jjtree().nodeArity() != 0
|| (null != parent && parent.interactive) )
this.parser = new Parser(getIn());
else
this.parser.ReInit(getIn());
}
/** Overloaded to accept a read only console.
* @param console read only collection of input output streams. */
public void setConsole( ConsoleInterface console ) {
this.setConsole(new Console(console));
}
/** Initialize the BeanShell root system objects and help system. */
private void initRootSystemObject() {
BshClassManager bcm = getClassManager();
// bsh
setu("bsh", new NameSpace(null, bcm, "Bsh Object" ).getThis( this ) );
// bsh.system
setu( "bsh.system", SYSTEM_OBJECT);
setu( "bsh.shared", SYSTEM_OBJECT); // alias
// bsh.help
This helpText = new NameSpace(null, bcm, "Bsh Command Help Text" ).getThis( this );
setu( "bsh.help", helpText );
// bsh.cwd
setu( "bsh.cwd", System.getProperty("user.dir") );
// bsh.interactive
setu( "bsh.interactive", interactive ? Primitive.TRUE : Primitive.FALSE );
// bsh.evalOnly
setu( "bsh.evalOnly", Primitive.FALSE );
}
/** Assign the global namespace for this interpreter.
* <p> Note: It is preferred to keep the interpreter as long reference
* and use it to create discardable instances which can inherit from the
* parent and be dereferenced for collection. This method is only here
* for completeness.<p>
* The global namespace can be accessed in scripts using the variable
* 'this.namespace' or global.namespace as necessary.
* @param namespace global name space. */
public void setNameSpace( NameSpace namespace ) {
this.globalNameSpace = namespace;
if ( null != namespace ) try {
if ( ! (namespace.getVariable("bsh") instanceof This) ) {
initRootSystemObject();
if ( interactive )
loadRCFiles();
}
} catch (final UtilEvalError e) {
throw new IllegalStateException(e);
}
}
/** Retrieve the global namespace for this interpreter.
* <p> Note: It is preferred to keep the interpreter as long reference
* and use it to create discardable instances which can inherit from the
* parent and be dereferenced for collection. This method is only here
* for completeness.<p>
* The global namespace can be accessed in scripts using the variable
* 'this.namespace' or global.namespace as necessary. */
public NameSpace getNameSpace() {
return globalNameSpace;
}
/** Interactive interpreter command line execution.
* @param args optional file name for interpretation. */
public static void main(String[] args) {
if ( args.length > 0 ) {
String filename = args[0];
String[] bshArgs;
if ( args.length > 1 ) {
bshArgs = new String[args.length - 1];
System.arraycopy(args, 1, bshArgs, 0, args.length - 1);
} else
bshArgs = new String[0];
try {
Interpreter interpreter = new Interpreter();
interpreter.setu("bsh.args", bshArgs);
Object result =
interpreter.source(filename, interpreter.globalNameSpace);
if ( result instanceof Class ) try {
invokeMain((Class<?>) result, bshArgs);
} catch (Exception e) {
Object o = e;
if ( e instanceof InvocationTargetException )
o = e.getCause();
System.err.println("Class: " + result
+ " main method threw exception:" + o);
}
} catch (FileNotFoundException e) {
System.err.println("File not found: " + e);
} catch (TargetError e) {
System.err.println("Script threw exception: " + e);
if ( e.inNativeCode() )
e.printStackTrace(DEBUG.get(), System.err);
} catch (EvalError e) {
System.err.println("Evaluation Error: " + e);
} catch (IOException e) {
System.err.println("I/O Error: " + e);
}
} else {
try (FileReader readr = new FileReader(System.in);
Reader repl = new CommandLineReader(readr)) {
new Interpreter(repl, System.out, System.err, true).run();
} catch (IOException e) {
System.err.println("I/O Error closing command line reader: " + e);
}
}
}
/** Convenience method for invoking the main method of a class.
* @param clas with static main method.
* @param args the string arguments.
* @throws Exception thrown if something fails. */
public static void invokeMain(Class<?> clas, String[] args)
throws Exception {
Invocable main = Reflect.resolveJavaMethod(clas, "main",
new Class[] {String[].class}, true/*onlyStatic*/);
if ( null != main )
main.invoke(null, new Object[] {args});
}
/** Run interactively. (printing prompts, etc.) */
public void run() {
if ( evalOnly )
throw new RuntimeException("bsh Interpreter: No stream");
/*
We'll print our banner using eval(String) in order to
exercise the parser and get the basic expression classes loaded...
This ameliorates the delay after typing the first statement.
*/
if ( interactive && null == getParent() ) try {
eval("printBanner();");
} catch ( EvalError e ) {
println("BeanShell " + VERSION);
}
// init the callstack.
CallStack callstack = new CallStack(globalNameSpace);
Node node = null;
EOF = false;
int idx = -1;
while( !Thread.interrupted() && !EOF ) {
try {
if ( interactive )
console.prompt(getBshPrompt());
EOF = readLine();
if ( get_jjtree().nodeArity() > 0 ) { // number of child nodes
node = get_jjtree().rootNode();
// nodes remember from where they were sourced
node.setSourceFile( sourceFileInfo );
if ( DEBUG.get() )
node.dump(">");
if ( TRACE )
println("// " + node.getText());
Object ret = node.eval(callstack, this);
// sanity check during development
if ( callstack.depth() > 1 )
throw new InterpreterError(
"Callstack growing: "+callstack);
if ( ret instanceof ReturnControl )
ret = ((ReturnControl) ret).value;
if ( interactive ) {
if ( ret != Primitive.VOID ) {
setu("$_", ret);
setu("$"+(++idx%10), ret);
if ( showResults )
println("--> $" + (idx%10) + " = " + StringUtil.typeValueString(ret));
} else if ( showResults )
println("--> void");
}
}
} catch (ParseException e) {
error("Parser Error: " + e.getMessage(DEBUG.get()));
if ( DEBUG.get() )
e.printStackTrace();
if ( !interactive )
EOF = true;
parser.reInitInput(getIn());
} catch (InterpreterError e) {
error("Internal Error: " + e.getMessage());
if ( !interactive )
EOF = true;
} catch (TargetError e) {
error("Target Exception: " + e.getMessage() );
if ( e.inNativeCode() )
e.printStackTrace( DEBUG.get(), getErr() );
if ( !interactive )
EOF = true;
setu("$_e", e.getTarget());
} catch (EvalError e) {
if ( interactive )
error( "Evaluation Error: "+e.getMessage() );
else
error( "Evaluation Error: "+e.getRawMessage() );
if ( DEBUG.get() )
e.printStackTrace();
if ( !interactive )
EOF = true;
} catch (Exception e) {
error("Unknown error: " + e);
if ( DEBUG.get() )
e.printStackTrace();
if ( !interactive )
EOF = true;
} finally {
get_jjtree().reset();
// reinit the callstack
if ( callstack.depth() > 1 ) {
callstack.clear();
callstack.push( globalNameSpace );
}
}
}
if ( interactive && exitOnEOF )
System.exit(0);
}
// begin source and eval
/** Source a script from a url for interpretation.
* @param url the source url.
* @param namespace effective namespace.
* @return the return result from the script execution.
* @throws EvalError if a script error occurred.
* @throws IOException if a file read error occurred. */
public Object source(URL url, NameSpace namespace)
throws EvalError, IOException {
Interpreter.debug("Sourcing file: ", url.toString());
try (Reader fileRead = new FileReader(url.openStream());
Reader sourceIn = new BufferedReader(fileRead)) {
return eval(sourceIn, namespace, url.toString());
}
}
/** Source a script from a file for interpretation.
* @param file the source file.
* @param namespace effective namespace.
* @return the return result from the script execution.
* @throws EvalError if a script error occurred.
* @throws IOException if a file read error occurred. */
public Object source(File file, NameSpace namespace)
throws EvalError, IOException {
Interpreter.debug("Sourcing file: ", file);
Reader sourceIn = new BufferedReader( new FileReader(file) );
try {
return eval( sourceIn, namespace, file.getPath() );
} finally {
sourceIn.close();
}
}
/** Source a script from a filename for interpretation.
* @param filename the source file name.
* @param namespace effective namespace.
* @return the return result from the script execution.
* @throws EvalError if a script error occurred.
* @throws IOException if a file read error occurred. */
public Object source(String filename, NameSpace namespace)
throws EvalError, IOException {
return source(pathToFile(filename), namespace);
}
/** Source a script from a url for interpretation.
* @param url the source url.
* @return the return result from the script execution.
* @throws EvalError if a script error occurred.
* @throws IOException if a file read error occurred. */
public Object source(URL url) throws EvalError, IOException {
return source(url, globalNameSpace);
}
/** Source a script from a file for interpretation.
* @param file the source file.
* @return the return result from the script execution.
* @throws EvalError if a script error occurred.
* @throws IOException if a file read error occurred. */
public Object source(File file) throws EvalError, IOException {
return source(file, globalNameSpace);
}
/** Source a script from a filename for interpretation.
* @param filename the source file name.
* @return the return result from the script execution.
* @throws EvalError if a script error occurred.
* @throws IOException if a file read error occurred. */
public Object source(String filename) throws EvalError, IOException {
return source(filename, globalNameSpace);
}
/**
Spawn a non-interactive local interpreter to evaluate text in the
specified namespace.
Return value is the evaluated object (or corresponding primitive
wrapper).
@param sourceFileInfo is for information purposes only. It is used to
display error messages (and in the future may be made available to
the script).
@throws EvalError on script problems
@throws TargetError on unhandled exceptions from the script
*/
/*
Note: we need a form of eval that passes the callstack through...
*/
/*
Can't this be combined with run() ?
run seems to have stuff in it for interactive vs. non-interactive...
compare them side by side and see what they do differently, aside from the
exception handling.
*/
public Object eval(
Reader in, NameSpace nameSpace, String sourceFileInfo
/*, CallStack callstack */ )
throws EvalError
{
Object retVal = null;
Interpreter.debug("eval: nameSpace = ", nameSpace);
/*
Create non-interactive local interpreter for this namespace
with source from the input stream and out/err same as
this interpreter.
*/
Interpreter localInterpreter = new Interpreter(
in, getOut(), getErr(), false, nameSpace, this, sourceFileInfo);
CallStack callstack = new CallStack(nameSpace);
Node node = null;
boolean eof = false;
while( !eof )
{
try
{
eof = localInterpreter.readLine();
if (localInterpreter.get_jjtree().nodeArity() > 0)
{
node = localInterpreter.get_jjtree().rootNode();
// nodes remember from where they were sourced
node.setSourceFile( sourceFileInfo );
if ( TRACE )
println( "// " +node.getText() );
retVal = node.eval(callstack, localInterpreter);
// sanity check during development
if ( callstack.depth() > 1 )
throw new InterpreterError(
"Callstack growing: "+callstack);
if ( retVal instanceof ReturnControl ) {
retVal = ((ReturnControl)retVal).value;
break; // non-interactive, return control now
}
}
} catch(ParseException e) {
if ( DEBUG.get() )
// show extra "expecting..." info
error( e.getMessage(DEBUG.get()) );
// add the source file info and throw again
e.setErrorSourceFile( sourceFileInfo );
throw e;
} catch ( InterpreterError e ) {
throw new EvalError(
"Sourced file: "+sourceFileInfo+" internal Error: "
+ e.getMessage(), node, callstack, e);
} catch ( TargetError e ) {
// failsafe, set the Line as the origin of the error.
if ( e.getNode()==null )
e.setNode( node );
throw e.reThrow("Sourced file: "+sourceFileInfo);
} catch ( EvalError e) {
if ( DEBUG.get())
e.printStackTrace();
// failsafe, set the Line as the origin of the error.
if ( e.getNode()==null )
e.setNode( node );
throw e.reThrow( "Sourced file: "+sourceFileInfo );
} catch ( Exception e) {
if ( DEBUG.get())
e.printStackTrace();
throw new EvalError(
"Sourced file: "+sourceFileInfo+" unknown error: "
+ e.getMessage(), node, callstack, e);
} finally {
localInterpreter.get_jjtree().reset();
// reinit the callstack
if ( callstack.depth() > 1 ) {
callstack.clear();
callstack.push( nameSpace );
}
}
}
return Primitive.unwrap( retVal );
}
/** Optional method to release additional resources. The interpreter
* allows for multiple eval calls and therefor maintains certain state
* for subsequent calls. Only use this if completely done with current
* interpreter and desperate to clear as much resources as possible. */
public void reset() {
this.getClassManager().reset();
this.globalNameSpace.clear();
Name.clearParts();
Reflect.instanceCache.clear();
}
/**
Evaluate the inputstream in this interpreter's global namespace.
*/
public Object eval( Reader in ) throws EvalError
{
return eval( in, globalNameSpace, null == sourceFileInfo ? "eval stream" : sourceFileInfo );
}
/**
Evaluate the string in this interpreter's global namespace.
*/
public Object eval( String statements ) throws EvalError {
Interpreter.debug("eval(String): ", statements);
return eval(statements, globalNameSpace);
}
/**
Evaluate the string in the specified namespace.
*/
public Object eval( String statements, NameSpace nameSpace )
throws EvalError
{
return eval(
new StringReader(terminatedScript(statements)), nameSpace,
showEvalString("inline evaluation", statements) );
}
/** Produce source file info from the supplied statements.
* The script statements are truncated to 80 characters and new lines
* removed for use in error message output.
* @param type of file / evaluation.
* @param statement of script.
* @return snippet of the script for debug info. */
String showEvalString( String type, String statement ) {
if ( statement.length() > 80 )
statement = statement.substring( 0, 80 ) + " . . . ";
return type.concat(" of: ``")
.concat(statement.replace('\n', ' ').replace('\r', ' '))
.concat("''");
}
/** Convenience termination of unterminated script statements.
* @param statements with possible unterminated line.
* @return a properly line terminated statement. */
String terminatedScript(String statements) {
if ( statements.endsWith(";") )
return statements;
return statements + ";";
}
// end source and eval
/** Console delegate methods. */
public Reader getIn() { return console.getIn(); }
public PrintStream getOut() { return console.getOut(); }
public PrintStream getErr() { return console.getErr(); }
public final void println( Object o ) { console.println(o); }
public final void print( Object o ) { console.print(o); }
public final void error( Object o ) { console.error(o); }
public void setOut( PrintStream out ) { console.setOut(out); }
public void setErr( PrintStream err ) { console.setErr(err); }
// End ConsoleInterface
/**
Print a debug message on debug stream associated with this interpreter
only if debugging is turned on.
*/
public final static void debug(Object... msg)
{
if ( DEBUG.get() ) {
StringBuilder sb = new StringBuilder();
for ( Object m : msg )
sb.append(m);
Console.debug.println("// Debug: " + sb.toString());
}
}
/*
Primary interpreter set and get variable methods
Note: These are squeltching errors... should they?
*/
/**
Get the value of the name.
name may be any value. e.g. a variable or field
*/
public Object get( String name ) throws EvalError {
try {
Object ret = globalNameSpace.get( name, this );
return Primitive.unwrap( ret );
} catch ( UtilEvalError e ) {
throw e.toEvalError( Node.JAVACODE, new CallStack() );
}
}
/**
Unchecked get for internal use
*/
Object getu( String name ) {
try {
return get( name );
} catch ( EvalError e ) {
throw new InterpreterError("set: "+e, e);
}
}
/**
Assign the value to the name.
name may evaluate to anything assignable. e.g. a variable or field.
*/
public void set(String name, Object value)
throws EvalError {
CallStack callstack = new CallStack(globalNameSpace);
try {
if ( Name.isCompound(name) )
globalNameSpace.getNameResolver(name).toLHS(
callstack, this).assign(value, false);
else // optimization for common case
globalNameSpace.setVariable(name, value, false);
} catch (UtilEvalError e) {
throw e.toEvalError(Node.JAVACODE, callstack);
}
}
/**
Unchecked set for internal use
*/
void setu(String name, Object value) {
try {
set(name, value);
} catch ( EvalError e ) {
throw new InterpreterError("set: "+e, e);
}
}
public void set(String name, long value) throws EvalError {
set(name, new Primitive(value));
}
public void set(String name, int value) throws EvalError {
set(name, new Primitive(value));
}
public void set(String name, double value) throws EvalError {
set(name, new Primitive(value));
}
public void set(String name, float value) throws EvalError {
set(name, new Primitive(value));
}
public void set(String name, boolean value) throws EvalError {
set(name, value ? Primitive.TRUE : Primitive.FALSE);
}
/**
Unassign the variable name.
Name should evaluate to a variable.
*/
public void unset( String name )
throws EvalError
{
/*
We jump through some hoops here to handle arbitrary cases like
unset("bsh.foo");
*/
CallStack callstack = new CallStack();
try {
LHS lhs = globalNameSpace.getNameResolver( name ).toLHS(
callstack, this );
if ( lhs.type != LHS.VARIABLE )
throw new EvalError("Can't unset, not a variable: "+name,
Node.JAVACODE, new CallStack());
lhs.nameSpace.unsetVariable( lhs.getName() );
} catch ( UtilEvalError e ) {
throw new EvalError( e.getMessage(),
Node.JAVACODE, new CallStack(), e);
}
}
// end primary set and get methods
/**
Get a reference to the interpreter (global namespace), cast
to the specified interface type. Assuming the appropriate
methods of the interface are defined in the interpreter, then you may
use this interface from Java, just like any other Java object.
<p>
For example:
<pre>
Interpreter interpreter = new Interpreter();
// define a method called run()
interpreter.eval("run() { ... }");
// Fetch a reference to the interpreter as a Runnable
Runnable runnable =
(Runnable)interpreter.getInterface( Runnable.class );
</pre>
<p>
Note that the interpreter does *not* require that any or all of the
methods of the interface be defined at the time the interface is
generated. However if you attempt to invoke one that is not defined
you will get a runtime exception.
<p>
Note also that this convenience method has exactly the same effect as
evaluating the script:
<pre>
(Type)this;
</pre>
<p>
For example, the following is identical to the previous example:
<p>
<pre>
// Fetch a reference to the interpreter as a Runnable
Runnable runnable =
(Runnable)interpreter.eval( "(Runnable)this" );
</pre>
<p>
<em>Version requirement</em> Although standard Java interface types
are always available, to be used with arbitrary interfaces this
feature requires that you are using Java 1.3 or greater.
<p>
@throws EvalError if the interface cannot be generated because the
version of Java does not support the proxy mechanism.
*/
public Object getInterface( Class<?> interf ) throws EvalError
{