RetroBASIC

Retrogamecoding(.org) => Announcements => Topic started by: Cybermonkey on June 10, 2019, 10:45:11 AM

Title: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 10, 2019, 10:45:11 AM
So it's a while since I started combining the Pulsar2D framework with the My_Basic interpreter. Until today I had problems with DATA & READ but the issue could be solved today. There is no download, yet. It's a rather complicated compilation on Windows because I did not port the Library to C. Yeah, I am not so fluent in C, so I felt this wouldn't work. Anyway, I figured out that it is possible to compile a library with FreeBASIC so it can be used from C. The "only" thing is providing a *h.-file. And still the pulsarbasic.c file has about 2700 lines.
Anyway, this might be interesting for drfloyd since I am providing a tiny editor. So one can start the editor, load a file, press run and that's it. (The editor is based on my old project TinyIDE from 2010 and compiled with Lazarus.)
So the funny thing for me as a developer is that I am actually using 3 programming languages and compilers: ANSI C (gcc), BASIC (FreeBASIC) and Object Pascal (FreePascal). Can it be more versatile?
I hope I can provide a download soon.

Title: Re: PulsarBASIC - Teaser thread
Post by: Aurel on June 10, 2019, 01:05:51 PM
It looks interesting even i am not very much in game programming.  ;)
..for drFloyd ...hmm he just jumping from place to place ,
looking for "perfect" replacement for Blitz Basic.
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 10, 2019, 03:53:08 PM
Yes, that's right. Anyway, I think this is a project I will push further. There are a few things I want to implement, most important of all the creation of executables.
Oh, btw, I am using now the old editor of EGSL which is now the PulsarBASIC-Editor as seen in the screenshot.
Title: Re: PulsarBASIC - Teaser thread
Post by: Aurel on June 10, 2019, 06:34:13 PM
Quote
There are a few things I want to implement, most important of all the creation of executables.

That should be no problem on Windows.
Using BIND topology : runtime.exe + sourcefile(in binary form- not ascii file).
That technic i was used in Aurel Basic long time ago.
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 10, 2019, 07:18:01 PM
Yes, that's the technique I am using with PulsarLua. But it's a bit harder to implement it in C ...
Title: Re: PulsarBASIC - Teaser thread
Post by: Aurel on June 10, 2019, 09:25:48 PM
well yes
but must be there some C example about that ?
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 11, 2019, 07:15:59 AM
I didn't find one, yet. So if anyone has an example, feel free to post it here.  ;)
Title: Re: PulsarBASIC - Teaser thread
Post by: Aurel on June 11, 2019, 07:26:58 PM
I found it long time ago somewhere but i don't know now ...
Title: Re: PulsarBASIC - Teaser thread
Post by: Ed Davis on June 12, 2019, 02:05:22 AM
I didn't find one, yet. So if anyone has an example, feel free to post it here.  ;)
I didn't find one, yet. So if anyone has an example, feel free to post it here.  ;)
How about this?
First, the binder - to compile:  gcc -s -Os -o binder binder.c
Code: [Select]
/*
--binder=> bind runtime script output-filename -----------

To build: gcc -s -Os -o binder.exe binder.c

ed_davis2@yahoo.com

arg[1] = runtime
arg[2] = script
arg[3] = output-filename

copy_file(arg[1], arg[3]) -- copy runtime to output
copy_file(arg[2], arg[3], APPEND) -- append script to output

--append runtime length
write(arg[3], format(filesize(arg[1]):8:'0'), APPEND)
-- append script length
write(arg[3], format(filesize(arg[2]):8:'0'), APPEND)
-- append a pity eyecatcher
write(arg[3], format("Southern":8:'0'), APPEND)
halt

--interpreter---------------------------------------------------

-- find the size of the runtime
f = open(arg[0], "r")
seek(f, -24, SEEK_END)
new string buf(8)
read(f, buf, buf.size)
runtime_size = val(buf)

read(f, buf, buf.size)
script_size = val(buf)

read(f, buf, buf.size)
if buf <> "Southern" error("Eyecatcher not found!!!")

-- read in the script
seek(f, runtime_size, SEEK_SET)

new string buf(script_size)
read(f, buf, script_size)
close(f)

run(buf)
halt
*/

