CSC/ECE 517 Summer 2008/wiki1 7 n1: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
 
(29 intermediate revisions by the same user not shown)
Line 1: Line 1:
''Ruby's eval can parse and execute an arbitrary string of Ruby code. Does Java have a similar facility? Provide an illustration of what this facility is good for. Compare the ease of writing such code in Ruby vs. writing equivalent code in Java.''
''Ruby's eval can parse and execute an arbitrary string of Ruby code. Does Java have a similar facility? Provide an illustration of what this facility is good for. Compare the ease of writing such code in Ruby vs. writing equivalent code in Java.''


==Evaluation Options in Ruby==
==Flavors of Ruby eval==
The eval method is one of the most powerful features of Ruby.  The Kernel.eval will parse and execute an arbitrary string of legal Ruby source code.  To put it plainly, if your Ruby program can generate a string of valid Ruby code, the Kernel.eval method can evaluate that code.  This facility gives developers the ability to modify the runtime behavior of application.
The eval facility is one of the most powerful features of Ruby.  Kernel.eval method will parse and execute an arbitrary string of legal Ruby source code.  To put it plainly, if your Ruby program can generate a string of valid Ruby code, the Kernel.eval method can evaluate that code.  The eval facility gives developers the ability to modify the runtime behavior of program.  
 
Using eval method is straightforward in Ruby.


