Native CallsΒΆ

Shen runs under a variety of platforms including

Common Lisp
Clojure
Javascript
Python
Scheme
Ruby

Each platform originally had a different method for calling native code from Shen code. In February 2015 a joint agreement was made to streamline these conventions to present an uniform look and feel to native calls. Native calls are handled by package name conventions as follows

Common Lisp lisp
Ruby rb
Scheme scm
Clojure clj
Python py
Javascript js

The platform specific conventions follow.

Common Lisp (c) Mark Tarver 2015

(lisp.reverse [1 2 3 4 5]) in Shen/Common Lisp will apply the Common Lisp function REVERSE to [1 2 3].

In order to load a CL file, the readtable must be reset and re-reset after loading to conform to the differences between CL and Shen wrt case sensitivity. The function load-lisp in the following program will do that; (load-lisp "foo.lsp") will load the CL file foo.lsp.

 (define load-lisp File -> (trap-error
          (let LispReadTable (readtable upcase)
               Load ((protect LOAD) File)
               ShenReadTable (readtable preserve)
               loaded)
               (/. E (do (readtable preserve) (error (error-to-string E))))))

(define readtable
  Case -> (let String 
               (make-string "(SETF (READTABLE-CASE *READTABLE*) ~A)"
                            (cases (= Case upcase) ":UPCASE"
                                   (= Case downcase) ":DOWNCASE"
                                   (= Case preserve) ":PRESERVE"
                                   (= Case invert) ":INVERT"
                                   true (error "case ~A not recognised~%" Case)))
               ((protect EVAL) ((protect READ-FROM-STRING) String)))) 

Note that the CL functions loaded from a CL file in this way will revert to uppercase after load-lisp is finished. Hence a function 'foo' in the CL file wil be invoked by 'FOO' from within Shen.

The Shen reader will read a symbol like foo:bar as foo : bar. Programmers wanting to use package names may want to keep this activity within the native Lisp code. However if you want to invoke a CL packaged function in Shen this can be done through invoking the Lisp reader by READ-FROM-STRING. e.g. In CLisp

(12-) ((READ-FROM-STRING "LISP::CONS") u g)
[u | g]

Ruby (c) Greg Spurrier, 2015

By convention, functionality for interacting with Shen's host platform is placed within a package named with the host language's file extension. Therefore the Ruby interop forms begin with rb..

Constant References

Ruby constants, including classes and modules, are referenced by prefixing them with rb.# and replacing :: with #. For example:

rb.#Hash rb.#Math#PI rb.#RUBY_VERSION Method Invocation

Ruby method invocation uses a syntax inspired by Clojure and looks similar to normal function application. The method name, prefixed with rb., is used as the operator and the receiver is the first operand. Any additional operands are passed to the method as its arguments. For example:

(rb.reverse "hello") (rb.prepend "bye" "good") (rb.sqrt rb.#Math 4)

The [] and []= method names used for accessing and manipulating Ruby arrays and hashes cannot be used directly because the Shen reader interprets [] as an empty list. Use rb.<- and rb.-> instead. These mimic Shen's <-vector and vector-> operations on vectors.

Invoking Class Methods

The last example above invokes the Math module's sqrt class method. A shorter form is provided as a convenience:

(rb.#Math.sqrt 4)

Invocations of Kernel class methods can be further shortened by prefixing the method's name with rb.#. The following are all equivalent:

(rb.require rb.#Kernel "digest/md5") (rb.#Kernel.require "digest/md5") (rb.#require "digest/md5")

Hash Parameters

Many Ruby methods take a Hash object as their final parameter. Ruby provides a special syntax for this which is echoed by ShenRuby. Argument triples of the form 'key => value' are poured into Hash and passed as the method's final argument. For example:

(rb.#puts a => "hello" b => 37)

As in Ruby, the key-arrow-value triples must be the final normal arguments to the method.

Block Parameters

In addition to normal arguments, Ruby methods may also accept blocks. A block argument in ShenRuby is denoted by the symbol & followed by a function. These must be the final two elements in the method's argument list.

For example, to print each character of a string on a separate line using Shen's pr and nl system functions:

(rb.each_char "hello" &1 (/. X (do (pr X) (nl))))

Or, to sum the elements of a list using Ruby's Enumerable#reduce:

(rb.reduce [1 2 3] & +)

This use of reduce is possible because Shen's list and vector types are enumerable in ShenRuby.

Please note that these two examples can--and, in practice, should--be easily implemented in Shen without resorting to Ruby methods. They are here simply to demonstrate ShenRuby's syntax for block arguments.

Type Coercion

Shen booleans, numbers, strings, and symbols are implemented using the corresponding Ruby classes and no coercion is required.

Shen's list and vector types both implement the Ruby Enumerable interface and may be passed directly as arguments to methods expecting instances of Enumerable. When an Array is required, they can be coerced using rb.to_a.

ShenRuby provides two system functions for coercing Ruby Enumerable instances to Shen lists and vectors. These are rb-to-l and rb-to-v, respectively.

Setting Stack Size

Some operations in Shen, notably type checking of complicated types, require more stack space than is available by default in Ruby. If your program encounters a stack overflow, you can increase Ruby's stack size through the following methods.

Beginning in Ruby 2.0.0, the MRI stack size can be overridden via the RUBY_THREAD_VM_STACK_SIZE environment variable. A value of 2097152 is sufficient for the Shen Test Suite to pass under both OS X and Linux. JRuby uses the JVM's stack. It can be increased via the -J-Xss command line argument. A value of 32m (i.e., -J-Xss32m) is sufficient for the Shen Test Suite to pass under both OS X and Linux.

Scheme (c) Bruno Deferrari, 2015

Scheme functions live under the scm namespace (scm. prefix). For example: (scm.write [1 2 3 4]) invokes Scheme's write function with a list as an argument.

To send literal, unprocessed code to the underlying interpreter the scm. form can be used:

(0-) (scm. "(scm.+ 1 2 3 4)")
10

(1-) (scm. "(scm.define (func-name x) (scm.display x) (scm.newline))")
#

(2-) (func-name "test")
test
#

Note that the scm. prefix is still required, because Scheme functions have been imported inside the Shen environment with an scm. prefix, and all compiled code runs inside this environment. Because Scheme functions can have variable numbers of arguments and the code passed to scm. is not preprocessed, any imported function that is intended to support partial application has to be wrapped with a defun:

(3-) (defun for-each (F L) (scm.for-each F L))
for-each

(4-) (for-each (/. X (do (print (+ X X)) (nl))) [1 2 3 4 5])
2
4
6
8
10
#

(5-) (for-each (function print))
#