#include <stdio.h>
#include <string.h>
#include <errno.h>

#if defined(__linux__)
#include <unistd.h>
#include <sys/stat.h>
#define stricmp strcasecmp
#endif

enum {n_interp = 1, n_script = 2, n_final = 3, REPLY_SIZE = 4};

int main(int argc, char *argv[]) {
    FILE *interp, *script, *final;
    int c, rt_size, script_size;

    if (argc < 4) {
        printf("format: binder interp script output-file\n");
        return 2;
    }

    interp = fopen(argv[n_interp], "rb");
    if (!interp) {
        perror(argv[n_interp]);
        return 2;
    }

    script = fopen(argv[n_script], "rb");
    if (!script) {
        perror(argv[n_script]);
        return 2;
    }

    /* don't overwrite output without confirmation */
    final = fopen(argv[n_final], "rb");
    if (final) {
        char buf[REPLY_SIZE];

        fclose(final);
        printf("File %s exists - delete it? (y/n)\n", argv[n_final]);
        if (!fgets(buf, REPLY_SIZE, stdin)) {
            printf("Nothing ventured, nothing gained\n");
            return 2;
        }
        if (buf[0] != 'y') {
            printf("Please try again later\n");
            return 2;
        }
    }

    final = fopen(argv[n_final], "wb");
    if (!final) {
        perror(argv[n_final]);
        return 2;
    }

    rt_size = 0;
    while ((c = fgetc(interp)) != EOF) {
        fputc(c, final);
        ++rt_size;
    }
    fclose(interp);
    printf("Interp size is %d\n", rt_size);

    script_size = 0;
    while ((c = fgetc(script)) != EOF) {
        fputc(c, final);
        ++script_size;
    }
    fclose(script);
    printf("Script size is %d\n", script_size);

    fprintf(final, "%08d", rt_size);
    fprintf(final, "%08d", script_size);
    fprintf(final, "31415926");
    fclose(final);
    printf("%s size is %d + %d + %d = %d\n", argv[n_final],
        rt_size, script_size, 24, rt_size+script_size+24);
#if defined(__linux__)
    chmod(argv[n_final], 0755);
#endif

    printf("%s bound to %s in %s\n", argv[n_script], argv[n_interp], argv[n_final]);
    return 0;
}

Then the interpreter - to compile: gcc -s -Os -o pl0 pl0.c
Just showing the "load the code routine here".  Complete interpreter is attached as pl0.c.
Code: [Select]
/*------------------------------------------------------------------------
  -- find the size of the runtime
  f = open(arg[0], "r")
  seek(f, -24, SEEK_END)
  new string buf(8)
  read(f, buf, buf.size)
  runtime_size = val(buf)

  read(f, buf, buf.size)
  script_size = val(buf)

  read(f, buf, buf.size)
  if buf <> "Southern" error("Eyecatcher not found!!!")

  -- read in the script
  seek(f, runtime_size, SEEK_SET)

  new string buf(script_size)
  read(f, buf, script_size)
  close(f)

  data is stored in 3 8 char fields:

  0..7:   size of the exe
  8..15:  size of the appended script
  16..23: eyecatcher
 ------------------------------------------------------------------------*/