===Kernel.eval===
===Kernel.eval===
Using [http://www.ruby-doc.org/core/classes/Kernel.html#M001057 eval] method is straightforward in Ruby. For example:


  eval("puts \"Hello World\"") #--> Hello World
  eval("puts \"Hello World\"") #--> Hello World


===Kernel.eval with Kernel#binding===
This is overly simplistic example, but illustrate the concept well.  You can optionally specify a [http://www.ruby-doc.org/core/classes/Kernel.html#M001082 binding] with the eval method, and the evaluation will be performed in the context of the binding. For example:


  def get_binding
  def get_binding
Line 18: Line 17:
  a = 2
  a = 2
  the_binding = get_binding
  the_binding = get_binding
  eval("puts a", the_binding) #--> 1
  eval("puts a", the_binding)   #--> 1
  eval("puts a")             #--> 2
  eval("puts a")               #--> 2


===Object.instance_eval===
===Object.instance_eval===
In addition to switching context using binding, the [http://www.ruby-doc.org/core/classes/Object.html#M000607 instance_eval] method of Object allows you to evaluate a string or block in the context of an instance of a class. For example:
  class Paginator
  class Paginator
   def initialize
   def initialize
     @page_index = 0
     @page = 1
   end
   end
   def next
   def next_page
     @page_index += 1
     @page += 1
   end
   end
  end
  end
  paginator = Paginator.new
  paginator1 = Paginator.new
  paginator.next
  paginator2 = Paginator.new
  paginator.instance_eval("puts @page_index") #=> 1
  paginator1.next_page
paginator1.instance_eval("def \n current_page \n puts @page \n end")
eval("paginator1.current_page")                                    #=> 2
eval("paginator2.current_page")                                     #=> <NoMethodError>
 
Notice the current_page method is undefined for paginator2 instance of Paginator class, thus the error.


===Module.class_eval===
===Module.class_eval===
Similarly, the [http://www.ruby-doc.org/core/classes/Module.html#M000391 class_eval] or [http://www.ruby-doc.org/core/classes/Module.html#M000389 module_eval] method of Module allows you to evaluate a string or block in the context of a class or module.
You can use class_eval to add methods to a class as well as include other modules in a class.  Consider a slight modification to the example above:
class Paginator
  def initialize
    @page = 1
  end
  def next_page
    @page += 1
  end
end
paginator1 = Paginator.new
paginator2 = Paginator.new
paginator1.next_page
Paginator.class_eval("def \n current_page \n puts @page \n end")
eval("paginator1.current_page") #=> 2
eval("paginator2.current_page") #=> 1
The current_page method is now defined for Paginator class, therefore it is valid for both instances of Paginator.


==Benefits of Runtime Evaluation==
==Benefits of Runtime Evaluation==
There are many scenarios where the eval method can be utilized.
The evaluation facility is good for Metaprogramming.  It allows developers to write programs that are modifiable at run time, and have program write programs.  The simple examples shown in the previous section already demonstrate several powerful concepts, such as calling methods dynamically, and add method to a class at run time.  Here are couple of applications of this facility:
*The ability for program to write programs combine with generative programming concept could save developers tons of time.
*Development of Shell Command application similar to irb ([http://en.wikipedia.org/wiki/Interactive_Ruby_Shell Interactive Ruby Shell]).
*Mathematical Expression Evaluator
*Program that gives users the ability to write scripts that enhance its functions or dictates certain behaviors at runtime (i.e. scripting character behavior in game).


Calling methods dynamically:
==Evaluation Options in Java==
Java does not have eval method in its standard library.  However it is entirely possible to write an interpreter in Java that would provide capabilities like Ruby's eval facility.  Below are a few approaches.
 
===Java Reflection and Compiler API===
Java Reflection API gives you the ability to examine or modify the runtime behavior of applications.  Developers can dynamically load classes, examine private members reflectively, and invoke methods from a string.  However it cannot be used to parse and evaluate just any arbitrary string of Java code.  One workaround is to use generative programming.  Your program generates code on the fly, compile the generated source code into Java bytecodes at runtime, load the compiled class and invoke its methods dynamically.  For instance, in Java 5 you can generate and write code to a file on hard disk, use sun.tools.javac.Main to invoke javac and compile the file into class file, finally using Class.forName of Reflection API to instantiate a new object. Java 6 Compiler API makes it even easier by providing [http://java.sun.com/javase/6/docs/api/javax/tools/JavaCompiler.html JavaCompiler] interface.
 
JavaCompiler Example:
public class HelloWorldCompiler {
    public static void main(String[] args) throws Exception {
        String className = "HelloWorld";
        final String src = genSrc(className);
        JavaFileObject javaFileObject = new SimpleJavaFileObject(new URI(className + ".java"), Kind.SOURCE) {
            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
                return src;
            }
        };
        boolean status = compile(javaFileObject);
        System.out.println("status of compile: " + status);
    }
    private static String genSrc(String className) {
        StringBuilder sb = new StringBuilder();
        sb.append("public class ").append(className).append("\n");
        sb.append("{\n");
        sb.append("public static void main()\n");
        sb.append("  {\n");
        sb.append("    System.out.println(\"Hello World\");");
        sb.append("  }\n");
        sb.append("}\n");
        return sb.toString();
    }
    private static boolean compile(JavaFileObject... source) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticListener<JavaFileObject> listener = new DiagnosticListener<JavaFileObject>() {
            public void report(Diagnostic diagnostic) {
                System.out.println(diagnostic.getCode());
            }
        };
        JavaFileManager fileManager = compiler.getStandardFileManager(listener, null, null);
        final CompilationTask task = compiler.getTask(null, fileManager, listener, null, null, Arrays.asList(source));
        return task.call();
    }
}


===Write your own parser===
Another approach is to write a parser using [https://javacc.dev.java.net/ JavaCC].  JavaCC is a parser generator.  As developer, you writes a grammar specification, and JavaCC converts it to a Java program that can recognize matches to the grammar.  Advanced developers can implement a programming language and translate it to work with Java.  When combined with the concept of compilation at runtime and reflection, this is a powerful solution that can evaluate not only a string of Java code.


Evaluating input from user:
===Script Engines===
Not every developer has the knowledge, and resource to implement a parser using JavaCC.  Fortunately there are third party script engines that run in JVM, such as [http://www.beanshell.org/ BeanShell], Jpython, etc.  Some of the script engines implement their own parsers in JavaCC (see [http://www.jython.org/docs/compile.html Compiling Jython]) to translate well-known scripting language into Java bytecodes.  These engines often provide eval function.  For example using BeanShell, you can obtain a [http://java.sun.com/j2se/1.4.2/docs/api/java/util/Date.html java.util.Date] object from a string of Java code:


Add method to a class at run time
Interpreter i = new Interpreter();
Object result = i.eval("long time = 1212807831703l; new Date(time);");


==Evaluation Options in Java==
===Java Scripting API===
Java does not have eval method in its standard library due its nature of being static typing and a compiled language, not an interpreted language like Ruby.  However it is entirely possible to build an interpreter in Java that would provide similar facility.  Below are just few some approaches:
Java 6 introduces [http://java.sun.com/javase/6/docs/api/javax/script/package-summary.html javax.script] package, a framework that allows Java applications to host script engines.  The framework defines a standardized method for integrating script engines.  The idea is to support third party script engines by dropping any [http://jcp.org/en/jsr/detail?id=223 JSR-223] compliant script engine jar in the CLASSPATH and accessing it from your Java applications.  This mechanism is similar to the way JDBC drivers and JNDI are accessed. Below is an example from Sun, loading a script engine (based on Rhino: JavaScript for Java version 1.6R2) in Java SE 6:
#For simple application, the use of Hashtable with String lookup key would be enough to execute some prewritten piece of code.  This is hardly runtime evaluation.
#Java Reflection API providers developers the ability to examine or modify the runtime behavior of applications.  Developers can dynamically load Classes, examine private members, and invoke methods.  However it cannot be used to parse and evaluate any arbitrary string of Java code. One workaround is to generate code on the fly and write it out to disc, use sun.tools.javac.Main to invoke javac.exe and compile the file into class file, finally using Class.forName of Reflection API to invoke the new code.
#Write a parser using JavaCC.  A parser generator is a tool that reads a grammar specification and converts it to a Java program that can recognize matches to the grammar. In addition to the parser generator itself, JavaCC provides other standard capabilities related to parser generation such as tree building (via a tool called JJTree included with JavaCC), actions, debugging, etc.
#Using third party scripting framework that runs in JVM, such as BeanShell, and Jpython.
#Using Java 1.6 Scripting framework.  It allows Java applications to host script engines.  Third party script engines are supported by dropping any JSR-223 compliant script engine jar in the CLASSPATH and access the same from your Java applications.  This mechanism is similar to the way JDBC drivers, JNDI implementations are accessed.


  import javax.script.*;
  import javax.script.*;
Line 67: Line 137:


==Conclusion==
==Conclusion==
Getting the benefit of runtime evaluation in Java is certainly not impossible, but it isn't easyGenerally Java developers need to determine which of the evaluation options above best suits the applicationUsing third party scripting engine, the developers need to determine if third-party libraries are compatible with the Java version the project will be using.  They need to learn the third party library's API, and syntax for scripting engine.  The learning curve is much more steeper for those choose to writer a parser using JavaCC.  In addition to learning JavaCC syntax, developers must have knowledge in compiler design and have thorough understanding of Java language and grammar.  Java 1.6 introduces Scripting for Java Platform, a powerful scripting framework, but this does not eliminate the fact developers must learn another scripting language to use the framework.  Consider Java 1.6 scripting framework the simplest solution, a simple printing "Hello World" application requires 8 lines of code as opposite to 1 line of code in Ruby. Fortunately with Apache Bean Framework and JRuby to bridge Java and Ruby, developers won't have to choose or another.
Java does not have function like the eval method in its standard library, but it is possible to develop a similar facilityThat said, it is rather difficultIf using third party script engine, Java developers need to determine if third-party libraries are compatible with the Java version the project is currently using or will be using.  And there is the risk of library dependency conflict.  Once a script engine is chosen, Java developers also need to learn the API and likely new syntax used by the script engine.  The learning curve is much more steeper for those choose to write a parser using JavaCC.  In addition to learning JavaCC syntax, developers must have knowledge in compiler design concepts, such as top-down, parse-tree, etc.  Developers must also have thorough understanding of Java language and grammar.  Java 1.6 introduces Scripting for Java Platform, a powerful scripting framework, but it does not eliminate the fact developers must learn another scripting language.  Consider Java 1.6 scripting framework the simplest solution, a simple program printing "Hello World" requires about 8 lines of code (see example from the previous section) as opposite to just 1 line of code in Ruby. Fortunately having frameworks like [http://jakarta.apache.org/bsf/ Jakarta BSF] and [http://jruby.codehaus.org/ JRuby] to bridge Java and Ruby, developers won't have to choose one or another.
 
==Resources==
*[http://www.ruby-doc.org/docs/ProgrammingRuby/ Programming Ruby: The Pragmatic Programmer's Guide]
*[http://tryruby.hobix.com/ try ruby! (in your browser)]
*[http://www.javaworld.com/javaworld/jw-07-2006/jw-0717-ruby.html Ruby for the Java world] by Joshua Fox
*[http://www.javabeat.net/articles/73-the-java-60-compiler-api-1.html The Java 6.0 Compiler API] by Raja
*[http://jruby.codehaus.org/ JRuby]
*[http://www.beanshell.org/intro.html BeanShell]
*[http://java.sun.com/docs/books/tutorial/reflect/index.html Java Tutorial: Reflection API]
*[http://www.linuxjournal.com/article/9604 An Introduction to Metaprogramming] by Ariel Ortiz
*[http://java.sun.com/javase/6/docs/technotes/guides/scripting/programmer_guide/index.html Java Scripting Programmer's Guide]

Latest revision as of 01:05, 12 June 2008

Ruby's eval can parse and execute an arbitrary string of Ruby code. Does Java have a similar facility? Provide an illustration of what this facility is good for. Compare the ease of writing such code in Ruby vs. writing equivalent code in Java.

Flavors of Ruby eval

The eval facility is one of the most powerful features of Ruby. Kernel.eval method will parse and execute an arbitrary string of legal Ruby source code. To put it plainly, if your Ruby program can generate a string of valid Ruby code, the Kernel.eval method can evaluate that code. The eval facility gives developers the ability to modify the runtime behavior of program.

Kernel.eval

Using eval method is straightforward in Ruby. For example:

eval("puts \"Hello World\"") #--> Hello World

This is overly simplistic example, but illustrate the concept well. You can optionally specify a binding with the eval method, and the evaluation will be performed in the context of the binding. For example:

def get_binding
  a = 1
  binding
end
a = 2
the_binding = get_binding
eval("puts a", the_binding)   #--> 1
eval("puts a")                #--> 2

Object.instance_eval

In addition to switching context using binding, the instance_eval method of Object allows you to evaluate a string or block in the context of an instance of a class. For example:

class Paginator
  def initialize
    @page = 1
  end
  def next_page
    @page += 1
  end
end
paginator1 = Paginator.new
paginator2 = Paginator.new
paginator1.next_page
paginator1.instance_eval("def \n current_page \n puts @page \n end") 
eval("paginator1.current_page")                                     #=> 2
eval("paginator2.current_page")                                     #=> <NoMethodError>

Notice the current_page method is undefined for paginator2 instance of Paginator class, thus the error.

Module.class_eval

Similarly, the class_eval or module_eval method of Module allows you to evaluate a string or block in the context of a class or module.

You can use class_eval to add methods to a class as well as include other modules in a class. Consider a slight modification to the example above:

class Paginator
  def initialize
    @page = 1
  end
  def next_page
    @page += 1
  end
end
paginator1 = Paginator.new
paginator2 = Paginator.new
paginator1.next_page
Paginator.class_eval("def \n current_page \n puts @page \n end") 
eval("paginator1.current_page") #=> 2
eval("paginator2.current_page") #=> 1

The current_page method is now defined for Paginator class, therefore it is valid for both instances of Paginator.

Benefits of Runtime Evaluation

The evaluation facility is good for Metaprogramming. It allows developers to write programs that are modifiable at run time, and have program write programs. The simple examples shown in the previous section already demonstrate several powerful concepts, such as calling methods dynamically, and add method to a class at run time. Here are couple of applications of this facility:

  • The ability for program to write programs combine with generative programming concept could save developers tons of time.
  • Development of Shell Command application similar to irb (Interactive Ruby Shell).
  • Mathematical Expression Evaluator
  • Program that gives users the ability to write scripts that enhance its functions or dictates certain behaviors at runtime (i.e. scripting character behavior in game).

Evaluation Options in Java

Java does not have eval method in its standard library. However it is entirely possible to write an interpreter in Java that would provide capabilities like Ruby's eval facility. Below are a few approaches.

Java Reflection and Compiler API

Java Reflection API gives you the ability to examine or modify the runtime behavior of applications. Developers can dynamically load classes, examine private members reflectively, and invoke methods from a string. However it cannot be used to parse and evaluate just any arbitrary string of Java code. One workaround is to use generative programming. Your program generates code on the fly, compile the generated source code into Java bytecodes at runtime, load the compiled class and invoke its methods dynamically. For instance, in Java 5 you can generate and write code to a file on hard disk, use sun.tools.javac.Main to invoke javac and compile the file into class file, finally using Class.forName of Reflection API to instantiate a new object. Java 6 Compiler API makes it even easier by providing JavaCompiler interface.

JavaCompiler Example:

public class HelloWorldCompiler {
   public static void main(String[] args) throws Exception {
       String className = "HelloWorld";
       final String src = genSrc(className);
       JavaFileObject javaFileObject = new SimpleJavaFileObject(new URI(className + ".java"), Kind.SOURCE) {
           public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
               return src;
           }
       };
       boolean status = compile(javaFileObject);
       System.out.println("status of compile: " + status);
   }
   private static String genSrc(String className) {
       StringBuilder sb = new StringBuilder();
       sb.append("public class ").append(className).append("\n");
       sb.append("{\n");
       sb.append("public static void main()\n");
       sb.append("  {\n");
       sb.append("    System.out.println(\"Hello World\");");
       sb.append("  }\n");
       sb.append("}\n");
       return sb.toString();
   }
   private static boolean compile(JavaFileObject... source) {
       JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
       DiagnosticListener<JavaFileObject> listener = new DiagnosticListener<JavaFileObject>() {
           public void report(Diagnostic diagnostic) {
               System.out.println(diagnostic.getCode());
           }
       };
       JavaFileManager fileManager = compiler.getStandardFileManager(listener, null, null);
       final CompilationTask task = compiler.getTask(null, fileManager, listener, null, null, Arrays.asList(source));
       return task.call();
   }
}

Write your own parser

Another approach is to write a parser using JavaCC. JavaCC is a parser generator. As developer, you writes a grammar specification, and JavaCC converts it to a Java program that can recognize matches to the grammar. Advanced developers can implement a programming language and translate it to work with Java. When combined with the concept of compilation at runtime and reflection, this is a powerful solution that can evaluate not only a string of Java code.

Script Engines

Not every developer has the knowledge, and resource to implement a parser using JavaCC. Fortunately there are third party script engines that run in JVM, such as BeanShell, Jpython, etc. Some of the script engines implement their own parsers in JavaCC (see Compiling Jython) to translate well-known scripting language into Java bytecodes. These engines often provide eval function. For example using BeanShell, you can obtain a java.util.Date object from a string of Java code:

Interpreter i = new Interpreter();
Object result = i.eval("long time = 1212807831703l; new Date(time);");

Java Scripting API

Java 6 introduces javax.script package, a framework that allows Java applications to host script engines. The framework defines a standardized method for integrating script engines. The idea is to support third party script engines by dropping any JSR-223 compliant script engine jar in the CLASSPATH and accessing it from your Java applications. This mechanism is similar to the way JDBC drivers and JNDI are accessed. Below is an example from Sun, loading a script engine (based on Rhino: JavaScript for Java version 1.6R2) in Java SE 6:

import javax.script.*;
public class EvalScript {
   public static void main(String[] args) throws Exception {
       // create a script engine manager
       ScriptEngineManager factory = new ScriptEngineManager();
       // create a JavaScript engine
       ScriptEngine engine = factory.getEngineByName("JavaScript");
       // evaluate JavaScript code from String
       engine.eval("print('Hello, World')");
   }
}

Conclusion

Java does not have function like the eval method in its standard library, but it is possible to develop a similar facility. That said, it is rather difficult. If using third party script engine, Java developers need to determine if third-party libraries are compatible with the Java version the project is currently using or will be using. And there is the risk of library dependency conflict. Once a script engine is chosen, Java developers also need to learn the API and likely new syntax used by the script engine. The learning curve is much more steeper for those choose to write a parser using JavaCC. In addition to learning JavaCC syntax, developers must have knowledge in compiler design concepts, such as top-down, parse-tree, etc. Developers must also have thorough understanding of Java language and grammar. Java 1.6 introduces Scripting for Java Platform, a powerful scripting framework, but it does not eliminate the fact developers must learn another scripting language. Consider Java 1.6 scripting framework the simplest solution, a simple program printing "Hello World" requires about 8 lines of code (see example from the previous section) as opposite to just 1 line of code in Ruby. Fortunately having frameworks like Jakarta BSF and JRuby to bridge Java and Ruby, developers won't have to choose one or another.

Resources