IFCX Wings for Scripting
The WYSIWYG Literate Programming System
Version 0.7.2-SNAPSHOT
this is Free Software, copying authorized per the GNU General Public License Version 2.
Table of Contents
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.
(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]
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.
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]
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
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*]
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"}
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}
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}
[1, [2, 3, [4, 5]], 6].flatten()
[1, 2, 3, 4, 5, 6]
[1, 2, 3].collect { it * 2 }
[2, 4, 6]
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 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')
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]
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>
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}]
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
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]
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]
This is the Groovy PLEAC from http://pleac.sourceforge.net/pleac_groovy/datesandtimes.html copied May 5, 2008.
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.
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
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
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}
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=/}
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 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
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 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 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 (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>
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},{}]
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 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]
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);
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 (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
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.
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
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 take the train when you can fly?