int load_script(void) {
    FILE *fp;
    char buf[24], buf2[9];
    int size_read, rt_size;
    char exe_name[PATH_MAX];

/* get the exec name */
#if defined(__WIN32)
    GetModuleFileName(NULL, exe_name, MAX_PATH);
#elif defined(__linux__)
    char linkname[PATH_MAX];
    struct stat finfo;
    int len;

    sprintf(linkname, "/proc/%d/exe", getpid());
    if ((len = readlink(linkname, exe_name, PATH_MAX - 1)) > 0) {
        exe_name[len] = '\0';
    } else {
        perror("Error: ");
        return false;
    }
#endif

    fp = fopen(exe_name, "rb");
    if (!fp) {
        perror(exe_name);
        return false;
    }
    /* 24 bytes from end, to read 3 fields noted above */
    if (fseek(fp, -24, SEEK_END) != 0) {
        perror("fseek: ");
        return false;
    }

    /* read all 3 fields */
    size_read = fread(buf, 1, sizeof(buf), fp);
    /* eyecather matches? (3rd field) */
    if (memcmp(&buf[16], "31415926", 8) != 0) {
        printf("Eyecatcher not found\n");
        return false;
    }

    /* first field, the exe length */
    memcpy(buf2, buf, 8);
    buf2[8] = '\0';
    rt_size = strtol(buf2, NULL, 10);

    /* second field, the script length */
    memcpy(buf2, &buf[8], 8);
    buf2[8] = '\0';
    script_size = strtol(buf2, NULL, 10);

    script = malloc(script_size);

    /* seek to the start of the script */
    if (fseek(fp, rt_size, SEEK_SET) != 0) {
        perror("fseek: ");
        return false;
    }

    size_read = fread(script, 1, script_size, fp);
    fclose(fp);
    if (size_read != script_size) {
        printf("Could not read entire script: asked %d got %d\n", script_size, size_read);
        return false;
    }
    return true;
}

And a simple script to be bound:
Code: [Select]
const left = 0, middle = 1, right = 2;

procedure move(n, source, aux, dest);

    procedure printmove();

        procedure printpole(p);
        begin
            if p = 0 then
                write('left  ');
            elseif p = 1 then
                write('middle');
            elseif p = 2 then
                write('right ');
            end;
        end;

    begin {printmove}
        write('Move a disk from ');  printpole(source);
        write(' to ');               printpole(dest);
        writeln();
    end;

begin {move}
    if n = 1 then
        printmove();
    else
        move(n-1, source, dest, aux);
        printmove();
        move(n-1, aux, source, dest);
    end;
end;

var disks;

begin
    repeat
        writeln();
        write('Number of disks (2-10, 1 to stop)? ');
        read(disks);
        if (disks > 1) and (disks <= 10) then
            writeln();
            writeln('For ', disks, ' disks, the required moves are:');
            writeln();
            move(disks, left, middle, right);
        end;
    until disks = 1;
end.

To bind:

binder pl0 hanoi.pl0 hhh
(binder pl0.exe hanoi.pl0 hhh.exe on Windows)

Tested under Windows and Linux.
Let me know if you have questions/problems.
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 12, 2019, 03:52:12 PM
Ok, thanks that's what I was looking for. So far as I can see, one can't start a script simply by writing
Code: [Select]
pl0 hanoi.pl0
Error is
Quote
Eyecatcher not found
Would of course be nice that one could use the interpreter in both ways... well, I will have a closer look on the weekend.
Title: Re: PulsarBASIC - Teaser thread
Post by: Ed Davis on June 12, 2019, 03:57:22 PM
Ok, thanks that's what I was looking for. So far as I can see, one can't start a script simply by writing
Code: [Select]
pl0 hanoi.pl0
Error is
Quote
Eyecatcher not found
Would of course be nice that one could use the interpreter in both ways... well, I will have a closer look on the weekend.

Whoops!  In my original version, you could do that, but I remove the code :-)

Also, I was brain-dead when I wrote this.  Really only need the script size, not the runtime size.

If you are interested, I can post a new version with both features mentioned above.
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 12, 2019, 05:32:13 PM
Whoops!  In my original version, you could do that, but I remove the code :-)

Also, I was brain-dead when I wrote this.  Really only need the script size, not the runtime size.

If you are interested, I can post a new version with both features mentioned above.
That would be highly appreciated.  :D
I think I will this first implement in AllegroBASIC since it is easier to compile (C only).
Title: Re: PulsarBASIC - Teaser thread
Post by: Aurel on June 12, 2019, 06:30:42 PM
Yes Ed post it..

