Professional Documents
Culture Documents
2011
j of BASE,
TAF of C
CONTENTS
CONTENTS
Contents
1 Preface
2 R11 is out
3 Standalone TAFC
11
13
17
27
36
41
51
54
58
62
66
76
79
17 Easter eggs
89
100
110
129
J of BASE, TAF of C
By KZM
CONTENTS
CONTENTS
134
142
23 Conclusions
148
J of BASE, TAF of C
By KZM
1 PREFACE
Preface
ello everyone. Almost one year passed since Ive released my first book called This
::::: is how Globus works (link to it is at the section Conclusions). As time passed, I
realized that it needs the continuation. So here it is.
As far as you know, Temenos believes that T24 programming is for TAM department
only and technical consultants in Temenos shouldnt program at all. But what if you are
supporting T24, extracting data from it or doing some other kind of T24-related technical
work? Surely you need to program. Not necessarily in jBC. (You see, I no more call it Basic.)
So were going to dive into jBASE and jBC just below T24 level, see what we can do
there and what we cant, use some tricks (with all the responsibility of someone who isnt
going to ruin the things above). Well try to use different methods of solving an issue. Well
see here that we often dont need to write a T24 subroutine to achieve our goals.
As usual, all opinions are those of the author and all trademarks are what they usually
are. As usual the risk is yours so please train on a local PC or even better inside a
virtual machine.
As usual, I digress a lot but thats deliberate.
To MV gurus: many things in this book might look absolutely evident to you (and
therefore not worth mentioning at all) being at the same time kind of revelation for a T24
person.
J of BASE, TAF of C
By KZM
2 R11 IS OUT
R11 is out
m not going to cover all new cool features of R11; rather than that Id like to inform
:::: you that from technical point of view all looks very familiar. We still have OFS, we still
have jBC, jQL and jCL. This makes life of T24 supporter/reporter/whoever-we-are way too
easier.
From the previous book you already know how to install T24. The difference is that there
might be some new dependencies that are caused by usage (under Windows 32 platform) of
MS VC 2010 (which in turn might require OS upgrade) but some parts still require MS VC
2008. I hope that a tool like Dependency walker helped you to sort out the problems and
now you are ready to start at least jBASE... sorry, TAFC... at least in standalone mode. (It
looks that I tend to call it jBASE in one cases and TAFC in others dont pay any attention
to that please.)
First thing which Id like to do to T24 is to make a little fun. If youre ready to start
T24, create a text file called GLOBUS.BAN. This file should contain exactly 16 lines of text.
Which text? anything you like. Put this file to bnk.run directory and start T24. Whats
there instead of familiar world ASCII art? Correct, you see contents of your GLOBUS.BAN
file. Heres my startup screen:
T24 custom startup screen
GLOBUS Rev. 201015
SIGN.ON
-----------------------------------------------------------------------------|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-----------------------------------------------------------------------------13 JUN 2011 19:44:03 USER
[8817,IN]
ACTION
PLEASE ENTER YOUR SIGN ON NAME
J of BASE, TAF of C
By KZM
2 R11 IS OUT
You probably noticed that Globus revision is 201015... Ill use that one because all
dependencies of R11 left me no choice.
J of BASE, TAF of C
By KZM
3 STANDALONE TAFC
Standalone TAFC
ack to what I called a standalone mode. Now when TAFC is installed, we can create
:::: an empty directory as a sandbox to play around.
The following cmd file will serve as an entry point. Its no need to connect to localhost
via telnet we can just run that cmd file. Lets create a directory, say, sa-tafc at the root
of drive C and start creating this cmd file in it.
Whether were going to start it typing its name or double-clicking a shortcut, we place
ourselves into our chosen directory:
jrunSH.cmd - the beginning
1
2
3
@echo off
C:
cd \sa-tafc
Then (see lines 6 and 7 at the screen below) you need to specify where your installed
TAFC is located and where C compiler files are available. COMPILER HOME is not a standard
variable well use it later to save typing:
jrunSH.cmd - continued
4
5
6
7
8
set
set
set
set
HOME=C:\sa-tafc
TAFC_HOME=<path_to_TAFC>
COMPILER_HOME=<path_to_MS_VC_compiler>
JBCGLOBALDIR=%TAFC_HOME%
Note that wherever you see something like <path to TAFC> in cmd files or like, you
need to replace it to the real path that corresponds to your system settings and/or layout.
Below are certain settings that are mostly known to you already. Please note that directory for saved lists should in fact have the name &SAVEDLISTS&. In my previous book I
recommended to rename it to, say, SAVEDLISTS to avoid using special characters, but if you
dont want to do it here you are:
jrunSH.cmd - continued
9
10
11
12
13
14
15
16
17
18
19
set
set
set
set
set
set
set
set
set
set
JBCLISTFILE=%HOME%\^&SAVEDLISTS^&
JEDIFILENAME_MD=VOC
JEDIFILENAME_SYSTEM=%JBCGLOBALDIR%\src\SYSTEM
JBCEMULATE=prime
JBASE_ERRMSG_DIVIDE_BY_ZERO=19
JBASE_ERRMSG_ZERO_USED=35
JBASE_ERRMSG_NON_NUMERIC=19
JBC_CORE_DUMP=1
JEDIFILEPATH=%HOME%
JBCBASETMP=%HOME%\tmp_workfile
J of BASE, TAF of C
By KZM
3 STANDALONE TAFC
Path is deliberately set not to derive anything from Windows user setup; include and
lib (as well as the first item in the path) are for the compiler:
jrunSH.cmd - continued
20
21
22
23
set PATH=%COMPILER_HOME%\bin;%TAFC_HOME%\bin;C:\windows\system32
set INCLUDE=%COMPILER_HOME%\include;%INCLUDE%
set LIB=%COMPILER_HOME%\lib;%LIB%
24
set
set
set
set
JBASE_I18N=1
JBASE_CODEPAGE=utf8
JBASE_LOCALE=en_US
JBASE_TIMEZONE=Europe/London
By the way, JBASE CODEPAGE sets only how characters are displayed, so if you have any
non-ASCII characters, you might prefer to set it to be the same as your local code page.
Spooler here (dont forget to create that directory, as well as &SAVEDLISTS&):
jrunSH.cmd - continued
29
30
31
set JBCSPOOLERDIR=%HOME%\jspooler
set JBASE_IPC_LOCAL=1
32
What about JBASE IPC LOCAL? A colleague of mine supplied me with the following explanation:
By KZM
3 STANDALONE TAFC
Finally:
jrunSH.cmd - finished
33
34
set TERM=ntcon
jsh
Now run it. If you see jsh prompt here we are. Type jdiag and look at the output.
You can ignore the following warnings:
jdiag
WARNING:
WARNING:
...
WARNING:
...
WARNING:
...
WARNING:
WARNING:
...
WARNING:
...
WARNING:
Have you noticed the annoying sound from PC speaker accompanying, for example,
Backspace key pressed when theres nothing yet entered at jsh prompt? If no, probably
your speaker is suppressed somehow and you can skip several following paragraphs.
Those who heard that really annoying sound (and if you, as well as myself, want to get
rid of it) do the following: go to TAFC home directory, then to its subdirectory src and
find there file jbase nt.ti.
Then edit it. Comment bell setting:
jbase nt.ti - before
ntcon|dumb|ansi|NT console emulation,
am, xon,
cols#80, lines#24,
bel=^G, blink=\E[5m, bold=\E[1m, cbt=\E[Z,
jbase nt.ti - after
ntcon|dumb|ansi|NT console emulation,
am, xon,
cols#80, lines#24,
#
bel=^G,
blink=\E[5m, bold=\E[1m, cbt=\E[Z,
J of BASE, TAF of C
By KZM
3 STANDALONE TAFC
Have you noticed the setting set TERM=ntcon in jrunSH.cmd earlier? We just edited
the section where ntcon is mentioned and it should match environment variable TERM for
the whole thing to work.
But thats not all. We need to produce new terminal definition file. To do that you need
to run jtic utility (tic for Unix) with one parameter name of your terminal definition file
jbase nt.ti including the full path.
J of BASE, TAF of C
10
By KZM
ow lets see if were able to access T24 data from here. To do that some changes
:::::: in jrunSH.cmd are necessary. We can access T24 files using at least three different
methods:
Environment variable JEDIFILEPATH showing where files are stored. Unfortunately, in
directory bnk.data theres a lot of subdirectories that contain data files and well have to
mention all these directories individually. In addition, names of physical files are not the
same as their T24 names and the rules change from time to time data file for application
SPF, for example, had been F.SPF and now its F SPF. Same goes for dictionary files.
Use full path to data file (not forgetting DICT file as well). Why would we need to
specify DICT file location? Because its located not in the same directory as DATA file...
seems too complicated.
Use records in VOC file. So environment variable JEDIFILENAME MD should be pointing
to VOC file in bnk.run directory. Having changed this variable, run jrunSH.cmd and try to
list some file:
T24 data access - step 1
jsh ~ --> LIST F.SPF
No file name could be found for your query
Why? Check VOC record (use full path to VOC since notation like %JEDIFILENAME MD%
wouldnt work:
T24 data access - step 2
jsh ~ --> CT <path_to_bnk.run>\VOC F.SPF
F.SPF
001 F
002 ../bnk.data/eb/F_SPF
003 ../bnk.dict/F_SPF]D
Yes, we have relative path here both for data and dictionary files. So before starting
jsh we need to move to bnk.run directory and for convenience sake afterwards to return
back. We dont need the full path to specify environment variable JEDIFILENAME MD, therefore the only changes to our new file jrunT24.cmd which we create by copying jrunSH.cmd
are commands at lines 34 and 36:
jrunT24.cmd - the very end
33
34
35
36
set TERM=ntcon
cd <path_to_bnk.run>
jsh
cd %HOME%
J of BASE, TAF of C
11
By KZM
SYSTEM
SYSTEM
SYSTEM
20100712
Model Bank
O
../bnk.data
To be able to log in to T24 we also need the settings in lines 35-38 (T24 HOME variable is
not standard and was invented to make things easier):
The end of jrunT24.cmd - final version (for time being of course)
32
33
set TERM=ntcon
34
35
36
37
38
set
set
set
set
T24_HOME=<path_to_bnk.run>
JBASE_PATH=%T24_HOME%\t24bin
JBCOBJECTLIST=%T24_HOME%\lib;%T24_HOME%\t24lib
JBASE_JBCOBJECTLIST_DIR=1
39
40
41
42
cd %T24_HOME%
jsh
cd %HOME%
Now we are able to log in to T24 using the minimum set of environment variables.
Start jrunT24.cmd. Have you noticed that it loads way too slower than jrunSH.cmd? I
dont know the reason but probably its because all executables in t24bin and libraries in
t24lib are distributed to many subdirectories according to T24 updates approach (have
you noticed additional settings like JBASE PATH and JBASE JBCOBJECTLIST DIR which probably take care of that setup?)... I might be wrong here, but forget it for time being.
Why weve retained jrunSH.cmd? Well need it for standalone TAFC (where we dont
need T24 access and therefore many settings). Again, it starts faster. In addition, in it we also
can get rid of the following variables that are not necessary in this mode: JEDIFILENAME MD
and JEDIFILEPATH.
So our principle is to keep only what we need with full understanding
of what is what.
J of BASE, TAF of C
12
By KZM
ets write a small program accessing T24 data. Well try a slightly different way of
:::: writing and compiling programs. As I said earlier, to be able to access T24 data we
dont necessarily need a T24 subroutine a program might be sufficient. What are advantages
of a program? Here are some:
You dont need PGM.FILE record for it.
You dont need to log in to T24 to run it (though if your program wants some global
T24 variables, you have to log in and log out before running it).
You can deploy a program in a new environment just copying the executable (provided
that OS and jBASE versions are the same).
Well keep T24 area as clean as we can without even putting any files there. The name
of our program will be... surprisingly... prog1.
Just to check if all is OK with settings for compiling and runtime, firstly we put there
just a Hello-world-like output. File is to be named prog1.b (and here is the difference
from what we did before .b is an extension rather than part of file name like AB.CD.E.F
were try to follow the pure jBASE as much as we can):
prog1 - initial version
1
PROGRAM PROG1
2
3
CRT HELLO
4
5
6
STOP
END
To compile it we dont use BASIC and CATALOG as before it can be done in one step:
prog1 - compilation
jsh ~ --> jcompile prog1.b
prog1.c
mt -nologo -manifest prog1.dll.manifest -outputresource:prog1.dll;2 failed ,
command returned a code of -1
mt -nologo -manifest prog1.exe.manifest -outputresource:prog1.exe;1 failed ,
command returned a code of -1
We dont pay attention to failed messages despite them our program was compiled:
prog1 - compilation results
Directory of C:\sa-tafc
14.06.2011
20:27
J of BASE, TAF of C
<DIR>
13
By KZM
14.06.2011
13.06.2011
14.06.2011
14.06.2011
13.06.2011
14.06.2011
14.06.2011
14.06.2011
14.06.2011
14.06.2011
20:27
20:24
19:51
19:10
22:42
19:43
20:27
20:27
20:27
20:27
<DIR>
<DIR>
834
1.031
<DIR>
49
4.608
4.608
1.680
401.408
..
&SAVEDLISTS&
jrunSH.cmd
jrunT24.cmd
jspooler
prog1.b
prog1.dll
prog1.exe
prog1.obj
tmp_workfile
Files with extensions dll and obj are not necessary (so far they are not; well need dll
further on its described in one of the next chapters), so right now lets make the process
of compilation more smart:
prog1 - more clean compilation
jsh ~ --> jcompile prog1.b && del prog1.obj prog1.dll
No.
set TERM=ntcon
jcompile %1.b && del %1.dll %1.obj
Then we can start jrunComp.cmd with the parameter - source code file name (without
extension):
jrunComp.cmd run
C:\sa-tafc> jrunComp.cmd prog1
prog1.c
mt -nologo -manifest prog1.dll.manifest -outputresource:prog1.dll;2 failed ,
command returned a code of -1
mt -nologo -manifest prog1.exe.manifest -outputresource:prog1.exe;1 failed ,
command returned a code of -1
J of BASE, TAF of C
14
By KZM
No unnecessary files after that. But can we run the resulting prog1.exe just under
Windows shell? If you try to do that, youll get the error message about not being able
to find libTAFCfrmwrk.dll. Yes, it can not be run by itself it needs the appropriate
environment to present.
OK, lets create it. Just to output something to the screen its enough to have the
environment of jrunSH.cmd but we will also need T24 access for other programs. To test it
well add some logic into the source code so it now looks like:
prog1 - final version
1
PROGRAM PROG1
CRT HELLO
3
4
5
6
7
8
9
10
11
LOOP
WHILE READNEXT V.KEY FROM V.SEL DO
READ R.CMP FROM F.CMP, V.KEY ELSE
CRT NOT ABLE TO READ FILE
STOP
END
12
13
14
15
16
17
18
19
20
REPEAT
21
22
STOP
23
24
END
25
Now compile the program again, add C:\sa-tafc to the end of our environment variable PATH in jrunT24.cmd, start the latter and type prog1. If you see the raw contents of
COMPANY application on your screen, it works.
Note the absence of $INSERT I COMMON ... $INSERT I EQUATE statements which are
not necessary in this case.
J of BASE, TAF of C
15
By KZM
Thus weve created jBC program which is able to access T24 data but
fully resides outside T24 environment, which is good, for example, for
reporting purposes especially if there are several environments to
proceed.
J of BASE, TAF of C
16
By KZM
ou might ask whats the idea of writing a useless program? Well, useless for T24
::::: but useful for some other purpose just to prove that jBC is capable of many things
other than access T24 files. Another reason just for fun. In my previous book there was the
Fifteen puzzle written in jBC. So to have a break from serious matters I present another
game written in jBC so-called snake.
Ive written it quite long ago but recently found some time to improve it now it can be
played in full screen mode it means that if you set the number of rows and columns in
window where you run it to something more than 24x80 it will use all available space for a
play field.
Lets see the source. Im quoting myself stating that a good comment at the top of every
program wouldnt harm anyone:
1
2
3
4
5
6
Note that program clause at the top is also commented all works even without it.
Lets inform the user about options one of which is not to play at all:
Snake game - continued
7
8
9
10
11
12
CRT USE ARROW KEYS TO TURN SNAKE, + TO INCREASE LEVEL, P FOR PAUSE,\
Q FOR QUIT
CRT CONTINUE (Y/N) :
INPUT V.CONT
IF UPCASE(V.CONT) NE Y THEN STOP
Here goes the thing that is also new on a big screen (say, 70x120 instead of standard
24x80) it takes the snake much more time to get to the target and it soon becomes quite
boring. So I added this option for user to adapt the speed to the screen size and, of course,
performance of the computer:
Snake game - continued
13
14
15
16
17
18
GOSUB INIT
J of BASE, TAF of C
17
By KZM
Not we dive into init section. First of all, define speeds for difficulty levels, clear screen
and prepare the playfield:
256
257
258
259
260
261
ECHO OFF
262
263
264
265
266
267
268
269
CRT @(-1)
V.GAMEOVER =
V.LEFT.BORDER = 0
V.RIGHT.BORDER = SYSTEM(2) - 1
V.TOP.BORDER = 0
V.BOTTOM.BORDER = SYSTEM(3) - 3
V.GROW =
270
271
272
V.SIZE.X = V.RIGHT.BORDER-V.LEFT.BORDER-1
V.SIZE.Y = V.BOTTOM.BORDER-V.TOP.BORDER-1
273
274
275
;* playfield array
276
system(2) and system(3) give us the screen size. Now its time for other actions:
277
278
279
221
222
223
224
RETURN
225
Back to init. We store coordinates of snake body in a dynamic array (to allow the snake
to grow).
J of BASE, TAF of C
18
By KZM
V.Y = 1
281
282
V.SNAKE =
283
284
V.LEN = 5
;* will later increase it
FOR V.I = 1 TO V.LEN
V.X = V.I
V.SNAKE<-1> = V.X :@VM: V.Y
285
286
287
288
289
V.FIELD(V.X,V.Y) = X
GOSUB DRAW.CELL
NEXT V.I
290
291
292
293
GOSUB DRAW.BORDER
GOSUB CREATE.GOAL
294
295
296
V.DIRECT = 1
297
298
RETURN
299
300
301
END
302
Thats the end of init section and at the same time the very bottom of our source code.
We also need to take a look at sections draw.cell, draw.border and create.goal which
are mentioned in the code above.
draw.cell draws a symbol X as a part of snake body or clears it with a space so our
snake is able to move:
120
121
122
123
124
125
126
RETURN
127
@(35, V.BOTTOM.BORDER) is used to get rid of cursor appearing near the snake with
some terminal emulators.
draw.border is quite obvious:
104
105
J of BASE, TAF of C
19
By KZM
106
107
108
109
110
111
112
113
114
115
116
V.1 = V.TOP.BORDER + 1
V.2 = V.BOTTOM.BORDER - 1
FOR V.I = V.1 TO V.2
CRT @(V.LEFT.BORDER,V.I):|:
CRT @(V.RIGHT.BORDER,V.I):|:
NEXT V.I
117
118
RETURN
119
create.goal creates a new goal to pick up; that goal is represented by $ character.
We use random number generator with two cautions not to create it too close to the border
for junior levels and not to create it inside the body of the snake. Uses draw.cell to
draw the target:
226
227
228
229
230
V.SAVE.X = V.X
V.SAVE.Y = V.Y
231
232
233
234
LOOP
V.X = RND(V.SIZE.X)+1
V.Y = RND(V.SIZE.Y)+1
235
236
237
238
239
240
241
242
243
244
245
FIND V.X :@VM: V.Y IN V.SNAKE SETTING V.DUMMY ELSE BREAK ;* see if the
;* created goal is inside the snake
REPEAT
246
247
248
249
V.FIELD(V.X,V.Y) = $
GOSUB DRAW.CELL
250
251
V.X = V.SAVE.X
J of BASE, TAF of C
20
By KZM
252
V.Y = V.SAVE.Y
253
254
RETURN
255
The code isnt optimal; besides that there is further improvement that I might think
about: to have several goals at the same time will be much better for big screen size. You
can try it yourself as an exercise if you like.
Back to the top. Weve just finished the initiation of all that is necessary and drew the
screen. Whats next?
The beginning and the end of the main program loop are shown here:
Snake game - main section continued
19
20
LOOP
21
REPEAT
90
91
92
ECHO ON
STOP
93
Now see whats in between. We have to apply the technique that you will never use in
T24 - how to make the snake move while polling the keyboard to see if user pressed some
key. Firstly we need a keyboard polling loop inside our main loop:
Snake game - main section - polling loop
22
23
24
LOOP
V.BUFF = SYSTEM(14)
IF V.BUFF NE 0 THEN BREAK
25
26
27
28
29
30
31
32
33
34
35
36
GOSUB MOVEIT
IF V.GAMEOVER THEN
CRT @(45, V.BOTTOM.BORDER + 1): GAME OVER...
ECHO ON
37
J of BASE, TAF of C
21
By KZM
STOP
38
39
END
40
41
REPEAT
system(14) lets us know if theres anything in keyboard input buffer; in case theres
nothing we proceed with snake movement after some delay. The delay is calculated to adjust
the game speed to difficulty level and speed multiplier entered at the very start. Now we
move the snake (hope that comments are explaining the process quite clearly; here we also
check if weve scored and therefore snake shall grow, we check also if snake hit a border or
itself which means that the game is over):
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
IF V.GROW THEN
V.GROW =
V.LEN +=1
END ELSE
V.X = V.SNAKE<1,1>
V.Y = V.SNAKE<1,2>
V.FIELD(V.X,V.Y) =
GOSUB DRAW.CELL
DEL V.SNAKE<1>
END
143
144
145
146
147
148
149
150
BEGIN CASE
151
152
CASE V.DIRECT EQ 1
153
154
155
156
157
158
V.X += 1
IF V.X GT V.SIZE.X THEN
V.GAMEOVER = Y
RETURN
END
159
160
CASE V.DIRECT EQ 2
161
J of BASE, TAF of C
22
By KZM
162
163
164
165
166
V.Y += 1
IF V.Y GT V.SIZE.Y THEN
V.GAMEOVER = Y
RETURN
END
167
168
CASE V.DIRECT EQ 3
169
170
171
172
173
174
V.X -= 1
IF V.X LT 1 THEN
V.GAMEOVER = Y
RETURN
END
175
176
CASE V.DIRECT EQ 4
177
178
179
180
181
182
V.Y -= 1
IF V.Y LT 1 THEN
V.GAMEOVER = Y
RETURN
END
183
184
END CASE
185
186
187
188
189
190
191
IF V.FIELD(V.X,V.Y) EQ X THEN
V.GAMEOVER = Y
RETURN
END
192
193
* see if we scored
194
195
196
IF V.FIELD(V.X,V.Y) EQ $ THEN
V.SCORE += V.LEVEL
197
198
199
200
201
202
203
204
205
BEGIN CASE
CASE V.SCORE
V.LEVEL =
CASE V.SCORE
V.LEVEL =
CASE V.SCORE
V.LEVEL =
END CASE
206
207
208
209
GOSUB UPDATE.SCORE
GOSUB CREATE.GOAL
V.GROW = Y
J of BASE, TAF of C
23
By KZM
210
END
211
212
213
V.FIELD(V.X,V.Y) = X
GOSUB DRAW.CELL
214
215
216
217
RETURN
218
V.KEY = KEYIN()
44
45
BEGIN CASE
46
47
;* Esc
V.KEY.2 = KEYIN()
V.KEY.3 = KEYIN()
;* ]
48
49
50
51
52
53
54
BEGIN CASE
CASE V.KEY.3 EQ C ;* right
IF V.DIRECT NE 3 THEN V.DIRECT = 1
55
;* we dont reverse
;* the direction
56
57
58
59
60
61
62
63
64
CASE V.KEY.3 EQ A ;* up
IF V.DIRECT NE 2 THEN V.DIRECT = 4
65
66
END CASE
67
68
69
70
71
72
CASE 1
73
74
V.KEY = UPCASE(V.KEY)
75
76
BEGIN CASE
J of BASE, TAF of C
24
By KZM
77
CASE V.KEY EQ P
78
79
GOSUB SET.PAUSE
80
81
CASE V.KEY EQ Q
BREAK
82
83
84
END CASE
85
86
87
END CASE
The last piece in this puzzle is how we proceed the pause requested by the user another
P resumes the game:
94
95
96
97
98
99
100
LOOP
V.KEY.2 = KEYIN()
IF UPCASE(V.KEY.2) EQ P THEN BREAK
REPEAT
101
102
RETURN
103
J of BASE, TAF of C
25
By KZM
Another way of running a program which allows to save typing create another cmd file
to run a program directly without jsh. Copy jrunSH.cmd to, say, jrunEXE.cmd. Replace
jsh in the last line with:
jrunEXE.cmd - the last line
34
%1 %2 %3 %4 %5 %6 %7 %8 %9
This setup allows us to run snake3.exe just by typing jrunEXE.cmd snake3.exe from
Windows shell - as well as any other jBC program with up to 8 parameters.
Feel free to amend it either add several simultaneous goals or, maybe, introduce time
limitation to hit a goal... Enjoy!
J of BASE, TAF of C
26
By KZM
ack to serious things. You all know that there are iconv and oconv functions in jBC
:::: that have a lot of data conversion options. Some examples of that could be (we use the
eval trick described in my previous book):
OCONV sample
jsh ~ --> LIST . SAMPLE 1 EVAL "OCONV(TIME(), MTS)"
DICT .........
OCONV(TIME(), "MTS")
&SAVEDLISTS&
20:34:15
1 Records Listed
Or an example how to create a string representing the current timestamp in the format
used in standard T24 audit field date.time:
ICONV/OCONV sample in jBC
V.TD = TIMEDATE()
V.TIME = V.TD[1,2] : V.TD[4,2]
V.DATE = ICONV(V.TD[10,11], D)
V.TD = OCONV(V.DATE, DY2) : FMT(OCONV(V.DATE, DM), "R0%2")
: FMT(OCONV(V.DATE, DD), "R0%2") : V.TIME
R.OUTPUT<V.DATE.TIME> = V.TD
There is a lot of conversion codes (see jBC manual and jBASE knowledgebase). Also
there are some special codes for example, *A9999 shows record size (try the following
under jrunT24.cmd):
Another conversion code sample
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST F.COMPANY *A9999
@ID........
*A9999........
GB0010003
SG0010001
EU0010001
GB0010002
GB0010004
GB0010001
2166
2173
2173
2174
2166
2607
6 Records Listed
J of BASE, TAF of C
27
By KZM
If were way too suspicious lett check if the output is correct. To do that well change the
program prog1 to output the size of all records in COMPANY application data file rather
then their contents.
prog1 - the changes
18
19
20
21
Why bytelen() rather that len()? Because if we have a UTF-8 environment (and we do
have it, see environment variable JBASE I18N), len() will give us the number of characters
rather than the number of bytes in a record.
So - compile, run (jrunT24.cmd again since we need T24 data access), see:
Record sizes checked
jsh ~ -->
HELLO
GB0010003
SG0010001
EU0010001
GB0010002
GB0010004
GB0010001
prog1
>>>
>>>
>>>
>>>
>>>
>>>
2166
2173
2173
2174
2166
2607
Looks like it works. What have we just done? Achieved the same goal using 2 different
methods. Which one to choose? In this case - first (a built-in) one is easier. But its not
always the same so the final choice in every particular case is yours.
Please note that in this book some examples are run under jrunT24.cmd,
some under jrunSH.cmd etc. Make sure you lanch the same cmd that
is shown in an example, otherwise it might not work.
Little more about A conversion codes also known as A-correlatives. A*n means
field n, so if you dont have the dictionary file (or too lazy to type field names) you can
try the following:
Another conversion code sample
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST F.COMPANY *A1 *A2 *A3
@ID........
*A1...........
J of BASE, TAF of C
*A2...........
28
*A3...........
By KZM
GB0010003
SG0010001
EU0010001
GB0010002
GB0010004
GB0010001
R11 MF BRANCH
1
R11 LEAD SG CO
MPANY
R11 LEAD CO GE
RMANY
R11 LEAD MF CO
MPANY
R11 MF BRANCH
2
Model Bank
R11 MF BRANCH
1
R11 LEAD SG CO
MPANY
R11 LEAD CO GE
RMANY
R11 LEAD MF CO
MPANY
R11 MF BRANCH
2
18 Place De Ph
ilosophes,
CH 1205 Geneva
,
Switzerland
MF2
SG1
EU1
MF1
MF3
BNK
6 Records Listed
*A9998........
GB0010003
SG0010001
EU0010001
GB0010002
GB0010004
GB0010001
1
2
3
4
5
6
6 Records Listed
No, thats not record physical location number or like as I initially thought just a
sequential number in the output. Change the sequence of records with SSELECT and see:
And yet another conversion code sample
C:\sa-tafc> jrunT24.cmd
jsh ~ --> SSELECT F.COMPANY
> LIST F.COMPANY *A9998
@ID........
*A9998........
J of BASE, TAF of C
29
By KZM
EU0010001
GB0010001
GB0010002
GB0010003
GB0010004
SG0010001
1
2
3
4
5
6
6 Records Listed
A*n is not limited by actual number of fields (most probably executing something equivalent to var = arr<n> ), though there is a limitation (and this number is quite large
to terminate the session). To (most probably) get out-of-memory error you might try the
following:
Trying to terminate the session
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST F.COMPANY *A99999999
Session dies and we have a nice new file in our bnk.run directory:
Core dump
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST . LIKE ...dmp
DICT .........
TAFC_coredump-20110707213724386.dmp
1 Records Listed
Something more about solving problems in different ways. Theres an application in T24
called OFS.REQUEST.DETAIL which you most probably already know about. Lets see its
contents. Normally its something like:
OFS.REQUEST.DETAIL sample contents
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST F.OFS.REQUEST.DETAIL
@ID............. SCRPT11062000160~I
@ID............. SCRPT11062000160~I
MESSAGE.KEY..... SCRPT11062000160~I
APPLICATION..... AA.PRD.DES.PAYMENT.SCHEDULE
VERSION.........
J of BASE, TAF of C
30
By KZM
FUNCTION........ I
TRANS.REFERENCE. NOTICE.ACCOUNT-EUR-20100809
USER.NAME....... SUSER1
COMPANY......... GB0010001
DATE.TIME.RECD.. 09:08:03:875 10 MAR 2011
DATE.TIME.QUEUE.
DATE.TIME.PROC.. 09:08:03:928 10 MAR 2011
STATUS.......... PROCESSED
MSG.IN.......... AA.PRD.DES.PAYMENT.SCHEDULE,/I/PROCESS/1/0/,SUSER1/******/GB001
0001////,NOTICE.ACCOUNT-EUR-20100809/SCRPT11062000160~I,DESCRIPT
ION:1:1=Savings Account,PAYMENT.TYPE:1:1=INTEREST.ONLY,PAYMENT.T
YPE:2:1=INTEREST.ONLY,PAYMENT.METHOD:1:1=CAPITALISE,PAYMENT.METH
OD:2:1=CAPITALISE,PAYMENT.FREQ:1:1=e0Y e1M e0W e0D e0F,PAYMENT.F
REQ:2:1=e0Y e1M e0W e0D e0F,PROPERTY:1:1=CRINTEREST,PROPERTY:2:1
=DRINTEREST,DUE.FREQ:1:1=e0Y e1M e0W e0D e0F,DUE.FREQ:2:1=e0Y e1
M e0W e0D e0F,DEFAULT.NEGOTIABLE:1:1=YES,
MSG.OUT......... NOTICE.ACCOUNT-EUR-20100809/SCRPT11062000160~I/1,DESCRIPTION:1:
1=Savings Account,PAYMENT.TYPE:1:1=INTEREST.ONLY,PAYMENT.TYPE:2:
1=INTEREST.ONLY,PAYMENT.METHOD:1:1=CAPITALISE,PAYMENT.METHOD:2:1
=CAPITALISE,PAYMENT.FREQ:1:1=e0Y e1M e0W e0D e0F,PAYMENT.FREQ:2:
1=e0Y e1M e0W e0D e0F,PROPERTY:1:1=CRINTEREST,PROPERTY:2:1=DRINT
EREST,DUE.FREQ:1:1=e0Y e1M e0W e0D e0F,DUE.FREQ:2:1=e0Y e1M e0W
e0D e0F,DEFAULT.NEGOTIABLE:1:1=YES,ID.COMP.1:1:1=NOTICE.ACCOUNT,
ID.COMP.2:1:1=EUR,ID.COMP.3:1:1=20100809,CURR.NO:1:1=1,INPUTTER:
1:1=4964_SEAT.USER_I_INAU_OFS_SEAT,DATE.TIME:1:1=1103100908,DATE
.TIME:2:1=1103100908,AUTHORISER:1:1=4964_SEAT.USER_OFS_SEAT,CO.C
ODE:1:1=GB0010001,DEPT.CODE:1:1=2006
ACTION..........
GTS.CONTROL..... 1
NO.OF.AUTH...... 0
As we know, in OFS messages comma is a delimiter. Imagine wed like to see each part
of the message at a different line. What can we do here? Possible choices are:
Create an ENQUIRY with some special processing using ENQUIRY field conversion
...and build a web service based on that enquiry... no, forget a web service were aiming
to low level rather than adding more and more wrappers. In general, it looks feasible but
showing a single-valued field in several lines (like a multi-valued) might be a little tricky in
ENQUIRY.
Create an I-descriptor with user routine and then display the output either in ENQUIRY or in jsh. Good idea but lett go deeper and try not to harm any animal... sorry,
I meant keep T24 intact.
Create what in jBASE (and not only here but in all multi-valued world) is called
user exit. This technique is described in details in jBASE knowledgebase; lett take an
example from there and amend it to cater our needs.
J of BASE, TAF of C
31
By KZM
There are two methods to create a custom conversion code (read: user exit) - create
one routine per each new code or to put everything into one routine with standard name
JBCUserConversions. In this example well use the second one.
Our chosen name for a conversion code will be XZ. So then the source of the routine
JBCUserConversions will look like:
JBCUserConversions - amended
1
2
* Amended by KZM
SUBROUTINE JBCUserConversions (result, source, code, type, error)
BEGIN CASE
CASE code EQ "XZ"
IF type THEN
result = source : " was OCONV"
result = source
CHANGE , TO @VM IN result
END ELSE
result = source : " was ICONV"
END
4
5
6
7
8
9
10
11
*
*
12
13
CASE 1
error = 1
END CASE
14
15
16
17
RETURN
18
19
20
END
21
Some code was commented by me but retained to give you the better idea of this method.
Now lets compile it (for reasons yet unknown to me I wasnt able to compile any subroutine using jcompile so heres good old BASIC and CATALOG). But firstly we need to add
some environment variables to jrunSH.cmd:
New variables in jrunSH.cmd
29
30
31
set JBCOBJECTLIST=%HOME%\lib
set JBCDEV_LIB=%HOME%\lib
32
J of BASE, TAF of C
32
By KZM
JBCUserConversions
BASIC_1.c
Source file JBCUserConversions compiled successfully
jsh ~ --> CATALOG . JBCUserConversions
JBCUserConversions
Object JBCUserConversions cataloged successfully
mt -nologo -manifest C:\sa-tafc\lib\lib0.dll.manifest -outputresource:
C:\sa-tafc\lib\lib0.dll;2 failed , command returned a code of -1
Library C:\sa-tafc\lib\lib0.dll rebuild okay
jsh ~ -->
We can see that the file $JBCUserConversions appeared in sa-tafc (we can delete it),
the directory sa-tafc\lib was automatically created and what matters for us in it we
have the new file lib0.dll where the compiled routine is supposedly been placed; in any
case, jshow will let us know if it is so:
Check of JBCUserConversions
jsh ~ --> jshow -c JBCUserConversions
Subroutine:
C:\sa-tafc\lib\lib0.dll
jBC JBCUserConversions version 201014.0 Mon Jun 20 09:58:23 2011
jBC JBCUserConversions source file .
jsh ~ -->
As soon as were going to apply our new conversion codeXZto T24 data file, we need
our conversion subroutine to be visible for work in T24 environment. To achieve that we
need to add the directory with our lib0.dll here:
Correction of jrunT24.cmd
37
set JBCOBJECTLIST=%T24_HOME%\lib;%T24_HOME%\t24lib;%HOME%\lib
Now try to see the results. After toying a little with output width finally we can see
something more readable (the full command didnt fit here properly and of course should be
entered in one line):
Results of customization of JBCUserConversions
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST F.OFS.REQUEST.DETAIL ID-SUPP EVAL "@ID[1,20]"
EVAL "OCONV(MSG.IN, XZ)" EVAL "OCONV(MSG.OUT, XZ)"
@ID[1,20
.
OCONV(MSG.IN, "XZ")...........
J of BASE, TAF of C
OCONV(MSG.OUT, "XZ")..........
33
By KZM
SCRPT11062
000160~I
AA.PRD.DES.PAYMENT.SCHEDULE
/I/PROCESS/1/0/
SUSER1/******/GB0010001////
NOTICE.ACCOUNT-EUR-20100809/SC
RPT11062000160~I
DESCRIPTION:1:1=Savings Accoun
t
PAYMENT.TYPE:1:1=INTEREST.ONLY
PAYMENT.TYPE:2:1=INTEREST.ONLY
PAYMENT.METHOD:1:1=CAPITALISE
PAYMENT.METHOD:2:1=CAPITALISE
PAYMENT.FREQ:1:1=e0Y e1M e0W e
0D e0F
PAYMENT.FREQ:2:1=e0Y e1M e0W e
0D e0F
PROPERTY:1:1=CRINTEREST
PROPERTY:2:1=DRINTEREST
DUE.FREQ:1:1=e0Y e1M e0W e0D e
0F
DUE.FREQ:2:1=e0Y e1M e0W e0D e
0F
DEFAULT.NEGOTIABLE:1:1=YES
NOTICE.ACCOUNT-EUR-20100809/SC
RPT11062000160~I/1
DESCRIPTION:1:1=Savings Accoun
t
PAYMENT.TYPE:1:1=INTEREST.ONLY
PAYMENT.TYPE:2:1=INTEREST.ONLY
PAYMENT.METHOD:1:1=CAPITALISE
PAYMENT.METHOD:2:1=CAPITALISE
PAYMENT.FREQ:1:1=e0Y e1M e0W e
0D e0F
PAYMENT.FREQ:2:1=e0Y e1M e0W e
0D e0F
PROPERTY:1:1=CRINTEREST
PROPERTY:2:1=DRINTEREST
DUE.FREQ:1:1=e0Y e1M e0W e0D e
0F
DUE.FREQ:2:1=e0Y e1M e0W e0D e
0F
DEFAULT.NEGOTIABLE:1:1=YES
ID.COMP.1:1:1=NOTICE.ACCOUNT
ID.COMP.2:1:1=EUR
ID.COMP.3:1:1=20100809
CURR.NO:1:1=1
INPUTTER:1:1=4964_SEAT.USER_I_
INAU_OFS_SEAT
DATE.TIME:1:1=1103100908
DATE.TIME:2:1=1103100908
AUTHORISER:1:1=4964_SEAT.USER_
OFS_SEAT
CO.CODE:1:1=GB0010001
DEPT.CODE:1:1=2006
Note ID-SUPP keyword suppressing the output of @id since default display parameter
for it is too big to fit all we need into screen width.
If we hadnt included our lib directory to JBCOBJECTLIST, we would have got the following
error:
Error message
Error in named attribute XZ
Unknown conversion code.
J of BASE, TAF of C
34
By KZM
This conversion code now also can be used in ENQUIRY field conversion. Of course
to make your custom conversion code generally available you need the compiled subroutine
JBCUserConversions to be visible for all T24 users, whether its Classic or Browser (in latter
case its environment.vars or whichever file used in jBoss or TCServer is to be amended).
The template for an individual routine for each custom conversion you can find going to
TAFC home directory, then to subdirectory src. Its named jBASE UserExit.
J of BASE, TAF of C
35
By KZM
f course performance is the thing that is the most interesting for all of us. Lets see
::::: how a little trick can be used to gain some.
Dynamic arrays in jBC are quite handy but sometimes slow. For example, if we try to
populate a large array it might take some time. Here goes the code:
Test of dynamic array population - source of dynarr.b
1
2
3
* KZM, 2011
* Dynamic array tests
V.COUNT = 100000
V.ARR =
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
STOP
23
24
25
END
26
J of BASE, TAF of C
36
By KZM
But what if to start array population from the end? Probably in this case memory
allocation will work differently and this will affect the performance?
The code now is:
Test of dynamic array population - changes in the source
9
10
11
12
13
We dont discuss here the implications of using a big dynamic array (which isnt much
recommended by Temenos). This particular trick helped us to increase the speed of array
population. It was tested and worked the same way on 4 platforms. Will it work on your
platform and your environment? Not necessarily, its up to you to test if it helps.
The next example shows us that environment matters, and sometimes it really matters.
See below a program written by one of my good friends who also happens to be my colleague.
This program shows faster search of a substring in a string than jBC function index() does.
J of BASE, TAF of C
37
By KZM
asearch.b
1
DIM prefix(10000)
2
3
4
a = SPACE(1000*1000 - 1):.
CRT "LEN(a): ", LEN(a)
5
6
7
b = SPACE(10000 - 1):.
CRT "LEN(b): ", LEN(b)
8
9
10
11
12
13
msg = "INDEX"
GOSUB StartTest
position = INDEX(a, b, 1)
GOSUB EndTest
CRT "I = ":position
14
15
position = -1
16
17
18
19
20
21
msg = "KMP"
GOSUB StartTest
GOSUB kmp
GOSUB EndTest
CRT "I = ":position
22
23
STOP
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
kmp:
position = -1
prefix(1) = 0
k = 0
b_len = LEN(b)
FOR i = 2 TO b_len
b_char = b[i, 1]
LOOP WHILE k > 0 AND b[k + 1, 1] NE b_char DO
k = prefix(k)
REPEAT
IF b[k + 1, 1] EQ b_char THEN k = k + 1
prefix(i) = k
NEXT i
k = 1
a_len = LEN(a)
FOR i = 1 TO a_len
a_char = a[i, 1]
LOOP WHILE k > 1 AND (b[k + 1, 1] NE a_char) DO
k = prefix(k)
REPEAT
IF b[k + 1, 1] EQ a_char THEN k = k + 1
IF k EQ b_len THEN
position = i - k + 1
J of BASE, TAF of C
38
By KZM
48
49
50
51
52
GOTO kmp_done
END
NEXT i
kmp_done:
RETURN
53
54
55
56
57
58
StartTest:
CRT FMT(msg, "T#16"):" running... ":
T1 = TIME()
S1 = SYSTEM(9)
RETURN
59
60
61
62
63
64
EndTest:
T2 = TIME()
S2 = SYSTEM(9)
CRT "Elapsed = ":OCONV(T2-T1,"MTS"):", CPU = ":S2-S1
RETURN
65
After quite a long time of waiting, lets see if we hit an infinite loop or something. Ctrl-C
takes us to debugger; we go there twice to see if loop counter changes as time passes:
Test of substring search - continued
KMP
running... Interrupt signal
Source changed to .\asearch.b
0042
LOOP WHILE k > 1 AND (b[k + 1, 1] NE a_char) DO
jBASE debugger->v i
i
: 147570
J of BASE, TAF of C
39
By KZM
jBASE debugger->c
Interrupt signal
0046
IF k EQ b_len THEN
jBASE debugger->v i
i
: 149381
jBASE debugger->c
Wheres the pitfall? It was found out that the reason of so huge difference is the presence
of environment variable JBASE I18N in my environment. Try that:
Another test of substring search
C:\sa-tafc> jrunSH.cmd
jsh ~ --> set JBASE_I18N=
jsh ~ --> asearch.exe
LEN(a):
1000000
LEN(b):
10000
INDEX
running... Elapsed = 00:00:07, CPU = 6937
I = 990001
KMP
running... Elapsed = 00:00:02, CPU = 1438
I = 990001
jsh ~ -->
It looks that the work with utf-8 strings is much slower than with regular single-byte
character set; avoid utf-8 if of course its possible in your environment.
J of BASE, TAF of C
40
By KZM
Now you got an idea why it was given its name. Theres a lot of Brainf**k interpreters
but no one written in jBC. So I thought its a good idea to write one.
We start with allocating the space for the cells which should contain 30,000 positions
(each of them containing a zero value at the beginning); also we assign a pointer position
to 1:
1
2
3
4
bf-run.b
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* By V.Kazimirchik, 2011.
~~~~~~~~~~~~~
* Brainf**k interpreter written in jBC.
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6
7
8
DIM V.VALUES(30000)
MAT V.VALUES = 0
V.POINTER = 1
Now get the name of a file with BF source that is to be processed and read it (we can
safely read it all - we dont expect the source code of 1Gb or so):
bf-run.b - continued
9
10
V.FILE.IN = SENTENCE(1)
11
12
13
14
15
IF V.FILE.IN EQ THEN
CRT Usage: bf-run <file>
STOP
END
16
17
18
19
20
21
J of BASE, TAF of C
41
By KZM
22
V.VOLUME = LEN(V.SRC)
23
Left and right brackets are loop boundaries in BF (and loops can be nested). Here goes
the comment that I used to fetch them all for future use them while debugging this program.
Id rather leave it in the source:
bf-run.b - continued
24
25
26
27
28
29
30
31
32
33
34
35
36
37
V.LEFT.BRACKETS =
V.RIGHT.BRACKETS =
V.BR.CNT = 0
V.BR.CNT.MATCH = 0
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
J of BASE, TAF of C
42
By KZM
59
END
60
61
NEXT V.I
62
Now we are ready to start the main loop. Move along BF source code until its end,
skipping everything that isnt a BF command:
bf-run.b - continued
63
*~~~~~~~~~~~~~
64
65
V.CNT = 0
66
67
LOOP
68
69
70
V.CNT ++
IF V.CNT GT V.VOLUME THEN STOP
71
72
73
74
Now processing of a command begins; > moves the pointer one position to the right
(within the span of cell space):
75
76
77
78
bf-run.b - continued
BEGIN CASE
CASE V.INSTR EQ >
V.POINTER ++
IF V.POINTER GT 30000 THEN V.POINTER = 30000
79
bf-run.b - continued
CASE V.INSTR EQ <
V.POINTER -IF V.POINTER LT 1 THEN V.POINTER = 1
83
+ shall increase the value in a cell under the pointer (unsigned 8-bit integer is the
limitation defined by language specification):
bf-run.b - continued
84
85
86
CASE V.INSTR EQ +
V.VALUES(V.POINTER) ++
IF V.VALUES(V.POINTER) EQ 256 THEN V.VALUES(V.POINTER) = 0
87
J of BASE, TAF of C
43
By KZM
CASE V.INSTR EQ -
V.VALUES(V.POINTER) -IF V.VALUES(V.POINTER) EQ -1 THEN V.VALUES(V.POINTER) = 255
91
Dot instruction outputs the symbol with ASCII value taken from the cell under the
pointer:
bf-run.b - continued
92
CASE V.INSTR EQ .
93
94
95
96
97
V.CURR.VAL = V.VALUES(V.POINTER)
IF V.CURR.VAL GT 0 AND V.CURR.VAL LT 256 THEN
CRT CHAR(V.CURR.VAL):
END
98
Comma waits for a keyboard input and the ASCII value of a key which was pressed is
being put to the cell under the pointer:
bf-run.b - continued
99
CASE V.INSTR EQ ,
100
101
102
103
104
ECHO OFF
V.KEY = KEYIN()
ECHO ON
V.VALUES(V.POINTER) = SEQ(V.KEY)
105
Now comes the tricky part loops. If theres a non-zero value in the current cell we
continue processing:
bf-run.b - continued
106
CASE V.INSTR EQ [
107
108
109
If theres zero it means that the loop is finished and we have to jump right after the
matching right bracket:
J of BASE, TAF of C
44
By KZM
bf-run.b - continued
110
111
112
113
114
115
116
V.CNT = V.RIGHT.BRACKETS<V.POSN>
IF V.CNT EQ 0 THEN
CRT STRUCTURE ERROR AT POSITION : V.CNT
STOP
END
117
118
119
120
121
122
And here a loop end that also requires a decision to return to loop start or to move
forward:
bf-run.b - continued
CASE V.INSTR EQ ]
123
124
125
126
127
128
129
130
131
V.CNT = V.LEFT.BRACKETS<V.POSN>
132
133
134
135
REPEAT
136
137
STOP
138
139
140
END
141
Now compile it and try some BF examples (Im far from programming in BF neither I
want you to, so I found some examples on the Internet, keeping the copyright notices if they
were present; first try the Hello world described at the beginning of this chapter):
J of BASE, TAF of C
45
By KZM
Hello world in BF
C:\sa-tafc> jrunComp.cmd bf-run
bf-run.c
mt -nologo -manifest bf-run.dll.manifest -outputresource:bf-run.dll;2 failed ,
command returned a code of -1
mt -nologo -manifest bf-run.exe.manifest -outputresource:bf-run.exe;1 failed ,
command returned a code of -1
C:\sa-tafc> jrunEXE.cmd bf-run.exe helloworld.bf
Hello World!
Try more examples; you can copy them and save to files, then use file names one by one
as an argument to bf-run.exe (the latter prefixed with jrunEXE.cmd of course). No line
numbers in these examples because line breaks dont matter in BF; text notes can also be
copied they simply will be ignored:
More BF examples
[ This program prints Sierpinski triangle on 80-column display. ]
>
+ +
+
+
[ < + +
+
+
+ +
+ +
>
]
>
+ + + + + + + +
[
>
+ +
+ +
<
]
>
> + + >
> > + >
>
>
+
<
< <
< <
< <
< <
<
[
[
>
+
<
] > [ - < + > > > . < < ] > > >
[
[
- >
+ +
+
+
+
+
+ + [ >
+ + + +
<
]
>
. <
< [
- >
+ <
]
+
>
[
>
+
+
+ + + + + + + +
< < + > ] > . [
]
>
]
] +
< <
< [
- [
>
+
<
]
+
>
[
J of BASE, TAF of C
46
By KZM
- < + >
> > - [
- > + <
] + + >
[
<
>
]
<
<
< ]
< <
< <
] +
+ +
+ +
+ +
+ +
+
.
+
+
+
.
[
]
<
]
+
+
+
+
+
* * * * * M a d e * B y : * N Y Y R I K K I * 2 0 0 2 * * * * *
More BF examples
;; 99 bottles of beer on the wall
;; warning: alcohol destroys brain cells! (not as much as brainfuck)
;; written by Keymaker
++++[>+++++<-]>+++
[
[>+>+<<-]
>++++++[<+>-]+++++++++[<++++++++++>-]
>[<+>-]<]
+++>+++++++++[<+++++++++>-]
>++++++[<++++++++>-]<--[>+>+<<-]>>[<<+>>-]<->>++++[<++++++++>-]++++++++++
>+++++++++[>+++++++++++<-]>
[
[>+>+>+<<<-]>[<+>-]>>
[
[>+>+<<-]>>[<<+>>-]<
[<->-[<->-[<->-[<->-[<->-[<->-[<->-[<->-[<->-[<<<+>---------->->
[-]]]]]]]]]]]<
]
<<[>>++++++++++++[<++++<++++>>-]<<[.[-]>]<<]
>[<++++++[>++++++++<-]>.[-]]<<
<<<.<<<<<.[<]>>>>>>>>>.<<<<<..>>>>>>>>.>>>>>>>.
[>]>[>+>+<<-]>[<+>-]>[-[[-]+++++++++[<+++++++++++++>-]<--.[-]>]]<<
<<<.[<]>>>>>>>>>.>>>>>>>>>.[>]<<.<<<<<.<<<..[<]>>>>>>.[>]<<.
[<]>>>>>>>>>.>.[>]<<.[<]>>>>.>>>>>>>>>>>>.>>>.[>]<<.
[<]>.[>]<<<<<<.<<<<<<<<<<<..[>]<<<.>.
[>]>[>+>+>+<<<-]>[<+>-]>>
[
[>+>+<<-]>>[<<+>>-]<
[<->-[<->-[<->-[<->-[<->-[<->-[<->-[<->-[<->-[<<<+>---------->->
[-]]]]]]]]]]]<
]
<<[>>++++++++++++[<++++<++++>>-]<<[.[-]>]<<]
J of BASE, TAF of C
47
By KZM
>[<++++++[>++++++++<-]>.[-]]<<
<<<.<<<<<.[<]>>>>>>>>>.<<<<<..>>>>>>>>.>>>>>>>.
[>]>[>+>+<<-]>[<+>-]>[-[[-]+++++++++[<+++++++++++++>-]<--.[-]>]]<<
<<<.[<]>>>>>>>>>.>>>>>>>>>.[>]<<.<<<<<.<<<..[<]>>>>>>.[>]<<<<.>>>.
<<<<.<.<<<<<<<<<<.>>>>>>.[>]<<.[<]>>>>>>>>>.>.>>>>>>>>>.[>]<<.
<<<<<<<.[<]>>>>>>>>>.[<]>.>>>>>>>>>.[>]<<.<<<<.<<<<<<<<<<<<<.[>]<<<<<<<<<.
[>]<<.[<]>>>>>>>>.[>]<<<<<<.[<]>>>>>..[>]<<.<<<<<<<<<<<<.[<]>>>>.
[>]<<.<<<<.[<]>>>>>>.>>>.<<<<<<.>>>>>>>.>>>>>>>>>>.[>]<<<.>.
>>>[>+>+>+<<<-]>[<+>-]>>
[
[>+>+<<-]>>[<<+>>-]<
[<->-[<->-[<->-[<->-[<->-[<->-[<->-[<->-[<->-[<<<+>---------->->
[-]]]]]]]]]]]<
]
<<[>>++++++++++++[<++++<++++>>-]<<[.[-]>]<<]
>[<++++++[>++++++++<-]>.[-]]<<
[>+>+<<-]>[<+>-]+>[<->[-]]<
[-<<<[<]>>>>>>>>>>.<.[>]<<.[<]>>>>>>>>>>>.<<.<<<.[>]<<<<<<<<<<.[>]>>]<
<<<.<<<<<.[<]>>>>>>>>>.<<<<<..>>>>>>>>.>>>>>>>.
[>]>[>+>+<<-]>[<+>-]+>
[<->-[<+>[-]]]<
[++++++++[>+++++++++++++<-]>--.[-]<]<
<<<.[<]>>>>>>>>>.>>>>>>>>>.[>]<<.<<<<<.<<<..[<]>>>>>>.[>]<<.
[<]>>>>>>>>>.>.[>]<<.[<]>>>>.>>>>>>>>>>>>.>>>.[>]<<.
[<]>.[>]<<<<<<.<<<<<<<<<<<..[>]<<<<.>>>..
>>
]
More BF examples
;; collatz sequences
;; this program prints out collatz sequences from 1 to infinity
;; written by Keymaker
>>+>+<[[->>[>>]>>>[>>]+[<<]<<<[<<]>[>[>>]>>+>[>>]<+<[<<]<<<[<
<]>-]>[>>]>>[<<<<[<<]>+>[>>]>>-]<<<<[<<]+>>]<<[+++++[>+++++++
J of BASE, TAF of C
48
By KZM
+<-]>.<++++++[>--------<-]+<<]>>[>>]+[>>>>[<<+>+>-]<-[>+<-]+<
[<<->>-[<<+>>[-]]]>>>[<<<+<<+>>>>>-]<<<[>>>+<<<-]<<[[-]>+>>->
[<+<[<<+>>-]<[>+<-]<[>+<-]>>>>-]<[>+<-]+<[->[>>]<<[->[<+++>-[
<+++>-[<+++>-[<[-]++>>[-]+>+<<-[<+++>-[<+++>-[<[-]+>>>+<<-[<+
++>-[<+++>-]]]]]]]]]<[>+<-]+<<]>>>+<[->[<+>-[<+>-[<+>-[<+>-[<
+>-[<+>-[<+>-[<+>-[<+>-[<[-]>>[-]+>+<<-[<+>-]]]]]]]]]]]<[>+<]+>>]<<[<<]>]<[->>[->+>]<[-[<+>-[<->>+<-[<+>-[<->>+<-[<+>-[<>>+<-[<+>-[<->>+<-[<+>-[<->>+<-[<+>-[<->>+<-[<+>-[<->>+<-[<+>
-[<->>+<-[<+>-[<->>+<-[<+>-]]]]]]]]]]]]]]]]]]]>[<+>-]<+<[<+++
+++++++>-]<]>>[<+>->>]<<[>+>+<<-]>[<+>-]+>[<->[-]]<[-<<-]<<[<
<]]++++++[>+++++++<-]>++.------------.[-]>[>>]<<[+++++[>+++++
+++<-]>.<++++++[>--------<-]+<<]+<]>[<+>-]<]>>>[>>]<<[>[-]<-<
<]++++++++++.[-]<<<[<<]>>>+<[->[<+>-[<+>-[<+>-[<+>-[<+>-[<+>[<+>-[<+>-[<+>-[<[-]>>[-]+>+<<-]]]]]]]]]]<[>+<-]+>>]<<[<<]>>]
More BF examples - factorials from 1 to infinity
>++++++++++>>>+>+[>>>+[-[<<<<<[+<<<<<]>>[[-]>[<<+>+>-]<[>+<-]<[>+<-[>+<-[>
+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>[-]>>>>+>+<<<<<<-[>+<-]]]]]]]]]]]>[<+>]+>>>>>]<<<<<[<<<<<]>>>>>>>[>>>>>]++[-<<<<<]>>>>>>-]+>>>>>]<[>++<-]<<<<[<[
>+<-]<<<<]>>[->[-]++++++[<++++++++>-]>>>>]<<<<<[<[>+>+<<-]>.<<<<<]>.>>>>]
This program doesnt terminate; you will have to kill it.
Daniel B Cristofani (cristofdathevanetdotcom)
http://www.hevanet.com/cristofd/brainfuck/
More BF examples - Fibonacci numbers from 1 to 100
+++++++++++
>+>>>>++++++++++++++++++++++++++++++++++++++++++++
>++++++++++++++++++++++++++++++++<<<<<<[>[>>>>>>+>
+<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]<[>++++++++++[<-[>>+>+<<<-]>>>[<<<+>>>-]+<[>[-]<[-]]>[<<[>>>+<<<
-]>>[-]]<<]>>>[>>+>+<<<-]>>>[<<<+>>>-]+<[>[-]<[-]]
>[<<+>>[-]]<<<<<<<]>>>>>[+++++++++++++++++++++++++
+++++++++++++++++++++++.[-]]++++++++++<[->-<]>++++
++++++++++++++++++++++++++++++++++++++++++++.[-]<<
<<<<<<<<<<[>>>+>+<<<<-]>>>>[<<<<+>>>>-]<-[>>.>.<<<
[-]]<<[>>+>+<<<-]>>>[<<<+>>>-]<<[<+>-]>[<+>-]<<<-]
More BF examples
>++++++++++>>+<+[[+++++[>++++++++<-]>.<++++++[>--------<-]+<<]>.>[->[
<++>-[<++>-[<++>-[<++>-[<-------->>[-]++<-[<++>-]]]]]]<[>+<-]+>>]<<]
Output powers of two
And even more BF examples
++++[>+++++<-]>[<+++++>-]+<+[
>[>+>+<<-]++>>[<<+>>-]>>>[-]++>[-]+
J of BASE, TAF of C
49
By KZM
>>>+[[-]++++++>>>]<<<[[<++++++++<++>>-]+<.<[>----<-]<]
<<[>>>>>[>>>[-]+++++++++<[>-<-]+++++++++>[-[<->-]+[<<<]]<[>+<-]>]<<-]<<]
[Outputs square numbers from 0 to 10000.
Daniel B Cristofani (cristofdathevanetdotcom)
http://www.hevanet.com/cristofd/brainfuck/]
Finally, my favourite:
More, more BF examples
>>>>+>+++>+++>>>>>+++[
>,+>++++[>++++<-]>[<<[-[->]]>[<]>-]<<[
>+>+>>+>+[<<<<]<+>>[+<]<[>]>+[[>>>]>>+[<<<<]>-]+<+>>>-[
<<+[>]>>+<<<+<+<--------[
<<-<<+[>]>+<<-<<-[
<<<+<-[>>]<-<-<<<-<----[
<<<->>>>+<-[
<<<+[>]>+<<+<-<-[
<<+<-<+[>>]<+<<<<+<-[
<<-[>]>>-<<<-<-<-[
<<<+<-[>>]<+<<<+<+<-[
<<<<+[>]<-<<-[
<<+[>]>>-<<<<-<-[
>>>>>+<-<<<+<-[
>>+<<-[
<<-<-[>]>+<<-<-<-[
<<+<+[>]<+<+<-[
>>-<-<-[
<<-[>]<+<++++[<-------->-]++<[
<<+[>]>>-<-<<<<-[
<<-<<->>>>-[
<<<<+[>]>+<<<<-[
<<+<<-[>>]<+<<<<<-[
>>>>-<<<-<]]]]]]]]]]]]]]]]]]]]]]>[>[[[<<<<]>+>>[>>>>>]<-]<]>>>+>>>>>>>+>]<
]<[-]<<<<<<<++<+++<+++[
[>]>>>>>>++++++++[<<++++>++++++>-]<-<<[-[<+>>.<-]]<<<<[
-[-[>+<-]>]>>>>>[.[>]]<<[<+>-]>>>[<<++[<+>--]>>-]
<<[->+<[<++>-]]<<<[<+>-]<<<<
]>>+>>>--[<+>---]<.>>[[-]<<]<
]
[Enter a number using ()-./0123456789abcdef and space, and hit return.
Daniel B Cristofani (cristofdathevanetdotcom)
http://www.hevanet.com/cristofd/brainfuck/]
J of BASE, TAF of C
50
By KZM
10
y the way, arent you tired of that messages? I mean the messages that appear during
:::: compilation and are a bit misleading:
Annoying messages while complation
mt -nologo -manifest bf-run.dll.manifest -outputresource:bf-run.dll;2 failed ,
command returned a code of -1
mt -nologo -manifest bf-run.exe.manifest -outputresource:bf-run.exe;1 failed ,
command returned a code of -1
Hmmm... Judging on its command line, it has something to do with manifests and the
stuff alike which we in fact dont need.
Maybe some jcompile settings can help us but heres the simpler way. Write a program
(in jBC of course) called mt which does nothing but terminates correctly, then check if all is
correct with our programs which were compiled without the usage of this plug.
So:
mt.b
1
2
STOP
END
mt.b compilation
C:\sa-tafc> jrunComp.cmd mt
mt.c
mt -nologo -manifest mt.dll.manifest -outputresource:mt.dll;2 failed ,
command returned a code of -1
As you saw before, for compilation of jBC program mt is launched twice (and for a
subroutine - once, we saw it when compiled JBCUserConversions). In this case the error
message appeared only once - on the second attempt our mt.exe already existed. Try then:
J of BASE, TAF of C
51
By KZM
So no more messages, execution result is the same. Now comes the question - mt.exe
terminates normally (which is the default behaviour) but imagine that we want to terminate
a program abnormally?
Here comes the thing that you never use with T24 - abort. Amend the mt.b to return
an error:
mt.b
1
2
ABORT 201
END
Why 201? Just because I saw it in some source, dont remember which. Delete mt.exe,
try to compile mt.b and get the error message. Actually, three messages:
mt.b compilation expecting an error
C:\sa-tafc> jrunComp.cmd mt
mt.c
mt -nologo -manifest mt.dll.manifest -outputresource:mt.dll;2 failed ,
command returned a code of -1
** Error [ 201 ] **
Unable to open file
mt -nologo -manifest mt.exe.manifest -outputresource:mt.exe;1 failed ,
command returned a code of 51
So they are:
-1 because mt.exe itself didnt exist at this point of time.
201 is our jBASE error message.
51 is derived from our 201 error message - so far I have no idea why the number is
different.
Now revert to initial code of mt.b and compile it so we dont have more error messages
during compilation.
Where the text of error message 201 is stored? In TAFC home directory theres a file
called jbcmessages. Its standard hashed jBASE file so:
J of BASE, TAF of C
52
By KZM
Yes! We see here the error code 51 that is passed back to operating system.
So if you want to output some standard error messages and to analyze the results of your program execution after its exit now you
know where to look.
J of BASE, TAF of C
53
By KZM
11
he task of processing a text file might be a little tricky in jBC. Firstly dont forget that
::::: line length matters. If its more that 1024 characters then you might fall into very
common misunderstanding. To illustrate that lets create such text file. The code is:
textcreate.b
1
3
4
5
6
7
8
9
10
11
12
13
V.LINE =
14
15
16
17
18
19
20
21
* Now write it
22
FOR V.I = 1 TO 10
WRITESEQ V.LINE TO F.OUT.FILE ELSE
CRT FILE WRITE ERROR
STOP
END
NEXT V.I
23
24
25
26
27
28
29
STOP
30
31
32
END
33
We either create a file or truncate it if it already exists. A line with the length of 1100
bytes is written to it 10 times. Compile, run, see the test.txt in your favourite editor or in
JED:
test.txt - end of line
File . , Record test.txt
Command->
J of BASE, TAF of C
+1040 Insert
54
23:07:19
By KZM
0001 ------1050------1060------1070------1080------1090------1100
0002 ------1050------1060------1070------1080------1090------1100
0003 ------1050------1060------1070------1080------1090------1100
0004 ------1050------1060------1070------1080------1090------1100
0005 ------1050------1060------1070------1080------1090------1100
0006 ------1050------1060------1070------1080------1090------1100
0007 ------1050------1060------1070------1080------1090------1100
0008 ------1050------1060------1070------1080------1090------1100
0009 ------1050------1060------1070------1080------1090------1100
0010 ------1050------1060------1070------1080------1090------1100
-------------------------------- End Of Record --------------------------------
3
4
5
6
7
8
9
10
11
12
13
14
CRT V.LINE
15
16
STOP
17
18
19
END
20
J of BASE, TAF of C
55
By KZM
-------570-------580-------590-------600-------610-------620-------630-------640
-------650-------660-------670-------680-------690-------700-------710-------720
-------730-------740-------750-------760-------770-------780-------790-------800
-------810-------820-------830-------840-------850-------860-------870-------880
-------890-------900-------910-------920-------930-------940-------950-------960
-------970-------980-------990------1000------1010------1020----
By default readseq is able to read 1024 bytes at a time. (Or 1024 characters? Not sure
if we had some two-byte characters here what would be the output. But the point is not
that, though I hope we can look at some utf stuff later...)
How to overcome this default? There are two ways:
Repeat readseq as many times as its necessary to retrieve the whole line. The end of
this process can be understood by checking if the length of retrieved data is less than 1024.
Override this default using IOCTL function:
Corrected textread.b
1
2
3
INCLUDE JBC.h
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CRT V.LINE
24
25
STOP
26
27
28
END
29
J of BASE, TAF of C
56
By KZM
The obvious caveat of using this method is that you need to know how long is your longest
line.
Other useful things in ioctl() you can get the time and date of last update for each
individual record in a hashed jBASE file; check if records exists without reading it and others
(there are quite comprehensive examples of that in JBASE BASIC.pdf ).
J of BASE, TAF of C
57
By KZM
12
nother task which can be described here is: we have big text file (containing, say,
::::: 1,500,000 lines) that we need to split into several smaller ones following certain rule.
The rule is: theres an account number at the certain field in each line (line is delimited with
semicolons). There can be many lines in the file contaning the same account number, and
these lines are not necessarily located one right after another. We need to split this big file
into three smaller files but the particular account number shouldnt appear in more than one
resulting file. And last but not least these files should contain about the same number of
lines.
What that might be needed for? Well, for example, to start several parallel sessions of
creating accounting entries in order to avoid locks.
Trying to resolve this task I went through the following approaches:
Wrote a program in jBC that proceeds the file line by line and stores account numbers
in a dynamic array, keeping in another array the number of times that particular account
appears in the file. This number was then used to distribute accounts among resulting files.
And on the second run through the big file create smaller files. Ended up with very low
speed of find when dynamic array is quite big.
Having python installed on my PC, wrote a python program to do the same. Python
is much better in handling big amount of data in memory (for this task I used a key-value
array type which is called a dictionary ), so it took less than a minute to proceed. Anyway,
the task that seemed a one-time one became a regular chore. Understanding that people who
might need to perform it probably dont have python, I reverted to jBC with a thought to
create a temporary hashed file that can be used to keep the necessary work information.
But finally a simple thought came to my mind: I had Unix as jBASE server OS and
since the order of lines didnt matter I sorted the file by account numbers using appropriate
Unix command, so I simplified jBC program and put the following comment to the beginning:
Comment
1
2
3
4
5
6
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Split big file account-wise (for parallel load).
|
* By V.Kazimirchik.
|
* Firstly you have to sort this file:
|
* bash-3.00$ sort -t ";" +6 My.File > My.File.sorted
|
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Then I was able to create smaller files one by one read all lines belonging to particular
account from a big file and then decide whether all of them go to the currently written smaller
file or we have to create the next one and write them there.
This issue shows us that though jBC is more or less universal language, not necessarily
we use only it to achieve our targets. Another example of using an external tool: once I was
J of BASE, TAF of C
58
By KZM
asked if its possible to list not all values of a field in a list but only the particular. See the
usual output of listing fields day.no and turnover.debit in FBNK.ACCT.ACTIVITY (with
our day of choice as 16):
Listing output
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST FBNK.ACCT.ACTIVITY DAY.NO TURNOVER.DEBIT WITH DAY.NO EQ "16"
@ID............................................
DAY.NO
43214-201007
05
16
26
28
05
06
09
10
13
16
18
29
05
06
09
13
16
24
26
28
29
45748-201007
12165-201007
TURNOVER.DEBIT.....
-50
-3302.08
-10115
-50250
-40
-145
-125333.33
-1500
-61441.62
-57390.85
-18716.54
-38532.18
-21278.11
-52575
-46553.79
-210061.02
...
What if we want only data for day.no = 16 to be shown (with the total turnover being
calculated)? Hopefully without programming. Whatever I tried using LIST still includes
all values to the output. To add some difficulty, the position of the necessary value in a
field could be any (so standard methods of extraction which shall know the exact position
wouldnt work).
The answer could be if we dont need @ids we can (under Unix) filter the output via
grep command using something like (note that the command should be entered at one line):
Filtering output
jsh ~ --> bash
bash-3.00$ jsh -c "LIST FBNK.ACCT.ACTIVITY ID.SUPP DAY.NO TURNOVER.DEBIT
J of BASE, TAF of C
59
By KZM
-21278.11
-460.27
-148146.42
...
This method doesnt give us totals though and involves an external tool (under standard
Windows theres no grep though the port of this utility exists for this platform). Somewhat
better results we can get with SELECT...SAVING EVAL with saving the resulting list (again
its a long command shown at two lines just to fit the screen):
SELECT comes to the scene
C:\sa-tafc> jrunT24.cmd
jsh ~ --> SELECT FBNK.ACCT.ACTIVITY WITH DAY.NO EQ "16" SAVING
EVAL "DAY.NO:;:TURNOVER.DEBIT"
1216 Records selected
> SAVE.LIST report.csv
1216 record(s) saved to list report.csv
jsh ~ --> EDIT.LIST report.csv
.jBASE.el.4
TOP
.P
TOP
001 05;
002 16;
003 26;-50
004 28;-50
005 05;-3302.08
006 06;-10115
007 09;-50250
008 10;-40
009 13;-145
010 16;-145
011 18;-125333.33
012 29;-1500
013 05;-61441.62
014 06;-57390.85
015 09;-18716.54
J of BASE, TAF of C
60
By KZM
016 13;-38532.18
017 16;-21278.11
018 24;-52575
019 26;-46553.79
020 28;-46553.79
021 29;-210061.02
022 05;-1500000
.EXIT
Record .jBASE.el.4 exited from file .
List report.csv exited
jsh ~ -->
Furthermore, we can sort this list using SORT.LIST command. Then we can take it into
a tool like MS Excel or Openoffice to proceed further. Of course it adds additional layer of
manual work. Maybe its a better idea to write a program after all...
J of BASE, TAF of C
61
By KZM
13
ell... not so simple just one that cant be produced using other ways (or if theres no
:::::: time to investigate these other ways deeper). Here well use the last sample from
the previous chapter to produce a report. Firstly - just plain delimited text. (For simplicity
sake just make it for the BNK company only but lets add some complexity and try to
report not only some particular day but an arbitrary period of time.)
To proceed we need to understand that the file FBNK.ACCT.ACTIVITY might be big. Very
big depending on the number of accounts and movements of funds. So Id prefer to use
this opportunity to introduce so-called Basic SELECT. Whats the difference between it
and a regular jQL SELECT?
Basic SELECT isnt able to limit the selection it just selects all @ids. But its ready
to proceed records immediately and doesnt depend on number of records you can consider
it as a pointer moving over a file. Herewith goes another feature of it: if new records are
added to the file during the process, they most probably will be picked up as well. Quite the
opposite, jQL SELECT makes a snapshot of record @ids available in a file and of course
might take very long time doing that.
In the previous chapter we missed the fact that data is grouped month-wise so we tried
to sum up all movements occured on the 16th day of every month which makes not much
sense (unless its a tax payday and you want to calculate the total turnover and compare it to
average values). Thats why here well have a possibility to include any period of time to the
output. Assuming that @ids are in a form account id.year month we really need Basic
SELECT here since date is at the end of @id rather than at the beginning and therefore
jQL SELECT will be slow - especially if user decides to set quite a big range of dates to include
to the report. Creating a secondary index is possible but often is not an option so we dont
consider that (though in other case you might do).
For compiler to find T24 insert files (one of them I F.ACCT.ACTIVITY well certainly
need) its necessary to add the path to T24.BP to jrunComp.cmd:
Change in jrunComp.cmd
34
So heres the program. Necessary dates go as parameters which well quickly check (I
deliberately omit a check if a date is a fully valid one to save some space):
mvmtrep.b
1
2
3
4
5
J of BASE, TAF of C
62
By KZM
7
8
V.BEG = SENTENCE(1)
V.END = SENTENCE(2)
9
10
11
12
13
14
15
16
17
18
21
22
V.DLM = ;
23
For our report we need to proceed all records in FBNK.ACCT.ACTIVITY. Before we can
issue Basic SELECT we need the file to be opened:
mvmtrep.b - continued
24
25
26
27
28
29
30
31
32
33
SELECT F.AC.ACT TO 9
Now in a loop extract an @id; first of all see if it matches our range of dates:
mvmtrep.b - continued
34
35
* main loop
36
37
38
LOOP
READNEXT V.ID FROM 9 ELSE BREAK
39
J of BASE, TAF of C
63
By KZM
V.MTH = FIELD(V.ID, -, 2)
IF V.BEG[1,6] GT V.MTH OR V.END[1,6] LT V.MTH THEN CONTINUE
40
41
42
If it does then:
mvmtrep.b - finished
43
44
45
46
47
48
49
V.DAYNO.L = R.AC.ACT<IC.ACT.DAY.NO>
V.DAYNO.QTY = DCOUNT(V.DAYNO.L, @VM)
V.ACCT = FIELD(V.ID, -, 1)
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
REPEAT
70
71
STOP
72
73
END
74
J of BASE, TAF of C
64
By KZM
C:\sa-tafc>j runT24.cmd
jsh ~ --> mvmtrep 20100725 20100805
...
20100728;JPYUSD140160017;-18000
20100802;EUR144410003;-68152.09
20100804;EUR144410003;-1416239.08
20100805;EUR145550001;-20000
20100805;51284;-200000
20100728;USDSGD140160014;-3.74
20100805;EUR143040074;-20000
20100805;USD141960017;-37188.85
jsh ~ -->
This is the fastest way to get raw output. The programming itself shouldnt take more
than one hour.
Bottomline: consider using external tools exclusively or combined
with jQL or jBC, but sometimes just to write jBC program is the
easiest and/or the fastest way.
As you can see, the results are not sorted, theres no totals etc. To create a nice-looking
report we can use any of the following methods:
Amend the program to produce the final layout. If its a simple ASCII report like T24
account statement its quite easy to do.
As it was already suggested earlier, take the output to Excel-like tool.
Amend jBC program to create output in some fancy format like xml and proceed it
somewhere else. Very fashionable option but we dont consider it here assuming the fact that
there are some limitations in jBC. xml shouldnt be a problem (provided that you have an
external tool to proceed it) but other formats - like pdf - certainly will. And forget about
creating something like doc file - even Microsoft is unable to proceed all flavours of them.
Use some external tool to proceed the final output from the raw output we already
have. Will do that in the next chapter (though you might try the previous option if you like
it more).
J of BASE, TAF of C
65
By KZM
14
ere well try to create a nice report using an external tool. The external tool
rd
party
::::: were talking about might be either an operating system command or utility, 3
software or even another jBC program written specially for this purpose.
Firstly lets think how do we deliver the output of our jBC program to that tool? Well
try to use standard output redirection. Its possible to redirect the output to file but thats
not so interesting, neither its fast. To gain some performance wed better redirect the output
of one program to the input of another.
Report output redirection
C:\sa-tafc> jrunT24.cmd
jsh ~ --> mvmtrep 20100725 20100805 | more
...
20100728;USDSGD140160014;-3.74
20100805;EUR143040074;-20000
20100805;USD141960017;-37188.85
jsh ~ -->
More is to output one page and wait for the user command to continue... No, all was
output at once. Now try the same in OS shell:
Report output redirection - now OK
C:\sa-tafc> jrunT24.cmd
jsh ~ --> cmd
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\....\bnk\bnk.run> mvmtrep 20100101 20110101 | more
20100806;13218;-2
20100707;14044;-1000000
20100713;14044;-500657.53
-- More --
Here we see another difference between jsh and OS shell. We can continue with our
solution using the latter.
Of course we wouldnt use more command for our purpose we just made sure that this
approach works. As it was written above, the tool that we can use can be any executable
that is able to proceed the standard input provided by the report preparation program. In
Unix or Linux almost everything that we might need already presents in the system. Under
J of BASE, TAF of C
66
By KZM
CRT <<<EOF>>>
72
73
STOP
74
75
END
76
And here the code that is to proceed the data further on:
procrep.b
1
3
4
5
6
7
LOOP
INPUT V.LINE
IF V.LINE[1,9] EQ <<<EOF>>> THEN BREAK
IF V.LINE EQ THEN CONTINUE ;* we have an empty string
;* after each line - probably caused by
CRT <tr>
;* char(13) in line end under windows
8
9
10
11
12
13
14
V.CNT = DCOUNT(V.LINE, ;)
FOR V.I = 1 TO V.CNT
V.FLD = FIELD(V.LINE, ;, V.I)
CRT <td> : V.FLD : </td>
NEXT V.I
15
16
17
18
19
20
CRT </tr>
21
22
REPEAT
23
24
CRT </table></body></html>
25
26
STOP
27
28
29
END
30
J of BASE, TAF of C
67
By KZM
Now compiling all and toying a little with redirection (browsers arent able to my
knowledge to get contents from stdin , so were using a temporary file here) we were able
to see the output in the browser that is set in the system as a default one:
Creation of a nice report; long command is split again
C:\sa-tafc> jrunT24.cmd
jsh ~ --> cmd
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\....\bnk\bnk.run> mvmtrep 20100101 20110101 | procrep > %TEMP%\out.html
&& %TEMP%\out.html
J of BASE, TAF of C
68
By KZM
We can improve this report by adding a header, converting the date to more appropriate
format, add column headers, place some logo to the top etc. To add totals well need mvmtrep
to supply currency codes as well - we really are not going to add dollars to euros.
New source of mvmtrep.b:
1
2
Amended mvmtrep.b
* Program to report debit turnover
* for any period of time
3
4
5
$INCLUDE I_F.ACCOUNT
$INCLUDE I_F.ACCT.ACTIVITY
6
7
8
9
10
V.BEG = SENTENCE(1)
V.END = SENTENCE(2)
J of BASE, TAF of C
69
By KZM
11
12
13
14
15
16
17
18
19
20
21
22
23
24
V.DLM = ;
25
26
* open files
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
SELECT F.AC.ACT TO 9
46
47
* main loop
48
49
50
LOOP
READNEXT V.ID FROM 9 ELSE BREAK
51
52
53
V.MTH = FIELD(V.ID, -, 2)
IF V.BEG[1,6] GT V.MTH OR V.END[1,6] LT V.MTH THEN CONTINUE
54
55
56
57
58
J of BASE, TAF of C
70
By KZM
STOP
END
59
60
61
V.DAYNO.L = R.AC.ACT<IC.ACT.DAY.NO>
V.DAYNO.QTY = DCOUNT(V.DAYNO.L, @VM)
V.ACCT = FIELD(V.ID, -, 1)
62
63
64
65
66
67
68
69
70
71
72
73
74
V.CCY = R.ACCT<AC.CURRENCY>
END
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
REPEAT
94
95
CRT <<<EOF>>>
96
97
STOP
98
99
END
100
* Program to represent
* the report
J of BASE, TAF of C
71
By KZM
3
4
5
6
7
8
9
10
11
12
V.TOTAL.L =
V.TOT.QTY = 0
;* list of totals
13
14
15
16
17
18
19
LOOP
INPUT V.LINE
IF V.LINE[1,9] EQ <<<EOF>>> THEN BREAK
IF V.LINE EQ THEN CONTINUE ;* we have an empty string
;* after each line - probably caused by
CRT <tr>
;* char(13) in line end under windows
20
21
22
23
V.CNT = DCOUNT(V.LINE, ;)
FOR V.I = 1 TO V.CNT
V.FLD = FIELD(V.LINE, ;, V.I)
24
25
BEGIN CASE
26
27
28
CASE V.I EQ 1
V.FLD = V.FLD[2] : / : V.FLD[5,2] : / : V.FLD[1,4]
29
30
31
CASE V.I EQ 3
V.AMT = V.FLD
32
33
CASE V.I EQ 4
34
35
36
37
38
39
40
41
42
IF V.POSN EQ THEN
V.TOT.QTY ++
V.TOTAL.L<V.TOT.QTY> = V.FLD : @VM : V.AMT
END ELSE
V.TOTAL.L<V.POSN, 2> += V.AMT
END
43
44
END CASE
45
46
47
48
49
CRT </tr>
50
J of BASE, TAF of C
72
By KZM
REPEAT
51
52
CRT <tr><td><b>Totals:</b></td><td></td><td>
53
54
55
56
57
58
59
CRT </table></body></html>
60
61
STOP
62
63
64
END
65
J of BASE, TAF of C
73
By KZM
Of course you need to have the file logo.png in the directory where you put the html
file. For this example Ive used an online service to create a custom logo. To sort the data
you can use an OS command for sorting or (if the data amount is huge) create a temporary
jBASE file with @id structure that reflects the desired sort order. And dont forget that on
a client-server environment youll form the output on a server; so you need to make it visible
for a Web server as well and the client is to be supplied the correct URL to see the output.
Why use two different programs to prepare the raw data for the report and to proceed
it? Firstly the second program can be a standard one to proceed many similar reports.
Secondly to have flexibility: imagine that we need pdf to be the output format. Theres
nothing for pdf manupilation in jBC so well replace the procrep.exe with something else.
For simple pdf you can use Perl or Python, for more complex things Id suggest something
like TEX. Anyway, to create a fancy output youll either need some existing (and possibly
quite expensive) report engine or put a lot of labour to development of something more or
J of BASE, TAF of C
74
By KZM
less appropriate. See this was just an example (with hard-coding that shouldnt happen in
the final product rather youll need to merge some templates with data). You see, thats a
lot of work and the final decision is as usual yours.
J of BASE, TAF of C
75
By KZM
15
It works now. More about shell modes you can read in jBASE knowledgebase but be
aware of another difference sh mode doesnt store a select list. To illustrate that Ill
use an example from my previous book (which has a typo semicolon instead of a comma
right after FIELD(@ID sorry for that). This example shows how many T24 accounts have
history. But first of all return to jsh mode pressing F1:
Shells compatibility
sh ~ --> <F1>
jsh ~ --> SELECT FBNK.ACCOUNT$HIS SAVING UNIQUE EVAL "FIELD(@ID,;,1)"
406 Records selected
> SAVE.LIST AC.HIST
406 record(s) saved to list AC.HIST
jsh ~ --> EDIT.LIST AC.HIST
.jBASE.el.5
TOP
.P
TOP
001 17736
002 52237
003 36293
004 21307
005 57495
J of BASE, TAF of C
76
By KZM
006 57568
007 59803
008 17768
009 10437
010 42404
011 17051
012 41351
013 USD141300001
014 43397
015 20508
016 29823
017 41629
018 57948
019 11177
020 14516
021 43087
022 41971
.EXIT
Record .jBASE.el.5 exited from file .
List AC.HIST exited
jsh ~ -->
All looks correct so far. Then switch to sh mode (F2 again) and try the same:
Shells compatibility - continued
jsh ~ --> <F2>
sh ~ --> SELECT FBNK.ACCOUNT$HIS SAVING UNIQUE EVAL "FIELD(@ID,;,1)"
!!! Error message EVAL expression missing.
not found !!!
OK, thankfully theres another character to substitute a quote so SELECT works but
theres no active list after it:
Shells compatibility - continued
sh ~ --> SELECT FBNK.ACCOUNT$HIS SAVING UNIQUE EVAL \FIELD(@ID,;,1)\
J of BASE, TAF of C
77
By KZM
There is another mode msh (F3) but I havent investigated it yet. (I was told by
the colleague mentioned above that you can use OS commands and inheritance of SELECT
lists also works.) Next time you log in check in which mode you are this setting is saved
according to port number which, to be honest, doesnt make much sense nowadays since
every time you log in theres probably a different port number youre assigned. Check the
port of jsh using WHERE and then you can see where its stored:
Shells compatibility - the last note
jsh ~ --> <F3>
msh ~ --> WHERE
Port
*1
5
Device
ntcon
ntcon
Account
PID
Vladimir.Kaz 4004
Vladimir.Kaz 2868
Command
WHERE
jsh
J of BASE, TAF of C
78
By KZM
16
here are some things that can be achieved using manipulations with select lists. And
::::: this might make life easier, sometimes a lot. For example, how to put all records in
particular file to DL.DEFINE record? We can use a list to populate said record and it needs
to contain data in the format APPLICATION>@ID. How to get such list? Easy:
Creation of a list for DL.DEFINE
C:\sa-tafc> jrunT24.cmd
jsh ~ --> SELECT F.ABBREVIATION SAVING EVAL "ABBREVIATION>:@ID"
290 Records selected
> SAVE.LIST ABBR
290 record(s) saved to list ABBR
jsh ~ --> EDIT.LIST ABBR
.jBASE.el.1
TOP
.P
TOP
001 ABBREVIATION>AGC
002 ABBREVIATION>COND
003 ABBREVIATION>FT
004 ABBREVIATION>COMM
005 ABBREVIATION>ESDC
006 ABBREVIATION>FTCOM
007 ABBREVIATION>SON
008 ABBREVIATION>IP
009 ABBREVIATION>POS
010 ABBREVIATION>ST
011 ABBREVIATION>GAP
012 ABBREVIATION>SCV
013 ABBREVIATION>MVL
014 ABBREVIATION>TICKLER
015 ABBREVIATION>CHQP
016 ABBREVIATION>LRT
017 ABBREVIATION>AVL
018 ABBREVIATION>EEE
019 ABBREVIATION>ESM
020 ABBREVIATION>SC5
021 ABBREVIATION>VE
022 ABBREVIATION>LDP
.EXIT
Record .jBASE.el.1 exited from file .
List ABBR exited
J of BASE, TAF of C
79
By KZM
And now its ready for DL.DEFINE as soon as we copy this list (since DL.DEFINE
doesnt respect JBCLISTFILE environment variable and is yet unable to locate our select list):
Copying a list into T24
C:\sa-tafc> jrunT24.cmd
jsh ~ --> COPY FROM C:\sa-tafc\&SAVEDLISTS& TO &SAVEDLISTS& ABBR
1 records copied
Now enter the list name into field select.list of DL.DEFINE record:
Taking a list into DL.DEFINE
Model Bank
DL.DEFINE INPUT
UNIT.NAME......... TMNS000-ALL.ABBR
-----------------------------------------------------------------------------1. 1. 1 GB DESCRIPTN ALL ABBREVIATION
2. 1 GB SHORT.DESC.. ALL ABBR
3 LANGUAGE/COUNTRY..
4. 1 INDICES........
5 OPERATION......... S
6 SELECT.LIST....... ABBR_
7 TOP.LEVEL.TYPE....
8 TOP.LEVEL.ITEM....
9. 1 FILE.NAME......
10. 1 RECO
11. 1. 1 RECORD.DESC.
12. 1 SAVED.FROM.....
13. 1 SAVED.RELEASE..
14. 1 SAVED.DATE.....
15. 1 RESTORED.USER..
16. 1 RESTD.COMPANY..
-----------------------------------------------------------------------------29 JUN 2011 19:51:04 USER (09 AUG) VLADIMIR.K
[8427,INPAGE 1
>>>3>>>
ACTION
Now press Enter and voila we have 57 pages of the record being populated:
The list appears in DL.DEFINE
Model Bank
DL.DEFINE INPUT
UNIT.NAME......... TMNS000-ALL.ABBR
------------------------------------------------------------------------------
J of BASE, TAF of C
80
By KZM
Using SELECT...SAVING EVAL we can construct quite complex output for example,
set of OFS messages to proceed every record in unauthorised file. See the example of that:
Creation of OFS messages for mass processing - step 1
C:\sa-tafc> jrunT24.cmd
jsh ~ --> SELECT FBNK.FUNDS.TRANSFER$NAU WITH RECORD.STATUS EQ INAU
SAVING EVAL "FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,:@ID"
29 Records selected
>
(Following this example you need to be careful with the field record.status it might
also contain such values as CNAU or RNAU.)
Creation of OFS messages for mass processing - step 2
> SAVE.LIST QWE
29 record(s) saved to list QWE
J of BASE, TAF of C
81
By KZM
.jBASE.el.2
TOP
.P
TOP
001 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102217DKNB
002 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221SB54Y
003 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102211J2ZC
004 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102212BZ1C
005 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102211JH7V
006 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221D843N
007 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102218J7CZ
008 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102174JFHF
009 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221N587H
010 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221KKNH8
011 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221QVGZD
012 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102215SR4W
013 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221WPC4Z
014 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221SC80C
015 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221LL3FG
016 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102214H8GF
017 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102214HGVB
018 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221DNJHC
019 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221BPZQB
020 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102219VV0M
021 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221MD8GW
022 FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT10221KNLRM
.EXIT
Record .jBASE.el.2 exited from file .
List QWE exited
Now copy and paste the first line from the saved list:
Creation of OFS messages for mass processing - step 4
FUNDS.TRANSFER,/A/PROCESS//0,INPUTT/123456,FT102217DKNB
J of BASE, TAF of C
82
By KZM
FT102217DKNB//1,TRANSACTION.TYPE:1:1=AC,DEBIT.ACCT.NO:1:1=10693,CURRENCY.MKT.D
R:1:1=1,DEBIT.CURRENCY:1:1=USD,DEBIT.VALUE.DATE:1:1=20100809,CREDIT.ACCT.NO:1:
1=15997,CURRENCY.MKT.CR:1:1=1,CREDIT.CURRENCY:1:1=USD,CREDIT.AMOUNT:1:1=35820.
00,CREDIT.VALUE.DATE:1:1=20100809,PROCESSING.DATE:1:1=20100809,COMMISSION.CODE
:1:1=WAIVE,CHARGE.CODE:1:1=WAIVE,PROFIT.CENTRE.CUST:1:1=100318,RETURN.TO.DEPT:
1:1=NO,FED.FUNDS:1:1=NO,POSITION.TYPE:1:1=TR,AMOUNT.DEBITED:1:1=USD35820.00,AM
OUNT.CREDITED:1:1=USD35820.00,CREDIT.COMP.CODE:1:1=GB0010001,DEBIT.COMP.CODE:1
:1=GB0010001,LOC.AMT.DEBITED:1:1=35820.00,LOC.AMT.CREDITED:1:1=35820.00,CUST.G
ROUP.LEVEL:1:1=99,DEBIT.CUSTOMER:1:1=100318,CREDIT.CUSTOMER:1:1=100318,DR.ADVI
CE.REQD.Y.N:1:1=N,CR.ADVICE.REQD.Y.N:1:1=N,CHARGED.CUSTOMER:1:1=100318,TOT.REC
.COMM:1:1=0,TOT.REC.COMM.LCL:1:1=0,TOT.REC.CHG:1:1=0,TOT.REC.CHG.LCL:1:1=0,RAT
E.FIXING:1:1=NO,TOT.REC.CHG.CRCCY:1:1=0,TOT.SND.CHG.CRCCY:1:1=0,AUTH.DATE:1:1=
20100809,STMT.NOS:1:1=159340765753138.00,STMT.NOS:2:1=1-2,OVERRIDE:1:1=WITHDRA
WL.LT.MIN.BAL}WITHDRAWL MAKES A/C BAL LESS THAN MIN BAL,OVERRIDE:2:1=ACCT.DEBI
T.COLLATERAL}ACCOUNT &, DEBIT TO COLLATERAL{10693}100283.2.1,OVERRIDE:3:1=ACCT
.UNAUTH.OD}Unauthorised overdraft of & & on account &.{USD}24947.57}10693{USD{
24947.57{10693{100318{213{{,CURR.NO:1:1=1,INPUTTER:1:1=75_SUJA1_I_INAU_OFS_BRO
WSERTC,INPUTTER:2:1=3883_CONVERSION.DETAILS,DATE.TIME:1:1=1108161445,DATE.TIME
:2:1=1012220511,AUTHORISER:1:1=7657_INPUTTER_OFS_TAG,CO.CODE:1:1=GB0010001,DEP
T.CODE:1:1=1
EXIT
jsh ~ -->
After that you can continue with copy/paste even selecting all the rest at once will do.
(Though I noticed that very seldom some records were not processed so its better to issue
again the main SELECT after youve finished.) Alternatively, you can put the file containing
the SELECT list to the appropriate directory to be processed by batch file listener. Isnt
that a good replacement for EBS.AUTO.FUNCTION?
More about lists processing: two lists can be compared with logical AND, OR or
XOR operations applied to them. Here were getting T24 ACCOUNT records that were
created, then edited but the changes are not yet authorised and therefore these records
present in both live and $NAU files:
AND-LISTS example
C:\sa-tafc> jrunT24.cmd
jsh ~ --> SELECT FBNK.ACCOUNT
3620 Records selected
> SAVE.LIST ACCT1
3620 record(s) saved to list ACCT1
jsh ~ --> SELECT FBNK.ACCOUNT$NAU
J of BASE, TAF of C
83
By KZM
6 Records selected
> SAVE.LIST ACCT2
6 record(s) saved to list ACCT2
jsh ~ --> AND-LISTS ACCT1 ACCT2
1 Records selected
> LIST ONLY FBNK.ACCOUNT
USD109100001
1 Records Listed
To get account records that are new that is, were created but not yet authorised so they
appear in $NAU file only we use a negative select:
NSELECT example
jsh ~ --> SELECT FBNK.ACCOUNT$NAU
6 Records selected
> NSELECT FBNK.ACCOUNT
5 Records selected
> LIST ONLY FBNK.ACCOUNT$NAU
@ID................
10014
22071
22063
46922
10022
5 Records Listed
If we use two subsequent SELECTs, results will differ under jsh and sh modes (because the latter will not store the select list after first SELECT). See (well use the option
(R to suppress error messages Error 202 Record not on file):
Two subsequent SELECTs under jsh
jsh ~ --> SELECT FBNK.ACCOUNT$NAU
J of BASE, TAF of C
84
By KZM
6 Records selected
> SELECT FBNK.ACCOUNT (R
1 Records selected
> CLEARSELECT
jsh ~ -->
require-select is the correct solution in any case, since there might be no records at
all in FBNK.ACCOUNT$NAU and that will be handled correctly.
By the way, we can automate this task creating so-called paragraph in VOC:
J of BASE, TAF of C
85
By KZM
Creation of paragraph
C:\sa-tafc> jrunT24.cmd
jsh ~ --> JED VOC PA.SAMPLE
File VOC , Record PA.SAMPLE
Insert
21:17:12
Command->
0001 PA
0002 SELECT FBNK.ACCOUNT$NAU
0003 SELECT FBNK.ACCOUNT REQUIRE.SELECT
0004 LIST ONLY FBNK.ACCOUNT$NAU
----------------------------------- End Of Record -----------------------------
Paragraphs can have parameters; they also have a possibility of interaction with the user.
See example:
Creation of login paragraph
C:\sa-tafc> jrunT24.cmd
jsh ~ --> JED VOC K.LOGIN
File VOC , Record K.LOGIN
Insert
21:26:33
Command->
0001 PA
0002 ETS
0003 EX
0004 DATA INPUTT
0005 DATA 123456
0006 DATA <<C2,>>
----------------------------------- End Of Record -----------------------------
Save and run it and (provided that login and password are correct) you are in the chosen
application:
J of BASE, TAF of C
86
By KZM
ACCOUNT
------------------------------------------------------------------------------
ATTEMPTS: 0 -------[6485,IN]
If you dont want to disclose your password (which is indeed a good idea) change the
paragraph:
change in login paragraph
0001
0002
0003
0004
0005
0006
PA
ETS
EX
DATA INPUTT
DATA <<PASSWORD>>
DATA <<C2,>>
And then youll be prompted for the password (though your input will be still visible on
the screen).
J of BASE, TAF of C
87
By KZM
J of BASE, TAF of C
88
By KZM
17 EASTER EGGS
17
Easter eggs
ired? Lets relax a bit. You know about easter eggs in a context that is related to
::::: software it is some functionality that was by accident or deliberately put there and
isnt easy to see (normally you should know exactly how to activate one). As an example,
for very long time in every jBASE object file even one resulting from compilation of a local
routine there was a string Austin Powers. Now its not there possibly because I asked
Martin Bailey about that fact couple of years ago but if you have older release like R08
you might still have it in every .obj or .o file.
Another example GLOBUS.BAN case that Ive described earlier. One more now serious
example is the usage of no.fatal.error option in opf subroutine belonging to T24
API. Many things like that can be found out only if you have the opportunity to examine
the core source code. For example, that you can type debug in tSS and enter the debugger.
(And then you can store all available variables if you type V * > out.txt it really worth
looking at, especially after some OFS request like one described in the previous chapter.)
And often you can see an advice in discussion groups: use T24 routine THIS.AND.THAT
to achieve your goal. Should you? Maybe yes but firstly check if its a core or a part of the
official API. In case of core, better dont probably a person advising that has the access to
core sources and he thinks that its an appropriate one. But youre not able to see that source
and therefore youre at your own risk. Moreover, any routine might become deprecated in
the next release and disappear just like Austin Powers did.
There are also some parts that possibly are long since redundant but are still there
nevertheless. Ive written earlier about Universe object code that is still in Model Bank.
Another rather funny thing an application called BLOCK.LETTERS:
Application BLOCK.LETTERS
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST F.BLOCK.LETTERS
@ID.............
@ID.............
ASCII.CODE......
LINE.1..........
LINE.2..........
LINE.3..........
LINE.4..........
LINE.5..........
RECORD.STATUS...
CURR.NO.........
INPUTTER........
DATE.TIME.......
AUTHORISER......
CO.CODE.........
DEPT.CODE.......
AUDITOR.CODE....
AUDIT.DATE.TIME.
66
66
66
BBBB
B
B
BBBB
B
B
BBBB
1
1_INPUTTER
9410031621
1_AUTHORISER
GB0010001
200
J of BASE, TAF of C
89
By KZM
17 EASTER EGGS
@ID.............
@ID.............
ASCII.CODE......
LINE.1..........
LINE.2..........
LINE.3..........
LINE.4..........
LINE.5..........
RECORD.STATUS...
CURR.NO.........
INPUTTER........
DATE.TIME.......
AUTHORISER......
CO.CODE.........
DEPT.CODE.......
AUDITOR.CODE....
AUDIT.DATE.TIME.
51
51
51
33333
3
33333
3
33333
1
1_INPUTTER
9410031621
1_AUTHORISER
GB0010001
200
...
See? Here are all printable characters rendered for ASCII-art-like output. In Universe
there was a command with the same name if I remember correctly of course which
produced similar-looking output. Can we put it back to life? Why not? Lets try to produce
a custom GLOBUS.BAN using these renderings.
First of all we take the phrase from command line and proceed it according to screen
limitations:
1
2
3
4
5
6
DIM V.LINE.L(5)
MAT V.LINE.L =
7
8
V.PHRASE = SYSTEM(1000)
9
10
11
12
DEL V.PHRASE<1>
CHANGE @FM TO IN V.PHRASE
V.PHRASE = UPCASE(V.PHRASE[1,12])
Then open our files. Since we are overwriting GLOBUS.BAN here never try this program
on a server since other users might be affected.
custgban.b - continued
13
14
V.FILE = F.BLOCK.LETTERS
J of BASE, TAF of C
90
By KZM
17 EASTER EGGS
15
16
17
18
19
20
21
22
23
24
25
V.DIFF = 0
V.LEN = LEN(V.PHRASE)
V.OUT.LEN = V.LEN
V.OUT = 0
FOR V.I = 1 TO V.LEN
31
32
V.OUT ++
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
V.ID = SEQ(V.PHRASE[V.I,1])
IF V.ID EQ 32 THEN
;* space
V.L1 = STR( , 3)
;* make it narrower
V.L2 = STR( , 3)
V.L3 = STR( , 3)
V.L4 = STR( , 3)
V.L5 = STR( , 3)
V.DIFF += 2
;* and count the difference
END ELSE
READ R.BL FROM F.BL, V.ID ELSE
V.OUT.LEN -V.OUT -CONTINUE ;* ignore absent characters
END
V.L1 = FMT(R.BL<1>, 5L)
V.L2 = FMT(R.BL<2>, 5L)
V.L3 = FMT(R.BL<3>, 5L)
V.L4 = FMT(R.BL<4>, 5L)
V.L5 = FMT(R.BL<5>, 5L)
END
54
55
56
57
58
V.LINE.L(1)<V.OUT>
V.LINE.L(2)<V.OUT>
V.LINE.L(3)<V.OUT>
V.LINE.L(4)<V.OUT>
J of BASE, TAF of C
=
=
=
=
V.L1
V.L2
V.L3
V.L4
91
By KZM
17 EASTER EGGS
59
V.LINE.L(5)<V.OUT> = V.L5
60
61
NEXT V.I
64
65
66
67
68
71
72
73
74
75
76
: |
77
78
* Write characters
79
80
FOR V.I = 6 TO 10
81
82
83
V.MIDDLE =
V.PROC.L = V.LINE.L(V.I-5)
84
85
86
87
88
89
90
91
92
93
NEXT V.I
94
95
96
97
FOR V.I = 11 TO 16
J of BASE, TAF of C
92
By KZM
17 EASTER EGGS
V.LINE = V.EMPTYL
GOSUB WRITE.LINE
NEXT V.I
98
99
100
101
102
STOP
103
104
105
*~~~~~
WRITE.LINE:
106
107
108
109
110
111
112
RETURN
113
114
115
*~~~~~
END
116
J of BASE, TAF of C
93
By KZM
17 EASTER EGGS
custgban results
GLOBUS Rev. 201015
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SIGN.ON
-----------------------------------------------------------------------------|
|
|
|
|
H
H
IIIII
Y
Y
OOO
U
U
|
H
H
I
Y Y
O
O
U
U
|
HHHHH
I
Y
O
O
U
U
|
H
H
I
Y
O
O
U
U
|
H
H
IIIII
Y
OOO
UUUUU
|
|
|
|
|
|
|
-----------------------------------------------------------------------------06 JUL 2011 20:13:08 USER
[5257,IN]
ACTION
PLEASE ENTER YOUR SIGN ON NAME
J of BASE, TAF of C
94
By KZM
17 EASTER EGGS
Or:
custgban results
GLOBUS Rev. 201015
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SIGN.ON
-----------------------------------------------------------------------------|
|
|
|
|
H
H IIIII
Y
Y OOO U
U
TTTTT H
H EEEEE RRRR EEEEE
|
H
H
I
Y Y O
O U
U
T
H
H E
R
R E
|
HHHHH
I
Y
O
O U
U
T
HHHHH EEEEE RRRR EEEEE
|
H
H
I
Y
O
O U
U
T
H
H E
R R
E
|
H
H IIIII
Y
OOO UUUUU
T
H
H EEEEE R R EEEEE
|
|
|
|
|
|
|
-----------------------------------------------------------------------------06 JUL 2011 20:13:44 USER
[7289,IN]
ACTION
PLEASE ENTER YOUR SIGN ON NAME
Before we return to serious matters, lets try to solve the task of placing the current
system date to T24 login screen. Firstly we need a small program that forms a string with
current date and then triggers custgban with appropriate data. (Of course we could put this
functionality to custgban.b but Id like to illustrate the usage of chain command in jBC
which passes the execution to another program without ever returning back.)
1
date2gban.b
* Run custgban.exe with current date
V.DATE = OCONV(DATE(),"D")
CHAIN custgban : V.DATE
3
4
5
6
END
Then compile it and amend our login paragraph (well stop at the point where we are
able to see the login screen):
J of BASE, TAF of C
95
By KZM
17 EASTER EGGS
Result:
date2gban + custgban results
GLOBUS Rev. 201015
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SIGN.ON
-----------------------------------------------------------------------------|
|
|
|
|
00000
6
JJJJJ U
U L
22222 00000
1
1
|
0
0
6
J
U
U L
2 0
0
111
111
|
0
0
666
J
U
U L
22222 0
0
1
1
|
0
0
6 6
J J
U
U L
2
0
0
1
1
|
00000
66
JJJJ
UUUUU LLLLL
22222 00000 11111 11111
|
|
|
|
|
|
|
-----------------------------------------------------------------------------06 JUL 2011 20:13:10 USER
[3876,IN]
ACTION
PLEASE ENTER YOUR SIGN ON NAME
We also could pass the data between programs using a common area. Also we cound use
yet another table F.CODED.BLOCK.LETTERS... And last but not least we could just use
a paragraph named GLOBUS.BAN for this purpose it has the preference over a text file with
the same name:
J of BASE, TAF of C
96
By KZM
17 EASTER EGGS
jCL example
C:\sa-tafc> jrunT24.cmd
jsh ~ --> JED VOC GLOBUS.BAN
File VOC , Record GLOBUS.BAN
Insert
21:17:17
Command->
0001 PQN
0002 T (25,10), *=10, Welcome!, *=10
----------------------------------- End Of Record -----------------------------
SIGN.ON
------------------------------------------------------------------------------
==========Welcome!==========
Very simple example though jCL is capable of many things like hashed files opening,
reading and writing (the latter I surely dont recommend at the stage of logging in to T24),
if...else logic etc. Ive tried to use it to solve the task described in one of earlier chapters
output all records of ACCT.ACTIVITY containing movements for the 16th day of every
month (suppressing unnecessary values). It took me about 4 hours to compose a paragraph
that outputs the day number, record @id and debit turnover:
More complex jCL example
C:\sa-tafc> jrunT24.cmd
jsh ~ --> JED VOC TEST.ACCT.ACT
J of BASE, TAF of C
97
By KZM
17 EASTER EGGS
J of BASE, TAF of C
98
By KZM
17 EASTER EGGS
jsh ~ -->
To my opinion, jCL is slightly easier to learn than Brainf**k. But for very simple things
it still might be used. I really hate the idea of making this paragraph compatible with the
latest version of mvmtrep.b and test all the report processing chain described in an earlier
chapter. But if you have some spare time, you can try it yourself.
J of BASE, TAF of C
99
By KZM
18
hy all these exercises that most possibly will never be used under T24? I think its
:::::: good to know your main work tool especially what it can do and what it cant. I
can give you the following example: once I was given a code that split a long line into several
ones with smaller length (nothing complex like keeping words bounds just cut the line).
That code implemented a loop that cut pieces and put them to dynamic array one by one.
Having seen that, I asked: why not use fmt()? See:
fmtsample.b
1
V.IN = This is quite a long line that we have to break into parts \
: with length of 35 characters while not thinking about cutting \
: a word in two in the very middle. So we are going to use FMT() \
: for this tremendous task
V.OUT = FMT(V.IN, 35L)
CHANGE @TM TO @FM IN V.OUT
3
4
5
6
7
8
9
10
11
12
13
14
STOP
15
16
17
END
18
The output:
fmtsample output
C:\sa-tafc> jrunSH.cmd
jsh ~ --> fmtsample
Line 1 is: This is quite a long line that we h
Line 2 is: ave to break into parts with length
Line 3 is: of 35 characters while not thinkin
Line 4 is: g about cutting a word in two in th
Line 5 is: e very middle. So we are going to u
Line 6 is: se FMT() for this tremendous task
Unfortunately its not in manuals that if you use fmt() to format a string padding it
to less width than it actually has youll end up with that string delimited with text marks
(@tm, or ASCII 251). I came to that looking at some strange output in a report where a
programmer issued an fmt() for a longer string and strange characters appeared as a result.
J of BASE, TAF of C
100
By KZM
I happened to have two different consultants to give me a chunk of code doing the same
things as one-two lines can do. One of them after seeing my proposition said: didnt
know that, will use it from now on. Another told me why should I care since my code
works? So here you see the difference between T24 consultant and good T24 consultant.
Another issue. Once upon a time I had a discussion with my colleagues about some
recommendations in the Temenos document describing programming standards. Among
other things, it says (quoting from my memory): use remove...from to retrieve items
from a list in a loop, its faster than extract each element using for...next. I then made a
test to see if its true. But firstly we need to see where we can obtain a list which isnt very
small. So:
In search of a big SELECT
C:\sa-tafc> jrunT24.cmd
jsh ~ --> COUNT FBNK.CUSTOMER
423 Records counted
Still not enough... Well then use a trick - put all the fields of every ACCOUNT record
into the select list:
BSELECT helps us
jsh ~ --> BSELECT FBNK.ACCOUNT
774007 Records selected
>
Well, much better now. Here goes the code of our test:
retritem.b
1
2
3
4
5
J of BASE, TAF of C
101
By KZM
6
7
8
END
Here we use named common to understand if this program was invoked in a fresh
session to avoid inaccuracy of results caused by, say, cache etc. But before continuing, lets
test this approach first. Just add stop...end after that code (it probably will work and
without that but its better to keep formalities). Compile and run it:
COMMON area test
C:\sa-tafc> jrunComp.cmd retritem
retritem.c
C:\sa-tafc>jrunT24.cmd
jsh ~ --> retritem 1
jsh ~ --> retritem 1
jsh ~ -->
No it doesnt tell us that we need to run it in a new session. Obviuosly common area
wasnt preserved. Why? My colleague already mentioned here a few times told me that:
J of BASE, TAF of C
102
By KZM
set PATH=%COMPILER_HOME%\bin;%TAFC_HOME%\bin;C:\windows\system32\;%HOME%
Now:
COMMON area test
C:\sa-tafc> jrunComp.cmd retritem
retritem.c
C:\sa-tafc> jrunT24.cmd
jsh ~ --> retritem 1
jsh ~ --> retritem 1
Try again a a new session
jsh ~ -->
OK now. By the way, why weve just increased the value of v.run.nr and didnt get an
error? So it was 0 from the beginning? And where is it specified?
Here we touch an area that is called emulation. For T24 prime emulation is used.
Have you noticed an environment variable JBCEMULATE being set to prime? Take a look
into configuration file; the setting that interests us is named common:
jBASE emulation settings - prime
C:\sa-tafc> jrunSH.cmd
jsh ~ --> CT <path_to_TAFC>\config Config_EMULATE
...
192 #
193 # Emulation for Prime.
194 #
195 prime:
196 .dup = jbase
...
204 .named_common = zero
You can find decriptions of emulation settings in the file Config EMULATE.txt which is
located in the same directory as Config EMULATE. For example:
J of BASE, TAF of C
103
By KZM
So under different emulations our program will have different behaviour since were not
able to add 1 to an unassigned variable or a null without getting an error or, in fact, we
are since error messages are successfully suppressed by our current setup which is typical for
T24.
There are certain following environment variables namely, jbase errmsg zero used
and jbase errmsg non numeric which control jBASE behaviour in such cases. For
example, we have them being set to suppress error messages and not enter debugger. There
is a self-explanatory one jbase errmsg divide by zero as well.
To see how they work well tackle them and try to violate some rules. Example 1:
testnull.b
1
V.RUN.NR ++
CRT V.RUN.NR
2
3
4
STOP
5
6
END
testnull run
C:\sa-tafc> jrunComp.cmd testnull
testnull.c
C:\sa-tafc> jrunEXE.cmd testnull.exe
1
So far, so good. Add some control (using putenv() to change the environment):
testnull.b - changed
1
2
3
4
5
IF NOT(PUTENV(JBASE_ERRMSG_ZERO_USED=0)) THEN
CRT PUTENV failed
STOP
END
6
7
V.RUN.NR ++
J of BASE, TAF of C
104
By KZM
CRT V.RUN.NR
8
9
STOP
10
11
END
12
testnull run
C:\sa-tafc> jrunComp.cmd testnull
testnull.c
C:\sa-tafc>jrunEXE.cmd testnull.exe
Invalid or uninitialised variable -- NULL USED ,
Var V.RUN.NR , Line
7 , Source testnull.b
1
V.RUN.NR = ABC
V.RUN.NR ++
CRT V.RUN.NR
2
3
4
5
STOP
6
7
END
testnull run
C:\sa-tafc> jrunComp.cmd testnull
testnull.c
C:\sa-tafc> jrunEXE.cmd testnull.exe
1
IF NOT(PUTENV(JBASE_ERRMSG_NON_NUMERIC=0)) THEN
CRT PUTENV failed
STOP
END
6
7
8
9
V.RUN.NR = ABC
V.RUN.NR ++
CRT V.RUN.NR
J of BASE, TAF of C
105
By KZM
10
STOP
11
12
END
13
testnull run
C:\sa-tafc> jrunComp.cmd testnull
testnull.c
C:\sa-tafc> jrunEXE.cmd testnull.exe
Non-numeric value -- ZERO USED ,
Variable (UNKNOWN) , Line
8 , Source testnull.b
1
See the difference? Id strongly recommend to test your programs with these variables
set to 0.
Back to out task in case you havent forgotten yet what it was. And it was to see
which method used for retrieving an array element is faster remove or for...next. Heres
the code (I repeat the first part that checks if this program was run already to refresh
your memory):
retritem.b
1
2
3
4
5
6
7
8
Then we get the method being used this time (passed as first parameter, defaults to 1):
retritem.b
9
10
11
V.METHOD = SENTENCE(1)
IF V.METHOD NE 2 THEN V.METHOD = 1
14
J of BASE, TAF of C
106
By KZM
Unfortunately as a quick test showed that list wasnt still long enough, to expand it
just add it to itself 5 times:
retritem.b
V.CNT = 5
FOR V.I = 1 TO V.CNT
V.BIG.L := @FM : V.BIG.L
NEXT V.I
15
16
17
18
V.STRT = TIME()
20
21
IF V.METHOD EQ 1 THEN
22
23
LOOP
REMOVE V.ID FROM V.BIG.L SETTING V.STATUS
IF V.STATUS EQ 0 THEN BREAK
REPEAT
24
25
26
27
28
29
30
31
32
33
34
35
36
END
37
38
39
40
STOP
41
42
END
43
J of BASE, TAF of C
107
By KZM
retritem.c
C:\sa-tafc> jrunT24.cmd
jsh ~ --> retritem 1
774007 Records selected
10
jsh ~ --> exit
C:\sa-tafc> jrunT24.cmd
jsh ~ --> retritem 2
774007 Records selected
5
jsh ~ -->
Not sure if other platforms give the same result you can try it yourself to see. You
might see 0 as a result for both methods in this case your computer is faster than that
virtual machine of mine (almost 100% sure that it is). Then just increase the array being
tested.
Last for this chapter cant resist to put here <-1> usage note. You might have said
that in the line 17 of retritem.b I could have used the following syntax:
retritem.b
17
V.BIG.L<-1> = V.BIG.L
Why Im not the big fan of <-1>? Not only it might be slower (as some sources say
especially when adding subvalues) but it also works a bit differently from my chosen method.
See:
additem.b
1
2
3
4
5
6
7
8
9
V.ARR =
V.ARR<-1> =
V.ARR<-1> =
V.ARR<-1> =
V.ARR<-1> =
V.ARR<-1> =
V.ARR<-1> =
V.ARR<-1> =
10
11
V.ARR.2 =
J of BASE, TAF of C
108
By KZM
V.ARR.2<1>
V.ARR.2<2>
V.ARR.2<3>
V.ARR.2<4>
V.ARR.2<5>
V.ARR.2<6>
V.ARR.2<7>
12
13
14
15
16
17
18
=
=
=
=
=
=
=
19
20
21
22
CRT V.ARR
CRT V.ARR.2
23
24
25
STOP
26
27
END
28
Before you compile and run it, ask yourself: will the output be the same?
additem run
C:\sa-tafc> jrunComp.cmd additem
additem.c
C:\sa-tafc> jrunEXE.cmd additem
A^^B^^C
^^A^^B^^C
I know I hadnt any empty values at the start for my last test but in other cases it could
be different so I just always use the safer method.
J of BASE, TAF of C
109
By KZM
19
hen a file reaches 2Gb in size it might become corrupted in case it has j4 type that
:::::: many T24 files do. The common question is what to do to have your data safe
should it happen?
There are several opportunities:
Convert to some newer type that supports more than 2Gb size. Namely: jp or jr.
Before doing that think: do you really need so big files in your system? What about backups
taking longer and longer time? Most probably you no more need the historical data in that
file to be always available? 2 Gb issue mainly concerns such things like STMT.ENTRY
or FUNDS.TRANSFER ($his for the latter) and these files accumulate data that rarely or
never change. (You might say that data never changes there yes, individual records do not
but from logical point of view by rarely I meant that in FT history an additional record
for existing deal might be created later in case of reversal, for example.)
Another option is to proceed with Archive functionality of T24. It creates a new
data file $arc and puts there records that belong to dates earlier than a date that
you set for archiving. The problem here that only one additional file is created (and what
if it reaches 2Gb as well?). In adition, not all records for old dates might be moved. (I
remember a rather funny reply of a helpdesk for such issue: For the CATEG.ENTRY record
135920009041014.000001, the value date is 18 MAR 3005. As this is a forward categ.entry
generated for the PL category 52-344, this is not archived.) Again, youll need to create
new enquiries for $arc file. (And if you need a report that takes data from both files youll
need to develop a nofile enquiry.) Last but not least if you upgrade your T24 and the file
structure changes, $arc file will still keep the old structure at least it did last time Ive
checked.
My favourite option to distribute the data file. Distribution means that several
physical data files can be logically one file for jBASE (and therefore for T24). The procedure
is available at the helpdesk on request (in my opinion its not complete but it works). Of
course you can also try to apply archival described above and then distribute the $arc
file.
Lets proceed with example of file distribution right now.
Firstly we need to choose a file. It should be not very little file; it should be T24 file (since
were going to play with VOC entries a little to achieve one nice thing that goes beyond the
official procedure but makes life much easier).
Browsing the subdirectories of bnk.data with files sorted by size, what we see? aa...
better not to meddle with AA stuff... ac...biggest are stmt.entry and categ.entry...
Could be used despite about 10,000 records only but their @ids are not fit to what Im
thinking about and I think about organizing parts of a distributed files (promptly named
part files) on date principle so we have separate part file(s) for particular periods of time
and then we can store them separately to reduce backup time or even put away from our
server and get back only when required. Unfortunately in @ids of both stmt.entry and
categ.entry only the system time of their creation presents rather than the bank date and
J of BASE, TAF of C
110
By KZM
Definitely most of this temporary stuff can be removed (but ask helpdesk first). Further
on goes F ENQUIRY SELECT which holds selections for all users also temporary stuff:
Contents of F.ENQUIRY.SELECT
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST ONLY F.ENQUIRY.SELECT
@ID.......
%DOCUMENT.REQUIRED_GB0010001_BUILDUSER0112_1299579055130_
SC.HOLD.SUM.BY.SEC_GB0010001_BUILDUSER13_workarea039915088101_tab2
INV.PROG.LIST_GB0010001_BUILDUSER21_INVESTMENT027973490300_
AC.DETAILS.ARRANGEMENT_GB0010001_BUILDUSER1_Arrangement010244547401_
FT.TXN.DELIVERY.LIVE_GB0010001_BUILDUSER2_FTLIVE010974091003_
AA.REQUEST.PAYOFF_GB0010001_CREDITMANAGER1_RequestPayoff097624965310_
J of BASE, TAF of C
111
By KZM
CREDIT.INT.CONDS.SCV_GB0010001_BUILDUSER1_CreditInterest014492600803_
SA.SCORE.CARD.MIFID.DETS_GB0010001_PWMRM_PFO096742495804_
EXPLC.OVERVIEW.SCV_GB0010001_BUILDUSER76_LCOverview039672578001_tab4
PEND.OSTATION.CHQS_GB0010001_BUIILDUSER11_ENQUIRY067292378900_
AA.MANAGE.PENDING.ACTIVITY_GB0010001_BU007_Pending077083817506_
EXP.CASHFLOW_GB0010001_BUILDUSER33_1299678443320_
CUSTOMER.DETAILS.SCV_GB0010001_BUILDUSER31
%TELLER.ID,_GB0010001_BUILDUSER86_1299735445922_
AA.DETAILS.ARRANGEMENT.ACCOUNT_GB0010001_CSAGENT1_AccountDates077083916510_
SC.MIFID.CLIENT.INFORMATION_GB0010001_BUILDUSER31_1299751531128_
TF.LCAC.IMP.CSM_GB0010001_BUILDUSER67_ChargesEnquiry074954634803_tab5
ACCT.DETAILS.ACCOUNT_GB0010001_BUILDUSER1_AccountStatic041964563301_
CUSTOMER.SIGN.SCV_GB0010001_PRA_CustomerSignature051375229304_
LC.EXP.ADD.DETAILS.SCV_GB0010001_BUILDUSER76_AdditionalDetails090643220803_tab4
FT.STO.EXEC_GB0010001_SUJA1_FRAMEA023864269600_tab2
CUSTOMER.PHOTO.SCV_GB0010001_BUILDUSER32
REPO.POSITION_GB0010001_BUILDUSER13_1299576318731_
DEPOSITS.DETAILS.SCV_GB0010001_CSAGENT1_DepositsDetailsEnquiry083873421919_
INTERNET.BANKING.CSM_GB0010001_BUILDUSER099_INTERNETBANKING001095068801_tab2
DE.CUSTOMER.PREFERENCES.SCV_GB0010001_BUIILDUSER6565
LD.DEP.PRODWISE.CORP.CSM_GB0010001_BUILDUSER119_AccountEnquiry041964671803_
DEPOSITS.DETAILS.SCV_GB0010001_CSAGENT1_DepositsDetailsEnquiry045773678908_
Finally a good candidate was found - its a history file of FUNDS.TRANSFER application
(which Ive mentioned above already; it normally becomes very big after some time). Record
@id contains the bank day when it was created and the record goes to the history immediately
after COB in most cases (we wouldnt consider transactions with future value date that might
still stay in live file for some time Im not 100% sure about that case). But generally
we can think that if the record went to history then after some time say, couple of months
the deal isnt going to create any new records in the history so the part file(s) might reside
outside bnk.data and be even readonly. Lets try this approach.
@id of FUNDS.TRANSFER$HIS looks like that (though in your case it might be different,
see AUTO.ID.START>FUNDS.TRANSFER and the helptext or the sample in my previous book
in this case):
@IDs of FBNK.FUNDS.TRANSFER$HIS
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST ONLY FBNK.FUNDS.TRANSFER$HIS
@ID......................
FT10186BSXJQ;1
FT10187VGWVN;1
FT102171VDH0;1
FT10217WHJ8Y;1
FT10217TTH0L;1
J of BASE, TAF of C
112
By KZM
FT101861ZC2M;1
FT102070BQB9;1
FT10210G0XJT;1
FT102143R6QS;1
FT10186M9Q0S;1
...
Here two digits after FT represent year number and they are followed by day number in
the year. Sorted output tells us that the earliest date for such record is 10186 which stands
for.. for...
EVAL helps us again
C:\sa-tafc> jrunT24.cmd
jsh ~ --> LIST . SAMPLE 1 EVAL "OCONV(ICONV(10186,DJ),D)"
DICT .........
OCONV(ICONV(10186,"DJ"),"D")
%HOME%
05 JUL 2010
1 Records Listed
TODAY......
GB0010003
SG0010001
GB0010001-COB
EU0010001
GB0010003-COB
GB0010001
EU0010001-COB
GB0010002
GB0010002-COB
GB0010004
GB0010004-COB
SG0010001-COB
20100809
20100809
20100809
20100809
20100809
20100809
20100809
20100809
20100809
20100809
20100809
20100809
12 Records Listed
J of BASE, TAF of C
113
By KZM
Well, that doesnt give us great time span but well try anyway. Were putting all records
for July 2010 into one file and records that are after that to another. Firstly we need to
create the stub file:
Stub file creation
C:\sa-tafc> jrunT24.cmd
jsh ~
[ 417
[ 417
jsh ~
Of course we need to change partitioning algorithm which is used by jBASE to determine in which part file particular record is to be found (or placed into). As it was already
mentioned, only @id is available for such algorithm. To achieve that we write a subroutine:
Creation of algorithm
C:\sa-tafc> jrunT24.cmd
jsh ~ --> JED . FT-PART-ALGO
File . , Record FT-PART-ALGO
Insert
Command->
0001 * algorithm for FT$HIS
0002 SUBROUTINE FT-PART-ALGO(sReserved, sKey, sPartNumber)
0003
0004
nDate = sKey[3,5]
0005
0006
IF ISDIGIT(nDate) THEN
0007
0008
IF nDate LT 10213 THEN
0009
sPartNumber = 1
0010
END ELSE
0011
sPartNumber = 2
0012
END
0013
J of BASE, TAF of C
114
19:52:04
By KZM
0014
END ELSE
;* we are cautious
0015
sPartNumber = 99
0016
END
0017
0018
RETURN
0019
0020 END
0021
-------------------------------- End Of Record --------------------------------
Stop, stop... we are no more in the situation when we can separate a subroutine from
T24. To ensure that T24 will work normally with that file we need to put our algorithm to
T24 libraries:
Recompilation of algorithm
jsh ~ --> DECATALOG . FT-PART-ALGO
Object FT-PART-ALGO decataloged successfully
Library C:\sa-tafc\lib\lib0.dll rebuild okay
jsh ~ --> set JBCDEV_LIB=<path_to_bnk.run>\lib
jsh ~ --> CATALOG . FT-PART-ALGO
FT-PART-ALGO
Object FT-PART-ALGO cataloged successfully
Library <path_to_bnk.run>\lib\lib2.dll rebuild okay
jsh ~ --> jshow -c FT-PART-ALGO
Subroutine:
J of BASE, TAF of C
<path_to_bnk.run>\lib\lib2.dll
jBC FT-PART-ALGO version 201014.0 Mon Jul 18 19:57:12 2011
jBC FT-PART-ALGO source file .
115
By KZM
Now we can add our algorithm to our stub file. Its a good idea to verify the file after
each step:
Attachment of algorithm to stub file
C:\sa-tafc> jrunT24.cmd
jsh ~ --> CREATE-DISTRIB -pUSER,FT-PART-ALGO FT-HIS-ALL
jsh ~ --> VERIFY-DISTRIB FT-HIS-ALL
Partitioning Algorithm is USER Subroutine FT-PART-ALGO
User subroutine OK.
There are no Part files defined
Good, exactly where we expected it to be. Create and add part files (note that we need
part file 99 to handle exceptions according to the algorithm; such handling is absolutely
mandatory):
Creation and assigment of the part files
jsh ~ --> CREATE-FILE FT-HIS-PART1 TYPE=J4 1 101
[ 417 ] File FT-HIS-PART1]D created , type = J4
[ 417 ] File FT-HIS-PART1 created , type = J4
jsh ~ --> CREATE-FILE FT-HIS-PART2 TYPE=J4 1 101
[ 417 ] File FT-HIS-PART2]D created , type = J4
[ 417 ] File FT-HIS-PART2 created , type = J4
jsh ~ --> CREATE-FILE FT-HIS-PART99 TYPE=J4 1 101
[ 417 ] File FT-HIS-PART99]D created , type = J4
[ 417 ] File FT-HIS-PART99 created , type = J4
jsh ~ --> CREATE-DISTRIB -a FT-HIS-ALL 1 FT-HIS-PART1
Part file FT-HIS-PART1, Part number 1 added
jsh ~ --> CREATE-DISTRIB -a FT-HIS-ALL 2 FT-HIS-PART2
Part file FT-HIS-PART2, Part number 2 added
jsh ~ --> CREATE-DISTRIB -a FT-HIS-ALL 99 FT-HIS-PART99
Part file FT-HIS-PART99, Part number 99 added
jsh ~ --> VERIFY-DISTRIB FT-HIS-ALL
Partitioning Algorithm is USER Subroutine FT-PART-ALGO
User subroutine OK.
Part file FT-HIS-PART1, part number 1 - OK
Part file FT-HIS-PART2, part number 2 - OK
J of BASE, TAF of C
116
By KZM
J of BASE, TAF of C
117
By KZM
...
jsh ~ --> COUNT FT-HIS-PART1
465 Records counted
jsh ~ --> COUNT FT-HIS-PART2
216 Records counted
Well... doesnt look like we have all 682 records here. Thats why our file 99 was necessary:
Check of data - continued
jsh ~ --> LIST FT-HIS-PART99 ONLY
FT-HIS-PART99.
FTAA10186QY2ZR;1
1 Records Listed
We see that the @id doesnt follow the standard so it went as an exception. The total
number of records is correct:
Check of data - finished
jsh ~ --> COUNT FT-HIS-ALL
682 Records counted
We also see here that we can address the individual part files as well as a whole distributed
file.
But the file isnt yet been recognized by T24. To achieve that firstly lets think: do we
need all new data files in the bnk.run? Of course no. We need to move them to bnk.data and
then adjust VOC entries for T24 to find all of them. Well move FT-HIS-ALL, FT-HIS-PART1,
FT-HIS-PART2 and FT-HIS-PART99 to ../bnk.data/ft and then:
Necessary VOC entries - stub file
jsh ~ --> COPY FROM VOC FBNK.FUNDS.TRANSFER$HIS,FBNK.FUNDS.TRANSFER-SAVE
1 records copied
J of BASE, TAF of C
118
By KZM
Do the same with part files #2 and #99 and check it:
Necessary VOC entries - final check
jsh ~ --> VERIFY-DISTRIB FBNK.FUNDS.TRANSFER$HIS
Partitioning Algorithm is USER Subroutine FT-PART-ALGO
User subroutine OK.
Part file FT-HIS-PART1, part number 1 - OK
Part file FT-HIS-PART2, part number 2 - OK
Part file FT-HIS-PART99, part number 99 - OK
As you might have noticed, these VOC entries use the same dictionary the one for initial
history file, so we dont need the files FT-HIS-ALL]D, FT-HIS-PART1]D, FT-HIS-PART2]D and
FT-HIS-PART99]D in bnk.run and you can delete them. Alternatively, we could have used
DATA keyword in part files creation command, like:
Creation of data part only for a part file
jsh ~ --> CREATE-FILE DATA FT-HIS-PART1 TYPE=J4 1 101
J of BASE, TAF of C
119
By KZM
Also we can back up and remove the old data file from ../bnk.data/ft.
Now the final check log in to T24 and type FT L ; L at AWAITING APPLICATION
prompt, then press F5:
T24 now uses a distributed file
Model Bank
ID
RECORD.STATUS INPUTTER
FUNCT.
-----------------------------------------------------------------------------1 FT10186B51RP;1
MAT
4145_SEAT.USER__OFS_SEAT
2 FT10186BB96G;1
MAT
4899_SEAT.USER__OFS_SEAT
3 FT10186BC4NJ;1
MAT
4145_SEAT.USER__OFS_SEAT
4 FT10186BJ634;1
MAT
291_SEAT.USER__OFS_SEAT
5 FT10186BJNYP;1
MAT
5436_SEAT.USER__OFS_SEAT
6 FT10186BSXJQ;1
MAT
4116_SEAT.USER__OFS_SEAT
7 FT10186BT3NR;1
MAT
7038_SEAT.USER__OFS_SEAT
8 FT10186C5QG3;1
MAT
4957_SEAT.USER__OFS_SEAT
9 FT10186CBZZ5;1
MAT
4360_SEAT.USER__OFS_SEAT
10 FT10186CFWVV;1
MAT
4145_SEAT.USER__OFS_SEAT
11 FT10186CH4PQ;1
MAT
7038_SEAT.USER__OFS_SEAT
12 FT10186CH48K;1
MAT
522_SUJA1__OFS_BROWSERTC
13 FT10186CJWQ0;1
MAT
3789_SEAT.USER__OFS_SEAT
14 FT10186CTSLV;1
MAT
3436_SEAT.USER__OFS_SEAT
15 FT10186D9F66;1
MAT
7285_SEAT.USER__OFS_SEAT
16 FT10186DB93N;1
MAT
7392_SEAT.USER__OFS_SEAT
-----------------------------------------------------------------------------18 JUL 2011 21:01:46 USER (09 AUG) VLADIMIR.K
[5377,INPAGE
1 >>>3>>>
ACTION
AWAITING PAGE INSTRUCTIONS
If you are still not sure, dont leave this screen and open another session, then find out
the port number for T24 session above and see opened files for it:
Yet another final check
jsh ~ --> WHERE
Port
13
*14
Device
ntcon
Account
t24
PID
5584
ntcon
Vladimir.Kaz 4608
Command
<path_to_TAFC>\bin\jsh para K.LOG
EX
jsh
WHERE
J of BASE, TAF of C
120
By KZM
Port
...
13
13
13
13
13
13
...
File name
..\bnk.data\ft\FT-HIS-PART99
..\bnk.data\ft\FT-HIS-PART2
..\bnk.data\ft\FT-HIS-PART1
..\bnk.data\ft\FT-HIS-ALL
..\bnk.data\ft\FBNK_FUNDS_TRANSFER#NAU
..\bnk.data\ft\FBNK_FUNDS_TRANSFER
You see that while live and $nau files are the same as they were the $his file is represented by our distributed one.
By default, all part files are opened whenever a stub is opened; this can be changed by
setting the environment variable jedi distrib defopen to 1.
Now to decrease backup time we can think of moving older part of the distributed
file away from bnk.data. You can try it yourself; the only thing that is necessary apart from
moving the file is to change its VOC entry. I even made this file readonly and all still worked.
Its a good idea also to resize each part file.
Having done that, why not to try another option - temporarily remove that part file
pretending theres no space on the disk. Can we do it? The day numbers in that file were up
to (but not including) 10213 so move this file away (or simply rename) and firstly doublecheck with verify-distrib it shouldnt see part 1 (cannot open error 2). Then try to
see the implications:
One part file is missing
C:\sa-tafc> jrunT24.cmd
jsh ~ --> VERIFY-DISTRIB FBNK.FUNDS.TRANSFER$HIS
Partitioning Algorithm is USER Subroutine FT-PART-ALGO
User subroutine OK.
Part file FT-HIS-PART1, part number 1 - Cannot open (error 2)
Part file FT-HIS-PART2, part number 2 - OK
Part file FT-HIS-PART99, part number 99 - OK
jsh ~ --> SELECT FBNK.FUNDS.TRANSFER$HIS
217 Records selected
jBASE now sees less records than it did before. What T24 says? Use FT L ; L command
again:
J of BASE, TAF of C
121
By KZM
ID
RECORD.STATUS INPUTTER
FUNCT.
-----------------------------------------------------------------------------1 FT10214HKVSZ;1
MAT
514_INPUTTER
2 FT10214J13N8;1
MAT
514_INPUTTER
3 FT10214J60FB;1
MAT
777_INPUTTER
4 FT10214JRRSS;1
MAT
514_INPUTTER
5 FT10214K5FD9;1
MAT
772_INPUTTER
6 FT10214L1TJP;1
MAT
777_INPUTTER
7 FT10214NT8QV;1
MAT
778_INPUTTER__OFS_SWIFTIN
8 FT10214P581G;1
MAT
512_INPUTTER
9 FT10214PSD8J;1
MAT
513_INPUTTER
10 FT10214PYSQN;1
MAT
512_INPUTTER
11 FT10214RC54G;1
MAT
778_INPUTTER__OFS_SWIFTIN
12 FT10214W9RR0;1
MAT
512_INPUTTER
13 FT10214W860N;1
MAT
512_INPUTTER
14 FT10214YS176;1
MAT
514_INPUTTER
15 FT10214YTPFX;1
MAT
299_INPUTTER
16 FT10216CPVP3;1
MAT
762_INPUTTER
-----------------------------------------------------------------------------22 JUL 2011 10:57:58 USER (09 AUG) VLADIMIR.K
[6059,INPAGE
1 >>>3>>>
ACTION
AWAITING PAGE INSTRUCTIONS
See that the list starts from other @ids than it did when all part files were available?
The behaviour is a bit unexpected to me since I thought that an error message would be
raised and well have to set jedi distrib defopen to 1 and correct user enquiries. Anyway,
it will serve us well if correction of enquiry is described anyway otherwise users might end
up with wrong reports that miss some data. As a base well use a sample enquiry-related
build.routine named ENQ.NARROW from my previous book and rename it to ENQ.WARNING.
ENQ.WARNING subroutine - the beginning
1
2
3
4
5
6
7
8
9
10
*---------*
SUBROUTINE ENQ.WARNING(P.ENQ)
* V.Kazimirchik (KZM), 2011.
* Build routine for enquiry to see if all data is
* available for an enquiry since particular part file
* (1 in our case) might be absent.
*-----------------------------------------------------*
$INSERT T24.BP I_COMMON
$INSERT T24.BP I_EQUATE
$INSERT T24.BP I_ENQUIRY.COMMON
J of BASE, TAF of C
122
By KZM
Nothing unusual at the beginning. (Note that here $INSERT T24.BP I COMMON etc is
used instead of $INSERT I COMMON to avoid additional parameters to basic command
this wouldnt cause any harm unless Temenos changes the product name and tries to wipe
out all references to the old one as it was attempted with Globus before.) Now we open
FT history file and get the part files information using statement status. Note enq.error
variable that is used to pass an error back to user:
ENQ.WARNING subroutine - continued
11
12
13
F.FT.HIST =
CALL OPF(F.FUNDS.TRANSFER$HIS, F.FT.HIST)
14
15
16
17
18
19
20
21
22
23
V.PARTS.L = V.INFO.L<24>
V.FILES.L = V.INFO.L<25>
27
28
29
30
31
32
33
V.PART.ONE = V.FILES.L<1,V.POSN>
Here we set the threshold for the dates and get the selection from user input:
ENQ.WARNING subroutine - continued
34
35
36
37
38
39
40
V.ONE.OPENED =
J of BASE, TAF of C
123
By KZM
Selections loop started. We check if a selection has proper format (we allow only @id
LK FTYYDDD..., note that several FTYYDDD... parts may be input one after another).
@id and LK parts are controlled at enquiry level, well see it below.
ENQ.WARNING subroutine - continued
41
42
43
44
Take the date, check it against our threshold set above and... carefully read the comment
below:
ENQ.WARNING subroutine - continued
45
V.DATE = V.SEL[3,5]
IF V.DATE LT V.MIN.DATE AND V.ONE.OPENED EQ THEN
46
47
48
49
50
51
52
53
54
55
*
*
*
*
*
*
*
Open the part file (but do it only once see variable v.one.opened here and above);
set error to be returned to user if file is not present:
ENQ.WARNING subroutine - continued
56
57
58
59
60
61
62
63
64
END
65
And heres the end of this subroutine (including the error message reporting wrong selection criteria):
66
67
J of BASE, TAF of C
124
By KZM
RETURN
END
68
69
70
NEXT V.I
71
72
RETURN
73
74
END
75
Compile the routine (again use set JBCDEV LIB=<path to bnk.run>\lib before catalog for T24 to be able to see the subroutine.
Time for enquiry. It will be quite simple. See blue lines for selection control and attachment of the build routine.
ENQUIRY FT.HIST
Model Bank
ENQUIRY SEE
ENQUIRY........... FT.HIST
-----------------------------------------------------------------------------1 PAGE.SIZE ........ 4,19
2 FILE.NAME......... FUNDS.TRANSFER$HIS
6. 1 SELECTION.FLDS. @ID
8. 1 SEL.FLD.OPER... LK
9. 1 REQUIRED.SEL... Y
12. 1 BUILD.ROUTINE.. ENQ.WARNING
14. 1 FIELD.NAME..... @ID
15. 1. 1 OPERATION... @ID
16. 1 COLUMN......... 4
17. 1 LENGTH.MASK.... 25L
35. 1 SINGLE.MULTI... S
14. 2 FIELD.NAME..... 2
15. 2. 1 OPERATION... DEBIT.ACCT.NO
16. 2 COLUMN......... 19
17. 2 LENGTH.MASK.... 12L
35. 2 SINGLE.MULTI... S
14. 3 FIELD.NAME..... 11
15. 3. 1 OPERATION... CREDIT.ACCT.NO
16. 3 COLUMN......... 32
17. 3 LENGTH.MASK.... 12L
35. 3 SINGLE.MULTI... S
14. 4 FIELD.NAME..... 2
15. 4. 1 OPERATION... DEBIT.AMOUNT
16. 4 COLUMN......... 45
17. 4 LENGTH.MASK.... 16R
35. 4 SINGLE.MULTI... S
14. 5 FIELD.NAME..... 5
J of BASE, TAF of C
125
By KZM
Now lets try it (part file #1 should be absent). Log in to T24, type ENQ FT.HIST , then
input your selection, e.g.:
ENQUIRY FT.HIST - runtime
Model Bank
ENQUIRY INPUT
J of BASE, TAF of C
126
By KZM
4. 3. 1 LIST........
1. 4 SELECTION.TYPE.
2. 4 SELECTION.FIELD
3. 4 OPERAND........
4. 4. 1 LIST........
-----------------------------------------------------------------------------25 JUL 2011 22:00:36 USER (09 AUG) VLADIMIR.K
[8212,INPAGE 1
>>64>>>
ACTION
AWAITING APPLICATION
Now close the session, open a new one, rename (or put back) part file #1 and run the
same enquiry with the same selection:
ENQUIRY FT.HIST - runtime
Model Bank
-----------------------------------------------------------------------------FT1018602K9X;1 44679
42544
61000.00 GBP 05 JUL 2010
FT1018603VB7;1 PL60060
41904
10000.00 USD 05 JUL 2010
FT101860DKVB;1 44822
41629
62000.00 CHF 05 JUL 2010
FT101860Z660;1 43885
41618
305000.00 USD 05 JUL 2010
FT1018613LSJ;1 22578
40878
1300.00 USD 05 JUL 2010
FT1018613MFV;1 20656
11298
200000.00 USD 05 JUL 2010
FT101861F3C2;1 44784
40967
275000.00 USD 05 JUL 2010
FT101861L2C8;1 20656
13013
200000.00 USD 05 JUL 2010
FT101861NMHL;1 20656
16397
200000.00 USD 05 JUL 2010
FT101861PBVG;1 44326
42207
405000.00 USD 05 JUL 2010
FT101861PVKC;1 14761
10707
10000.00 USD 05 JUL 2010
FT101861T5MN;1 23825
10723
1250.00 USD 05 JUL 2010
FT101861Z0B6;1 10715
40827
4500.00 EUR 05 JUL 2010
FT101861ZC2M;1 20656
28169
200000.00 USD 05 JUL 2010
FT1018622CJX;1 23701
15393
GBP 05 JUL 2010
FT1018625DB8;1 41149
43915
USD 05 JUL 2010
-----------------------------------------------------------------------------25 JUL 2011 22:06:52 USER (09 AUG) VLADIMIR.K
[439,IN]PAGE
1 >>>3>>>
ACTION
AWAITING PAGE INSTRUCTIONS
J of BASE, TAF of C
127
By KZM
By the way, the table F.ENQUIRY.SELECT was mentioned above the place where user
selections are stored. See:
Stored selection criteria
jsh ~ --> CT F.ENQUIRY.SELECT FT.HIST-VLADIMIR.K
FT.HIST-VLADIMIR.K
001 <k>]<k>]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]<d>]]]]]]]]]]]]]]]]]]]]
]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
]]]]]]]]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d
>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>]<d>
002 @ID
003 LK
004 FT10186... FT10217...
005 FT.HIST
006
007
008
009
010
011
012
013
014
015 1
016 439_VLADIMIR.K
017 2207251206
018 439 VLADIMIR.K
019 GB0010001
020 1
J of BASE, TAF of C
128
By KZM
20
orting a dynamic array is a very common task. Apart from it works solution of
:::: implementing a so-called bubble sort I often see the suggestion to use locate for
this purpose, like see a test program below:
Source of testsort.b
1
2
3
V.ARR =
FOR V.I = 1 TO 1000
V.ARR := @FM : RND(1000)
NEXT V.I
DEL V.ARR<1>
5
6
7
8
9
10
V.SORTED =
11
12
13
* Now sort
14
15
16
17
18
19
20
21
22
FOR V.I =
IF V.I
CRT
CRT
V.I
END
23
24
25
26
27
28
1 TO 1000 STEP 10
EQ 101 THEN
...
= 901
29
CRT
FOR V.J = 1 TO 10
CRT V.SORTED<V.I+V.J-1> : , :
NEXT V.J
30
31
32
33
34
NEXT V.I
35
36
STOP
37
38
END
39
129
By KZM
testsort run
C:\sa-tafc> jrunComp.cmd testsort
testsort.c
C:\sa-tafc> jrunEXE.cmd testsort
1,3,3,4,6,8,8,10,11,11,
13,13,19,19,20,21,22,23,23,24,
26,27,28,29,29,30,30,32,34,34,
37,38,39,39,43,44,48,49,50,51,
51,52,56,56,59,59,59,59,61,62,
63,63,64,64,66,67,68,69,70,70,
70,70,71,72,77,78,82,84,85,86,
86,87,88,88,90,91,92,93,93,94,
94,95,97,97,98,99,102,103,104,105,
106,107,107,109,111,112,114,114,115,115,
...
888,889,896,897,898,898,900,900,901,903,
903,905,905,907,908,908,910,913,913,914,
915,915,916,918,919,921,924,924,925,926,
927,928,928,931,932,932,932,933,936,936,
936,937,940,940,941,942,943,943,944,944,
946,946,947,947,948,949,952,952,954,955,
957,959,961,961,962,963,965,965,965,966,
967,967,967,968,968,971,972,972,973,973,
976,977,978,979,979,981,981,983,985,986,
991,991,992,992,992,995,995,996,997,999,
C:\sa-tafc>
But recently Ive stumbled into sselectv statement that as far as I could tell is also
suitable for this purpose:
New source of testsort.b
1
2
3
4
5
6
7
8
9
V.ARR =
FOR V.I = 1 TO 1000
V.ARR := @FM : RND(1000)
NEXT V.I
DEL V.ARR<1>
10
11
V.SORTED =
12
13
* Now sort
14
J of BASE, TAF of C
130
By KZM
15
16
17
18
19
20
21
FOR V.I =
IF V.I
CRT
CRT
V.I
END
22
23
24
25
26
27
1 TO 1000 STEP 10
EQ 101 THEN
...
= 901
28
CRT
FOR V.J = 1 TO 10
CRT V.SORTED<V.I+V.J-1> : , :
NEXT V.J
29
30
31
32
33
NEXT V.I
34
35
STOP
36
37
END
38
Well, though first method has more flexibility in providing different sort options (including
descending sort order), the second one is more simple. About the sorting options of locate weve used ascending numeric order which seemed the most appropriate for this case since
our items were numeric. For items containing amounts the right alignment is preferred, for
text the left one. However, in jBC weak typing is used for variables and, for example, even
if a variable looks like a numeric, it might be a text. See the following example:
Source of testtype.b
1
* test typing
3
4
V.DAY = V.DATE[1,2]
5
6
7
DEBUG
V.DAY *= 1
9
10
STOP
11
12
13
END
14
J of BASE, TAF of C
131
By KZM
This explains some jBC code that might seem stupid but in fact isnt (like adding zero to
a variable). You might think that int(v.day) or dround(v.day, 0) will make it numeric?
No, they wont at least under my platform.
One more consideration: of course 17 is equal to 17. But we saw that after multiplying
by 1 the variable is stored as a float one which means that it isnt actually 17 but might
be something like 16.9999999963. So its time to see what precision is.
Add some small number to numeric variable and compare it to a string one. In brief, is
17 equal to 17.00000001? The answer is: sometimes yes, sometimes no.
Amended source of testtype.b
1
* test typing
2
3
4
5
V.DAY = V.DATE[1,2]
6
7
8
V.DAY.N = V.DAY * 1
V.DAY.N += 0.00000001
9
10
11
12
13
14
15
J of BASE, TAF of C
132
By KZM
STOP
16
17
18
END
19
Compile, run, see that 17 is equal to 17.00000001. Now add precision statement:
Even more amended source of testtype.b
1
* test typing
PRECISION 13
3
4
5
6
V.DAY = V.DATE[1,2]
7
8
V.DAY.N = V.DAY * 1
V.DAY.N += 0.00000001
9
10
11
12
13
14
15
16
17
STOP
18
19
20
END
21
And now they are different. Value of 13 is set in I COMMON and thats a default for T24
(though both manual and knowledgebase insist that precision might be up to 9).
Be careful about that you might have noticed that in the samples in this book we quite
rarely include I COMMON. Use precision 13 in case of doubt (as an example, use it if you
do any math with part file numbers in file distribution algorihtm).
J of BASE, TAF of C
133
By KZM
21
base cache is quite a new thing that havent yet found its way into manuals (at least
In the file JBC.h that is located in your TAFC
::::: to ones that are available to me).
include subdirectory you can find the following:
JBC.h excerpt
*
* In-memory cache functions
*
DEFC VAR CachePut(VAR, VAR, VAR)
DEFC VAR CacheGet(VAR, VAR)
DEFC VAR CacheDelete(VAR, VAR)
DEFC VAR CacheExists(VAR, VAR)
DEFC VAR CacheClear(VAR)
DEFC VAR CacheClearAll()
DEFC VAR CacheKeyList(VAR, VAR)
DEFC VAR CacheBucketList(VAR)
*
The solution of a task of finding a value in memory corresponding to some key isnt
that obvious in jBC. Firstly, this language lacks pass-by-reference mechanism where you
can store a value in a variable, variable name in another variable and get the value using
the second variable as a key (though you can call a subroutine this way, e.g. call @var).
Dynamic arrays are not fully fit to that purpose to find something in an array using find
or locate isnt very fast, especially with big arrays (since they proceed with full scan of
contents). Lets see if this cache thing helps us with that.
To test search speed well need a big list. To simplify things a little, lets not search a big
file in Model Bank like we did before just write a program to create such list with random
@ids. Well also need a smaller list that contains some subset of these @ids that will be used
in the search. The source is quite self-explanatory we create 1,000,000 entries in this list,
@ids are 16-characters long and are generated randomly; 10,000 of which will be searched
and their position in the big list is also chosen on a random basis (note also that we sort the
smaller list to avoid same processing order during search phase:
Source of createbiglist.b
1
2
3
4
5
6
7
8
9
10
J of BASE, TAF of C
134
By KZM
11
END
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
V.POSN.L =
FOR V.I = 1 TO 10000
V.POSN.L := @FM : RND(1000000) + 1
NEXT V.I
DEL V.POSN.L<1>
27
28
29
30
31
32
V.ID =
33
34
35
36
37
FOR V.J = 1 TO 16
V.RND = RND(26) + 65
V.ID := CHAR(V.RND)
NEXT V.J
;* A...Z
38
39
GOSUB WRITE.LINE
40
41
NEXT V.I
42
43
44
CLOSESEQ F.OUT.FILE
CLOSESEQ F.SMALL.FILE
45
46
47
48
STOP
49
50
51
*~~~~~
WRITE.LINE:
52
53
54
55
56
57
58
J of BASE, TAF of C
135
By KZM
59
60
61
62
63
64
IF V.POSN THEN
WRITESEQ V.ID TO F.SMALL.FILE ELSE
CRT WRITE ERROR
STOP
END
END
65
66
RETURN
67
68
69
*~~~~~
END
70
Why not 10,000? Well, it means that rnd() gave us about 14% of non-unique numbers
when being asked to produce a random value ranging from 1 to 1,000,000. Anyway, we can
use such quantity for out purpose.
See the lists:
Big list
File &SAVEDLISTS& , Record BIG.LIST
Command->
00000001 REFEVSOFXPOVNLSK]Value #1
00000002 XNYGLFPYLZTLMCQV]Value #2
00000003 WJHAEGPLHNVJUCUA]Value #3
00000004 OIWKMACMOKAZARJZ]Value #4
00000005 KCAWDNDIOKNSYGTT]Value #5
00000006 RQLIKMJABTOWZVPQ]Value #6
00000007 VNMKQDZMTIVLUPSO]Value #7
00000008 JBJZSGDKYUKBVLQM]Value #8
J of BASE, TAF of C
21:12:20
136
By KZM
00000009
00000010
...
00999991
00999992
00999993
00999994
00999995
00999996
00999997
00999998
00999999
01000000
PMLGDXZFFFRCNQFO]Value #9
FEGKGSJMYQCIPLMP]Value #10
DYCPHJHYOOWJMAKJ]Value
HCQGWAIJTIGEUAHN]Value
BUTOORTKNHLTQXXA]Value
ISAYKZRFHITZAKNT]Value
RSAHJTXDXUQGXGPN]Value
BBFCFYDTAZALUABK]Value
ZJIMHTLFCNSKANSQ]Value
SQVGUUWRTBWLXKVR]Value
THRUAWIOEGWRTGAA]Value
MADDMXQBXXCVGTFQ]Value
#999991
#999992
#999993
#999994
#999995
#999996
#999997
#999998
#999999
#1000000
Small list
File &SAVEDLISTS& , Record SMALL.LIST
Command->
00001 AAAJYCRVCVVTCPGR
00002 AAAZPNAPWREDNBUI
00003 AADMYVIPBUZVYXLG
00004 AAFJVKHAZQVGGFBH
00005 AALLHJGMMVGMOVAU
00006 AAMGWASPCHCUEKMH
00007 AAMMDQTYPCRJNEZI
00008 AAZSJPCISROAZRBV
00009 AAZVIDZTDOJCWUVO
00010 ABBOEXEBUGVMVSIY
...
08650 ZZTHEXEAORLTCFNF
08651 ZZTHGTTCCUSVFBPT
08652 ZZVVNUHJBRESYRGX
08653 ZZVZLTSTKJGZHFYW
08654 ZZZKZPCQBUSQRYVA
21:14:23
Now its time for the proceeding program. Well use getlist statement to get our lists,
create both a dynamic array and a cache and then will try to compare the search speed:
Source of procbiglist.b
1
2
3
INCLUDE JBC.h
4
5
6
7
J of BASE, TAF of C
137
By KZM
STOP
END
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
CRT STOPPING...
STOP
41
42
43
44
END
45
Compile, launch, wait again. Output ends with a lot of question marks indicating that
a value wasnt obtained from the cache. Why? Is there size limit? Maybe. Lets test it.
Create a cache and then read the first entry repeatedly to see when its forgotten:
Source of testcache.b
1
2
3
INCLUDE JBC.h
J of BASE, TAF of C
138
By KZM
5
6
7
8
V.ID =
9
10
FOR V.J = 1 TO 16
V.RND = RND(26) + 65
V.ID := CHAR(V.RND)
NEXT V.J
11
12
13
14
;* A...Z
15
16
17
18
19
20
21
IF V.I EQ 1 THEN
V.FIRST.ID = V.ID
END ELSE
V.RET = CacheGet(BUCKET1, V.FIRST.ID)
IF NOT(V.RET) THEN
CRT CacheGet() error at iteration # : V.I
STOP
END
END
22
23
24
25
26
27
28
29
30
31
NEXT V.I
32
33
CRT OK
34
35
STOP
36
37
38
END
39
No... OK at the end. Well, if this is a cache, it really shouldnt forget a record that
was retrieved quite recently. So well use a reverse search of the cache now:
New source of testcache.b
1
2
3
INCLUDE JBC.h
4
5
V.ID.L =
6
7
J of BASE, TAF of C
139
By KZM
9
10
V.ID =
11
12
FOR V.J = 1 TO 16
V.RND = RND(26) + 65
V.ID := CHAR(V.RND)
NEXT V.J
13
14
15
16
;* A...Z
17
18
19
20
21
22
23
V.ID.L<-1> = V.ID
24
25
NEXT V.I
26
27
28
29
30
31
32
33
34
35
36
37
38
CRT OK
39
40
STOP
41
42
43
END
44
J of BASE, TAF of C
140
By KZM
.............................................................
.............................................................
........................................:::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::CacheGet() error at iteration #56282
So, oldest 56,282 values were purged. Well, heres another excerpt from JBC.h explaining
that:
JBC.h excerpt
*
EQUATE CACHE_MAX_SIZE
EQUATE CACHE_PURGE_THRESHOLD
DEFC VAR CacheSetOption(VAR,
DEFC VAR CacheGetOption(VAR,
TO 1
;* Maximum bucket size (default: 1M)
TO 2
;* Bucket purging threshold (default: 90)
VAR, VAR)
VAR)
Anyway, our intention was to find out if caching mechanism can be used for a fast search
of a value based on its key. So simply reduce cache size from 1,000,000 to 900,000, create
the lists anew and try again. The results of the search tests now are:
Execution of procbiglist (with 900,000 items)
GETTING LISTS...
CREATING CACHE...
STARTING SEARCH IN DYN ARRAY...
11
STARTING SEARCH IN CACHE...
21
STOPPING...
We see that in this particular case dynamic array wins, though it might not be the case
under other platform or using other algorithm of caching (e.g. creating several buckets etc).
What to use then? If the volume isnt very big, use dynamic array
(or a dimensioned array if the number of items is fixed). If volume
is quite big - create a temporary hashed file for that purpose.
J of BASE, TAF of C
141
By KZM
22
ere we try to get the data from a remote system that doesnt give us any access except
::::: interaction with jBASE agent. Unfortunately, we can use only Java or C# for that.
But theres another thing in jBC called callj so we can try to marry it with Java to be
able to access a remote system from local jBC.
Lets start jBASE agent first. For simplicity sake it wouldnt be a sophisticated scheme
with public/private keys or account/password authentication (like it has to be in any production environment). To start our agent, we take jrunT24.cmd, copy it to jrunAgent.cmd
and amend it:
jrunAgent.cmd - the very end
39
40
41
42
set TERM=ntcon
cd <path_to_bnk.run>
jbase_agent
cd %HOME%
If you start it without administrative rights, you might see a popup window saying that
some features of that process were blocked; I ignored it and still all worked well.
Now its time to write some Java code. Here it is (last time Ive written something in
Java was exactly one year ago when I composed my first book so dont pay any attention to
its style; Im not Java man so all I can say is it works):
Source of callagent.java
1
2
3
4
5
6
7
8
9
10
11
12
13
import
import
import
import
import
import
import
import
import
import
import
com.jbase.jremote.DefaultJConnectionFactory;
com.jbase.jremote.JConnection;
com.jbase.jremote.JConnectionFactory;
com.jbase.jremote.JDynArray;
com.jbase.jremote.JStatement;
com.jbase.jremote.JResultSet;
com.jbase.jremote.JRemoteException;
com.jbase.jremote.JRecordNotFoundException;
com.jbase.jremote.JExecuteResults;
com.jbase.jremote.JFile;
java.util.Properties;
J of BASE, TAF of C
142
By KZM
14
15
16
17
import
import
import
import
java.io.ByteArrayOutputStream;
java.io.OutputStreamWriter;
java.io.Writer;
java.io.UnsupportedEncodingException;
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
try {
36
37
JConnection c = factory.getConnection();
38
39
switch (mode)
40
41
42
43
case 1:
44
45
46
47
48
49
extcmd = extcmd.substring(1);
50
51
52
53
if (results != null) {
54
resarray = results.getCapturingVar();
int nattr = resarray.getNumberOfAttributes();
for(int a = 1; a <= nattr; a++)
{
returnText = returnText + resarray.get(a) + "\n";
}
55
56
57
58
59
60
61
J of BASE, TAF of C
143
By KZM
62
return returnText;
63
64
case 2:
65
66
67
68
69
70
71
if (vFile != null) {
72
73
74
75
76
resarray = vFile.read(getrec);
if (resarray != null) {
returnText = resarray.toString();
}
77
78
79
80
81
} else {
returnText = "Record not found";
}
82
83
84
85
} else {
returnText = "File not found";
}
86
87
88
89
return returnText;
90
91
92
93
c.close();
94
95
} catch (JRemoteException e) {
returnText = "Connection or other error";
}
96
97
98
99
return returnText;
100
101
102
103
This code was designed to work in two modes one to execute jsh commands and another
to retrieve a record from a file. To be able to compile it we need to place Java Developer
Kit (preferrably 1.6) to some directory on local PC and to add the following 3 lines to
jrunSH.cmd:
J of BASE, TAF of C
144
By KZM
set JAVA_HOME=<path_to_java_DK>
set PATH=%PATH%;%JAVA_HOME%\jre\bin\server;%JAVA_HOME%\bin
set CLASSPATH=%HOME%;%TAFC_HOME%\java\lib\jremote.jar;%CLASSPATH%
39
40
41
set TERM=ntcon
jsh
If there were no messages all was OK and the file callagent.class appeared in the
current directory. Now to jBC part.
Source of callagent.b
1
* BASIC-JAGENT BRIDGE
V.CMD.L = SYSTEM(1000)
DEL V.CMD.L<1>
;* its program name itself
CHANGE @FM TO IN V.CMD.L
3
4
5
6
7
8
CRT result
9
10
STOP
11
12
END
13
Now compile and run it; parameters are: mode (1 or 2), IP address of the host where
jBASE agent is running, port which is used by jBASE agent, then for mode 1 the
command, for mode 2 file and record @id to retrieve.
Compilation and execution of jBC code accesing jBASE agent
C:\sa-tafc> jrunSH.cmd
jsh ~ --> jcompile callagent.b
J of BASE, TAF of C
145
By KZM
callagent.c
jsh ~ --> callagent 1 127.0.0.1 20002 COUNT FBNK.ACCOUNT
3620 Records counted
jsh ~ --> callagent 2 127.0.0.1 20002 F.SPF SYSTEM
<1>20100712<2>Model Bank<3>O<4><5>../bnk.data<6><7><8>201015<9>1<10><11><12>20
0<13><14>TAPE<15>Y<16>N<17>5<18><19>../bnk.data<20>../bnk.run<21>../bnk.dict<2
2><23>NT<24>./backup.hd &M&<25>./restore.hd &M&<26><27>5<28>500<29><30>201012<
31><32>10<33><34><35><35,1>BEF<35,2>ESP<35,3>ITL<35,4>JPY<35,5>PTE<35,6>XAU<35
,7>KWD<35,8>XAG<36>EURGBTMNS000<37>20111215<38><39><39,1>AA<39,2>AB<39,3>AC<39
,4>AD<39,5>AI<39,6>AL<39,7>AM<39,8>AP<39,9>AR<39,10>AS<39,11>AZ<39,12>BE<39,13
>BL<39,14>BR<39,15>CM<39,16>CO<39,17>CR<39,18>DC<39,19>DD<39,20>DE<39,21>DM<39
,22>DW<39,23>DX<39,24>EB<39,25>ET<39,26>EU<39,27>FD<39,28>FR<39,29>FT<39,30>FX
<39,31>GP<39,32>IA<39,33>IB<39,34>IC<39,35>IM<39,36>LC<39,37>LD<39,38>LI<39,39
>LM<39,40>MC<39,41>MD<39,42>MF<39,43>MM<39,44>MS<39,45>NR<39,46>NS<39,47>OF<39
,48>OO<39,49>OP<39,50>OV<39,51>PC<39,52>PD<39,53>PM<39,54>PV<39,55>PW<39,56>RE
<39,57>RP<39,58>SA<39,59>SB<39,60>SC<39,61>SE<39,62>SL<39,63>SP<39,64>ST<39,65
>SW<39,66>SY<39,67>TK<39,68>TT<39,69>TV<39,70>TX<39,71>WR<39,72>WS<39,73>XT<40
><41>Y<42>N<43>500<44><45><46><47><48><49>1<50><51><52>YES<53><54><55><56><57>
<58><59><60><61><62><63><64>YES<65><66><67><68><69><70><71><72><73><74><75><76
><77><78><79><80><81>5<82>2837_INPUTTER_I_INAU<83><83,1>1103110411<83,2>110311
0411<84>2837_INPUTTER<85>GB0010001<86>1
As you see, were running our jBC code under environment that doesnt have access to
T24 data at all (jrunSH.cmd).
What we then see in jBASE agent window:
jBASE agent window
(Tue Aug 30 2011 10:57:37.245000|3544|3984) NOTICE RequestHandlerService::open:
connected with localhost, RequestHandlerService.cpp +221
(Tue Aug 30 2011 10:57:39.776000|3544|3984) NOTICE Closing connection to localho
st, RequestHandlerService.cpp +127
(Tue Aug 30 2011 10:57:39.792000|3544|3984) NOTICE Shutting down Connection, JAg
entSocketServer.cpp +115
(Tue Aug 30 2011 10:57:40.073000|424|2432) NOTICE PID 3544 exited., WorkerProces
s.cpp +102
(Tue Aug 30 2011 10:57:48.355000|3624|3772) NOTICE RequestHandlerService::open:
connected with localhost, RequestHandlerService.cpp +221
(Tue Aug 30 2011 10:57:48.730000|3624|3772) NOTICE Closing connection to localho
st, RequestHandlerService.cpp +127
(Tue Aug 30 2011 10:57:48.745000|3624|3772) NOTICE Shutting down Connection, JAg
entSocketServer.cpp +115
(Tue Aug 30 2011 10:57:49.073000|424|2432) NOTICE PID 3624 exited., WorkerProces
J of BASE, TAF of C
146
By KZM
s.cpp +102
J of BASE, TAF of C
147
By KZM
23 CONCLUSIONS
23
Conclusions
feel heres the place I have to stop at for time being, otherwise it would never be finished.
:::: Enjoy, acquire more knowledge and dont forget to share it. Thanks to: jBASE/MV
community, LATEX community, Python community. (And of course to a colleague of mine
who preferred to remain incognito.)
cban 2011 Vladimir Kazimirchik except for samples of brainf**k code that have
copyrights of their own.
Useful links:
http://creativecommons.org/licenses/by-nc-sa/3.0 license of this book details
http://www.temenos.com Official Temenos site
http://groups.google.com/group/jbase jBASE Google group
Last but not least my previous (and telling the truth the very first) book:
http://dl.dropbox.com/u/20902365/t24/tihgw.pdf This Is How Globus Works...
J of BASE, TAF of C
148
By KZM