Guide
to the

Ublu

Midrange and Mainframe
Life Cycle Extension Language
NASA JPL Candle in Space
Copyright © 2016, 2022 Jack. J. Woehr
jwoehr@softwoehr.com
 All Rights Reserved

SoftWoehr LLC
http://www.softwoehr.com
PO Box 51
Golden, CO 80402-0051
USA

Table of Contents

Who is this Guide for?

It's for people who just want an introduction to using Ublu, or who have downloaded and built Ublu, read enough of the Ublu Reference to know how to launch Ublu, and now want more help understanding Ublu.

The Guide is meant to be easy to read, and extra stuff you may not want to bother with is written like this in small italics.

Why Ublu?

Ublu is an interpretive language for remote systems programming of midrange or mainframe hosts from a Java platform such as Linux, Mac, OpenBSD or Windows. It also can run natively on IBM i ®, IBM z/OS USS ® or any other reasonable Java platform.

I wrote Ublu because I wanted a language to run on OpenBSD/Mac/Linux/Windows to perform ad-hoc process automation primarily on IBM i ®. I was supporting consulting clients by writing individual utility programs using JTOpen which I have used since 1998 to control the AS/400. I decided to consolidate the programs in a language, and the result is Ublu.

Additionally, Ublu can call Java directly allowing the user him- or herself to extend the language interpretively in nearly any direction desired.

It is convenient to operate legacy systems remotely from modern environments:

I also previously wrote PigIron, a Java bridge to IBM z/VM ® System Management API (SMAPI). I provide PigIron support in Ublu. I had been using my FIJI ForthIsh Java Interpreter to drive PigIron, which works very well but is arcane, and also ObjectRexx, which is a wonderful language which doesn't run on as many platforms as Java. Ublu turns out to be an excellent language environment for PigIron SMAPI operations.

Ublu is a new language which was coded quick and easy in Java on top of other open source libraries. The "quick and easy" approach derives from a work environment in which folks just wanted results and had no particular interest in the language itself.

Ublu is easy to build, install, read, and use. It is handsome but not beautiful. Ublu works like an interactive shell scripting language but also has some advanced language features such as compiling, tasking, debugging and a TCP/IP port server mode.

What does Ublu do?

When you've got your complete application written in Ublu, there's an Ublu command gensh which autogenerates a shell script to work under either bash or ksh. The script takes arguments via command-line switches and pass them to your program's top-level function. The script effects a closure on your top-level function.

For a formal summary of everything, see the Ublu Reference. That Reference tells you how to install and run Ublu and lists the Open Source project dependencies as well as documenting every command and facility built into Ublu.

Work in Progress

Ublu is a work in progress and, like all "living" software, likely to remain so. There are always more aspects to IBM i programming to support. Everything that's in Ublu is there because I/We needed it right then. The db command was almost the first thing added, which is effectively an interpretive shell around JDBC. On the other hand, we never needed classic record file access because everyone on staff was SQL-oriented and knew little about physical file access. Thus, only later I added the file and record commands for classic record file access.

Increasingly, Ublu is extended in Ublu itself (often using the calljava command). See the extensions directory.

If you find Ublu useful, please let me know!

If you find a bug, or wish to issue a feature request, you can create an issue in the issue tracker.

Discussing Ublu Online

Currently I discuss Ublu on the Ublu, Java & JTOpen forum of the OSS on IBM i Ryver team.

Invoking Ublu

This is discussed at length in the Invocation section of the Reference, but the executive summary is that, assuming you unpacked in /opt/ublu yielding /opt/ublu/ublu.jar etc., then you can start simple command-line Ublu via java -jar /opt/ublu/ublu.jar or you can proceed to get fancier with ...

A Typical Installation

Linux/Mac/OpenBSD

The basics are that you can run ublu, goublu, or windowing ublu without any of this setup if you understand the command lines sufficiently. But it's easier just to do it this way:

Now you can run Ublu:

Of course the config files are entirely optional and can be added later, or reference to them omitted entirely from the aliases you create to invoke Ublu.

Windows

Now you can run Ublu in windowing mode. Alternatively, you can just java -jar C:\Ublu\ublu.jar to run Ublu in a Windows command console, which works well.

Here's a 256x256 Windows icon for your Ublu shortcut, save as Ublu.ico and assign to the shortcut:

A windows icon for Ublu

Quick Tour of the Ublu Language

Overview

Ublu is mostly composed of commands and a language interpreter. There are also are other Ublu features such as server mode and the tasker, but commands and the language interpreter are the nexus.

Commands

Ublu commands are like individual application programs which are invoked with their arguments under the language interpreter. Command arguments are often references to objects which have been created by other commands. You usually don't have to know much about objects passed from command to command other than what they're supposed to represent.

Most arguments are passed to the dash-commands associated with the command, e.g.

ask -to @answer -say ${ Do you wish to reply to this message? (y/n) }$

The ask command here has two dash-commands, -to and -say. -to has an argument which says to which variable to put the answer to the question about to be asked the user, and -say takes a quoted string which is the prompt to the user.

Here is a link to the Ublu Reference's list of commands by category.

Language Interpreter

The heart of the Ublu language is a text interpreter loop. There can be multiple nested or branching interpreter instances with different variable spaces in one Ublu session, but basically they're all doing the same sort of thing: an outer input loop is getting input and running an inner interpretive loop to interpret a single line of text.

There are actually several different but very similar loops, e.g., the debugger interpreter loop is different from the normal runtime intepreter loop.