My own version is written long time ago in Emergence basic

runtime.exe  put it into memory - size let say 100

source.bas - put in another memory block in binary form - size let say 50

concat two memory blocks into  one


my_runtime = mem1 + mem2


save runtime on disk  as exe file

when you run your program runtime jump to location of 101 to read source
and start processing it until the end of file is reached.

Is that ok?
Title: Re: PulsarBASIC - Teaser thread
Post by: Ed Davis on June 12, 2019, 07:58:46 PM
If you are interested, I can post a new version with both features mentioned above.
That would be highly appreciated.  :D

New version is attached.
Title: Re: PulsarBASIC - Teaser thread
Post by: Ed Davis on June 13, 2019, 07:52:44 AM
My own version is written long time ago in Emergence basic

runtime.exe  put it into memory - size let say 100

source.bas - put in another memory block in binary form - size let say 50

concat two memory blocks into  one


my_runtime = mem1 + mem2


save runtime on disk  as exe file

when you run your program runtime jump to location of 101 to read source
and start processing it until the end of file is reached.

Is that ok?

If it works, then it is ok!
:-)
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 13, 2019, 08:24:37 AM
If you are interested, I can post a new version with both features mentioned above.
That would be highly appreciated.  :D

New version is attached.
Thanks a lot. That looks quite handy. I will try to implemented it in AllegroBASIC on Saturday.
Title: Re: PulsarBASIC - Teaser thread
Post by: Aurel on June 13, 2019, 08:13:48 PM
Yes Cyb
it works of course
but only on 32bit
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 14, 2019, 07:11:06 AM
Yes Cyb
it works of course
but only on 32bit
Why so? I compiled it on Linux 64bit and it works fine. On Windows I usually compile my programs for 32bit destination.
Title: Re: PulsarBASIC - Teaser thread
Post by: Aurel on June 14, 2019, 08:40:44 AM
No i was talking about my old binder written in Emergence basic 32bit.
I don't tried this one ..because i have in plan to use Oxygen basic for that job.
But of course i will try Ed binder too.
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 14, 2019, 10:16:00 AM
No i was talking about my old binder written in Emergence basic 32bit.
I don't tried this one ..because i have in plan to use Oxygen basic for that job.
But of course i will try Ed binder too.
Ok, now I understand.  :)
Title: Re: PulsarBASIC - Teaser thread
Post by: Aurel on June 14, 2019, 10:48:34 AM
Well
Ed Binder is not just binder than
PLO interpreter + binder
what is PLO i am not sure - looks like
some sort of pascal language ..right Ed?
Is this bytecode compiler?
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 14, 2019, 12:00:10 PM
https://en.wikipedia.org/wiki/PL/0
Title: Re: PulsarBASIC - Teaser thread
Post by: Ed Davis on June 14, 2019, 01:20:14 PM
Ed Binder is not just binder than

There are two C programs in the zip file:
binder.c - which is just a binder.
pl0.c - pl/0 interpreter.  Once compiled, this one handles either bound programs or programs passed on the command line.

PLO interpreter + binder
what is PLO i am not sure - looks like
some sort of pascal language ..right Ed?
Is this bytecode compiler?

PL/0 was a simple language Wirth (Algol-W/Pascal/Modula/Oberon creator) created for teaching students how to write compilers, in the compiler courses he taught.  It is not a strict subset of Pascal - a few minor differences.  First book about it was Algorithms + Data Structures = Programs, circa 1976.   An execellent book!  You can get it for a song on Amazon - highly recommended, even today.  He also has an excellent compiler book, in PDF form, on his website.  Again, this one is also highly recommended.  If you have any interest in compilers and interpreters  :)

Anyway, I flunked my compilers class, but about 30 years ago (has it been that long?), I took up the subject again, and translated Wirth's Pascal version of PL/0 to C.  Recently I updated it for Linux.

And yes, it is a byte-code interpreter (Wirth called them p-codes).

