Du skriver en lille Java compiler og lader det kompilere det brugeren har skrevet :-))
Beklager, men når et javaprogram en gang er kompileret blive compilerprogrammet lagt væk (det fylder en hulens masse), og den relativt lille Java Virtuelle Maskine udfører så. Det er ikke som i script sprogene hvor programmet løbende vliver kompileret of udført, der er det nemt lige at bede om at få eval'et en streng. I Java må du selv kompilere strengen.
->jword: Ja, det er rigtigt, og man også kalde 'javac' med Runtime.exec().
Men er det nu så enkelt? For så vidt argumentet er en fil, der indeholder en lovlig definition af en klasse, går det godt, men hvis filen f.eks. indeholder et fritstående statement såsom:
Integer i=new Integer(5);
- virker det ikke. I så fald skal der ske en form for "indpakning" af koden i en klasse.
Hvis du jåber på at det vil vær muligt at sige: compile( "Integer iUnik=new Integer(5);" ); og derefter bruge den nye vaiabel: if ( iUnik.intValue() > 4 ) { ... }
er jeg ret sikker på at det skal gøres 'manuelt'. de to linier kompilerer i helt forskellige scopes, så de to anvendelser af variabelnavnet 'iUnik' kan slet ikke se hinanden.
->jakoba: Enig. Men det er den form for funktionalitet jeg er på udkig efter eller lidt mere generelt: Jeg har et ønske om at kunne foretage successiv/step-vis kompilering og eksekvering i en åben Java-session.
Jeg har som nævnt lidt kig på "BeanShell", som faktisk (blandt andet) kan præcis dette, så vidt jeg kan overskue, men jeg vil også gerne forstå, hvordan man teknisk griber det an. Det er nok noget med reflections, tænker jeg.
Jeg lavede nedenstående som demo engang. Det har den fordel at det er kort, men den bagdel at det er meget rudimentært. 2-bogstav variabelnavne, så max 26 variable og den eneste datatype er int, de eneste operatorer er =, *, /, -, + og parenteser.
men måske den kan bruges som skelet til det du vil have :)
/* ** implement a very small interpreter */
import java.util.*; import java.io.*;
class ParseError extends Exception { public ParseError( String message ) { super( message ); } } // endclass ParseError
class RuntimeError extends Exception { public RuntimeError( String message ) { super( message ); } } // endclass RuntimeError
interface Element { // the groups and atoms of a statement public int getValue( ) throws RuntimeError; public int setValue( int value ) throws RuntimeError; } //end interface Element
class Value implements Element {
private int value;
public Value ( int value ) { this.value = value; }
public int getValue( ) throws RuntimeError { return this.value; }
public int setValue( int value ) throws RuntimeError { throw new RuntimeError( "attempt to change the value of a constant." ); }
} //endclass Value
class Variable implements Element {
private static final Variable[] vt = new Variable[26]; // the nametable
private int value; private boolean initialized; private String name;
private Variable ( String name ) { // instances created only by getVariable() this.initialized = false; this.name = name; }
static Variable getVariable( String name ) { for ( int i=0; i<vt.length; i++ ) { // return existing variable if ( vt[i] != null && vt[i].name.equals(name) ) return vt[i]; } for ( int i=0; i<vt.length; i++ ) { // or create a new one if ( vt[i] == null ) return ( vt[i] = new Variable( name ) ); } throw new Error( "Impossible error. More that 26 variables used." ); }
public int getValue( ) throws RuntimeError { if ( this.initialized ) return this.value; throw new RuntimeError( "variable " +name +" not initialized." ); }
public int setValue( int value ) throws RuntimeError { this.initialized = true; return ( this.value = value ); }
} //endclass variable
class Triple implements Element {
private Element operand1, operand2; private char operator;
public Triple ( Element op1, char operator, Element op2 ) { this.operand1 = op1; this.operator = operator; this.operand2 = op2; }
public int getValue() throws RuntimeError { switch ( this.operator ) { case '=': return operand1.setValue( operand2.getValue() ); case '+': return operand1.getValue() + operand2.getValue(); case '-': return operand1.getValue() - operand2.getValue(); case '*': return operand1.getValue() * operand2.getValue(); case '/': { int divisor = operand2.getValue(); if ( divisor != 0 ) return operand1.getValue() / divisor; throw new RuntimeError( "attempted division by 0.'" ); } default: throw new RuntimeError( "unknown operator code '" +operator +"'." ); } }
public int setValue( int value ) throws RuntimeError { throw new RuntimeError( "attempt to change the value of an expression." ); }
} // endclass Triple
// adds a one-token lookahead class Tokenizer extends StringTokenizer {
String next;
public Tokenizer( String s ) { super( s ); this.next = ( hasMoreTokens() ) ? nextToken() : " "; }
static final String names = "ABCDEFGHIJKLMNOPQRSTUVWXUZ"; static final String digits = "0123456789";
static Element lValue() throws ParseError { String name = t.fetchNext(); if ( names.indexOf(name) >= 0 ) return Variable.getVariable( name ); if ( digits.indexOf(name) >= 0 ) return new Value( Integer.parseInt( name ) ); throw new ParseError( "bad left side variable '" +name +"'." ); }
static Element parenthetical() throws ParseError { Element temp = expression(); if ( t.skipPredicted(")") ) return temp; throw new ParseError( "bad parenthetical, ')' expected." ); }
static Element factor() throws ParseError { String nxt = t.fetchNext(); if ( nxt.equals( "(" ) ) return parenthetical(); if ( names.indexOf(nxt) >= 0 ) return Variable.getVariable( nxt ); if ( digits.indexOf(nxt) >= 0 ) return new Value( Integer.parseInt( nxt ) ); throw new ParseError( "Bad Factor at '" +nxt +"'." ); }
static Element statement( String s ) { t = new Tokenizer( s ); try { Element op1 = lValue(); Element op2; if ( t.skipPredicted("=") ) { op2 = expression(); } else { throw new ParseError( "no assignment in statement." ); } if ( t.skipPredicted(";") ) return new Triple( op1, '=', op2 ); throw new ParseError( "missing ';' at end of statement." ); } catch ( ParseError e ) { System.out.println( "error in line: " +s ); System.out.println( "Parsing error: " +e.getMessage() ); } return new Value( -666 ); }
} // endclass Compile
// the main class compile and execute a file containing a simple program class CalcFunction {
static final int MAX = 100; // max 100 statements static Element[] statements = new Element[MAX]; static String[] lines = new String[MAX]; static int programLength = 0;
static String readLine( InputStream reader ) { String line = ""; try { int ch; while ( (ch=reader.read()) != 13 && ch != -1 ) { if ( ch != 10 ) line += (char)ch; } } catch ( Exception e ) { System.out.print( "error in readLine." ); } return line; }
static void compile( String fileName ) { FileInputStream fil; try { fil = new FileInputStream( fileName ); } catch ( Exception e ) { System.out.print( "no such file found." ); throw new Error( "compilation terminated in error." ); } String line = readLine( fil ); while ( ! ( line.equals("") ) ) { lines[programLength] = line; statements[ programLength++ ] = Compile.statement( line ); line = readLine( fil ); } }
static void execute( ) { for ( int i=0; i<programLength; i++ ) { System.out.println( i +": " +lines[i] ); try { System.out.println( " calculated value is " +statements[i].getValue() ); } catch ( RuntimeError e ) { System.out.println ( "statement nr " +i +" failed: " +e.getMessage() ); } } }
A = 5 ; B = A * 2 + 9 ; C = A + B * ( 1 - 1 ) ; C = A + B / ( 1 - 1 ) ; // divide by zero C = 7 + C ; D = B + C + A * 2 ; E = A * ( B + C ) ; F = E / 5 ; G = A + ( B + ( C + ( D + ( E + ( F * 2 ) ) ) ) ) ; 9 = 4 * A ; // assign to a number
*/
/* and gives the below output:
--- Compiling 'c:/testprogram.txt'. error in line: 9 = 4 * A ; Parsing error: bad left side variable '9'.
--- Running 'c:/testprogram.txt'. 0: A = 5 ; should give 5 calculated value is 5 1: B = A * 2 + 9 ; should give 19 calculated value is 19 2: C = A + B * ( 1 - 1 ) ; calculated value is 5 3: C = A + B / ( 1 - 1 ) ; statement nr 3 failed: attempted division by 0.' 4: C = 7 + C ; calculated value is 12 5: D = B + C + A * 2 ; calculated value is 41 6: E = A * ( B + C ) ; calculated value is 155 7: F = E / 5 ; calculated value is 31 8: G = A + ( B + ( C + ( D + ( E + ( F * 2 ) ) ) ) ) ; calculated value is 294 9: 9 = 4 * A ; calculated value is -666
En yderligere kommentar: Det er for så vidt ikke problematisk at kompilere kode i flere omgange ved f.eks. at anvende com.sun.tools.javac.Main.compile() (eller 'javac' + Runtime.exec()) og reflections: ... String klasse; Class c = Class.forName(klasse); Object o = c.newInstance(); Method m = c.getDeclaredMethod("main", ...); m.invoke(o, ...); ...
Eksempel: De enkelte steps tænkes lagret i eksterne filer, som dynamisk indlæses, kompileres og evt. eksekveres, hvis main()-metoden findes. Yderligere foregår dette i en applikation med en tekst-box, hvor koden tastes ind eller filnavnene angives.
/* 1. step: */ public class SampleX { public int i; SampleX() { i=5; } }
/* 2. step */ public class Sample { public static void main(String args[]) { SampleX s=new SampleX(); System.out.println(s.i); } }
Dette går fint. Sample kan oprette et SampleX-objekt uden problemer.
Hvad der imidlertid ikke fungerer er, hvis man forsøger med flg.: /* 3. step */ public class Sample2 { public static void main(String args[]) { System.out.println(s.i); } }
Her er 's.i' ikke kendt. Problemet er altså, hvordan man holder de allerede oprettede referencer "i live" i sessionen.
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.