Parsing is very simplistic. The parse parses, and having parsed, moves on, nor can all your piety and wit make it go back one lex (unit of parsing).

The text interpreter loop parses the next whitespace-delimited element (a lex) and tries to locate it as an Ublu command or as a defined function. Matching is case-sensitive.

If no match is found, the loop falls out to the outer input loop with an error. The outer loop then waits for more input to pass to the inner interpretive loop.

If the interpretive loop succeeds in finding the next lex in the input as a command or function, it passes the rest of the line to the command or to the function interpreter. Both do their work then pass back the remnant of the input line that wasn't consumed in their work.

Thus, line processing is a relay: the loop hands off to the command/function, which then hands back to loop, and on to next command/function ... until the line is consumed or an error is encountered.

If an error is encountered, the rest of the input is discarded. Typically the interpretive loop will fall out to the outer parsing loop. However, there is a TRY CATCH THROW exception facility in Ublu allowing the program to catch errors without losing its place in the executing program.

While the interpretive loop is line-oriented, certain constructs can span lines, particularly a delimited string or an execution block. Strings are delimited
${ like this }$
and execution blocks
$[ like this ]$
so it is easy for the loop to identify them and demand another line from the outer loop if the closing delimiter hasn't been found yet.

Language control flow constructs are just a form of command. Typically they consume execution blocks from the input:
IF @truefalse THEN $[ command arg arg command arg arg command arg ... ]$ ELSE $[ command arg ... ]$
As noted, these execution blocks can span multiple lines.

A Full Ublu Program

Here is a full Ublu program. It uses FUNC to define a number of functions allowing the user to checks a message queue on an IBM i server and lists the messages, asking the user on each if s/he would like to reply, and parsing the reply if one is given.  Take a look at the program (and its associate shell script given here) and then we will discuss the program line-by-line below.

The Ublu codes samples are shown in this document with syntax coloring from a jEdit mode ublu.xml reproduced here via Code2HTML.
The jEdit mode is in
share/jEdit with instructions how to install it at the head of the mode file.
There is also a Vim mode for Ublu in share/ublu-vimfiles-master . Just copy the dir structure under that directory to your $HOME/.vim directory.

examples/autoreply.ublu

   1 # autoreply.ublu ... Find and reply to all *INQ & *NOTIFY messages in a given MSGQ
   2 # jack j. woehr jwoehr@absolute-performance.com jwoehr@softwoehr.com
   3 # 2015-03-10
   4 
   5 # instance message queue
   6 FUNC getMsgQ ( system user password ifspath msgq ) $[
   7     LOCAL @as400
   8     as400 -to @as400 @@system @@user @@password
   9     msgq -to @@msgq -as400 @as400 -instance @@ifspath
  10 ]$
  11 
  12 # get list of messages needing reply
  13 FUNC getReplyMsgs ( msgq replylist ) $[
  14     msgq -- @@msgq -query -reply -to @@replylist
  15 ]$
  16 
  17 # get messages, walk list and offer user chance to reply to each
  18 FUNC autoreply ( system user password ifspath ) $[
  19     LOCAL @msgq LOCAL @replylist LOCAL @key
  20     LOCAL @answer LOCAL @tf LOCAL @reply
  21     getMsgQ ( @@system @@user @@password @@ifspath @msgq )
  22     getReplyMsgs ( @msgq @replylist )
  23     FOR @msg in @replylist $[
  24         msg -- @msg -to @key -key
  25         put -from @msg
  26         ask -to @answer -say ${ Do you wish to reply to this message? (y/n) }$
  27         test -to @tf -eq @answer y
  28         IF @tf THEN $[
  29             ask -to @reply -say ${ Please enter your reply }$
  30             msgq -- @msgq -sendreplybinkey @key @reply
  31             put ${ Reply sent. }$
  32         ]$
  33     ]$
  34 ]$
  35 

Understanding examples/autoreply.ublu

   1 # autoreply.ublu ... Find and reply to all *INQ & *NOTIFY messages in a given MSGQ
   2 # jack j. woehr jwoehr@absolute-performance.com jwoehr@softwoehr.com
   3 # 2015-03-10
   4 

These lines are, of course, comments. Comments go to the end of the line.