Code: [Select]
    do {
        int f_ret;      /* instruction register */
        const Code ir = code[pc];
        pc++;
        switch (ir.opcode) {
            case o_lit: top++; stack[top] = ir.adr; break;
            case o_lod: top++; stack[top] = stack[display[ir.level] + ir.adr]; break;
            case o_sto: stack[display[ir.level] + ir.adr] = stack[top]; top--; break;
            case o_cal:  /* generate new block mark */
                stack[top + 1] = display[ir.level + 1];
                stack[top + 2] = pc;
                display[ir.level + 1] = top + 1;
                pc = ir.adr;
                break;
            case o_rtf:
                f_ret = stack[top];
                // fall-thru
            case o_rtp:
                top = display[ir.level];
                display[ir.level] = stack[top];
                pc = stack[top + 1];
                top -= (ir.adr + 1);
                if (ir.opcode == o_rtf) {
                    top++;
                    stack[top] = f_ret;
                }
                break;
            case o_int: top += ir.adr; break;
            case o_jmp: pc = ir.adr; break;
            case o_jpc:
                if (stack[top] == 0)
                    pc = ir.adr;
                top--;
                break;
            case o_pop: top--; break;

            case o_neg: stack[top] = -stack[top]; break;
            case o_add: top--; stack[top] += stack[top+1]; break;
            case o_sub: top--; stack[top] -= stack[top+1]; break;
            case o_mul: top--; stack[top] *= stack[top+1]; break;
            case o_div: top--; stack[top] /= stack[top+1]; break;
            case o_mod: top--; stack[top] %= stack[top+1]; break;
            case o_equ: top--; stack[top] = stack[top] == stack[top+1]; break;
            case o_neq: top--; stack[top] = stack[top] != stack[top+1]; break;
            case o_lss: top--; stack[top] = stack[top] <  stack[top+1]; break;
            case o_geq: top--; stack[top] = stack[top] >= stack[top+1]; break;
            case o_gtr: top--; stack[top] = stack[top] >  stack[top+1]; break;
            case o_leq: top--; stack[top] = stack[top] <= stack[top+1]; break;
            case o_and: top--; stack[top] = stack[top] && stack[top+1]; break;
            case o_or:  top--; stack[top] = stack[top] || stack[top+1]; break;

            case o_rdi: {
                int temp;

                fflush(stdout);
                if (scanf("%d", &temp) < 0)
                    perror("scanf: ");
                top++;
                stack[top] = temp;
                break;
            }
            case o_wrl: printf("\n"); break;
            case o_wrt: printf("%d ", stack[top]); top--; break;
            case o_wrs: {
                int len = lit_data[ir.adr];
                char *cp = &lit_data[ir.adr + 1];
                printf("%*.*s", len, len, cp);
                break;
            }
            default:
                printf("Invalid opcode %d at position %d\n", ir.opcode, pc - 1);
                // fall-thru
            case o_hlt: pc = 0; break;
        }
        if (top > last_stack) {
            last_stack = top;
            if (verbose)
                printf("stack increased to %d at %d\n", top, pc);
        }
    } while (pc > 0);

Only data type is integer.  But it does have nested procedures, which can be interesting; no parameters though.

It is definitely worth the time to study Wirth's compilers - after PL/0, take a look at Pascal-S.
Title: Re: PulsarBASIC - Teaser thread
Post by: Ed Davis on June 14, 2019, 01:57:27 PM
There are two C programs in the zip file:
binder.c - which is just a binder.
pl0.c - pl/0 interpreter.  Once compiled, this one handles either bound programs or programs passed on the command line.
...
 no parameters though.

I forgot!  I updated this one to include parameters.  Plus, blocks are more Modula2/Oberon like than Pascal.  So not strictly PL/0, more PL/0+ :-)
Title: Re: PulsarBASIC - Teaser thread
Post by: Aurel on June 14, 2019, 03:47:36 PM
Ed
sorry Cyb if i babeling on your topic...

