Adding Ruby to Your SAS Tool-Box

NESUG 2007 Coders' Corner Adding Ruby to Your SAS Tool-Box Daniel Olguin, First Coast Service Options, Jacksonville, FL ABSTRACT With 3 relatively e...
Author: Dustin Shields
1 downloads 4 Views 208KB Size
NESUG 2007

Coders' Corner

Adding Ruby to Your SAS Tool-Box Daniel Olguin, First Coast Service Options, Jacksonville, FL ABSTRACT With 3 relatively easy and painless steps you can add Ruby, a programmer friendly, Object-Oriented language, to your SAS® Tool-Box.

INTRODUCTION As powerful as DATA Step programming is, there are still limitations that can be frustrating such as the inability to write functions or Graphical User Interfaces (GUIs). Thanks to the javaobj and JRuby we can, with very little effort, push beyond these limitations and do things that were once only possible with pricey additional software or jumping through the many hoops imposed by Java for even the simplest of tasks.

JRUBY JRuby is an effort by programmers, Charles Nutter and Thomas Enebo, now working at Sun to port the Ruby language to the Java™ Virtual Machine (JVM). The 1.0 version was released in June of 2007 and is compatible with Ruby 1.8.5. Ruby is an OO language with functional programming capabilities. Its designer, Yukihiro Matsumoto, wanted a language that was concise, succinct and a joy to use. Ruby is a dynamic language like Python and like Python prides itself on having ‘batteries included’. Adding JRuby (and therefore Ruby) to your SAS Tool-Box is done in 3 Steps: 1)

Download JRuby and Install a.

If you don’t already have Java installed then download and install. I’ve tested with both 1.4 and 1.5


Modify the SAS configuration file


Create an Adapter to handle the communication between SAS and JRuby

Download JRuby and Install The JRuby code can be located at: Unzip to a location accessible to your SAS installation. I’m using SAS® Learning Edition 4.1 on my C: drive so I’ll unzip to c:\Java\jruby-1.0.

Set the Environment Variable for JRUBY_HOME to the location you unzipped to. On Windows: 1)

Open the Control Panel


Open System Properties


Select the Advanced Tab and Press Environment Variables


Set JRUBY_HOME to C:\Java\jruby-1.0 (or where ever you unzipped to)


NESUG 2007

Coders' Corner

Modify the SAS configuration file Now locate your sasv9.cfg file. On my machine that’s, C:\Program Files\SAS\SAS Learning Edition 4.1\nls\en\sasv9.cfg.

Important: Make a backup!


Options used when SAS is accessing a JVM for JNI processing


-JREOPTIONS=(-Djava.class.path="C:\Java\jruby-1.0\lib;C:\Java\jruby1.0\lib\myjruby.jar;C:\Java\jruby-1.0\lib\jruby.jar;C:\Java\jruby-1.0\lib\asm2.2.3.jar" -Dsas.jre=public -Djruby.reflection=true)

The changes to be made are in the –JREOPTIONS. The changes you make may need to take into account your existing environment if you are already using the javaobj in your code. The change to the java.class.path is to add the jars necessary for jruby (which will all be located in JRUBY_HOME\lib) and one jar (which I called myjruby.jar) for the Adapter. Sas.jre=public is because I’m using Java 1.5 rather than the jre which comes with the SAS install.

Adapter for SAS to JRuby communication The javaobj requires that calls be done using a specific format. (See the SAS Documentation for details) A simple (not for production) Adapter to get you started is provided at the end of the paper. Compile and jar it as myjruby.jar to match the classpath change in the sasv9.cfg file. You can now reference it in the DATA step: declare JavaObj t ('JRubyAdapterForSAS'); t.exceptionDescribe(1); t.callVoidMethod("Init"); t.exceptionDescribe(1); length result2 $100 myscript $100; /* Define a function in ruby */ script = "def times2(num); return num * 2; end;"; link EvalToString; /* Execute the function with a parameter and return the result */ script = "x=times2(5)"; link EvalToInt; stop; EvalToString: do; t.callStringMethod("evalToString", strip(script), result2); t.exceptionDescribe(1);


NESUG 2007

Coders' Corner

put script= result2=; end; return; EvalToInt: do; t.callIntMethod("evalToInt", script, result); t.exceptionDescribe(1); put script= result=; end; return;