As we discussed above, execution blocks (e.g., the body of a function) delimited by $[ and ]$ can span lines. The way the interpretive loop does this is that it keeps calling for input from the outer parsing loop until it finds the closing delimiter. Effectively, the entire execution block is one line. So line comments (those commented by the # command) should not be placed inside function bodies or they will comment out the entire function body! Instead, inside functions use the \\ command.
   6 FUNC getMsgQ ( system user password ifspath msgq ) $[
   7     LOCAL @as400
   8     as400 -to @as400 @@system @@user @@password
   9     msgq -to @@msgq -as400 @as400 -instance @@ifspath
  10 ]$
Here the function getMsgQ is defined. The argument list is not typed in any way. These are merely token names for whatever will appear in that position inside the parentheses when the function is invoked. Inside the body of the function these arguments will be referenced by prefixing them with @@ e.g., @@password.

Note that the parentheses are separated from both the function name and from the argument list!!

In the example, I happened to use the bad style of naming arguments the same name as Ublu commands (system user and msgq) which, of course, the editor then syntax-colored, mistaking them for keywords. This is entirely legal in Ublu but, again, it's bad style.

The function body is the execution block between $[ and ]$

On line 7, a local variable is declared. All variables ("tuple variables" as they are called, or just "tuples") are identified by the @ prefix, whether they are LOCAL or global (or interpreter-level-local).

Tuple variables come into existence just by being mentioned. Arguments to a function may be tuples or plain words, Ublu commands typically know what to do with either when passed them as arguments.

The LOCAL declaration LOCAL @as400 serves to says that there will be a local tuple variable which will hide any other variables of the same name in the program while in this execution block and then disappear at the end of the execution block, i.e., at the end of function execution. Equally, the variable name @as400 could have been used without declaration, but then it would persist beyond the end of the function, and further, if there happened to already exist such a variable in the program, it would be referencing that same variable, possibly unintentionally.

Lines 8 and 9 are Ublu commands.

Line 8 says, "Create a remote server instance for the system function argument using the user and password function arguments. Store that instance in the declared local variable @as400".

Line 9 says, "Create an object referencing the message queue on the server represented by @as400 and identified by the IFS path represented by the function argument ifspath and store that object to the function argument msgq , presumably a variable, with an error resulting if a variable was not provided in that position in the function argument list."

The variable named which is provided in the function argument list in that position does not have to be named anything like the declaring argument list named it. It could be called @fred for instance. It does not even have to have been previously declared. It comes into global existence by being named. It can later be deleted via the tuple -delete @fred command, if so desired. Or, it might be a LOCAL variable from a calling function.
  13 FUNC getReplyMsgs ( msgq replylist ) $[
  14     msgq -- @@msgq -query -reply -to @@replylist
  15 ]$
These lines declare another function which says, "Given a message queue instance and a variable name in which to store the result of this function, create a list of messages needing reply waiting on the remote IBM i message queue represented by the message queue instance."
  18 FUNC autoreply ( system user password ifspath ) $[
  19     LOCAL @msgq LOCAL @replylist LOCAL @key
  20     LOCAL @answer LOCAL @tf LOCAL @reply
  21     getMsgQ ( @@system @@user @@password @@ifspath @msgq )
  22     getReplyMsgs ( @msgq @replylist )
  23     FOR @msg in @replylist $[
  24         msg -- @msg -to @key -key
  25         put -from @msg
  26         ask -to @answer -say ${ Do you wish to reply to this message? (y/n) }$
  27         test -to @tf -eq @answer y
  28         IF @tf THEN $[
  29             ask -to @reply -say ${ Please enter your reply }$
  30             msgq -- @msgq -sendreplybinkey @key @reply
  31             put ${ Reply sent. }$
  32         ]$
  33     ]$
  34 ]$
  35 

These lines declare autoreply which is the top-level function in the file. Note that we declared these functions from low-level to top level. This is not necessary, since the functions are not interpreted until called, but it's easier to understand this way.

Ublu function definitions offer almost no compiler security. They are simply parsed and stored as execution blocks associated with a call specification and no validation of their runtime action is performed.

autoreply takes as its arguments a system name, user id, password and IFS path. Arguments to the function, whether they are plainwords or variables are referenced inside the function by their declared name decorated with @@ . If a function is declared:
FUNC foo ( bar )
then the argument bar is referenced inside the body of the function as @@bar
FUNC foo ( bar ) $[ put -from @@bar ]$
autoreply  also declares a few local variables that will hide global and more outer local variables of the same name and then disappear at the end of the execution block in which they are declared.

autoreply  calls the function getMsgQ to get the message queue instance.

autoreply then calls getReplyMsgs to get a list messages awaiting a reply.

autoreply then loops in a FOR loop through the list and uses ask command to ask the user if the user wishes to reply each individual message. It does test on the response against the character y and IF the result of that test is true, gets a reply via ask and replies to the message queue, informing the user via put that the reply was sent.

Generating launcher scripts for Ublu programs via gensh

The autoreply program would still require understanding of Ublu to load and execute, but our help desk operator doesn't have to deal with that. As noted above, the Ublu gensh command can generate a shell script so that autoreply can become effectively a shell command that takes options to identify system and queue. gensh shell scripts allow the designer to turn Ublu functions into closures.

Here is the gensh-generated  shell script to run the top-level function autoreply from the program autoreply.ublu

examples/autoreply.sh

   1 # autoreply.sh ... Find and reply to all *INQ & *NOTIFY messages in a given MSGQ 
   2 # autogenerated Wed Mar 09 19:26:19 MST 2016 by jax using command:
   3 # gensh -to autoreply.sh -path /opt/ublu/ublu.jar -optr s SERVER @server ${ Server }$ -optr u USER @user ${ User }$ -optr p PASSWORD @password ${ Password }$ -optr q QPATHIFS @qpathifs ${ IFS path to message queue, e.g., /QSYS.LIB/QSYSOPR.MSGQ or /QSYS.LIB/QUSRSYS.LIB/MYUSERID.MSGQ }$ ${ autoreply.sh ... Find and reply to all *INQ & *NOTIFY messages in a given MSGQ }$ /opt/ublu/examples/autoreply.ublu ${ autoreply ( @server @user @password @qpathifs ) }$
   4 
   5 # Usage message
   6 function usage { 
   7 echo "autoreply.sh ... Find and reply to all *INQ & *NOTIFY messages in a given MSGQ "
   8 echo "This shell script was autogenerated Wed Mar 09 19:26:19 MST 2016 by jax."
   9 echo "Usage: $0 [silent] -h -s SERVER -u USER -p PASSWORD -q QPATHIFS "
  10 echo "  where"
  11 echo "  -h      display this help message and exit 0"
  12 echo "  -s SERVER   Server  (required option)"
  13 echo "  -u USER User  (required option)"
  14 echo "  -p PASSWORD Password  (required option)"
  15 echo "  -q QPATHIFS IFS path to message queue, e.g., /QSYS.LIB/QSYSOPR.MSGQ or /QSYS.LIB/QUSRSYS.LIB/MYUSERID.MSGQ  (required option)"
  16 echo "---"
  17 echo "If the keyword 'silent' appears ahead of all options, then included files will not echo and prompting is suppressed."
  18 echo "Exit code is the result of execution, or 0 for -h or 2 if there is an error in processing options"
  19 }
  20 
  21 #Test if user wants silent includes
  22 if [ "$1" == "silent" ]
  23 then
  24     SILENT="-silent "
  25     shift
  26 else
  27     SILENT=""
  28 fi
  29 
  30 # Process options
  31 while getopts s:u:p:q:h the_opt
  32 do
  33     case "$the_opt" in
  34         s)  SERVER="$OPTARG";;
  35         u)  USER="$OPTARG";;
  36         p)  PASSWORD="$OPTARG";;
  37         q)  QPATHIFS="$OPTARG";;
  38         h)  usage;exit 0;;
  39         [?])    usage;exit 2;;
  40 
  41     esac
  42 done
  43 shift `expr ${OPTIND} - 1`
  44 if [ $# -ne 0 ]
  45 then
  46     echo "Superfluous argument(s) $*"
  47     usage
  48     exit 2
  49 fi
  50 
  51 # Translate options to tuple assignments
  52 if [ "${SERVER}" != "" ]
  53 then
  54     gensh_runtime_opts="${gensh_runtime_opts}string -to @server -trim \${ ${SERVER} }$ "
  55 else
  56     echo "Option -s SERVER is a required option but is not present."
  57     usage
  58     exit 2
  59 fi
  60 if [ "${USER}" != "" ]
  61 then
  62     gensh_runtime_opts="${gensh_runtime_opts}string -to @user -trim \${ ${USER} }$ "
  63 else
  64     echo "Option -u USER is a required option but is not present."
  65     usage
  66     exit 2
  67 fi
  68 if [ "${PASSWORD}" != "" ]
  69 then
  70     gensh_runtime_opts="${gensh_runtime_opts}string -to @password -trim \${ ${PASSWORD} }$ "
  71 else
  72     echo "Option -p PASSWORD is a required option but is not present."
  73     usage
  74     exit 2
  75 fi
  76 if [ "${QPATHIFS}" != "" ]
  77 then
  78     gensh_runtime_opts="${gensh_runtime_opts}string -to @qpathifs -trim \${ ${QPATHIFS} }$ "
  79 else
  80     echo "Option -q QPATHIFS is a required option but is not present."
  81     usage
  82     exit 2
  83 fi
  84 
  85 # Invocation
  86 java -jar /opt/ublu/ublu.jar ${gensh_runtime_opts} include ${SILENT}/opt/ublu/examples/autoreply.ublu autoreply \( @server @user @password @qpathifs \) 
  87 exit $?
  88 

Some Obvious Questions about Ublu

The above explanations raise several new questions:

We'll answer those questions next.

What are the data types in Ublu?

Ublu can be described as an "object-disoriented language".

Ublu is a form of interpretive Java that hides as much as possible about object usage.

There are basically five (5) data types in the Ublu language:
  1. plainword
  2. object
  3. variable
  4. string
  5. execution block

A plainword is a single whitespace-delimited lex

An object represents something or other, often a something-or-other on a remote server but it's pretty opaque. You generally know which objects you have in hand go with which Ublu commands. It's simple because the same commands that use the objects provide the objects in the first place.

Objects are passed around in variables ("tuple variables" or "tuples") marked with the @ sign. Variables come into existence by being mentioned. If they're in an execution block between $[ and ]$ they can be marked LOCAL and disappear at the end of the block. Otherwise, they persist until expressly deleted or until the interpreter in whose context they reside exits.

A string in Ublu is parsed from a sequence of plainwords wrapped by ${ and }$ e.g. ${ hi there }$ Many commands in Ublu will take either a string, a plainword, or a variable as an argument. This is symbolized in the command descriptions in the Ublu Reference by ~@{something}. The ~ means also that the argument to the dash-command can be popped from Ublu's LIFO stack.

An execution block is a body of Ublu program text wrapped in $[ and ]$ . Execution blocks are used as the body of control constructs, such as IF ... THEN ... ELSE , function definitions and TRY ... CATCH , e.g.

IF @trueorfalse THEN $[ joblist -as400 @myserver -to @jl ]$

What are those dash thingies after Ublu commands?

The dash-commands after Ublu commands pass arguments to the command, often objects being kept in variables.

as400 -to @myServer somehostname.com someuserid somepassword
joblist -as400 @myServer

would retrieve a joblist from somehostname.com provided someuserid had authorization to request it.

One special dash-command is double-dash -- the "eponymous dash-command" which represents an object identified in the command description with same name as the command itself. For example, the as400 command instances and operates on objects representing a connection to a remote IBM i server. Once you have instanced such an object and stored it -to @someServer you can use it and then later test if it is still connected by passing the same variable back to the as400 command like this:
as400 -as400 @someServer -connected
which will put either true or false. But you could also have simply used the eponymous dash-command:
as400 -- @someServer -connected
since, for the as400 command, -- represents -as400.

What is a put ?

 The as400 command stored -to @myServer which variable was then passed to joblist command as -as400 @myServer

-to is a dash-command to almost every Ublu command. It says where the put is to go.

Most Ublu commands put a result. That result could be text or an object. If no -to destination is specified, then the put goes to standard out. If the result is an object and the put is to standard out,  the object is rendered in text. Ublu knows how to render objects in text in a useful fashion.

The full discussion of put requires an understanding of Data Sinks discussed in the Ublu Reference.

tuple -assign instead of put

Once an object is assigned to a tuple variable, put tends to have the sometimes-undesirable effect of turning the value it puts into a string.

To assign an object from one tuple or another (including from the tuple stack), use tuple -assign @destination-tuple ~@value-source.

How are arguments passed?

Arguments in Ublu are passed by reference, not of by value.

When you pass a variable as an argument to a command or function, that variable is accessible to the function and any changes the function makes to that argument are changes to the variable itself.

Arguments in function argument lists are actually token-pasted into the execution block which is the body of the function.

Token pasting in function argument marshalling allows some unusual programming techniques as we'll see later, e.g., in the section on the Tuple Stack.

Local variables as arguments

Local variables only are accessible within the execution block in which they are declared. This encompassed nested blocks within the declaring block (unless a local of the same name is declared in the inner block, thus hiding the outer block's local).

This means you can pass a local as a positional argument to a function called within a block and that function can reference the provided value normally.

FUNC bar ( myarg ) $[ put -from @@myarg ]$
FUNC foo ( ) $[ LOCAL @a put -to @a 17 bar ( @a ) ]$

foo ( ) when invoked will display the number 17.

A limit on local variables as arguments

Because calls are by reference and locals have only block scope, there exist cases where it appears you can pass a local to a function but you really cannot. Here's an instance of that:

examples/test/localvarthread.ublu
   1 # Test background threading with local vars and global vars.
   2 # Example from Ublu https://github.com/jwoehr/ublu
   3 # Copyright (C) 2016 Jack J. Woehr http://www.softwoehr.com
   4 # See the Ublu license (BSD-2 open source)
   5 
   6 # A simple function to run in a thread
   7 FUNC localVarThread ( var ) $[
   8     LOCAL @myvar 
   9     LOCAL @start 
  10     LOCAL @end 
  11     put -from @@var 
  12     eval -to @myvar + 1 @@var
  13     put -to @start 1
  14     put -to @end 9 
  15     DO @start @end $[
  16         eval -to @myvar + @myvar 1 put -from @myvar  system ${ sleep 10 }$
  17     ]$
  18     put done!
  19 ]$
  20 
  21 # Doesn't work with locals.
  22 # Locals in the calling function don't exist in the context of the created thread.
  23 FUNC runItLocal ( number ) $[    
  24     LOCAL @x
  25     put -to @x 100000
  26     eval -to @x + @@number 7
  27     put -to @p ${ localVarThread ( @x ) }$
  28     thread -to @t -from @p
  29     thread -thread @t -start
  30 ]$
  31 
  32 # This works, uses a global variable  
  33 FUNC runIt ( number ) $[
  34     put -to @x 100000
  35     eval -to @x + @@number 7
  36     put -to @p ${ localVarThread ( @x ) }$
  37     thread -to @t -from @p
  38     thread -thread @t -start
  39 ]$
  40 

In the above example, a thread is launched with a string argument, effectively an execution block although passed as a string in a variable. Even though the thread is launched in the same execution block as the local variable declaration, the thread's runtime environment is not nested within the execution block, so a local variable cannot be passed to the thread.

The examples directory

Ublu is distributed with an examples directory. Most of these are code actually used in production, though of course there is NO WARRANTY OR GUARANTY of correctness or fitness for a given application (see the LICENSE).

Example files consist of definitions of functions embodying factors of common operations, and of the shell scripts generated by gensh used to launch the functions.  Not all examples are pristine examples of Ublu coding style, which is evolving. Generally, the more parameterization, the better.

The Test Suite

Under the examples directory is the test directory which contains individual routines used by the evolving Ublu test suite.

The test suite itself resides in examples/test/suite. examples/test/suite/test.all.sh is the shell script test suite launcher generated by Ublu's gensh command. It invokes Ublu on examples/test/suite/test.all.ublu with arguments.

$ ./test.all.sh -h
test.all.sh ... run test suite from test suite directory 
This shell script was autogenerated Wed Aug 09 04:47:03 MDT 2017 by jax.
Usage: ./test.all.sh [silent] [-h] [-X...] [-Dprop=val] -d DEFAULTS -w WORKDIR -i IFSPATH -c DESCRIPTION [-k DELWORK]
        where
        -h              display this help message and exit 0
        -X xOpt         pass a -X option to the JVM (can be used many times)
        -D some.property="some value"   pass a property to the JVM (can be used many times)
        -d DEFAULTS     full path to defaults file  (required option)
        -w WORKDIR      Work directory for test  (required option)
        -i IFSPATH      IFS path to test report physical file to be created  (required option)
        -c DESCRIPTION  description for test run pf created  (required option)
        -k DELWORK      delete all files in work dir if value is Y 

test.all.ublu expects that certain Ublu variables and constants be set before it runs. The required -d option to test.all.sh points to a defaults file which contains these values in Ublu syntax. examples/test/suite/test.defaults.example.ublu is a sample test suite defaults file. Copy this file anywhere and edit it to supply valid defaults.

examples/test/suite/sample.test.driver.sh is a sample test driver script to invoke test.all.sh. It is convenient to use such a driver script if you will be invoking the test suite multiple times. Let's examine that script to see what the test suite requires.

   1 # Sample test driver
   2 RUNNAME=$1
   3 COMMENT=$2
   4 /opt/ublu/examples/test/suite/test.all.sh silent \
   5 -Djavax.net.ssl.trustStore=/opt/ublu/keystores/ublutruststore \
   6 -Djavax.net.ssl.keyStore=/opt/ublu/keystore/ubluserverstore \
   7 -Djavax.net.ssl.keyStorePassword=xxxxxxxx \
   8 -d ~/work/Ublu/Testing/test.defaults.myibmi.ublu \
   9 -w ~/work/Ublu/Testing/workdir \
  10 -k Y \
  11 -i /QSYS.LIB/UBLUTEST.LIB/${RUNNAME}.FILE \
  12 -c "${COMMENT}" \
  13 2>&1 | tee ${RUNNAME}.txt
sample.test.driver.sh takes 2 arguments:
  1. RUNNAME is the name of the test run. This name will be used as an IBM i PF file name, so it must follow the rules of PF file naming. I use Tyyyymmddn for year/month/day and run number.
  2. COMMENT is whatever comment about the test you wish to make. It will be used as the description for the IBM i PF file named by RUNNAME so keep it short.
Line 4 of the script invokes test.all.sh with several arguments:

The extensions directory

Ublu is distributed with an extensions directory which contains extensions to the base Ublu system which are written in Ublu itself.

As the IBM i system upon which Ublu primarily operates is so feature-rich, it is impractical to put every useful utility in the base system. Therefore, as Ublu evolves, more and more features will likely be relegated to the extensions directory to be loaded via include as needed by the application programmer.

Again, there is NO WARRANTY OR GUARANTY of correctness or fitness for a given application (see the LICENSE).

Object-disoriented

A few words about "object-disoriented". Object languages associate methods with specific data structures and define these relationships in classes. Ublu began as a collection of single-purpose utilities designed procedurally and then grew into a language when it became necessary to pass objects from utility to utility in order to get more useful work done.

You have seen how Ublu works and read about the put command. The put command was the beginning of Ublu "object-disorientation". When Ublu emerged from the need to pass objects rather than string values from one utility to the next, the put command was an immediate result of that change to an interpretive language. I needed to see quickly what was going on.

A lot of effort went into put so it would give useful insight into anything passed around from command to command in Ublu without demanding that the programmer remember what was where. It is always safe to simply put @foo because a useful string representation is returned. And put -to @bar @foo put whatever is in @foo also into @bar without worrying about object class.

What Ublu's language structure comes down to is that:

E.g, in the example ...

as400 -to @sys mysys myuid mypwd
as400 -- @sys -alivesvc CENTRAL
true
... effectively, an Ublu as400 class creates an object instance stored in @sys. That object has a method alivesvc(CENTRAL) invoked on it.

Ublu's default "object-disoriented" syntax is wordier than a true object language, in that the class is named each time (the "command"), but it tends to be easy to use interpretively. It's clunky, yet it is reminiscent of traditional "big iron"-style command syntax and is pretty easy to remember.

Autonomes offer easier syntax

Autonomes simplify Ublu syntax. An autonome is a tuple variable whose value is of one of many recognized types allowed by Ublu to stand for its most typical command and the eponymous dash-command.

Thus, assuming that @job contains a valid instance created by the job command,

job -- @job -info
can be expressed
@job -info

And in

user -to @someuser -as400 @myserver FRED
@someuser -enable
the phrase
@someuser -enable
will enable FRED's account precisely the same as
user -- @someuser -enable
"Precisely" because, in fact, autonomism is effected by editing in place the input stream.

The Tuple Stack

The Forth programming language taught me the use of a LIFO stack in programming. The Ublu Tuple stack allows some interesting programming metaphors that allow coding functions that get around some of the clunkiness of basic Ublu syntax. Instead of stowing results in tuple variables, functions can push their results to the stack to be popped as arguments by the next function. Additionally, the token-pasting nature of Ublu FUNC arguments allow even dash-commands to be presented to an Ublu command via the argument list to a function.

Here is an example (examples/stringpush.ublu) of this object-disoriented slackomorphic style of programming:
 1 # stringpush.ublu
 2 # Example from Ublu Midrange and Mainframe Life Cycle Extension language
 3 # https://github.com/jwoehr/ublu
 4 # Copyright (C) 2016 Jack J. Woehr http://www.softwoehr.com
 5 # See the Ublu license (BSD-2 open source)
 6 
 7 # Execute the string command providing the op and 2 arguments
 8 # and push the result to the tuple LIFO stack
 9 # Example: $-2 ( -cat ~ ~ ) will pop the tuple stack twice,
10 # concatenate the two string values and push the result back.
11 FUNC $~2 ( op a b ) $[ string -to ~ @@op @@a @@b ]$
12 # Here's an example session:
13 # > put -to ~ ${ arf arf arf }$
14 # > put -to ~ ${ foo bar woof }$
15 # > $~2 ( -cat ~ ~ )
16 # > put ~
17 # foo bar woof arf arf arf
18 # ...
19 # Note that "the first shall be last".
20 # ...
21 # > put -to ~ ${ arf arf arf }$
22 # > put -to ~ ${ arf arf arf }$
23 # > $~2 ( -eq ~ ~ )
24 # > put ~
25 # true
26 
27 # end
28 

Tuple Stack Pop in Function Arguments

Using the ~ symbol for a tuple stack pop as an argument to a function can lead to unexpected results.

Function arguments are passed by token pasting as discussed above (How are arguments passed?). So when a function body references the argument, it sees precisely what you provided as the runtime argument. Ergo, in an example like:

   1 FUNC foo ( bar ) $[
   2     put @@bar
   3     put @@bar
   4 ]$
   5   
   6 FUNC foo1 ( bar ) $[
   7     LOCAL @bar1
   8     put -to @bar1 @@bar
   9     put @bar1
  10     put @bar1
  11 ]$

the function foo ( bar ) is incorrect, because the second reference to @@bar will find an empty stack:

> put -to ~ woof
> foo ( ~ )
woof
Ublu:1:UbluInterpreter.Thread[main,5,main]:SEVERE:ublu.util.Interpreter.loop():Command "put" threw exception null
java.util.EmptyStackException
        ...

whereas foo1 ( bar ) is correct:

   
>  put -to ~ woof
> foo1 ( ~ )
woof
woof

Calling Java

Ublu can call Java directly via the calljava command. This allows the user to extend the language interpretively in nearly any direction desired. This is useful, because as I continually extend Ublu, it is not practical to support every option for every object in the JTOpen library with Ublu sytax

For instance, at present writing, there is no Ublu dash-command to fetch the reply status of a queued message. Maybe I'll add that later, if I need it enough. For the present, if I need that reply status, I calljava on the queued message object easily obtainable from Ublu.

> as400 -to @mysys MYSYS.ORG MYPROFILE MYPASSWORD
> joblog -to @jl -as400 @mysys -new MYJOBNAME MYUID 349989
> joblog -to ~ -- @jl -length
> joblog -- @jl -to ~ -qm 0 ~
> FOR @i in ~ $[ calljava -obj @i -method getReplyStatus -to @result put @result ]$

Of course, in this sort of programming, downloading and perusing the Ublu javadocs and JTOpen library javadocs is essential. In the example above, one would have to know that Ublu's joblog -qm is returning a collection of com.ibm.as400.access.QueuedMessage instances.

Goublu, the enhanced Ublu console

Goublu is a Go language front end that launches Ublu and provides a better console interface to the running Ublu instance than the console support provided by Java.

On most platforms, the Java console does not provide command line editing nor command line recall. Goublu provides both, along with other features such as macro expansion for commonly used command lines.

While "plain" Ublu may be better for running Ublu applications, Goublu is a more congenial environment than "plain" Ublu for interactive Ublu usage.

Goublu must be compiled on the specific platform. You can install the Go language development environment and follow the instructions to get Goublu and build it on your machine.

Goublu Screenshot

Running Ublu as a window

The -w switch on the Ublu command line starts Ublu as a windowing application. This is useful if you want to paste in and save out swatches of text. In other modes of development, it may be less congenial an environment that plain Ublu or the Goublu console front end for Ublu.

When Ublu is started in windowing mode:

Windowing Ublu

I use the following properties file $HOME/.config/ublu/ubluwin.properties on Ubuntu:

#Ublu Windowing Properties
#Sun Jul 02 18:00:59 MDT 2017
UbluTextAreaBGColor=ff000000
UbluTextAreaFGColor=e3e90000
UbluTextAreaFont=Liberation Mono
UbluInputAreaFontSize=16
UbluInputAreaFontStyle=0
UbluTextAreaFontStyle=0
UbluInputAreaBGColor=ff000000
UbluInputAreaFont=Liberation Mono
UbluTextAreaFontSize=16
UbluInputAreaFGColor=ff36b80b

When I start Ublu java -jar /opt/ublu/ublu.jar -w $HOME/.config/ublu/ubluwin.properties Ublu looks like this:

!! Windowing Ublu, you may wish to set props -set signon.handler.type BUILTIN so that on a password or username error Ublu prompts graphically instead of to the console. See the Ublu Reference on the props command.

Running Ublu on Android

UserLAnd Ubuntu(Android 8 and above)

The modern Linux environment on Android 8 and above is UserLAnd.

UserLAnd is very solid and complete. The Ubuntu flavor even has Golang which allows us to build Goublu as well as Ublu. The procedure shown below allows you to build the current main branch of Ublu and Goublu. You can alternatively download a source release of either or both. You can even download a release build of Ublul, but if you want Goublu, you will have to build it.

Now you can start Ublu in line mode with java -jar /opt/ublu/ublu.jar or in Goublu terminal mode with $GOPATH/bin/goublu.

If you want to run Ublu in windowing mode, you will use VNC Viewer which will require some setup.

  1. Install VNC Viewer from Google Play Store
  2. Open VNC Viewer. It will offer you the choice to create an account and subscribe, but that is not necessary for creating a local connection.
  3. Create a local connection for localhost:5901 ... don't start the connection yet, because UserLAnd isn't serving yet.
  4. In a UserLAnd shell, start vncserver
  5. Now go to VNC viewer and start the local connection you defined.
  6. In UserLAnd execute
    1. export DISPLAY=:1
    2. java -jar /opt/ublu/ublu.jar -w
    to start windowed Ublu.
  7. Switch to the VNC Viewer and use windowed Ublu.
  8. When you are done with the VNC Viewer remember to execute vncserver -kill :1 so that the vncserver cleans up after itself. If you shut down UserLAnd (e.g., by turning off your phone) without having exited the vncserver, you will have rm -rf /tmp/.X11-unix to clear the lockfile.

VNC is a wrapper around the X Windowing System. UserLAnd Ubuntu's default windowing manager for the XWindowing System is twm which is pretty sparse. I'm using FVMW.

Troubleshooting windowing

Ublu on UserLAnd Ubuntu on Android 8 Goublu on UserLAnd Ubuntu on Android 8 Ublu window UserLAnd Ubuntu (twm)on Android 8
Ublu on UserLAnd Ubuntu on Android 8 Goublu on UserLAnd Ubuntu on Android 8 Ublu window UserLAnd Ubuntu (twm) on Android 8
Ublu, iACS, xterm: UserLAnd Ubuntu (fvwm) on Android 11
Ublu, iACS, xterm: UserLAnd Ubuntu (fvwm) on Android 11

GNURootDebian (before Android 8)

Note: GNURootDebian stopped working at Android 8 and the project was totally rewritten as UserLAnd. See the UserLAnd Ubuntu section below for information on current technology.

This section is included for historical purposes

Ublu can run under GnuRoot Debian.

  1. Install GnuRoot Debian
  2. Open a GnuRoot Debian terminal
  3. Finish install steps as prompted
  4. Test Xwindowing support to confirm install of base system finished
  5. apt-get upgrade
  6. apt update
  7. apt upgrade
  8. apt install jedit
  9. Use ssh to copy your Ublu .zip archive to a directory such as /opt/ublu
  10. Unpack archive
  11. Run Ublu from the terminal or open XWindows to run Ublu in a Window

Learning GnuRoot Debian can require some patience. You may have to install manually via apt or apt-get some packages I have not mentioned above.

Ublu in a terminal under GnuRoot Debian on Android Ublu in an xterm under GnuRoot Debian on Android Ublu in a window under GnuRoot Debian on Android
Ublu on GnuRoot Debian on Android Ublu in an xterm under GnuRoot Debian on Android Ublu in a window under GnuRoot Debian on Android

Notes

ublu.util.Interpreter.loop()

Here is the Java code (or some fairly recent version of same, consult the source) for the interpretive loop.
   1     public COMMANDRESULT loop() {
   2         COMMANDRESULT lastCommandResult = COMMANDRESULT.SUCCESS;
   3         String initialCommandLine = getArgArray().toHistoryLine();
   4         while (!getArgArray().isEmpty() && !good_bye && !isBreakIssued()) {
   5             // /* Debug */ System.err.println(" arg array is " + getArgArray());
   6             if (getArgArray().isNextTupleNameOrPop()) {
   7                 Tuple t = getArgArray().peekNextTupleOrPop();
   8                 if (Autonome.autonomize(t, getArgArray())) {
   9                     continue;
  10                 } else {
  11                     getLogger().log(Level.SEVERE, "non-autonomized tuple or pop : {0}", getArgArray().next());
  12                     lastCommandResult = COMMANDRESULT.FAILURE;
  13                     break;
  14                 }
  15             }
  16             String commandName = getArgArray().next().trim();
  17             if (commandName.equals("")) {
  18                 continue; // cr or some sort of whitespace got parsed, skip to next
  19             }
  20             if (getCmdMap().containsKey(commandName)) {
  21                 CommandInterface command = getCmd(this, commandName);
  22                 try {
  23                     setArgArray(command.cmd(getArgArray()));
  24                     lastCommandResult = command.getResult();
  25                     if (lastCommandResult == COMMANDRESULT.FAILURE) {
  26                         break; // we exit the loop on error
  27                     }
  28                 } catch (IllegalArgumentException ex) {
  29                     getLogger().log(Level.SEVERE, "Command \"" + commandName + "\" threw exception", ex);
  30                     lastCommandResult = COMMANDRESULT.FAILURE;
  31                     break;
  32                 } catch (java.lang.RuntimeException ex) {
  33                     /* java.net.UnknownHostException lands here, as well as  */
  34  /* com.ibm.as400.access.ExtendedIllegalArgumentException */
  35                     getLogger().log(Level.SEVERE, "Command \"" + commandName + "\" threw exception", ex);
  36                     lastCommandResult = COMMANDRESULT.FAILURE;
  37                     break;
  38                 }
  39             } else if (getFunctorMap().containsKey(commandName)) {
  40                 try {
  41                     TupleNameList tnl = parseTupleNameList();
  42                     if (tnl != null) {
  43                         lastCommandResult = executeFunctor(getFunctor(commandName), tnl);
  44                         if (lastCommandResult == COMMANDRESULT.FAILURE) {
  45                             break;
  46                         }
  47                     } else {
  48                         getLogger().log(Level.SEVERE, "Found function {0} but could not execute it", commandName);
  49                         lastCommandResult = COMMANDRESULT.FAILURE;
  50                         break;
  51                     }
  52                 } catch (java.lang.RuntimeException ex) {
  53                     getLogger().log(Level.SEVERE, "Function \"" + commandName + "\" threw exception", ex);
  54                     lastCommandResult = COMMANDRESULT.FAILURE;
  55                     break;
  56                 }
  57             } else {
  58                 getLogger().log(Level.SEVERE, "Command \"{0}\" not found.", commandName);
  59                 lastCommandResult = COMMANDRESULT.FAILURE;
  60                 break;
  61             }
  62         }
  63         if (!isIncluding() && !initialCommandLine.isEmpty()) {
  64             if (getHistory() != null) {
  65                 try {
  66                     getHistory().writeLine(initialCommandLine);
  67                 } catch (IOException ex) {
  68                     getLogger().log(Level.WARNING, "Couldn't write to history file " + getHistory().getHistoryFileName(), ex);
  69                 }
  70             }
  71         }
  72         setGlobal_ret_val(lastCommandResult.ordinal());
  73         return lastCommandResult;
  74     }
  75 
This guide is a work in progress ... continued ...