IFCX Wings for Scripting

The WYSIWYG Literate Programming System

by James P. White

Version 0.7.2-SNAPSHOT

this is Free Software, copying authorized per the GNU General Public License Version 2.

Table of Contents

Introduction 1

Examples 1

Apache Ant 1

GroovyBeans 1

Inject and Curry – calculate the functional way 1

Regular Expressions 1

Conversions 1

Dates 1

Maps 1

Collections 1

Sublists 1

Ranges 1

System Exec 1

XML 1

Apache Ivy 1

Beware the map! 1

Files 1

Database 1

PLEAC 1

Shopping Cart 1

GUI Forms and Map Binding 1

Pretty Printing 1

Serialization 1

Multiple Languages 1

Ruby/JRuby 1

Python/Jython 1

OCaml 1

Jaskell 1

Scala 1

Adenine 1

Sleep 1

Java 1

BeanShell 1

Registering a New Language 2

Roadmap... 2

Why Wings? 2

Introduction

IFCX Wings (http://www.ifcx.org/wiki/Wings.html) aims to bring the aesthetics and ease-of-use of Mathematica's notebook-oriented environment to the Open Source Software world and to do so for arbitrary programming and scripting languages. As a preliminary proof-of-concept, this OpenOffice Writer document contains a macro implemented using Groovy For OpenOffice (http://www.ifcx.org/wiki/GroovyForOpenOffice.html) that enables Groovy scripts to be evaluated in-place. This little mash-up currently does nothing intelligent with syntax, nor does it do any of the cool display formatting that is possible (it simply displays the result of the last expression as a string). Also it is limited to just Groovy script (whereas the idea is to support any language) and the execution environment is within OpenOffice (which has some special potential uses, but a client/server implementation will have more general uses and fewer quirks).

The way Wings works is that you enter some Groovy script into paragraphs with the 'GroovyCode' style, then invoke the 'Wings:WingsEval.groovy' macro (which is bound here via 'Tool:Customize...' to the cmd-Enter key as well as the 'Wings:Evaluate' menu). The result of the last expression will then be displayed as a string in a paragraph with the 'GroovyResult' style. The cursor will then move to the next 'GroovyCode' paragraph, unless an exception occurs in which case it will not move.

If the key binding doesn't work for you (they don't seem to be preserved very well across releases and/or platforms), then you can recreate it (unless you're using OOo 2.2 in which the UI for creating key bindings to macros stored in documents is broken) via the 'Tools:Customize...' dialog. Choose the "Keyboard" tab, then scroll the "Category" pane all the way to the bottom to find the 'OOo Macros' entry. Twiddle the triangles to locate the 'Wings:GroovyEval.groovy' macro in this 'Wings-0_x' document and set the key chord you like.

A simple example:

1 + 2 * 3 / 4

2.5

1 / 2

0.5

If an exception or syntax occurs, that will be displayed in a 'GroovyException' styled paragraph and the cursor will remain where it was.

1 / 0

java.lang.ArithmeticException: / by zero

If you try to do an evaluation and the cursor is not located in a 'GroovyCode' paragraph, then an empty one will be inserted at the cursor's location.

Beware that you should turn off quote replacement in 'Tools:AutoCorrect...:Custom Quotes' or you will get funky syntax errors. Automatic "uncorrection" is one of the features that need to be added to the macro. Another desirable feature is to make undo work as a single action, unfortunately the OpenOffice API does not currently allow that.

Examples

(0..10).collect { it ** 10 }

[0, 1, 1024, 59049, 1048576, 9765625, 60466176, 282475249, 1073741824, 3486784401, 10000000000]



((Integer.MAX_VALUE-5)..Integer.MAX_VALUE).collect { Math.pow(it, 2) }

[4.6116859926575841E18, 4.6116859969525514E18, 4.6116860012475187E18, 4.611686005542486E18, 4.6116860098374533E18, 4.6116860141324206E18]



def factorial(n) {
    BigInteger f = 1
    for (i in 2..n) { f *= i }
    f
}
/* The limit here is just over 5000! because OpenOffice currently can't wrap a single "word" longer than 16383 (2**14 - 1) characters. */
def r = factorial(5000)

80658175170943878571660636856403766975289505440883277824000000000000

def ackermann(m, n) {
  if (m <= 0)
    return n + 1;
  
  if (n <= 0) /* m will be > 0 here */
    return ackermann(m-1, 1);

  /* both m and n will be > 0 */
  return ackermann(m-1, ackermann(m, n - 1));
}

ackermann(3, 5)

253

//BigInteger big(BigInteger n) { n }
// Integer is big enough because we'll run out of space before the numbers get big.
Integer big(Integer n) { n }

hackCache = [([big(0), big(0)]):big(1)]

//def hackermann(BigInteger m, BigInteger n) {
def hackermann(Integer m, Integer n) {
   def cache = hackCache
    
   def stack = [[m, n]]

   while (stack) {
      def top = stack.pop()

      if (cache[top]) {
      } else if (top[0] == 0) {
         cache[top] = big(top[1] + 1)
      } else if (top[1] == 0) {
         def r = cache[[big(top[0].minus(1)), big(1)]]
         if (r) {
             cache[top] = r
         } else {
            stack.push(top)
            stack.push([big(top[0].minus(1)), big(1)])
         }
      } else {
         def r = cache[[top[0], big(top[1].minus(1))]]
         if (r) {
             def s = cache[[big(top[0].minus(1)), r]]
             if (s) {
                 cache[top] = s
             } else {
                 stack.push(top)
                 stack.push([big(top[0].minus(1)), r])
             }
         } else {
             stack.push(top)
             stack.push([top[0], big(top[1].minus(1))])
         }
      }
   }

   return cache[[m, n]]
}
// With some hacking we can get a little further. Careful, this takes a while to run...
// hackermann(4, 1)
hackermann(2, 1)

5

results = []
10.times { results << it } // Change the 10 to something else to see an assertion failure.
assert results == [0,1,2,3,4,5,6,7,8,9]
results

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Wings can handle standard output and error now. Input is RSN.

10.times { println it }

0
1
2
3
4
5
6
7
8
9


The Iterable.grep(Filter) method returns a list of those elements where the filter.isCase(element) returns Groovy true.

zf = new java.util.zip.ZipFile(new File('/Users/jim/Projects/Groovy/groovy-1.0/embeddable/groovy-all-1.0.jar'))

zf.entries().grep { it =~ /Ant/ }.sort()

[org/codehaus/groovy/antlr/AntlrParserPluginFactory.class, org/codehaus/groovy/antlr/AntlrParserPlugin$1.class, org/codehaus/groovy/antlr/AntlrASTProcessor.class, org/codehaus/groovy/antlr/syntax/AntlrClassSource.class, org/codehaus/groovy/antlr/AntlrParserPlugin.class, org/codehaus/groovy/antlr/AntlrSourceSummary.class, org/codehaus/groovy/antlr/AntlrASTProcessSnippets.class, org/codehaus/groovy/ant/AntProjectPropertiesDelegate.class, groovy/util/AntBuilder.class, groovy/util/AntBuilderLocator.class]

Apache Ant

Ant is a great way to do things with files. The Groovy AntBuilder supplies the fileScanner pseudo-task that lets you iterate over filesets. Filesets have filtering options to cover pretty much every conceivable situation. Note that the type to supply to attributes like 'basedir', 'dir', or 'file' is java.io.File (rather than simply a path string).

ant = new AntBuilder()
(ant.fileScanner {
   fileset(dir:'${user.home}/Projects/Groovy/groovy-1.0/src/test/gls', includes:'**/*.groovy')
}).collect { it }

[/Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/CompilableTestSupport.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/ch06/s05/GName1Test.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/ch08/s04/FormalParameterTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/ch08/s04/RepetitiveMethodTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/BlockScopeVisibilityTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/ClassVariableHidingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/CompilableTestSupport.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/MultipleDefinitionOfSameVariableTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/NameResolvingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/StaticScopeTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/AssertTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldClosureSyntaxRemovalTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldPropertySyntaxRemovalTest.groovy]

def glob(File dir, String pat) {
   (new AntBuilder().fileScanner {
      fileset(dir:dir, includes:pat)
   }).collect { it }
}

glob (new File('/Users/jim/Projects/Groovy'), 'groovy-1.0/src/*/gls/*/*.groovy')

[/Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/BlockScopeVisibilityTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/ClassVariableHidingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/CompilableTestSupport.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/MultipleDefinitionOfSameVariableTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/NameResolvingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/StaticScopeTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/AssertTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldClosureSyntaxRemovalTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldPropertySyntaxRemovalTest.groovy]

If you like a method so much that you want to do Groovy's trick of "adding methods" to an existing class that you can't otherwise extend, you can use the Category feature. A Category is a class, which when specified in a 'use (category [, category]*) { statements }' statement, causes the static methods to be matched by their first parameter.

class GlobberCat {
   static Iterator glob(File dir, String pat) {
      (new AntBuilder().fileScanner {
         fileset(dir:dir, includes:pat)
      }).iterator()
   }
}

use (GlobberCat) {
   new File('/Users/jim/Projects/Groovy').glob('groovy-1.0/src/*/gls/*/*Test.groovy').collect { it }
}

[/Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/BlockScopeVisibilityTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/ClassVariableHidingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/MultipleDefinitionOfSameVariableTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/NameResolvingTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/scope/StaticScopeTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/AssertTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldClosureSyntaxRemovalTest.groovy, /Users/jim/Projects/Groovy/groovy-1.0/src/test/gls/syntax/OldPropertySyntaxRemovalTest.groovy]

This is some ordinary text.

GroovyBeans

class Country
{
  String name
  String capital
}

def world = [new Country(name:"Austria", capital:"Vienna")
             , new Country(name:"Canada", capital:"Ottawa")
             , new Country(name:"United States of America", capital:"Washington, DC")]

world.collect { country -> "The capitol of ${country.name} is *in* ${country.capital}." }

[The capitol of Austria is *in* Vienna., The capitol of Canada is *in* Ottawa., The capitol of United States of America is *in* Washington, DC.]

Groovy supports literal values for Maps and Lists. A new instance is created for each evaluation of the literal (unlike String, GString?, and Class literals).

def v = [*0..2]
def u = [*0..2]
def x = [:]
2.times { u[it] = [:] ; v[it] = x }
[u[0].is(u[1]), v[0].is(v[1])]

[false, true]

Inject and Curry – calculate the functional way

Fun with closures. Collection.inject takes an initial value and a closure for two parameters. The first parameter is the previous (or initial) value and the second is the current element of the collection. The result is the last value returned.

def max = [1,2,31,-5,99,15].inject(null) { lastNum, num ->
     (lastNum < num) ? num : lastNum
}
assert max == 99
max

99

Null is less than any number.

null > Integer.MIN_VALUE

false

Regular Expressions

The java.lang.String.split method chops up a string given a string that is a regex pattern and returns an array of strings. Notice the curly braces in the printed result. Also Groovy's string quote stripping doesn't descend into the array elememts.

def twister = "She sells sea shells at the sea shore of Seychelles."
twister.split(/ /)

{"She", "sells", "sea", "shells", "at", "the", "sea", "shore", "of", "Seychelles."}

This uses Groovy's regex matcher operator and a pattern which matches runs of "word" class characters. A matcher is iterable, but doesn't have a helpful printed form, so we run collect on it to gather up the matching strings into a list. Notice the square brackets indicating a list and the quotes are stripped from the elements that are strings.

def twister = "She sells sea shells at the sea shore of Seychelles."
(twister =~ /\w+/).collect { it }

[She, sells, sea, shells, at, the, sea, shore, of, Seychelles]

The ==~ operator is true if the regex matches the entire string. The grep method chooses items from a collection if the closure returns true.

System.getProperties().keySet().grep { it ==~ /java.*e/ }

[java.home, java.vm.name, java.runtime.name, java.vm.specification.name, java.specification.name]

The =~ operator is true if the regex matches anywhere.

System.getProperties().grep { it.key =~ /sanitize/ }

[groovy.sanitized.stacktraces=org.codehaus.groovy.runtime.
                org.codehaus.groovy.
                groovy.lang.
                gjdk.groovy.lang.
                sun.
                java.lang.reflect.
                org.ifcx.wings.
                java.lang.Thread]

The following is adapted from Mastering Regular Expressions (3rd Edition) by Jeffrey Friedl (Chapter 8; Java, pg 378):

String url = 'http://regex.info:9090/blog'
String regex = '(?x) ^(https?):// ([^/:]+) (?::(\\d+))?'
java.util.regex.Matcher m = java.util.regex.Pattern.compile(regex).matcher(url);

def result
if (m.find())
{
  result = """Overall [${m.group()}] (from ${m.start()} to ${m.end()})
Protocol [${m.group(1)}] (from ${m.start(1)} to ${m.end(1)})
Hostname [${m.group(2)}] (from ${m.start(2)} to ${m.end(2)})
""" +
  // Group #3 might not have participated, so we must be careful here
  ((m.group(3) == null) ? "No port; default of '80' is assumed"
     : "Port is [${m.group(3)}] (from ${m.start(3)} to ${m.end(3)})")

}
result

Overall [http://regex.info:9090] (from 0 to 22)
Protocol [http] (from 0 to 4)
Hostname [regex.info] (from 7 to 17)
Port is [9090] (from 18 to 22)

This is a slightly modified version with a few more Groovyisms.

String url = 'http://regex.info/blog'
// Note a correction from original regex in the text which ended: ... (?:(\\d+))?
String regex = '(?x) ^(https?):// ([^/:]+) (?::(\\d+))?'
def matcher = url =~ regex

def result
if (matcher.find())
{
  result = """Overall [${matcher.group()}] (from ${matcher.start()} to ${matcher.end()})
Protocol [${matcher[0][1]}] (from ${matcher.start(1)} to ${matcher.end(1)})
Hostname [${matcher[0][2]}] (from ${matcher.start(2)} to ${matcher.end(2)})
""" +
  // Group #3 might not have participated, so we must be careful here
  ((matcher[0][3] == null) ? "No port; default of '80' is assumed"
     : "Port is [${matcher[0][3]}] (from ${matcher.start(3)} to ${matcher.end(3)})")

}
result

Overall [http://regex.info] (from 0 to 17)
Protocol [http] (from 0 to 4)
Hostname [regex.info] (from 7 to 17)
No port; default of '80' is assumed

This CSV regex processor is also from Mastering Regular Expressions.

def csvregex = /\G(?:^|,)(?:"([^"]*+(?:""[^"]*+)*+)"|([^",]*+))/
def matcher = '1," 4311400","GOURMET COFFEE SYSTEMS, INC", 33046' =~ csvregex
def quotes = '' =~ /""/
def fields = []

while (matcher.find()) {
   if (matcher.start(2) >= 0) {
      fields.add(matcher.group(2))
   } else {
      fields.add(quotes.reset(matcher.group(1)).replaceAll(/"/))
   }
}
fields.collect { field -> "*$field*" }

[*1*, * 4311400*, *GOURMET COFFEE SYSTEMS, INC*, * 33046*]

Here it is in a few less lines...

def instring = '1," 4311400","GOURMET COFFEE SYSTEMS, INC", 33046'

def csvregex = /\G(?:^|,)(?:"([^"]*+(?:""[^"]*+)*+)"|([^",]*+))/
def quotes = '' =~ /""/

def fields = []

def matcher = instring =~ csvregex
while (matcher.find()) {
   fields.add((matcher.start(2) >= 0) ? matcher.group(2) : quotes.reset(matcher.group(1)).replaceAll(/"/))
}

fields.collect { field -> "*$field*" }

[*1*, * 4311400*, *GOURMET COFFEE SYSTEMS, INC*, * 33046*]

Conversions

1.0 + '2'

1.02

1 + '2'

12

1 + ('2' as BigInteger)

3

1 + ('2' as BigDecimal)

3

1 + ('2' as float)

3.0

1 + ('2' as int)

51

1 + ('22' as int)

org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '22' with class 'java.lang.String' to class 'java.lang.Integer'

(1.0).class

class java.math.BigDecimal

def plusBD(BigDecimal a, BigDecimal b) { a + b }
plusBD('1', '2')

groovy.lang.MissingMethodException: No signature of method: Script41.plusBD() is applicable for argument types: (java.lang.String, java.lang.String) values: {"1", "2"}

Dates

The plus operator is overloaded for Date.

new Date() + 7

Tue May 13 14:58:14 PDT 2008

But oddly enough minus is not (yet).

new Date() - new Date('1/1/2007')

groovy.lang.MissingMethodException: No signature of method: java.util.Date.minus() is applicable for argument types: (java.util.Date) values: {Mon Jan 01 00:00:00 PST 2007}

Maps

Iterating on a map will apply a closure taking either one or two parameters. If it is two parameters then the first is the MapEntry.key and the second is the MapEntry.value.

[a:1, b:2, c:3].collect { key, value -> value + 10 }

[11, 12, 13]

Modifying the value property of a Map.Entry modifies the underlying map (unless it is immutable of course).

def map = ['a':1, 'b':2, 'c':3]
def doubler = { key, value -> map[key] = value * 2 }
map.each (doubler)
map

{a=2, b=4, c=6}

We don't need to define a variable to hold a closure of course, we can just supply one as a literal and Groovy also lets us omit the parenthesis. Furthermore, in Groovy 1.5 the value of the each method is the target (previously it was void). And finally, the key of a map literal entry is a string by default, so quotes are not needed.

def map = [a:1, b:2, c:3, d:5]
map.each { it.value *= 4 }

{a=4, b=8, c=12, d=20}

Of couse we don't need a variable for the map, as a literal can be operated on just as well.

[a:1, b:2, c:3, d:5].each { it.value += it.key.toUpperCase() as int }

{a=66, b=68, c=70, d=73}

Sometimes you want a map literal but some or all of the keys are computed rather than being literal themselves. In that case enclose the key value in parenthesis.

[(10.power(3)):'kilo', (10.power(6)):'mega', (10.power(9)):'giga']

{1000=kilo, 1000000=mega, 1000000000=giga}

Collections

[1, [2, 3, [4, 5]], 6].flatten()

[1, 2, 3, 4, 5, 6]

[1, 2, 3].collect { it * 2 }

[2, 4, 6]

Sublists

Subscripting a list using a range results in a sublist. Negative numbers in ranges refer to the end of the list (the last element is -1, the one before that is -2, etc.).

If you have a collection and then access it as a property or a method call, and that property or method call doesn't exist for the collection's class, then the property acces or method call will be performed on each element of the collection and the resulting values are collected and returned as a list.

def tokens = [[num:1], [num:2], [num:3], [num:4], [num:5]]
assert tokens[-1..-3] == [[num:5], [num:4], [num:3]]
assert tokens[-1..-3].num == [5, 4, 3]
assert tokens[-3..-1].num == [3, 4, 5]
assert tokens[-1..-3].num.sum() == 12


Ranges

Ranges are lists.

[(10..20).contains(5), (10..20).contains(21)]

[false, false]

[(10..20).contains(15), (10..20).contains(20), (10..20).contains(10)]

[true, true, true]

The elements of a range are not ranges.

(10..20).contains(14..16)

false

So to find out if one range subsumes another, then test if all the elements are in the list (Collection.containsAll).

(10..20).containsAll(14..19)

true

(10..20).containsAll(15..25)

false

Groovy has inclusive and exclusive range limits. It also has relative-to-the-end-of-a-list indicies which are negative values. Ranges can have negative limits of course, but you can’t combine the two in quite the way you might expect.

[(14..-1), (-14..-1), (14..<-1), (-14..<-1), (14..0), (-14..0), (14..<0), (-14..<0)]

[14..-1, -14..-1, 14..0, -14..-2, 14..0, -14..0, 14..1, -14..-1]

(1..3).class

class groovy.lang.IntRange

(1..3).properties

{reverse=false, to=3, class=class groovy.lang.IntRange, from=1, toInt=3, fromInt=1, empty=false}

[(14..<-1), (-14..<-1), (14..<0), (-14..<0)]

[14..0, -14..-2, 14..1, -14..-1]

def a = [1,2,3,4,5]
a[1..-2]

[2, 3, 4]

def a = [1,2,3,4,5]
def r = 1..<-1
a[r]

[2, 1]

At the SDJUG they neeeded a random number for the raffle...

list = [*1..33]
Collections.shuffle(list)
list

[9, 19, 26, 14, 32, 12, 15, 17, 23, 16, 18, 8, 33, 2, 3, 25, 21, 4, 28, 13, 27, 5, 7, 6, 1, 29, 24, 20, 10, 30, 11, 22, 31]

Someone asked for help converting some Perl to Groovy for displaying the Cryllic alphabet:

('\u0410'..'\u0430').join()

АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯа

And they wanted it in a UTF-8 file:

new File(System.getProperty('user.home'), 'cyrillic.txt').write(('\u0410'..'\u0430').join(), 'UTF-8')


System Exec

One of Groovy's more amusing features is the 'execute' method on strings, which returns a j.l.Process. Groovy then has the helper method 'getText' which will gather up the output of the process into a string. These little examples usually won't do much on Windows (unless you've done something clever with Cygwin), but you can substitute the name of your favorite executable (like command.exe) too make it go.

'uname'.execute().text

Darwin

'ls /Users/jim/Movies'.execute().text

2004_Strictly_West_Coast.wmv
48InchCrash.mpg
Easter Bunnies.mpg
MicroFlyingBot.wmv
Other
Robotics.mov
Thumbs.db
fillmaff.wmv
genesis.mov
jtgma.wmv
santa1.mpeg
santa2.mpeg

Of course that uses the system's exec mechanism, so we miss nice things shell features like wild cards. Working through a shell is a way to solve that.

commandList = [ 'ls -d ~/[a-l]*', 'set | grep HOME' ]
process = [ 'bash' , '-c' , commandList.join ( ';' ) ].execute ( )
process.text

/Users/jim/environment.plist
/Users/jim/filter.groovy
/Users/jim/foo.html
/Users/jim/groovy
/Users/jim/ifcx-properties.xml
BASH_EXECUTION_STRING='ls -d ~/[a-l]*;set | grep HOME'
HOME=/Users/jim
JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home
OPENOFFICE_MOZILLA_FIVE_HOME=/Applications/NeoOffice.app/Contents/MacOS

Java's Runtime.exec expects a funky format for the environment variables (a legacy from Java 1.0). Obviously it should use a Map these days, but it doesn't currently.

ENV = [:]
ENV.MYVAR = 'Foo Bar'
ENV.X = 'Baz'
String[] ENVtoArray() { ENV.collect { k, v -> "$k=$v" } }
ENVtoArray()

{"MYVAR=Foo Bar", "X=Baz"}

"bash -c set".execute(ENVtoArray(), null).text

BASH=/bin/bash
BASH_ARGC=()
BASH_ARGV=()
BASH_EXECUTION_STRING=set
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="3" [1]="2" [2]="17" [3]="1" [4]="release" [5]="i386-apple-darwin9.0")
BASH_VERSION='3.2.17(1)-release'
DIRSTACK=()
EUID=501
GROUPS=()
HOSTNAME=ip70-187-187-207.oc.oc.cox.net
HOSTTYPE=i386
IFS=$' \t\n'
MACHTYPE=i386-apple-darwin9.0
MYVAR='Foo Bar'
OPTERR=1
OPTIND=1
OSTYPE=darwin9.0
PATH=/usr/gnu/bin:/usr/local/bin:/bin:/usr/bin:.
PPID=13215
PS4='+ '
PWD=/
SHELL=/bin/bash
SHELLOPTS=braceexpand:hashall:interactive-comments
SHLVL=1
TERM=dumb
UID=501
X=Baz
_=/bin/bash

For mathematical folks, being able to handle formulas for both input and output by integrating with symbolic math engines, which can be done using the scripting engine framework API just like we do for any other language, would be the cat's meow. Of course there are other language processors that Wings aims to integrate such as Scilab, GNU Octave, Rlab, Matrex, Matlab, etc. that would be of more interest to folks who like formulas.


// Formula's text form: (199 + 2.6 %e) over %pi
(199 + 2.6 * Math.E) / Math.PI

65.59333289709824



words = []
['one two three', 'four', '+', 'five six'].join().eachMatch(/\w+/) { words += it[0] }
assert words == ['one', 'two', 'threefour', 'five', 'six']
println words

[one, two, threefour, five, six]


words

[one, two, threefour, five, six]



print 'Hello World'

Hello World


([*0..4]).each { println it }

0
1
2
3
4

[0, 1, 2, 3, 4]

println "123".readLines()

[123]


XML

def nodeToHTML(node)
{
   def children = node.children()
   if (children.size()) {
      return """<li><span class="folder">${node.name()}</span>
    <ul>${children.collect { nodeToHTML(it) }.join('\\n')}</ul>
</li>
"""
   } else {
      return """<li><span class="file">${node.name()}</span></li>"""
   }
}

def xml = new XmlSlurper().parseText('''<Folder>
  <Item/>
  <Subfolder>
      <Item/>
      <Item/>
  </Subfolder>
</Folder>''')

nodeToHTML(xml)

<li><span class="folder">Folder</span>
    <ul><li><span class="file">Item</span></li>\n<li><span class="folder">Subfolder</span>
    <ul><li><span class="file">Item</span></li>\n<li><span class="file">Item</span></li></ul>
</li>
</ul>
</li>

Apache Ivy

Wings integrates Apache Ivy ("the agile package manager") for adding other packages to the classpath. By default Ivy configured to access the public Maven 2 repository (Ibiblio) along with a local per-user cache.

The current form of integration is that you specify an Ivy File using a Groovy MarkupBuilder embedded in the global 'XWINGS.IVY'. That will also result in the dependencies being resolved and added to the Wings classpath for this document. No progress feedback is currently provided by Wings, so you will have to watch your system console to find out what is happening when it takes a long time (which may happen for example if it has to download a large artifact). If the resolution is successful, the array of artifacts is the result so you can see what was set on the classpath.

A future IFCX development will be annotation processors (both Javadoc-style and Java 5 Annotations) to simplify and automate dependency management. So that the "@use" annotation in this script would be equivalent to the following Ivy file.

/**
 * @use org:'commons-lang', name:'commons-lang', rev:'2.4'
 */

xwings.uses(org:'commons-lang', name:'commons-lang', rev:'2.4')

XWINGS: No ivy:settings found for the default reference 'ivy.instance'. A default instance will be used
XWINGS: [null] no settings file found, using default...
XWINGS: [null] :: loading settings :: url = jar:file:/Users/jim/Library/Preferences/NeoOffice-2.2/user/uno_packages/cache/uno_packages/2LgJpq_/GroovyScripting.oxt/external_jars/org.apache.ivy_2.0.0.rc1_20080502133514.jar!/org/apache/ivy/core/settings/ivysettings.xml
XWINGS: [resolve] :: resolving dependencies :: UNKNOWN#WingsDocument;working@ip70-187-187-207.oc.oc.cox.net
XWINGS: [resolve] confs: [default]
XWINGS: [resolve] found commons-lang#commons-lang;2.4 in public
XWINGS: [resolve] :: resolution report :: resolve 112ms :: artifacts dl 2ms
XWINGS: ---------------------------------------------------------------------
XWINGS: | | modules || artifacts |
XWINGS: | conf | number| search|dwnlded|evicted|| number|dwnlded|
XWINGS: ---------------------------------------------------------------------
XWINGS: | default | 1 | 0 | 0 | 0 || 1 | 0 |
XWINGS: ---------------------------------------------------------------------

[file:/Users/jim/.ivy2/cache/commons-lang/commons-lang/jars/commons-lang-2.4.jar]

import org.apache.commons.lang.WordUtils;

String message = "hello ivy !";

[ "standard message" :message
 , "capitalized by " : WordUtils.class.name
 , "capitalized" : WordUtils.capitalizeFully(message)]

{standard message=hello ivy !, capitalized by =org.apache.commons.lang.WordUtils, capitalized=Hello Ivy !}



/**
 * @use org:'net.sf.json-lib', name:'json-lib', rev:'2.2.1', conf:'default->runtime', classifier:'jdk15'
 */

/*
xwings.context.loglevel = 'warn'

// This is basically what it looks like internally.

XWINGS.IVY {
   info(organisation:"org.ifcx", module:"WingsIvyTest")
   dependencies {
       dependency(org:'net.sf.json-lib', name:'json-lib', rev:'2.2.1', conf:'default->runtime')
       {
           artifact(name:'json-lib', type:'jar', 'm2:classifier':'jdk15')
               //, url:'http://repo1.maven.org/maven2/net/sf/json-lib/json-lib/2.2.1/json-lib-2.2.1-jdk15.jar')
       }
   }
}
*/

// xwings.uses (org:'net.sf.json-lib', name:'json-lib', rev:'2.2.1', conf:'default->runtime', classifier:'jdk13')

xwings.uses (org:'net.sf.json-lib', name:'json-lib', rev:'2.2.1', conf:'default->runtime', classifier:'jdk15')

XWINGS: No ivy:settings found for the default reference 'ivy.instance'. A default instance will be used
XWINGS: [null] no settings file found, using default...
XWINGS: [null] :: loading settings :: url = jar:file:/Users/jim/Library/Preferences/NeoOffice-2.2/user/uno_packages/cache/uno_packages/2LgJpq_/GroovyScripting.oxt/external_jars/org.apache.ivy_2.0.0.rc1_20080502133514.jar!/org/apache/ivy/core/settings/ivysettings.xml
XWINGS: [resolve] :: resolving dependencies :: UNKNOWN#WingsDocument;working@ip70-187-187-207.oc.oc.cox.net
XWINGS: [resolve] confs: [default]
XWINGS: [resolve] found net.sf.json-lib#json-lib;2.2.1 in public
XWINGS: [resolve] found commons-beanutils#commons-beanutils;1.7.0 in public
XWINGS: [resolve] found commons-logging#commons-logging;1.0.3 in public
XWINGS: [resolve] found commons-collections#commons-collections;3.2 in public
XWINGS: [resolve] found commons-lang#commons-lang;2.3 in public
XWINGS: [resolve] found commons-logging#commons-logging;1.1 in public
XWINGS: [resolve] found log4j#log4j;1.2.12 in public
XWINGS: [resolve] found logkit#logkit;1.0.1 in public
XWINGS: [resolve] found avalon-framework#avalon-framework;4.1.3 in public
XWINGS: [resolve] found javax.servlet#servlet-api;2.3 in public
XWINGS: [resolve] found net.sf.ezmorph#ezmorph;1.0.4 in public
XWINGS: [resolve] :: resolution report :: resolve 518ms :: artifacts dl 10ms
XWINGS: [resolve] :: evicted modules:
XWINGS: [resolve] commons-logging#commons-logging;1.0.3 by [commons-logging#commons-logging;1.1] in [default]
XWINGS: ---------------------------------------------------------------------
XWINGS: | | modules || artifacts |
XWINGS: | conf | number| search|dwnlded|evicted|| number|dwnlded|
XWINGS: ---------------------------------------------------------------------
XWINGS: | default | 11 | 0 | 0 | 1 || 10 | 0 |
XWINGS: ---------------------------------------------------------------------

[file:/Users/jim/.ivy2/cache/avalon-framework/avalon-framework/jars/avalon-framework-4.1.3.jar, file:/Users/jim/.ivy2/cache/commons-beanutils/commons-beanutils/jars/commons-beanutils-1.7.0.jar, file:/Users/jim/.ivy2/cache/commons-collections/commons-collections/jars/commons-collections-3.2.jar, file:/Users/jim/.ivy2/cache/commons-lang/commons-lang/jars/commons-lang-2.3.jar, file:/Users/jim/.ivy2/cache/commons-logging/commons-logging/jars/commons-logging-1.1.jar, file:/Users/jim/.ivy2/cache/javax.servlet/servlet-api/jars/servlet-api-2.3.jar, file:/Users/jim/.ivy2/cache/log4j/log4j/jars/log4j-1.2.12.jar, file:/Users/jim/.ivy2/cache/logkit/logkit/jars/logkit-1.0.1.jar, file:/Users/jim/.ivy2/cache/net.sf.ezmorph/ezmorph/jars/ezmorph-1.0.4.jar, file:/Users/jim/.ivy2/cache/net.sf.json-lib/json-lib/jars/json-lib-2.2.1-jdk15.jar]

import net.sf.json.*

//def strAsJsonArray = "[1,2,3]" as JSONArray
[JSONArray.fromObject("[1, 2, 3]"), JSONObject.fromObject("{integer:1, boolx: true}")]

[[1,2,3], {"integer":1,"boolx":true}]

Beware the map!

It is dangerous to use property access ('object.class') when trying to get the class of an object of unknown type. That is because if the object happens to be a map, then the property name will be used to do a get on the map and not fall through to the getter (Object.getClass()) as you're intending with the shorthand. Ditto for Object.getMetaClass(). But since we know we're dealing with an enumeration here, we can favor the terse style. This example shows calling a literal closure, which also illustrates why Groovy doesn't permit statement blocks simply enclosed by curly braces (as it would be ambiguous with closures).

enum Day { mon, tue, wed }
{ e -> "${e.class.name}.$e" } (Day.mon)

Day.mon

Day.tue

tue

// Can't do this because we can't know if it is a statement or a closure...
{ 2 }

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, Script32.groovy: 2: Ambiguous expression could be either a parameterless closure expression or an isolated open code block;
   solution: Add an explicit closure parameter list, e.g. {it -> ...}, or force it to be treated as an open block by giving it a label, e.g. L:{...} @ line 2, column 1.
1 error

Files

def isDirectoryEmpty(File baseDir)
{
   List dirlist = [baseDir]

   while (dirlist) {
      File dir = dirlist.pop()
      File[] files = dir.listFiles()
      for (f in files) {
          if (f.isFile()) return false
          dirlist.push(f)
      }
   }

   return true
}

[new File('.').getCanonicalPath(), isDirectoryEmpty(new File('/tmp')), isDirectoryEmpty(new File('/tmp/foobar'))]

[/, false, true]

Database

Sample code from http://groovy.codehaus.org/Embedded+Derby+DB+examples.

First we add the JDBC driver to the classpath using the public Maven artifact.

xwings.uses (groupId:'hsqldb', artifactId:'hsqldb', version:'1.8.0.7')

[file:/Users/jim/.ivy2/cache/hsqldb/hsqldb/jars/hsqldb-1.8.0.7.jar]

The original batch style:

import groovy.sql.*
import java.sql.*

Class.forName('org.hsqldb.jdbcDriver')

protocol = "jdbc:hsqldb:";
def props = new Properties();
// props.put("user", "user1");
// props.put("password", "user1");

sql = Sql.newInstance(protocol + "mem:people", props);
 
/* Creating table, adding few lines, updating one */
sql.execute("""create table people(id int, name varchar(40), second_name
varchar(40), phone varchar(30), email varchar(50))""");

println("Created table 'people'");

sql.execute("insert into people values (1,'John', 'Doe', '123456','johndoe@company.com')");
sql.execute("insert into people values (2,'Bill', 'Brown', '324235','billbrown@company.com')");
sql.execute("insert into people values (3,'Jack', 'Daniels', '443323','jackdaniels@company.com')");

println("Inserted people");

sql.execute("update people set phone='443322', second_name='Daniel''s'where id=3");

println("Updated person");

/* Simple query */
def rows = sql.rows("SELECT * FROM people ORDER BY id");
rows.each {println it}

/* Dropping table 'people' */
sql.execute("drop table people")
println ("Table 'people' dropped")

Created table 'people'
Inserted people
Updated person
{ID=1, NAME=John, SECOND_NAME=Doe, PHONE=123456, EMAIL=johndoe@company.com}
{ID=2, NAME=Bill, SECOND_NAME=Brown, PHONE=324235, EMAIL=billbrown@company.com}
{ID=3, NAME=Jack, SECOND_NAME=Daniel's, PHONE=443322, EMAIL=jackdaniels@company.com}
Table 'people' dropped


Converted to Wings-style. Here we can simply chop up the script into pieces that do "just enough" to move along to where we're interested in the result. Not only does that eliminate the need for printlns or asserts to check on what has happened, it also allows the bits of script to be reevaluated in various orders. That is common for dealing with something that fails, you can just redo that part, or to verify that some code does what is expected you might leave it out when manually evaluating a particular sequence.

import groovy.sql.*
import java.sql.*

protocol = "jdbc:hsqldb:";
def props = new Properties();
// props.put("user", "user1");
// props.put("password", "user1");

sql = Sql.newInstance(protocol + "mem:people", props);

groovy.sql.Sql@1d0ebc

/* Creating table, adding few lines, updating one */
sql.execute("""create table people(id int, name varchar(40), second_name
varchar(40), phone varchar(30), email varchar(50))""")

false

sql.execute("insert into people values (1,'John', 'Doe', '123456','johndoe@company.com')");
sql.execute("insert into people values (2,'Bill', 'Brown', '324235','billbrown@company.com')");
sql.execute("insert into people values (3,'Jack', 'Daniels', '443323','jackdaniels@company.com')");

false

sql.execute("update people set phone='443322', second_name='Daniel''s'where id=3");

false

/* Simple query */
def rows = sql.rows("SELECT * FROM people ORDER BY id");
rows.each {println it}

{ID=1, NAME=John, SECOND_NAME=Doe, PHONE=123456, EMAIL=johndoe@company.com}
{ID=2, NAME=Bill, SECOND_NAME=Brown, PHONE=324235, EMAIL=billbrown@company.com}
{ID=3, NAME=Jack, SECOND_NAME=Daniel's, PHONE=443322, EMAIL=jackdaniels@company.com}

[{ID=1, NAME=John, SECOND_NAME=Doe, PHONE=123456, EMAIL=johndoe@company.com}, {ID=2, NAME=Bill, SECOND_NAME=Brown, PHONE=324235, EMAIL=billbrown@company.com}, {ID=3, NAME=Jack, SECOND_NAME=Daniel's, PHONE=443322, EMAIL=jackdaniels@company.com}]

/* Dropping table 'people' */
sql.execute("drop table people")

false

Groovy DataSet. For this to work you need to execute the code above except for the dropping of the table.

peopleSet = sql.dataSet('People')

groovy.sql.DataSet@bc4578

peopleSet.each { println it }

[ID:1, NAME:John, SECOND_NAME:Doe, PHONE:123456, EMAIL:johndoe@company.com]
[ID:2, NAME:Bill, SECOND_NAME:Brown, PHONE:324235, EMAIL:billbrown@company.com]
[ID:3, NAME:Jack, SECOND_NAME:Daniel's, PHONE:443322, EMAIL:jackdaniels@company.com]


peopleSet.getClass()

class groovy.sql.DataSet

Something awry here. (FIXME)

def bills = peopleSet.findAll { it.NAME == 'Bill' }
bills.each { println it }

groovy.lang.GroovyRuntimeException: Could not find the ClassNode for MetaClass: org.codehaus.groovy.runtime.metaclass.ClosureMetaClass@64807d[class Script75$_run_closure1]

Not having any joy with Derby at the moment for some reason.

xwings.uses(groupId:'org.apache.derby', artifactId:'derby', version:'10.1.3.1', type:'jar')

[file:/Users/jim/.ivy2/cache/org.apache.derby/derby/jars/derby-10.1.3.1.jar]

org.apache.debry.jdbc.EmbeddedDriver x

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, Script80.groovy: 1: unable to resolve class org.apache.debry.jdbc.EmbeddedDriver
 @ line 1, column 38.
1 error

zf = new java.util.zip.ZipFile(new File("/Users/jim/.ivy2/cache/org.apache.derby/derby/jars/derby-10.1.3.1.jar"))

zf.entries().grep { it =~ /Driver/ }.sort()

[org/apache/derby/jdbc/Driver169.class, org/apache/derby/jdbc/Driver20.class, org/apache/derby/jdbc/EmbeddedDriver.class, org/apache/derby/jdbc/InternalDriver.class, org/apache/derby/jdbc/Driver30.class]

PLEAC

This is the Groovy PLEAC from http://pleac.sourceforge.net/pleac_groovy/datesandtimes.html copied May 5, 2008.

Section 3: Dates and Times

Introduction

Batch style:

//----------------------------------------------------------------------------------
// use Date to get the current time
println new Date()
// => Mon Jan 01 07:12:32 EST 2007
// use Calendar to compute year, month, day, hour, minute, and second values
cal = Calendar.instance
println 'Today is day ' + cal.get(Calendar.DAY_OF_YEAR) + ' of the current year.'
// => Today is day 1 of the current year.
// there are other Java Date/Time packages with extended capabilities, e.g.:
// http://joda-time.sourceforge.net/
// there is a special Grails (grails.codehaus.org) time DSL (see below)
//----------------------------------------------------------------------------------

Tue May 06 15:02:14 PDT 2008
Today is day 127 of the current year.


Wings style:

new Date()

Tue May 06 15:02:18 PDT 2008

cal = Calendar.instance
'Today is day ' + cal.get(Calendar.DAY_OF_YEAR) + ' of the current year.'

Today is day 127 of the current year.

Finding Today's Date

Batch style:

//----------------------------------------------------------------------------------
cal = Calendar.instance
Y = cal.get(Calendar.YEAR)
M = cal.get(Calendar.MONTH) + 1
D = cal.get(Calendar.DATE)
println "The current date is $Y $M $D"
// => The current date is 2006 04 28
//----------------------------------------------------------------------------------

The current date is 2008 5 6


Wings style:

cal = Calendar.instance
Y = cal.get(Calendar.YEAR)
M = cal.get(Calendar.MONTH) + 1
D = cal.get(Calendar.DATE)
"The current date is $Y $M $D"

The current date is 2008 5 6

Printing a Date

Batch style:

//----------------------------------------------------------------------------------
import java.text.SimpleDateFormat
import java.text.DateFormat
df = new SimpleDateFormat('E M d hh:mm:ss z yyyy')
cal.set(2007, 0, 1)
println 'Customized format gives: ' + df.format(cal.time)
// => Mon 1 1 09:02:29 EST 2007 (differs depending on your timezone)
df = DateFormat.getDateInstance(DateFormat.FULL, Locale.FRANCE)
println 'Customized format gives: ' + df.format(cal.time)
// => lundi 1 janvier 2007
//----------------------------------------------------------------------------------

Customized format gives: Mon 1 1 03:02:24 PST 2007
Customized format gives: lundi 1 janvier 2007


Wings style:

import java.text.SimpleDateFormat
df = new SimpleDateFormat('E M d hh:mm:ss z yyyy')
cal.set(2007, 0, 1)
df.format(cal.time)

Mon 1 1 03:02:24 PST 2007

import java.text.DateFormat
df = DateFormat.getDateInstance(DateFormat.FULL, Locale.FRANCE)
df.format(cal.time)

lundi 1 janvier 2007

Shopping Cart

I was watching a lightning talk about Scala illustrated with the classic shopping cart example, so I whipped up this version while they showed the Scala version. While Groovy doesn't have Scala's type inferencing and static typing performance, it is even terser. This version doesn't follow their example of initializing all the quantities to zero because that isn't plausible if the catalog can be large. It does however use strong typing on the catalog to show we can have that if we want it. Also the Scala version showed the special price handling in the total function, and while I could do that too (and even put it all on one line), I think it is a better separation of concerns to have that it a separate function.

The famous Elvis operator (':?') is used which demonstrates a key difference between Java and Groovy which is the notion of "true". Groovy Truth is weakly typed and treats not only false, and Boolean.FALSE as false, but also null, empty strings, empty collections, numbers with a zero value, and regex matchers with no match. Anything else is true. So Elvis evaluates the left-hand side, and if it is true then returns that value (which could of course be something like a non-zero number or non-empty string). Otherwise it returns the value of the right-hand side. This is not simply the ternary operator because the LHS appears only once and is evaluated only once. There is an example of Collection.inject earlier in this document.

class Catalog extends HashMap { }

class CashRegister {
   Catalog catalog
   def quantities = [:]
   def scan(String code) { quantities[code] = (quantities[code] ?: 0) + 1 }
   def price(String code) { catalog[code].specialPrice ?: catalog[code].price }
   def total() { (quantities.collect { item -> item.value ? item.value * price(item.key) : 0 }).sum() }
   // def total() { quantities.inject(0) { total, item -> total + (item.value ? item.value * price(item.key) : 0) } }
}

register = new CashRegister(catalog: [gum:[price:1.00], comb:[price:2.33], apple:[price:0.45, specialPrice:0.30]] as Catalog)

CashRegister@3d902f

register.scan('gum')

1

register.total()

1.00

register.scan('comb')

1

register.scan('apple')

1

register.scan('apple')

2

register.total()

3.93

register.scan('gum')

2

register.quantities

{gum=2, comb=1, apple=2}

GUI Forms and Map Binding

This is the first bit of Groovy I wrote (well, almost, SwingBuilder.build was my first contribution to Groovy as a replacement for the boilerplate previously needed to use SwingBuilder) to find out how binding of a model to a GUI view would be done in Groovy (the acid test for any language). The trick here is actually just Java Collections in that the map entry you get when iterating over a mutable map will modify the map if you change the value part. Once again this example doesn't show well in Wings (although the frame will get displayed) because it doesn't (yet) handle standard output.

javax.swing.SwingUtilities.isEventDispatchThread()

false



def someBean=['abc':1,'def':2,'xyz':3,'foo':99]

// def MYFRAME

groovy.swing.SwingBuilder.build {
    MYFRAME=frame(title:'My Frame') {
    def map=[:] // from text field widget to pair entry in someBean
    panel() {
        // initialize map with labels and inital values from someBean
        for (pair in someBean) {
            t=textField(text:pair.value)
            label(text:pair.key).labelFor=t
            // map textfield to someBean label/value pair
            map[t]=pair
        }
        // Notice that I can supply a closure and it gets adapted to an ActionListener.
        button(text:'OK', actionPerformed:{
            // update map with current value from widgets
            for (pair in map)
                pair.value.value=pair.key.text
            // show we got updated
            for (pair in someBean)
                println("${pair.key}: ${pair.value}")
            // We should look for our parent frame to close, but this is handy:
                MYFRAME.hide()
        })
       }
   }
   MYFRAME.pack()
   MYFRAME.show()
}

while (MYFRAME.isVisible()) { System.sleep(1000) }

abc: 1
def: 2
xyz: 3
foo: 99


MYFRAME

javax.swing.JFrame[frame3,4,22,306x61,hidden,layout=java.awt.BorderLayout,title=My Frame,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,22,306x39,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=449,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]

javax.swing.SwingUtilities.invokeAndWait { MYFRAME.pack() ; MYFRAME.show() }
while (MYFRAME.isVisible()) { System.sleep(1000) }

abc: 1
def: 2
xyz: 3
foo: 99


Classes are different than Groovy scripts. The Groovy Script class provides the convenience methods for printing and Wings does the right thing for JSR-223 so that we can get the output into this document. But if you use 'println' in a Groovy Class then it's going to use the GDK println which will use System.out which will go to the system console. So here's one way of getting the output to remain well behaved in a scripting context.

class GUI {

  def config = new Expando(dir:System.getProperty('user.dir'))

  static def swing = new groovy.swing.SwingBuilder()

  Script script

  GUI(Script s) { script = s }

  def settingDialog = swing.dialog(
      title: "Setting dialog", id: "settingDialog",
        modal: true, pack:true, locationByPlatform:true) {
      panel {
        flowLayout(alignment : java.awt.FlowLayout.LEFT)
        label('Directory')
        def dirField = textField(id : 'dirField', columns : 30, text : config.dir)
        //widget(dirField)
        button(text : 'Change', actionPerformed : {
           config.dir = dirField.text
           script.println config
        })
      }
    }
 
  void prompt() { settingDialog.show() }

}

def gui = new GUI(this)
gui.prompt()
gui.config

{dir=/}

Pretty Printing

Someone on groovy-user asked how to do pretty-printing of lists and maps in Groovy. Although Groovy has a bunch of stuff dealing with output and formatting, none of it is really suited to pretty-printing. This is the start of a way to do it (but I'm gonna go a different way since hypertext is better for this sort of thing). It is factored into an improved indenter (writer rather than stream based, it's a value object, and written as Java so it's useful in less groovy environments) and simple formatter.

import java.io.PrintWriter;
import java.io.Writer;
import org.codehaus.groovy.tools.Utilities;

public class IndentWriter extends PrintWriter
{
   protected boolean needIndent = true;
   protected String indentString;
   protected int indentLevel = 0;
   
   public IndentWriter(Writer w) { this(w, " ", 0, true); }
   public IndentWriter(Writer w, String indent, int level, boolean needs)
   { super(w, true); indentString = indent; indentLevel = level; needIndent = needs }
   
   public int getIndent() { return indentLevel; }
   
   public IndentWriter plus(int i) {
      return new IndentWriter(out, indentString, indentLevel + i, needIndent);
   }
   
   public IndentWriter minus(int i) {
      return (plus(-i));
   }

   public IndentWriter next() { return plus(1); }
   public IndentWriter previous() { return minus(1); }
   
   protected void printIndent() {
      needIndent = false;
      super.print(Utilities.repeatString(indentString, indentLevel));
   }
   
   protected void checkIndent() { if (needIndent) { needIndent = false; printIndent(); }; }
   
   public void println() { super.println(); needIndent = true; }

   public void print(boolean b) { checkIndent(); super.print(b); }
   public void print(char c) { checkIndent(); super.print(c); }
   public void print(char[] s) { checkIndent(); super.print(s); }
   public void print(double d) { checkIndent(); super.print(d); }
   public void print(float f) { checkIndent(); super.print(f); }
   public void print(int i) { checkIndent(); super.print(i); }
   public void print(long l) { checkIndent(); super.print(l); }
   public void print(Object obj) { checkIndent(); super.print(obj); }
   public void print(String s) { checkIndent(); super.print(s); }

// public void close() { }
// public void closeForReal() { super.close() }
}

class PrettyWriter extends IndentWriter
{
   public PrettyWriter(Writer w) { super(w, ' ', 0, true); }
   public PrettyWriter(Writer w, String ins, int level, boolean needsIt) { super(w, ins, level, needsIt); }
   public PrettyWriter plus(int i) { return new PrettyWriter(out, indentString, indentLevel + i, needIndent) }

   public void print(Collection list) {
      println('[')

      def indent = (this + 1)
      list.each { indent.print it ; indent.println ';' }
      indent.flush()

      print(']')
   }
   public void println( Collection list) { print list; println() }

   public void print(Map map) {
      println('[')

      def indent = (this + 1)
      map.entrySet().each {
          indent.print it.key; indent.print ' : '; (indent + 1).print it.value ; indent.println ';'
      }
      indent.flush()

      print(']')
   }

   public void println(Map map) { print map; println() }
}

if (!(out instanceof PrettyWriter)) {
   // We can change this, but it isn’t sticking currently...
   out = new PrettyWriter(out)
}

//println out.getClass()
//print ([1, 2, [3, [4, [a:1, b:2, c:3], 5, 6]], [aaa:111, bbb:222, ccc:[1, 2, 3,4]], 7])
//println out.getClass()
//13.times { println "$it .....................ABCDEF" }
//out.flush()

println ([1, 2, [3, [4, [a:1, b:2, c:3], 5, 6]], [aaa:111, bbb:222, ccc:[1, 2, 3,4]], 7])

[
    1;
    2;
    [
        3;
        [
            4;
            [
                a : 1;
                b : 2;
                c : 3;
            ];
            5;
            6;
        ];
    ];
    [
        aaa : 111;
        bbb : 222;
        ccc : [
                1;
                2;
                3;
                4;
            ];
    ];
    7;
]


At the moment setting the 'out' binding isn't sticky between evals. But we can just set it again.

out = new PrettyWriter(out)
println ([1, 2, [3, [4, [a:1, b:2, c:3], 5, 6]], [aaa:111, bbb:222, ccc:[1, 2, 3,4]], 7])

[
    1;
    2;
    [
        3;
        [
            4;
            [
                a : 1;
                b : 2;
                c : 3;
            ];
            5;
            6;
        ];
    ];
    [
        aaa : 111;
        bbb : 222;
        ccc : [
                1;
                2;
                3;
                4;
            ];
    ];
    7;
]


Serialization

Serialization works just fine with a file of course, but for demonstration purposes a BAOS is tidier.

def someList = ["foo", "bar"]
def bos = new ByteArrayOutputStream()
def oos = new ObjectOutputStream(bos)
println someList
oos.writeObject(someList)
oos.close()
def serstr = bos.toString()
// def serstr = new String(bos.toByteArray())
println serstr
// def ois = new ObjectInputStream(new StringBufferInputStream(serstr))
// def ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))
def ois = new ObjectInputStream(new ByteArrayInputStream(serstr.getBytes()))
def readList = ois.readObject()
println readList
readList == someList

[foo, bar]
¨Ìsrjava.util.ArrayListxÅ“ô«aùIsizexpwtfootbarx
[foo, bar]

true



Multiple Languages

The current version of the Wings prototype supports any language with a JSR-223 Java scripting engine (other sorts of language APIs are planned of course).

Some of these engines take a while to initialize, and at the moment Wings doesn’t provide any feedback or control with long running scripts. So you should be patient and perhaps check your CPU activity monitor if you think Wings is stuck. After the first hit though, all these engines zip right along.

Ruby/JRuby

Ruby is a popular language of late. JRuby is an implementation for the JVM, and Wings supports it by default.

require 'java'
include_class 'java.util.TreeSet'
set = TreeSet.new
set.add "zooey"
set.add "Bar"
set.add "bazaar"

set.each do |v|
  puts "value: #{v}"
end

value: Bar
value: bazaar
value: zooey


Python/Jython

Python has many adherents. Jython is an implementation for the JVM, and Wings supports it by default.

print 1 + 2 + 3 + 4 + 5
print [sum(range(5)), range(6), range(10, 1, -3)]

15
[10, [0, 1, 2, 3, 4, 5], [10, 7, 4]]


A little database example (TODO:refind that URL).

First we add the JDBC driver to the classpath using the public Maven artifact. We do this in GroovyCode at the moment.

xwings.uses (groupId:'hsqldb', artifactId:'hsqldb', version:'1.8.0.7')

[file:/Users/jim/.ivy2/cache/hsqldb/hsqldb/jars/hsqldb-1.8.0.7.jar]

## @use (groupId:'hsqldb', artifactId:'hsqldb', version:'1.8.0.7')
Class.forName("org.hsqldb.jdbcDriver")

from java.sql import DriverManager
from java.lang import Class

db = DriverManager.getConnection("jdbc:hsqldb:mem:people", None, None)

c = db.createStatement()
rs = c.executeQuery("select * from People")

#_types = {Types.INTEGER:rs.getInt, Types.FLOAT:rs.getFloat}

while rs.next():
  row = []
  meta = rs.getMetaData()
  for i in range(meta.getColumnCount()):
    col = i + 1
    datatype = meta.getColumnType(col)
    v = _types.get(datatype, rs.getString)(col)
    row.append(v)
  print tuple(row)
rs.close()
c.close()
db.close()

Traceback (innermost last):
  File "<unknown>", line 20, in ?
NameError: _types


java.sql.SQLException: java.sql.SQLException: User not found: BZIMMER

from com.ziclix.python.sql import zxJDBC

#conn = DriverManager.getConnection("jdbc:hsqldb:mem:people", None, None)
db = zxJDBC.connect("jdbc:hsqldb:mem:people", None, None, "org.hsqldb.jdbcDriver")
c = db.cursor()
c.execute("select * from bz")
for row in c:
  print row
c.close()
db.close()

Traceback (innermost last):
  File "<unknown>", line 3, in ?

Scheme

This is JSR-223 script engine for SISC - Java implementation of Scheme language. SISC is available for download at http://sisc-scheme.org/. We have built and tested with SISC version 1.16.6.

(+ 1 2 4 5)

12

Note on accessing jsr-223 Binding variables from Scheme:

SISC implementation has support for pluggable top level environments. But, SISC engine expects to map symbol to ints and use that when looking those ints up later. But, jsr-223 supports variables though a Map and map entries maybe changed/deleted/added freely -- both from Java and the underlying scripting language. So, the way jsr-223 Bindings works is as follows:

Scheme user has to use "var" procedure to access/update the variables exposed by jsr-223 bindings. To access variable "foo" from Scheme, you use (var 'foo). To update variable "foo", you need to use (var 'foo <value>).

For example, you can use the following expression to print the current file name.

(display (var 'javax.script.filename))


Similarly, the following expression

(var 'context)

org.ifcx.wings.openoffice.WingsContext@eff818

can be used to access the current script context from scheme.

(var 'xwings)

{_use=org.ifcx.wings.openoffice.openoffice.document.scripts.groovy.Wings.WingsEval$_doEvaluate_closure15@7fa4e0, enginesURL=http://ifcx.svn.sourceforge.net/svnroot/ifcx/thirdparty/scripting/engines/, IVY=org.ifcx.wings.openoffice.openoffice.document.scripts.groovy.Wings.WingsEval$_doEvaluate_closure13@21a6cc, context=org.ifcx.wings.openoffice.WingsContext@eff818, uses=org.ifcx.wings.openoffice.openoffice.document.scripts.groovy.Wings.WingsEval$_doEvaluate_closure15@7fa4e0, CONTEXT=org.ifcx.wings.openoffice.WingsContext@eff818, POM=org.ifcx.wings.openoffice.openoffice.document.scripts.groovy.Wings.WingsEval$_doEvaluate_closure14@4ddbc5, register=org.ifcx.wings.openoffice.openoffice.document.scripts.groovy.Wings.WingsEval$_doEvaluate_closure12@978d3d}

(import hashtable)


(define foo (make-hashtable))


foo

#<hashtable #<native procedure equal?> #<native procedure hash-by-equal> ()>

(hashtable/put! foo 'bar 123)

false

(hashtable/get foo 'bar)

123

foo

#<hashtable #<native procedure equal?> #<native procedure hash-by-equal> ((bar . 123))>

(hashtable/map (lambda (k v) (cons (cons k v) (odd? v))) foo)

(((bar . 123) . #t))

OCaml

OCaml (Objective Caml) is a popular version of ML sporting OO features. OCaml-Java is an implementation for the JVM, which Wings supports by default.

print_endline "printing from ocaml!";
1 + 2

printing from ocaml!

Value(long)=3

5 * 6

Value(long)=30

The following example is from http://caml.inria.fr/pub/old_caml_site/Examples/oc/basics/sieve.ml:

(***********************************************************************)
(* *)
(* Objective Caml *)
(* *)
(* Pierre Weis, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 2001 Institut National de Recherche en Informatique et *)
(* en Automatique. All rights reserved. This file is distributed *)
(* only by permission. *)
(* *)
(***********************************************************************)

Manipulation of lists.

   - [] is the empty list
   - :: is the infix operator that builds list cells.

Hence 1 :: (2 :: []) is the list that contains 1 and 2 in this order.

interval min max = [min; min+1; ...; max-1; max]

let rec interval min max =
  if min > max then [] else min :: interval (succ min) max

Value(long)=0

Case analysis on list l is written

   match l with
      | [] -> ''nil case''
      | x :: tail -> ''non nil case, with x (the head of l) and tail (the tail of l) available''

Function can perform direct case analysis on their argument, if introduced by the function keyword. Hence,

   let f (x) =
      match x with
         | [] -> ...
         | ...

can be abreviated as

   let f = function
      | [] -> ...
      | ...

filter p L returns the list of the elements in list L that satisfy predicate p

let rec filter p = function
  | [] -> []
  | a :: r -> if p a then a :: filter p r else filter p r

Value(long)=0

Application: removing all numbers multiple of n from a list of integers

let remove_multiples_of n =
  filter (fun m -> m mod n <> 0)

Value(long)=0

The sieve itself

let sieve max =
  let rec filter_again = function
  | [] -> []
  | n :: r as l ->
      if n * n > max then l else n :: filter_again (remove_multiples_of n r)
  in
    filter_again (interval 2 max)

Value(long)=0

Let's give it a whirl and print the results.

List.iter (fun n -> print_int n; print_string " ") (sieve 100)

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

Value(long)=0

Since the JSR-223 engine for OCaml doesn't do much display stuff yet, we can't just call the function and see anything

sieve 100;

Value(block)=fr.x9c.cadmium.kernel.Block@685f00



Chapter 1  The core language

This part of the manual is a tutorial introduction to the Objective Caml language. A good familiarity with programming in a conventional languages (say, Pascal or C) is assumed, but no prior exposure to functional languages is required. The present chapter introduces the core language. Chapter 3 deals with the object-oriented features, and chapter 2 with the module system.

1.1  Basics

For this overview of Caml, we use the interactive system, which is started by running ocaml from the Unix shell, or by launching the OCamlwin.exe application under Windows. This tutorial is presented as the transcript of a session with the interactive system: lines starting with # represent user input; the system responses are printed below, without a leading #.

Under the interactive system, the user types Caml phrases, terminated by ;;, in response to the # prompt, and the system compiles them on the fly, executes them, and prints the outcome of evaluation. Phrases are either simple expressions, or let definitions of identifiers (either values or functions).

1+2*3;;

Value(long)=7

- : int = 7

 

let pi = 4.0 *. atan 1.0;;
print_float pi;
pi

3.14159265359

Value(block)=fr.x9c.cadmium.kernel.Block@d5ef4b

val pi : float = 3.14159265358979312

 

let square x = x *. x;;

Value(long)=0

val square : float -> float = <fun>

 

square(sin pi) +. square(cos pi);;

Value(block)=fr.x9c.cadmium.kernel.Block@73124b

- : float = 1.

The Caml system computes both the value and the type for each phrase. Even function parameters need no explicit type declaration: the system infers their types from their usage in the function. Notice also that integers and floating-point numbers are distinct types, with distinct operators: + and * operate on integers, but +. and *. operate on floats.

1.0 * 2;;

fr.x9c.cadmium.kernel.CadmiumException: callback exception

This expression has type float but is here used with type int

Recursive functions are defined with the let rec binding:

let rec fib n =
   if n < 2 then 1 else fib(n-1) + fib(n-2);;

Value(long)=0

val fib : int -> int = <fun>

 

fib 10;;

Value(long)=89

- : int = 89

1.2  Data types

In addition to integers and floating-point numbers, Caml offers the usual basic data types: booleans, characters, and character strings.

(1 < 2) = false;;

- : bool = false

 

'a';;

- : char = 'a'

 

print_string "Hello world";;

Hello world

Value(long)=0

- : string = "Hello world"

Predefined data structures include tuples, arrays, and lists. General mechanisms for defining your own data structures are also provided. They will be covered in more details later; for now, we concentrate on lists. Lists are either given in extension as a bracketed list of semicolon-separated elements, or built from the empty list [] (pronounce “nil”) by adding elements in front using the :: (“cons”) operator.

let l = ["is"; "a"; "tale"; "told"; "etc."];;

Value(long)=0

val l : string list = ["is"; "a"; "tale"; "told"; "etc."]

 

"Life" :: l;;

Value(block)=fr.x9c.cadmium.kernel.Block@290aac

- : string list = ["Life"; "is"; "a"; "tale"; "told"; "etc."]

As with all other Caml data structures, lists do not need to be explicitly allocated and deallocated from memory: all memory management is entirely automatic in Caml. Similarly, there is no explicit handling of pointers: the Caml compiler silently introduces pointers where necessary.

As with most Caml data structures, inspecting and destructuring lists is performed by pattern-matching. List patterns have the exact same shape as list expressions, with identifier representing unspecified parts of the list. As an example, here is insertion sort on a list:

let rec sort lst =
   match lst with
     [] -> []
   | head :: tail -> insert head (sort tail)
 and insert elt lst =
   match lst with
     [] -> [elt]
   | head :: tail -> if elt <= head then elt :: lst else head :: insert elt tail
 ;;

let list_print format = List.iter (fun i -> Printf.printf format i);;

print_int (List.hd (sort [111; 22; 3; 44; 555]));;

fr.x9c.cadmium.kernel.CadmiumException: callback exception



(List.iter (fun i -> Printf.printf “%d” i) (sort [111; 22; 3; 44; 555])

fr.x9c.cadmium.kernel.CadmiumException: callback exception

val sort : 'a list -> 'a list = <fun>

val insert : 'a -> 'a list -> 'a list = <fun>

list_print “%d” [111; 22; 3; 44; 555]

fr.x9c.cadmium.kernel.CadmiumException: callback exception


let myf = Format.formatter_of_out_channel stdout ;;
Format.fprintf myf "%s %d" "x =" 1;;

Value(long)=0

 

print_int 1;;List.length ["is"; "a"; "tale"; "told"; "etc."];;
(*print_int List.length(sort l);;*)
List.length l;;
printf sort l

1

fr.x9c.cadmium.kernel.CadmiumException: callback exception

- : string list = ["a"; "etc."; "is"; "tale"; "told"]

The type inferred for sort, 'a list -> 'a list, means that sort can actually apply to lists of any type, and returns a list of the same type. The type 'a is a type variable, and stands for any given type. The reason why sort can apply to lists of any type is that the comparisons (=, <=, etc.) are polymorphic in Caml: they operate between any two values of the same type. This makes sort itself polymorphic over all list types.

print_endline (sort [6;2;5;3]);;

fr.x9c.cadmium.kernel.CadmiumException: callback exception

- : int list = [2; 3; 5; 6]

 

sort [3.14; 2.718];;

Value(block)=fr.x9c.cadmium.kernel.Block@3aaf52

- : float list = [2.718; 3.14]

The sort function above does not modify its input list: it builds and returns a new list containing the same elements as the input list, in ascending order. There is actually no way in Caml to modify in-place a list once it is built: we say that lists are immutable data structures. Most Caml data structures are immutable, but a few (most notably arrays) are mutable, meaning that they can be modified in-place at any time.

1.3  Functions as values

Caml is a functional language: functions in the full mathematical sense are supported and can be passed around freely just as any other piece of data. For instance, here is a deriv function that takes any float function as argument and returns an approximation of its derivative function:

#let deriv f dx = function x -> (f(x +. dx) -. f(x)) /. dx;;

val deriv : (float -> float) -> float -> float -> float = <fun>

 

#let sin' = deriv sin 1e-6;;

val sin' : float -> float = <fun>

 

#sin' pi;;

- : float = -1.00000000013961143

Even function composition is definable:

#let compose f g = function x -> f(g(x));;

val compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>

 

#let cos2 = compose square cos;;

val cos2 : float -> float = <fun>

Functions that take other functions as arguments are called “functionals”, or “higher-order functions”. Functionals are especially useful to provide iterators or similar generic operations over a data structure. For instance, the standard Caml library provides a List.map functional that applies a given function to each element of a list, and returns the list of the results:

#List.map (function n -> n * 2 + 1) [0;1;2;3;4];;

- : int list = [1; 3; 5; 7; 9]

This functional, along with a number of other list and array functionals, is predefined because it is often useful, but there is nothing magic with it: it can easily be defined as follows.

#let rec map f l =

match l with

[] -> []

| hd :: tl -> f hd :: map f tl;;

val map : ('a -> 'b) -> 'a list -> 'b list = <fun>

Jaskell

The following examples are copied from http://docs.codehaus.org/display/JASKELL/Using+Jaskell.

$$<<hello $name>>$$ where
  name="Tom";
end

hello Tom



tuple

A tuple is an associative array.

{} is an empty tuple.

{name="tom"; age=1} is a tuple with two members.

{str="abc", list=[1,2,'3'], tuple={}} is a tuple with 3 members.

',' or ';' can be used to seperate tuple members.

Tuple member can be de-referenced with a '.'. For example:

{name="tom"; age=1}.name

tom

evaluates to "tom".

if-then-else

if-then-else is the only native conditional statement in Jaskell.

Like many imperative languages such as Java, the "else" clause is optional. When 'else' clause is omitted, 'null' is used as the value of this clause.

The following expression evaluates to 5:

if 1==1 then 1 else 5

1



switch-case

There's no native switch-case support in the Jaskell programming language.

However, the 'jaskell.prelude.switch' function can be used for this purpose.
For example:

switch (3-2)
.case(0, "zero")
.case(1, "one")
.default("unknown")
.end

one

evaluates to "one".

Operators

Similar to Java, Jaskell supports the following binary operators with natural semantics:
'==', '!=', '>', '<', '>=', '<=', '+', '-', '*', '/', '%', '&&', '||', 'and', 'or'.

Slightly different from Java though, '==' and '!=' calls "Object.equals()", and comparison operators such as '>', '<' calls "Comparable.compareTo()".

Hence, the following expressions all evaluate to true:

"abc"=="abc"

true

"abc"!="ABC"

true

"10"<"2"

true

"abc"=="abc" and "abc"!="ABC"

true

"abc"!="ABC" or "10"=="2"

true

'and' and '&&', 'or' and '||' are equivalent.

Unary operators such as '!', '', 'not' are also supported. 'not' and '!' are equivalent. They are both the logical negation operator. '' reads "negative". "~1" is same as "-1".

Jaskell supports ':', '++', '@', '#' operators for list operation. 
':' is used to prepend an element to a list.

1:[2,3]

[1,2,3]

evaluates to [1,2,3]

'++' is used to concatenate two lists together:

[1,2]++[3,4]

[1,2,3,4]

evaluates to [1,2,3,4]

'@' is used to get an element from a list/tuple by index/key:

[1,2,3,4]@1

2

evaluates to 2.

{name="tom";age=10}@"age"

10

evaluates to 10.

Besides (@), jaskell also supports the familiar "[]" notion as a syntax sugar to indicate a list/array subscript. For example,

[1,2,3,4][1]

2

also evaluates to 2, which is exactly the same as the (@) operator.

Another use of the "[]" syntax sugar is to indicate an array type, so one can say:

int[]

'#' is used to get the size of a list/tuple:

#[1,2]

2

evaluates to 2.

For array and list, the "length" method can also be called to read the length, which is more familiar to most Java programmers:

[1,2].length

jfun.jaskell.AbstractMemberException: abstract member: length

'=>' operator is a variant of "if-then'. "cond => x" is equivalent to "if cond then x". '=>' is typically overridden to provide "deduction" kind of logic.

':=' is an operator with no predefined meaning. It can be overloaded to provide custom semantics though.



let

'let' is a keyword that allows definition of variable and function.

let
  a = 1;
  b = 2;
  a+b;
end

3

is an expression that evaluates to 3.

pattern match

When defining function, different function body can be provided based on the pattern of certain parameters.

Symbol '|' is used to seperate different patterns. 
For example, the following function reverse a list:

reverse [] = [] //in case of empty list
| x:xl = reverse xl ++ [x]; // in case of non-empty list.


reverse [1, 2, 3, 4]

[4,3,2,1]

The following function tests if a parameter is a tuple with a member named "ind":

hasMember {ind} = true
| _ = false;


[hasMember {ind=a}, hasMember { foo=ind }, hasMember { }]

[true,false,false]

It is possible to name a pattern, so that this name can be used to reference the object of this pattern. 
Special symbol '@' can be used to name a pattern.
The following function returns the tuple itself if it contains member "ind", an empty tuple is returned otherwise:

test t@{ind} = t
| _ = {};


[test{ind=a}, test{a=1, b=2}]

[{ind=a},{}]

Scala

Let's try some Scala! This will take a while the first time, so be patient and just wait for the I-beam cursor to start blinking again.

1 + 2

res0: Int = 3

Here's the array quicksort from Scala By Example.

def sort(xs: Array[Int]) {
        def swap(i: Int, j: Int) {
                val t = xs(i); xs(i) = xs(j); xs(j) = t
        }
        def sort1(l: Int, r: Int) {
                val pivot = xs((l + r) / 2)
                var i = l; var j = r
                while (i <= j) {
                        while (xs(i) < pivot) i += 1
                        while (xs(j) > pivot) j -= 1
                        if (i <= j) {
                                swap(i, j)
                                i += 1
                                j -= 1
                        }
                }
                if (l < j) sort1(l, j)
                if (j < r) sort1(i, r)
        }
        sort1(0, xs.length - 1)
}

sort: (Array[Int])Unit

val x = Array(4,3, 77, 77, 55, 3, 1)

x: Array[Int] = Array(4, 3, 77, 77, 55, 3, 1)

sort(x)


Notice we got no result from the sort method. We look at the array which was sorted in place.

x

res2: Array[Int] = Array(1, 3, 3, 4, 55, 77, 77)

Here's the functional version.

def sort(xs: Array[Int]): Array[Int] =
        if (xs.length <= 1) xs
        else {
                val pivot = xs(xs.length / 2)
                Array.concat(
                        sort(xs filter (pivot >)),
                                xs filter (pivot ==),
                                sort(xs filter (pivot <)))
        }

sort: (Array[Int])Array[Int]

Now we get an array value as the result of calling sort.

sort(Array(1, 22, 11, 99, -283, -100, 1000, 10))

res3: Array[Int] = Array(-283, -100, 1, 10, 11, 22, 99, 1000)

Adenine

Adenine is the language of the MIT Haystack Semantic Desktop. It features a RDF data model, Pythonic syntax, and is implemented for the JVM. IFCX.org has extricated it's implementation from Eclipse and packaged it with a JSR-223 engine adapter so that Wings can support it by default.

+ 1 2 3

6

The current Adenine engine doesn't support top-level constructs and instead evaluates everything as expressions (blocks). The IFCX implementation provides an implied @base for the engine instance.

#@base <http://ifcx.org/example>
add { :john
    :hasChild ${
        rdf:type :Person ;
        dc:title "Joe"
    }
}


printset (query {:john ?x ?y})

<urn:vZLIcHyew6SyVB4:hasChild> <urn:f37guB1meqyhIu9>


Adenine provides access to arbitrary Java classes.

importjava "java.lang" System Runtime StringBuffer
= rt (Runtime.getRuntime)
help rt

Methods:
void exit(int)
void runFinalizersOnExit(boolean)
void loadLibrary(java.lang.String)
void load(java.lang.String)
void gc()
void runFinalization()
java.lang.Runtime getRuntime()
long freeMemory()
long maxMemory()
void addShutdownHook(java.lang.Thread)
boolean removeShutdownHook(java.lang.Thread)
void halt(int)
java.lang.Process exec(java.lang.String, [Ljava.lang.String;)
java.lang.Process exec([Ljava.lang.String;, [Ljava.lang.String;)
java.lang.Process exec([Ljava.lang.String;, [Ljava.lang.String;, java.io.File)
java.lang.Process exec(java.lang.String, [Ljava.lang.String;, java.io.File)
java.lang.Process exec([Ljava.lang.String;)
java.lang.Process exec(java.lang.String)
int availableProcessors()
long totalMemory()
void traceInstructions(boolean)
void traceMethodCalls(boolean)
java.io.InputStream getLocalizedInputStream(java.io.InputStream)
java.io.OutputStream getLocalizedOutputStream(java.io.OutputStream)


print rt

java.lang.Runtime@ad48a1


Adenine is an imperative language with Python-like syntax (indentation level delimits blocks).

# Begins on the next line for clarity
= x 1
= sum 0
while (!= x 20)
    = sum (+ sum x)
    = x (+ 1 x)
print x
print sum

20
190


The ^ shorthand is a URI that refers to the "current document", which in this case is the instatiation of the Adenine engine for this Wings document. In the future it will refer to the Wings document itself.

# An example Adenine source file

@prefix sample: <http://example.org/sample#>
@prefix dc: <http://purl.org/dc/elements/1.1/>

add { ^
    rdf:type daml:Ontology ;
    dc:title "Sample Ontology"
}

add { sample:age
    rdf:type daml:DatatypeProperty ;
    rdfs:isDefinedBy ^ ;
    rdfs:label "Age" ;
    rdfs:range xsd:int
}

printset (query {^ ?x ?y})

<http://purl.org/dc/elements/1.1/title> "Sample Ontology"
<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.daml.org/2001/03/daml+oil#Ontology>


printset (query {?x ?y ^})

<http://example.org/sample#age> <http://www.w3.org/2000/01/rdf-schema#isDefinedBy>
<urn:5yzn0pGySwevFF2> <http://haystack.lcs.mit.edu/schemata/adenine#resource>
<urn:z7GQDtjYyll5Ahw> <http://haystack.lcs.mit.edu/schemata/adenine#resource>
<urn:9Ph8gCmdplDCe2A> <http://haystack.lcs.mit.edu/schemata/adenine#resource>
<urn:SFv0nB5QJUJTVPm> <http://haystack.lcs.mit.edu/schemata/adenine#resource>
<urn:E7mbdeBo3olPBRi> <http://haystack.lcs.mit.edu/schemata/adenine#resource>


printset (query {sample:age rdfs:label ?y})

"Age"


printset (query {?x ?y adenine:line})

<urn:jD5DQHejoXBElZR> <http://haystack.lcs.mit.edu/schemata/adenine#resource>
<urn:yiUfOdGBb6V4m6y> <http://haystack.lcs.mit.edu/schemata/adenine#resource>
<urn:OxC4lC7tETVSWqt> <http://haystack.lcs.mit.edu/schemata/adenine#resource>
<urn:pWFHNbcMVAUhNMH> <http://haystack.lcs.mit.edu/schemata/adenine#resource>


= x @(1 2 3 4 5)

[1, 2, 3, 4, 5]

print @(1 2 3 4 5)

[1, 2, 3, 4, 5]



Sleep

xwings.uses ([org:'org.dashnine', name:'sleep', rev:'2.1rc2X', url:'http://sleep.dashnine.org/download/sleep.jar']
           , [org:'com.sun.script', name:'engine-sleep', rev:'1.0X', url:”${xwings.enginesURL}sleep/build/sleep-engine.jar”])

[file:/Users/jim/.ivy2/cache/com.sun.script/engine-sleep/jars/engine-sleep-1.0X.jar, file:/Users/jim/.ivy2/cache/org.dashnine/sleep/jars/sleep-2.1rc2X.jar]

XWINGS.register(stylePrefix:'Sleep', engineName:'sleep')

{engineName=sleep, codeStyle=SleepCode, exceptionalStyle=SleepException, resultStyle=SleepResult, outputStyle=WingsOutput, errorsStyle=WingsErrors, inputStyle=WingsInput}

Manual: http://sleep.dashnine.org/manual/

Developer’s blog: http://rsmudge.livejournal.com/

Sleep isn’t working because the scripting engine adapter doesn’t have input/output implemented.

import java.awt.Point;

$p = [new Point];
setField($p, x => 33, y => 45);
println($p);



Java

There is a JSR-223 engine for Java in the scripting.dev.java.net suite, but it requires JDK 1.6 to build and run. It probably works fine but it hasn't been tested here yet. There are other ways to implement Java support such as Apache Commons JCI and perhaps most interestinly for Wings would be to use the Groovy integrated Java/Groovy compiler (thanks JetBrains!). In the meantime there is BeanShell which is a lot like Groovy but is more complete wrt. Java language syntax.

BeanShell

BeanShell (http://www.beanshell.org/) is a scripting engine that supports Java syntax and is an approved JCP standard as JSR-274.

BUG: If the get from an artifact URL fails, we wind up with an empty JAR. Yikes!

xwings.uses ([org:'org.beanshell', name:'bsh', rev:'2.0b4']
             , [org:'com.sun.script', name:'engine-bsh', rev:'1.0C',
                url:”${xwings.enginesURL}beanshell/build/bsh-engine.jar”])

[file:/Users/jim/.ivy2/cache/com.sun.script/engine-bsh/jars/engine-bsh-1.0C.jar, file:/Users/jim/.ivy2/cache/org.beanshell/bsh/jars/bsh-2.0b4.jar]

xwings.register(stylePrefix:'Java', engineName:'beanshell' )
 /* factoryClassName:'bsh.engine.BshScriptEngineFactory' */

{engineName=beanshell, codeStyle=JavaCode, exceptionalStyle=JavaException, resultStyle=JavaResult, outputStyle=WingsOutput, errorsStyle=WingsErrors, inputStyle=WingsInput}

foobar = 1;
//println ("Hello World");

java.lang.NullPointerException

Registering a New Language

Here is an example of registering a language in a Wings document. This shows regisering Scala, which Wings now supports by default.

xwings.uses([[url:"${xwings.enginesURL}scala/build/scala-engine.jar", rev:"2008-05-12"]
, [url:"${xwings.enginesURL}scala/lib/scala-library.jar", rev:"2008-05-11"]
, [url:"${xwings.enginesURL}scala/lib/scala-compiler.jar", rev:"2008-05-11"]])

XWINGS: No ivy:settings found for the default reference 'ivy.instance'. A default instance will be used
XWINGS: [null] no settings file found, using default...
XWINGS: [null] :: loading settings :: url = jar:file:/Users/jim/Library/Preferences/NeoOffice-2.2/user/uno_packages/cache/uno_packages/2LgJpq_/GroovyScripting.oxt/external_jars/org.apache.ivy_2.0.0.rc1_20080502133514.jar!/org/apache/ivy/core/settings/ivysettings.xml
XWINGS: [resolve] :: resolving dependencies :: UNKNOWN#WingsDocument;working@ip70-187-187-207.oc.oc.cox.net
XWINGS: [resolve] confs: [default]
XWINGS: [resolve] found org.ifcx.wings.use_url#http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_build_scala-engine.jar;2008-05-12 in local
XWINGS: [resolve] found org.ifcx.wings.use_url#http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-library.jar;2008-05-11 in local
XWINGS: [resolve] found org.ifcx.wings.use_url#http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-compiler.jar;2008-05-11 in local
XWINGS: [resolve] downloading http://ifcx.svn.sourceforge.net/svnroot/ifcx/thirdparty/scripting/engines/scala/build/scala-engine.jar ...
XWINGS: [resolve] [SUCCESSFUL ] org.ifcx.wings.use_url#http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_build_scala-engine.jar;2008-05-12!http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_build_scala-engine.jar.jar (733ms)
XWINGS: [resolve] downloading http://ifcx.svn.sourceforge.net/svnroot/ifcx/thirdparty/scripting/engines/scala/lib/scala-library.jar ...
XWINGS: [resolve] [SUCCESSFUL ] org.ifcx.wings.use_url#http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-library.jar;2008-05-11!http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-library.jar.jar (4844ms)
XWINGS: [resolve] downloading http://ifcx.svn.sourceforge.net/svnroot/ifcx/thirdparty/scripting/engines/scala/lib/scala-compiler.jar ...
XWINGS: [resolve] [SUCCESSFUL ] org.ifcx.wings.use_url#http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-compiler.jar;2008-05-11!http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-compiler.jar.jar (5689ms)
XWINGS: [resolve] :: resolution report :: resolve 2286ms :: artifacts dl 11272ms
XWINGS: ---------------------------------------------------------------------
XWINGS: | | modules || artifacts |
XWINGS: | conf | number| search|dwnlded|evicted|| number|dwnlded|
XWINGS: ---------------------------------------------------------------------
XWINGS: | default | 3 | 3 | 0 | 0 || 3 | 3 |
XWINGS: ---------------------------------------------------------------------

[file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_build_scala-engine.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_build_scala-engine.jar-2008-05-12.jar, file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-compiler.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-compiler.jar-2008-05-11.jar, file:/Users/jim/.ivy2/cache/org.ifcx.wings.use_url/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-library.jar/jars/http___ifcx.svn.sourceforge.net_svnroot_ifcx_thirdparty_scripting_engines_scala_lib_scala-library.jar-2008-05-11.jar]

def factory = new org.ifcx.scripting.scala.ScalaScriptEngineFactory()
[factory.languageVersion, factory.engineVersion]

[2.7.1.final, 1.0d3]

Here we tell Wings to associate some paragraph styles with the Scala engine. We set the name of both the resultStyle and outputStyle to ScalaOutput here because we're only getting one type of output from the Scala interpreter right now and it is likely the manner of doing that will change in the future. This will make it easier for such changes to be backward compatible. Uh, nevermind. Turns out the formatting logic gets a bit confused if the output and result styles have the same name and we wind up with extra blank paragraphs getting inserted.

xwings.register(stylePrefix:'Scala', engineName:'scala', resultStyle:"ScalaOutput")

{engineName=scala, codeStyle=ScalaCode, exceptionalStyle=ScalaException, resultStyle=ScalaOutput, outputStyle=WingsOutput, errorsStyle=WingsErrors, inputStyle=WingsInput, uses=null}

Once this initialization stuff is working reliablity, it can be embedded in the WingsEval Groovy macro. You can see how that's done by looking at Tools:Macros:Organize Macros:Groovy... then twiddling the triangles to navigate to Wings.odt (this document name):Wings:WingsEval.groovy. A soon to come enhancement will be including the Ivy dependencies in the registration so it all happens lazily and out-of-sight.

Take Off!

Okay! We're ready to evaluate some Scala! This will take a while the first time, but if you've gotten this far without errors, odds are good that you'll eventually get the right results.

1 + 2

res0: Int = 3

Roadmap...

List of things To Do

Handle standard in, out, err.

Handle long running scripts.

Progress indicator.

Stop/Cancel dialog.

Display standard I/O during evaluation.

JSR-223 support using engines loadable via Ivy.

Preprocessing for Ivy/Maven @use Javadoc annotation.

In Wings.

Maven plug-in.

XDoclet for Ant+Ivy scripts.

Client/Server interaction and other non-JVM languages.

SystemExec

ShellExec

Move to next code to traverse TextContents tree.

Maven & Ivy Repository support.

IFCX repository.

Ivy RoundUp scripts & repository.

Get language implementors to publish working implementations in Maven repositories.

Multiple versions of the same language.

That already works by using different style names, but this refers to the idea of having suitable metadata and dependency/configuration support to automate it.

Object-Oriented HTML (OOHTML).

Rich display (tables, trees, graphs, large structures).

Preserve manual formatting when updating output & results.

Tables for SQL ResultSets.

Batch testing and report ala Fit: Framework for Integrated Test/Fitnesse.

In Wings.

JUnit test code generator.

Language integration (probably OOHTML rather than JSR-223).

Impress integration for use in slide presentations.

GWT-powered Wings for web browsers.

Eclipse integration for convenient access by programmers and programming language feature support (syntax checking, coloring, auto-completion).

Integrate simple code highlighter and formatter.

NetBeans too?

Articles

Demonstrate how Groovy makes OOo UNO programming as "easy" as OOo Basic.

In progress at http://wiki.services.openoffice.org/wiki/API/Samples/Groovy.

Debugging OOo UNO client code using an IDE.

The Wings debug project uses IntelliJ IDEA and can be seen at http://ifcx.svn.sourceforge.net/viewvc/ifcx/Wings/trunk/Wings/.

And on and on...

Why Wings?

Why take the train when you can fly?

IFCX.ORG