I see on many your and other authors
stack-this ,..stack--that

How should look stack-less interpreter?
like mine..even i use some sort of stack for FOR/GOSUB blocks
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 14, 2019, 08:26:45 PM
Ok, back to topic, although this thread is already far away from it ...
I've got a question to the load_script function: is the complete listing saved in the "static char *script"?
I actually don't get where's the decision if the script is bound and executed or loaded from a file.
Title: Re: PulsarBASIC - Teaser thread
Post by: Ed Davis on June 15, 2019, 12:36:33 AM
I've got a question to the load_script function: is the complete listing saved in the "static char *script"?

Yes, in load_script(), starting at line 1036:

Code: [Select]
    script = malloc(script_size);

    /* seek to the start of the script */
    if (fseek(fp, -(script_size + extra_size), SEEK_END) != 0) {
        perror("load_script fseek end 2");
        return false;
    }

    size_read = fread(script, 1, script_size, fp);

I actually don't get where's the decision if the script is bound and executed or loaded from a file.

In the main() routine.

It loops around the args, and if one does not start with "-", then it assumes it is a filename.

Then at line 1093:

Code: [Select]
    if (!fn && !load_script()) {
        show_help();
        return 2;
    }

So, if the fn is NULL (e.g., !fn) && !load_script() -- load_script() is a boolean -- we show the help and exit.

I probably need more comments - sorry about that!

If you have other questions, feel free to ask.

Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 15, 2019, 07:51:09 AM
Ok, thanks, this I understood. Now I still don't get the decision whether the script or the file is executed. There's that "init_scan" function; does it only execute if a file is loaded otherwise the "load_script" is used? (Sorry for my dumb questions but as I said C is rather confusing for me.)

In AllegroBASIC it's that simple (for now):
Code: [Select]
if (argc>1) {
if(mb_load_file(bas, argv[1]) == MB_FUNC_OK)
_alloc_data_seq();
mb_run(bas,true);
_free_data_seq();
}
else {
tinyfd_messageBox("No Script", "AllegroBASIC 0.6.5\nUsage: allegrobasic[.exe] script.bas", "ok", "error", 1);

}

And in PulsarLua the decision whether to use the script or the file, looks like this (Pascal is so much easier to read...):
Code: [Select]
if isbound = true then
begin
if luaL_dostring (L,PChar(prog))<>0 then
begin
//show if an error occured
Messagebox(1,'Error',lua_tostring (L,-1));
closeapplication;
halt;
end;

end
else
  if (luaL_dofile(L, script))<>0 then
   begin
//show if an error occured
Messagebox(1,'Error',lua_tostring (L,-1));
closeapplication;
halt;
  end;
  lua_close(L);
end.
("isbound" is a boolean which is filled at runtime - if I found out that the script is bound then it's true otherwise false.)
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 15, 2019, 03:11:36 PM
Ok, forget my last question. It works!!  :D
No I will try the compilation of AllegroBASIC on Windows. Keep your fingers crossed ...
Edit: added a screenshot from the new AllegroBASIC editor including the output of the binder program.
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 16, 2019, 07:45:46 AM
Phew, it took me over an hour to figure out how to compile it on Windows. The problem was that the "load_script" function needs <windows.h> which interferes with  <allegro.h>.
So if any of you will ever use (the old version of) Allegr: use <winalleg.h> instead of <windows.h>.
But anyway, this is about PulsarBASIC, not AllegroBASIC, so the latter one will get its' own thread.
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 19, 2019, 08:07:07 PM
Ok, PulsarBASIC works now on Linux. The editor is (almost) the same as for AllegroBASIC. See the attached screenshot for a custom colour configuration. (It's called "himbeere", it's also available for Geany and my favourite colour scheme.)
Title: Re: PulsarBASIC - Teaser thread
Post by: Cybermonkey on June 24, 2019, 05:20:13 PM
So this Screen is from the AllegroBASIC editor, but the colour config files are compatible of course. The following three are shipped with AllergroBASIC (soon!)