Oxygen Basic
Information => Open Forum => Topic started by: RobbeK on June 16, 2014, 02:00:41 PM
-
Hi all,
Maybe something interesting to compare written in different languages (script / p-code / native ) -- execution speed / developing time / is the syntax adaptive , readily capable towards the solution etc ...
(with the abundance of Guru's here, there should be no problem to get a fairly correct answer ... 8) )
The exercise (as a benchmark) : the perfect numbers below 10000 printed with their sums.
A perfect number is such that it is the same as the sum of its dividers (1 always included)
so : 6 is perfect because = 1 + 2 + 3
(for the moment maybe less than 50 perfect numbers are known )
The code should go by Brute Force (and including scanning the odd numbers (no odd perfect number is known, but it is not proven they do not exist -- so no "step 2") ) -- no Mersenne number tricks , just brute force ...
Running on an old computer it took almost 8 sec in NewLisp (script).
I'll port to CLisp (p-code) , Steel Bank CL (native) , Haskell (Glasgow Haskell Compiler) and tB script [opt + O2], FreeBasic ..
(took less than 10' to write/test/eval in NewLisp )
best Rob
.
-
Why this code is not written in Oxygen ???
Someone might think that this forum is new forum for Lisp ;D
-
Ubuntu 12.04 LTS 64 bit Script BASIC 2.2
FUNCTION perf(n)
sum = 0
FOR i = 1 TO n - 1
IF n % i = 0 THEN
sum = sum + i
END IF
NEXT i
IF sum = n THEN
perf = 1
ELSE
perf = 0
END IF
END FUNCTION
FOR n = 2 TO 10000 STEP 2
IF perf(n) THEN PRINT n,"\n"
NEXT
jrs@laptop:~/sb/sb22/test$ time scriba perfnum.sb
6
28
496
8128
real 0m15.891s
user 0m15.873s
sys 0m0.004s
jrs@laptop:~/sb/sb22/test$
... so no "step 2"
jrs@laptop:~/sb/sb22/test$ time scriba perfnum.sb
6
28
496
8128
real 0m31.748s
user 0m31.706s
sys 0m0.004s
jrs@laptop:~/sb/sb22/test$
-
C BASIC Ubuntu 64 bit
#include <stdio.h>
#include "cbasic.h"
MAIN
BEGIN_FUNCTION
DIM AS int i, n, sum;
DEF_FOR (n = 1 TO n <= 10000 STEP INCR n)
BEGIN_FOR
sum = 0;
DEF_FOR (i = 1 TO i <= n-1 STEP INCR i)
BEGIN_FOR
IF (n MOD i EQ 0) THEN_DO sum = sum + i;
NEXT
IF (sum EQ n) THEN_DO PRINT ("%i\n", n);
NEXT
RETURN_FUNCTION(0);
END_FUNCTION
jrs@laptop:~/C_BASIC/bench$ time ./perfnum
6
28
496
8128
real 0m0.261s
user 0m0.256s
sys 0m0.000s
jrs@laptop:~/C_BASIC/bench$
Here is the same C BASIC programming running on Koding.com.
scriptbasic@vm-0:~/Applications/C_BASIC$ time ./perfnum
6
28
496
8128
real 0m0.317s
user 0m0.311s
sys 0m0.004s
scriptbasic@vm-0:~/Applications/C_BASIC$
-
Hi John,
Your equivalent code doesn't correspond to the original Lisp in full. You will get more overhead storing individual i's into a list or array and printing them out later on as summands. ;)
Let me have a good sleep and then I'll try my hand at FBSL's BASIC, DynAsm, and DynC.
(Aurel, isn't this an Open Forum board, after all?)
:)
-
(Aurel, isn't this an Open Forum board, after all?)
Yes Mike it is.That is why i am so nice..
And i don't have nothing against Rob posting about lisp..it is somehow funny.
BUT why ?
I am wondering why some people on BASIC-s forums like to post about some other languages ::)
I have never seen on this forums any post about BASIC .
If i see that would be not in a positive light and that is a truth.
-
Aurel,
This is an open source BASIC compiler project. Charles is trying new things and improving on others. Seeing how other languages might do something better gives Charles more options to refine his BASIC.
-
I think the most efficient way to build the factor lists, is to restrict them to proven perfect numbers.
Thus:
'OxygenBasic
include "$/inc/console.inc"
function perf(sys n) as sys
sum = 0
for i = 2 to <n
if mod(n,i) = 0
sum += i
end if
next
if sum = n-1 'PERFECT NUMBER
ls=n ":" tab "1"
for i = 2 to <n
if mod(n,i) = 0
ls+=" "+i
end if
next
printl ls
return n
end if
end function
for n = 2 to 10000
perf(n)
next
printl "done"
waitkey
-
Thank you all !!
Aurel : "I am wondering why some people on BASIC-s forums like to post about some other languages" -- imho , O2 is an embeddable (and dll - capable) language , not ? & that's the way I use it (all the time, in every time-critical application ). However, to have an idea about the gain I try every combination and starting from "zero oxygen" -- other languages like Python, Ruby etc.. already mix Lisp-like mechanisms (lists, tuples , variadic arguments, etc .. ) ; while it is imo certainly correct that Basic is one of the most underestimated languages (with on top a "cinderella treatment/respect" from academici , it will not hurt to pick up some ideas.
On the Lisp side the situation at this moment is such, that there are a lot of "mastodon" packages -- Mbytes in size, which are easy replaced by some oxygen code of 10-30 Kb in size. (they even will run faster ). (there are some other serious problems making stand-alones from these mega-packages btw ).
--
Factoring out the divider-extraction (and calculating their sum) by O2 delivers not much gain ... 7.5sec (attached)
more oxygen is needed (too many calls - main iteration loop should be inside the DLL - maybe better working in pieces with a static counter ? )
.
-
This particular task, is not kind to Lisp, or any other interpreter. It is one of those problems which require a dose of Assembler. only the the central iteration requires strong optimisation. It is simple enough to perform entirely inside the CPU, using registers only.
'OxygenBasic
include "$/inc/console.inc"
function perf(sys n) as sys
sum = 0
i = 2
mov ecx,n
sub ecx,2
mov edi,i
mov esi,sum
(
(
mov eax,n
mov edx,0
div edi
cmp edx,0
jnz exit
add esi,edi
)
inc edi
dec ecx
jg repeat
)
mov sum,esi
'for i = 2 to <n
' if mod(n,i) = 0
' sum += i
' end if
'next
if sum = n-1 'PERFECT NUMBER
ls=n ":" tab "1"
for i = 2 to <n
if mod(n,i) = 0
ls+=" "+i
end if
next
printl ls
return n
end if
end function
for n = 2 to 10000
perf(n)
next
printl "done"
waitkey
-
Thanks Charles ( from the past I remember FORTH which was very fast doing such things .. but the language seems vanished ?!)
That's seriously fast, John ... maybe you can reach the 5th perfect number within hours(?) it's somewhere between 33 000 000 and 34 000 000 (iirc).
The other languages tested are : (on a slow computer)
CLisp interpreted 57sec - compiled 5 sec (but these are big (unlimited) integers )
Racket Scheme 6 sec (bytecode + GNU lightning JIT)
Steel Bank Common Lisp (native x86) 1 sec
thinBasic script 28 sec
the fastest I get : thinBasic O2 (however, O2 checks the condition and tB builds the output - O2 runs till a perfect number is found, tB prints and O2 is called again .., so almost all iterations , be it interupted a few times are done in oxygen ) pre-JIT (the O2 function are built on forehand )
;----------------------------------
' Empty thinBasic CONSOLE file template
Uses "Console" , "oxygen"
Dim As Long pnext
Dim As Long pFinish
Dim t As Long
Dim src As String
src="
Function nextperfect( Long a, Long b) As Long link #pnext
dim i , j , sumdiv as long
for i=a to b
sumdiv=0
for j=1 to i/2
if mod(i,j) = 0 then
sumdiv += j
end if
next
if sumdiv = i then
return i
endif
next
End Function
Sub finish() link #pFinish
terminate
End Sub
"
O2_Basic src
If O2_Error Then
MsgBox 0,O2_Error
Stop
Else
O2_Exec
End If
Declare Function nextperfect(ByVal a As Long, ByVal b As Long) As Long At pnext
Declare Sub Finish() At pFinish
Function printperfect ( n As Long )
Dim i As Long
For i = n/2 To 2 Step -1
If Mod(n,i)=0 Then
Print Str$(i) & "+"
EndIf
Next
PrintL " 1 "
End Function
Function findthenumbers ( x As Long)
Dim i , n As Long
i=1
Do
n=nextperfect(i,x)
If n=0 Then Exit Do
Print Str$(n) & "="
printperfect(n)
i=n+1
Loop
End Function
PrintL "Perfect numbers tB+O2"
PrintL "=====================" & $CRLF
t=GetTickCount
findthenumbers(10000)
PrintL
PrintL Str$(GetTickCount -t ) & " mSec"
finish()
WaitKey
;-----------------------------------------------------------
around 0.55 sec (an up-to-date computer could be +1.5x faster? ).
The code is not similar with the Lisp memory manipulations - I have an idea how to do it with dynamic arrays , but I don't think it makes any sense. (?)
best, Rob
.
-
but I don't think it makes any sense. (?)
Once you know what the perfect number is, extracting the values that make it perfect shouldn't be the focus of the task. I think we have enough flawed numbers roaming around, we don't need to exacerbate it.
-
Yep, John
However, suddenly I realised I overlooked something extremely obvious :
If we have one divisor, we automatically have the other - (if a divides N then then obviously N/a is another one ) ;
while this speeds things up twice , there something much more important , we only have to look for numbers till squareroot (N).
And in the Scheme I worked with Lists i.o. streams , that's another 2x slow down -- :-\
- got it under 0.1 sec using bytecode + JIT , should be a lot faster on your C Basic ...
best Rob
-
Great!
Can you repost my C BASIC code with your discovery?
-
Sure,
Is that C-Basic downloadable somewhere, so I can (run this code)/test it .
The gain is enormous - for N=10000 only 100 numbers have to be scanned - what I overlooked is that in this specific case :
a*b=10000 , either a or b is < than 101 , and (call this number a) then b = 10000/a.
In the attached file I don't start with 1 because then N will be a divisor , as 1 is always a divisor I can do this in general , but I have to compare the divSum with N-1 (it's all marked)
best Rob -- the 46 mSec includes the printing to console and a tB do..loop (however, the code runs it only 4 times )
.
-
Nice improvement!
#include <math.h>
#include <stdio.h>
#include "cbasic.h"
MAIN
BEGIN_FUNCTION
DIM AS int i, n, sum;
DEF_FOR (n = 2 TO n <= 10000 STEP INCR n)
BEGIN_FOR
sum = 0;
DEF_FOR (i = 2 TO i <= sqrt(n) STEP INCR i)
BEGIN_FOR
IF (n MOD i EQ 0) THEN_DO sum = sum + i + n / i;
NEXT
IF (sum EQ n - 1) THEN_DO PRINT ("%i\n", n);
NEXT
RETURN_FUNCTION(0);
END_FUNCTION
jrs@laptop:~/C_BASIC/bench$ gcc -O3 perfnum.c -l m -o perfnum2
jrs@laptop:~/C_BASIC/bench$ time ./perfnum2
6
28
496
8128
real 0m0.009s
user 0m0.008s
sys 0m0.000s
jrs@laptop:~/C_BASIC/bench$
.
-
By precalculating sqrt(j), you should see a further improvement in performance
long k=sqrt(i)
for j=2 to k
...
-
Strange!
That actually increased the time ???
#include <math.h>
#include <stdio.h>
#include "cbasic.h"
MAIN
BEGIN_FUNCTION
DIM AS int i, n, s, sum;
DEF_FOR (n = 2 TO n <= 10000 STEP INCR n)
BEGIN_FOR
sum = 0;
s = sqrt(n);
DEF_FOR (i = 2 TO i <= s STEP INCR i)
BEGIN_FOR
IF (n MOD i EQ 0) THEN_DO sum = sum + i + n / i;
NEXT
IF (sum EQ n - 1) THEN_DO PRINT ("%i\n", n);
NEXT
RETURN_FUNCTION(0);
END_FUNCTION
jrs@laptop:~/C_BASIC/bench$ gcc -O3 perfnum.c -l m -o perfnum2
jrs@laptop:~/C_BASIC/bench$ time ./perfnum2
6
28
496
8128
real 0m0.012s
user 0m0.008s
sys 0m0.000s
jrs@laptop:~/C_BASIC/bench$
-
C might already be optimising sqrt() to a variable :)
-
8) Cool
A little competiion always enhances productivity ..
I had an idea about memoization , but I need pen and paper to work out something ...
28 -> 14 , 7 , 4 , 2 , 1
14 -> 7 , 2 , 1 ::: memo(14)=10
28 -> 14+memo(14).... +4 .. nope, nothing , can be done with n! etc .. , not here ...
thanks for the file , John
Charles : what makes the difference between k=sqrt(x) ... to sqrt(x) ? / floating point - integer compare every next cycle ?
it indeed runs faster 32mSec vs 46 mSec..
best Rob
-
The end comparator is currently translated literally in Oxygen and square roots are expensive!
I am considering whether to change this interpretation and assign the end value expression to a temp variable before entering the iteration.
-
Ah, thanks Charles .. I see
In Forth (but it's more than 30 yrs ago) there was something as
... [ 5 SQR ] LITERAL .... where [..] was calculated by the interpreter and the value entered into the code by the compiler.
(but then you need a kind of interpreter -- ? ) .. in interpreted mode, the interpreter just ran over the brackets and the LITERAL word , I think ..
best Rob
(thinking about it, with tB there's an interpreter at hand ... maybe with a kind of EXPAND function tB can replace "literals" by number values ? )
-
.... the final .....
Reaching the 5th number took around 50 minutes with this method.
Trying patern matching :
The divisors have a more or less similar behaviour -- powers of 2 with one "crack" in it then the 2x goes on , like
1 2 4 7(the crack) 14 28 .
It's easier to see when writing the PNumbers binary 1......10.....0 (exclusive 1 of a certain size & then exclusive 0 of a certain size )
Using such a generator , gave attached result ; but
- it of course generates a very lot of numbers that are not perfect / needs the usual filtering
- I can not prove it will generate all P numbers
Anyway the 5th number after 1/4 sec ...
soon outside the range of uint32 anyway ... (in this case I can switch to CLisp).
best Rob
.
-
Hi Rob, you can use quads instead of ints. It might give you the next number ..
-
Hi Charles,
I'll have a look if I can use the FBmath (Jean Debord) lib from freebasic in Oxygen (i only have the .inc and .a files iirc ) -- it has large integers.
I cheated a little and used the Mersenne numbers to generate the Perf's (in Scheme : a daughter of mother Lisp ).
From (other) tests I think it is about 4x slower than O2 .. (which I still consider fast )
best Rob
-- the binary pattern is remarkable ...
.
-
Wow! That's interesting. I wonder I you can predict perfect numbers, based on their bit pattern. Could there be a proof?
-
Hi Charles,
Yes, I do think all even perf N can be found this way - proving it, is something else.
I started now to write the code without the help of the Racket Scheme Number theory module.
Still based on Mersenne numbers, i do have to check the primality of these for generating perfect numbers.
I use Miller Rabin , a Monte Carlo method ( the classic (and naive) try every number below the sqrt and if nothing divides it's a prime, may take hours, days with these huge numbers ).
While with this method some pseudo primes may enter the system, we're extremely lucky here, because the divisors can be build in no time from the binary pattern (just sum them, and check against the number )
Common Lisp for the moment, but when I find a suitable big / large number lib for oxygen , it will be ported ..
best Rob (have a look at the time needed to find these numbers ... :D )
.
-
Common Lisp for the moment, but when I find a suitable big / large number lib for oxygen , it will be ported ..
Are you compiling as 32 bit or 64 bit under O2?
All my C BASIC examples are 64 bit gcc.
-
Some of these numbers could be 64 million bits wide :) You have to crunch them using techniques similar to the manual methods we learnt during our school days, before calculators.
There is an example: examples/math/BCDmul.o2bas. It is mostly assembly code, which I might be able to improve with a little more Basic.
-
Hi Charles, John ,
32bit here --
Yep, back to the 70s -- we did it all with 8bit numbers, not ? With Clisp the fun is over soon and runs out of (soft) 2048 (?) bit integers "soon" - (attached.) But it has modulo math operators built in (iirc). I think Racket and NewLisp have unlimited (limited by available memory ) numbers. But once again the operations are very simple , if extended this method , the Miller Rabin does not use a squareroot and the core of program (see attached too) is based on powers of 2 , which is just a binary shift of the blocks to the left with one carry / block. The perfect function gives either 0 or a perfect number. I did not test the results here - the Miller Rabin goes 8 deep - but there's still a chance a "deep-liar" surfaces (less chance than winning the lottery tomorrow ... but to be exact it needs verification).
For this reason I'll try the pattern matching algorithm which easily generates the divisors and a proof.
best Rob
.
-
hello Robbek
I would like to see your lisp code, the m switch in Clisp does not seem to increase the stack memory, but in sbcl you can increase the stack with --control-stack-size.
-
Hi Jack,
Ran some tests and using -m 100MB , I can get integers with over 200000 cyphers (the documentation says the limit is something as (expt 2 2000000) digits. (the floats go a lot higher)
Wrote a batch file with this -m setting, (without this CLisp claims only 3 Mb it seems).
It runs fine now -- reached the 17 th number in around 20'.
Does this code run in Steel Bank CL without modification ??
best Rob
.
-
Hi RobbeK,
I had to replace the hyphen character in the code, apparently it's a unicode character that my system does not understand,
but after that it run OK in sbcl.
clisp time 8.4 seconds
sbcl time 2.1 seconds
the function I used for the timing
(defun run-time (n)
(let ((tm (get-internal-real-time)))
(eval n)
(/ (- (get-internal-real-time) tm) 1000l0)))
(run-time '(printperfect 1000))
there's a difference between clisp and sbcl get-internal-real-time function, the run-time function above returns milliseconds in clisp but seconds in sbcl.
just in case others have the hyphen problem, the code follows.
(defun factor-out (number divisor)
(do ((e 0 (1+ e))
(r number (/ r divisor)))
((/= (mod r divisor) 0) (values r e))))
(defun mult-mod (x y modulus) (mod (* x y) modulus))
(defun expt-mod (base exponent modulus)
"Fast modular exponentiation by repeated squaring."
(labels ((expt-mod-iter (b e p)
(cond ((= e 0) p)
((evenp e)
(expt-mod-iter (mult-mod b b modulus)
(/ e 2)
p))
(t
(expt-mod-iter b
(1- e)
(mult-mod b p modulus))))))
(expt-mod-iter base exponent 1)))
(defun random-in-range (lower upper)
"Return a random integer from the range [lower..upper]."
(+ lower (random (+ (- upper lower) 1))))
(defun miller-rabin-test (n k)
"Test N for primality by performing the Miller-Rabin test K
times.
Return NIL if N is composite, and T if N is probably prime."
(cond ((= n 1) nil)
((< n 4) t)
((evenp n) nil)
(t
(multiple-value-bind (d s) (factor-out (- n 1) 2)
(labels ((strong-liar? (a)
(let ((x (expt-mod a d n)))
(or (= x 1)
(loop repeat s
for y = x then (mult-mod y y n)
thereis (= y (- n 1)))))))
(loop repeat k
always (strong-liar? (random-in-range 2 (- n 2)))))))))
(defun perfect (x)
(let ( (n (- (expt 2 x) 1 )))
(if (miller-rabin-test n 8)
(* n (expt 2 (- x 1)))
0 )))
(defun print-bin (x)
(princ (write-to-string x :base 2))
(terpri) )
(defun print-01 (str)
(let (( n 0) (s (write-to-string str :base 2) ))
(dotimes (i (length s) t)
(when (eql #\1 (char s i))
(incf n)))
(princ (write-to-string (length s)))
(princ " / " )
(princ (write-to-string n))
(princ " + ") (princ (write-to-string (- (length s) n)))
(terpri)))
(defun printperfect (x)
(let ( (p 0) )
(loop for i from 1 to x do
(setq p (perfect i))
(when (> p 0)
(print-bin p)
(print-01 p)
(princ p) (terpri) (terpri) ))))
-
Thanks Jack,
I'm used to Clisp in the mean time , but some special care is needed with underflow errors , and the number of arguments to a function is much lower than SBCL I think : 4K versus 64K ?
Any special GUI you use ?? ( I setup Japi ..(a suggestion from John) I think it's good - not too many gadgets, any easy to use ... )
attached .
(an experiment about divergence/convergence of an infinite sum of series of surface transformations .. )
best, Rob
.
-
Too cool Rob!
Maybe you and Ron can join forces and phoenix JAPI back to life. Since Google (Chrome / Chrome OS) has abandoned Java in their web solutions (Android is next) JAPI (Java) needs a purpose. :(
Script BASIC JAPI extension module (http://www.scriptbasic.org/forum/index.php/topic,283.0.html)
-
Do you find 3d surface visualisations useful, Rob?
-
Hi John,
Looks very interesting !! Japi has some strongpoints like "appendtext" on textarea's , and the image related functions are extremely fast (though for setting up an rgb matrix (array) int32's are used and not byte (char) - i imported these functions in Lisp , but oxygen has to set up the flat memory model ... (and give back the addresses to lisp , because a few Japi functions need int* like :
extern void j_getimagesource ( int , int , int , int , int , int* , int* , int* )
(char* is not problem in Lisp , it has something as c-string , zstring etc ....).
I think it can be done with the Common Lisp CFFI package, however I have severe problems making stand-alone executables when using this interface.
So, looking inside your dll code (I used Dependency Walker (another good tip .. from Mike this time .. I had some problems with name-mangling when creating DLL with freebasic -- ... in the end Charles showed the correct way .. )
.. but in your japi.bas(?) , i do not see any return value declarations when importing the functions (__j_ascriba_..... ) , i'm of course very interested in this ... (downloaded ScriptBasic now
Hi Charles, yes of course ...
The idea behind all this is a kind of question why something as the harmonic series (Zeta(1) ) goes to infinity 1+1/2+1/3.... (even the recipr. of the primes do this 1/2+1/3+1/5+1/7 ... while something as S=1/2+1/4+1/8 .. only delivers 1 as sum.
This is in mind the "fractal method" came up :
Start with a square
Make a new square from the mid points of sides
iterate
this delivers the above mentioned S series (easy to see the new square is half of its previous)
write this analytic and expand it
f(x)=2x(x-1)+1 where x is the location of the new corner point (if this is correct English) between 0 en 1
easy to see it is symmetrical around x=1/2 and has it maxima at 0 and 1 (logical because there is no rotation and the square generates itself .. the sum is then 1+1+1+ ................ :)
Remarkable is that is formula (obtaining by squaring the hypothenusa of one of the triangles inside the square ) is almost identical to the logistic function which IIRC was used by Feigenbaum to get his two constants which describe chaotic processes.
... it seems everything is related with everything in a way ....
but it goes further, ... very interesting series can be build form this starting point -- p.e. a side of the square x -> sqrt(x).
.. (well, some people like crossword puzzles , .. I prefer these kind of puzzles 8)
I final word about the underflow error in Common Lisp -- it's not that absurd (one can reason , ok if the number is that small we can make it equal with zero , not ??? ... yes, but the problem is we do not have -0 and +0 , and this may be a huge difference in a calculation / process ...
best, Rob
-
Hi Rob,
I'm happy to see that you found JAPI of interest. I think its' a gem. The Script BASIC extension module DLLs aren't like normal shared objects and do some initial calls for versioning and pre/post load logic. All calls to the extension modules are done with variadic functions which allows practically unlimited arguments and of any type supported. (long, double, strings and arrays) SB variables (typeless at the user level) are like variants in a way. Charles has been intimate with SB under the covers and has his own story to tell. 8) He is no longer a virgin and that's all I'm going to say.
and the image related functions are extremely fast
Surprised the hell out of me seeing it's a socket interface to the JRE.
Send me an e-mail to support@scriptbasic.org if you would like to join the Script BASIC forum and contribute to that project as well. It could be fun.
John
-
Hi John,
Ah, ok , I understand ...
"are done with variadic functions" .. parsing + a parsecount to start ? ...
"Surprised the hell out of me seeing it's a socket interface to the JRE" ... yep, and interesting for languages lacking GET/PUT (some languages do graphics extremely slow )
Attached something written in oxygen (changed something that was in the examples , it used the setpixel() method , this is a lot faster ...
---------------------
for j=1 to 44
j_drawimagesource (canvas,0,0,breite,hoehe,@r,@g,@b)
darken_green()
next
--------------------- 8) (draws the Mandelbrot and then darkens the greens .... )
best Rob
.
-
That's really slick. Nice job!
-
Hi John,
Even doing the graphics "pixel-wise" seems ok for most applications.
Just testing something -- a tree-friendly spirograph ::) -- maybe the grand-children like it ...
(just - a not so fantastic screen recording - but it gives an idea
.. in CLisp (compiled into byte code / + GNU Lightning JIT )
'll send a mail to sb.org this evening .. ...
best Rob
.
-
'll send a mail to sb.org this evening .. ...
We'll keep a light on for ya. ;)