CONCLUSION Three simple steps puts you in position to write and execute OO or functional Ruby code at the DATA step, read and execute ruby files, create GUIs, in short do anything that Ruby (or Java) can do!

REFERENCES Greetings from the Edge: Using javaobj in DATA Step by Richard A. DeVenezia, Remsen, NY A Conversation with Yukihiro Matsumoto,

RECOMMENDED READING The Ruby Way, second Edition, Hal Fulton, Addison Wesley, 2007

CONTACT INFORMATION Your comments and questions are valued and encouraged. Contact the author at: Daniel Olguin 2408 Spring Vale RD Jacksonville, FL 32246 E-mail: [email protected] SAS® and all other SAS Institute Inc. product or service names are registered trademarks or trademarks of SAS Institute Inc. in the USA and other countries. ® indicates USA registration. Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the USA and other countries. Other brand and product names are trademarks of their respective companies.


NESUG 2007

Coders' Corner

The following code is NOT production quality. It is enough to get you started trying out JRuby from the DATA step. Have Fun!

import javax.swing.JFrame; import javax.swing.JTextArea; import javax.swing.JCheckBox; import java.awt.*; import java.awt.BorderLayout;

import; import; import; import; import;

import org.apache.bsf.util.IOUtils; import org.jruby.Ruby; import org.jruby.javasupport.JavaEmbedUtils; import org.jruby.runtime.builtin.IRubyObject;

public class JRubyAdapterForSAS extends JFrame {

private JTextArea textComp; private JCheckBox cbClose;

private Ruby runtime;

public JRubyAdapterForSAS(){

this.setTitle("JRuby Adapter for SAS"); this.setLayout(new BorderLayout()); Container contentPane = this.getContentPane();


NESUG 2007

Coders' Corner

this.textComp = new JTextArea(); contentPane.add(this.textComp, BorderLayout.CENTER); this.textComp.setText("Ready!\n"); this.cbClose = new JCheckBox("Is Done",false); contentPane.add(this.cbClose, BorderLayout.SOUTH); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(new Dimension(700,500)); this.setVisible(true); this.Init(); } public void Init(){ try{ this.logMessage("Attempting to instantiate");

runtime = Ruby.getDefaultInstance(); this.logMessage(runtime.toString()); this.logMessage("JRubyHome: "+runtime.getJRubyHome()); this.logMessage("Runtime Instantiated Successfully!"); } catch(Throwable e){ this.logException(e); } } public void logException(Throwable e){ this.textComp.append("\n\nException: " + e.getMessage()); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); this.textComp.append(sw.toString()); } public void logMessage(String message){ this.textComp.append("\n" + message); } public boolean isDone(){


NESUG 2007

Coders' Corner

return this.cbClose.isSelected(); } public void close(){ this.setVisible(false); this.dispose(); } public String getJRubyHome(){ return runtime.getJRubyHome(); } public String getCurrentDirectory(){ return runtime.getCurrentDirectory(); } public String evalToString(String script){ return this.eval("evalToString", script).toString(); } private IRubyObject eval(String request, String script){ IRubyObject objRuby = null; try{ this.logMessage("\n" + request + " for: " + script); objRuby = runtime.evalScript(script); this.logMessage(objRuby.toString()); } catch(Throwable e){ this.logException(e); throw new RuntimeException(request + " for: " + script + " failed!"); } return objRuby; } private Object rubyToJava(IRubyObject objRuby, Class aclass){ return JavaEmbedUtils.rubyToJava(runtime, objRuby, aclass); } public Long evalToLong(String script){ return (Long) this.rubyToJava(this.eval("evalToLong", script), Long.class);


NESUG 2007

Coders' Corner

} public Integer evalToInteger(String script){ return (Integer) this.rubyToJava(this.eval("evalToInteger", script), Integer.class); } public int evalToInt(String script){ return ((Integer)this.rubyToJava(this.eval("evalToInt", script), Integer.class)).intValue(); } public void evalFile(String filename) throws FileNotFoundException, IOException { this.eval("evalFile", this.getFileContents(filename)); } private String getFileContents(String filename) throws FileNotFoundException, IOException { FileReader in = null; String script = null; try{ in = new FileReader(filename); script = IOUtils.getStringFromReader(in); } catch(FileNotFoundException e){ this.logException(e); throw e; } catch(IOException ex){ this.textComp.append("\n\nException: " + ex.getMessage()); this.logException(ex); throw ex; } return script; } }