Welcome back! Some uBasic news

56 views
Skip to first unread message

The Beez

unread,
Jan 5, 2023, 8:40:57 AM1/5/23
to 4tH-compiler
Dear 4tH-ers!

I must excuse myself for not having written anything in the last six months. But there is IMHO a good reason for that. As you may know I'm a consultant in a non-programming computer science field, but sometimes I'm also hired to do some operational work.

This was a very stressful job, making long hours and having the responsibility for a small team. This wouldn't have been a problem a few decades ago, but now I'm in my early sixties and this one frankly took most of my energy - leaving very little left for other activities.

But that one is done and while enjoying the holidays, I took up some of the slack which was left.

One of the things was adding "ON ERROR GOTO"  functionality to uBasic/4tH. I came to the conclusion that that syntax was not going to work for me, so I turned to Forth instead.

Here we have ['] MyWord CATCH. CATCH returns the exception thrown - translated to uBasic/4tH that means a function - x = TRY(_MyWord). And yes, you can add parameters as well, just like the related FUNC().

Unlike FUNC() you CANNOT return a value, because TRY() already returns the exception thrown. Of course, all stackpointers are preserved in case of an exception - and disregarded when no exceptions are thrown. Just like CATCH.

Of course, you have to be able to throw a user exception. For that I created RAISE(). RAISE() will take one parameter - but with one restriction. You cannot throw ANY exception, just user exceptions. You can retrieve the first user exception by issuing INFO("raise") - which is equivalent to RAISE(0). So you can throw any number of user exceptions, which will be returned intact by TRY().

Now, I've done quite some testing, but I decided the penultimate test would be the ANS Forth example given in Annex A. Since uBasic/4tH holds quite some Forthish stuff, it wasn't too difficult to implement:

---8<---
Proc _RetryIt                          ' call the routine
End

_RetryIt
  Local (1)
 
  Do
    Push 1, 2                          ' push two numbers on the stack
  While Try(_DoIt)                     ' repeat until no exception
    a@ = Pop() : a@ = Pop()            ' drop items from the stack
    Print "There was an exception"     ' signal there was an exception
  Loop
                                       ' no exception thrown, show character
  Print "The character was ";Chr(Pop())
Return  

_DoIt
  Local (1)
  a@ = Pop() : a@ = Pop()              ' drop items from the stack
Return (Func (_CouldFail))             ' call CouldFail

_CouldFail
  Local (1)
                                       ' return when "Q" is entered
  If Set(a@, Peek(Ask ("Enter: "), 0)) = Ord ("Q") Then Return (a@)
  Raise (1)                            ' raise user exception
Return (-1)                            ' this code is never reached
---8<---

And this is a sample run:

Enter: w
There was an exception
Enter: e
There was an exception
Enter: r
There was an exception
Enter: t
There was an exception
Enter: y
There was an exception
Enter: Q
The character was Q

0 OK, 0:61

Now, I know not all of you are very interested in uBasic/4tH, but I'll add the code to SVN after some more testing, so you may look at the Forth code yourself to satisfy your need for Forth code ;-)

It has been a bit restructured (refactoring is a thing), but I think the code has become even better. The whole invocation of the interpreter for TRY() and FUNC() has been isolated and
references to EXEC_GOTO and (POP) have been removed.

It worked out pretty much as I had imagined, the only thing I had forgotten all about was that EXEC_GOTO changed the program pointer, but once I figured that one out, it was a walk in the park. The implementation of RAISE() was pretty straight forward, only issuing the error message was a bit of a challenge.

So, this was my long overdue update. BTW, I have been making YouTube videos all this time. Take a look at this playlist, if you're interested:


Be seeing you,

Hans Bezemer

The Beez

unread,
Jan 6, 2023, 11:57:37 AM1/6/23
to 4tH-compiler
Quick note: I dropped the parenthesis from RAISE, since it is a command - not a function. Parenthesis in commands are only required when:
- Allocating or assigning memory (LOCAL, DIM, etc.);
- Passing parameters (RETURN, PROC, GOSUB).

And RAISE doesn't quite fit in. Did some additional tests too: same performance as previous versions. Additional bit of fun, PP4tH takes an extra half second, it seems, in comparison to 4tH. Have to dive into that. Not sure.

Hans Bezemer

The Beez

unread,
Jan 15, 2023, 9:09:26 AM1/15/23
to 4tH-compiler
Hi 4tH-ers!

Here is the documentation for the latest uBasic/4tH commands:

  1. TRY(e[(e[,e])]) Calls the subroutine at e. The label or line number may optionally be followed by a list of comma separated expressions. The results of these expressions are put on the stack. Returns zero if no exceptions were raised during the execution the subroutine at e. Negative return values are system-generated exceptions. Positive return values are uBasic-generated exceptions.
  2. RAISE e The RAISE statement throws an exception which will halt the system if uncaught by TRY(). The expression e must be positive and is an offset to the exception number returned by INFO("raise"). The RAISE statement can only raise user-generated exceptions. See TRY().
INFO() addition:
 
"raise" (constant) Value of first user exception
 
Errors
Q - "Exception raised", A user exception was raised and uncaught by TRY() [>26]
 
In the current version, that means that INFO("raise") returns 27. RAISE 0 consequently returns 27. And hence, RAISE 1 returns 28, etc. This is different from "THROW", which allows you to throw ANY (also system) exceptions - as ANS requires. But since IMHO uBasic/4tH is for an entirely different audience, I chose for a different approach - so people wouldn't be confused by some joker throwing all kinds of system exceptions. Thus, all uncaught user exceptions will appear to be <Q "Exception raised">.
 
Which will always correctly show the user what's actually going on. BTW, all system related (==4tH) exceptions will be shown as "System failure" - but TRY() will give the actual value - e.g. DIVby0 will come up as -10. A successful termination of uBasic/4tH is triggered by code "1" - not zero for obvious reasons, because since 4tH exceptions are thrown, these would NOT show up (0 THROW is a no-op).

BTW, "END" *can* be caught - but because the system stack is used AND checked when exiting (for memory leaks) depending on where and how the "END" is encountered, either "1" (OK) or "26" (MEMLEAK) can be encountered. Note that everything is still consistent - because CATCH will take care of all that.

Hans Bezemer

P.S. And then a final note - we also got SKIP and TEXT(), which were added at an earlier moment:
  1. SKIP e Skips all delimiters with ASCII code e from the current cursor position in the input buffer. After execution the cursor will point to the next non-delimiter character in the input buffer. If no non-delimiter characters are remaining in the input buffer, the input buffer is exhausted.
  2. TEXT(e) Returns a flag. If e represents a string pointer, it returns TRUE otherwise FALSE.
An enhanced GTK highlight file is enclosed in this post.

ubasic.lang

The Beez

unread,
Jan 19, 2023, 10:08:17 AM1/19/23
to 4tH-compiler
Hi 4tH-ers!

No announcement or anything - but something that kind of amazed me. A little bit of context first:

In 2009 I converted a small, educational Basic to 4tH - and at first it was (if I remember correctly) about 15K of 4tH sourcecode. But it lacked a *LOT* of features. It didn't even have a REM statement - which is a kind of a bummer when you're always commenting your source.

It didn't have functions. It didn't have TAB(), USING, CHR. Its FOR didn't have STEP- and FOR was the only loop it supported. Its IF didn't have ELSE. It didn't have parameter passing. It didn't have local variables. It didn't have files. It didn't have strings. It didn't have exception handling. It didn't have arrays. As a matter of fact, it only had a whole lot of nothing.

Through the years, I've added all those things, so now it is over three times its original size. It's almost a whopping 50K source now. I consider it a *BIG* program, the second largest I ever wrote in 4tH.

I've always remained interested in BASIC compilers, just to see if I can nick anything I could apply to uBasic/4tH. So lately I stumbled upon another BASIC compiler. It seems to have all the things I mentioned above - plus a few others. But if I just examine the example programs, apart from floating point support (which I often compensate by using "Brodie math") on average, it doesn't seem a whole lot of work to convert those.

I mean: take this one:

rem ----------------------------------------------------
rem CallFunc.BAS
rem ----------------------------------------------------

Print "CallFunc.BAS -- Test BASIC User-defined Function Statements"
Print "The next printed line should be from the Function."
Print
testvar = 17

x = TestFnc( 5, "Hello", testvar )

Print
Print "This is back at the main program. "
Print "The value of variable <testvar> is now "; testvar
Print "The returned value from the function is "; x

Print "Did it work?"
End

rem ----------------------------------------------------
rem Subroutine TestFnc
rem ----------------------------------------------------

Function TestFnc( xarg, yarg$, tvar )
   Print "This is written from the Function."
   Print "The value of variable <xarg> is"; xarg
   Print "The value of variable <yarg$> is "; yarg$
   Print "The value of variable <tvar> is "; tvar
   tvar = 99
   Print "The value of variable <tvar> is reset to "; tvar
   TestFnc = xarg + tvar
   Print "The Function should return "; TestFnc
End Function

And this is the uBasic/4tH version:

rem ----------------------------------------------------
rem CallFunc.BAS
rem ----------------------------------------------------

Print "CallFunc.BAS -- Test BASIC User-defined Function Statements"
Print "The next printed line should be from the Function."
Print
t = 17

x = FUNC(_TestFnc( 5, Dup("Hello"), t))

Print
Print "This is back at the main program. "
Print "The value of variable <t> is now "; t
Print "The returned value from the function is "; x

Print "Did it work?"
End

rem ----------------------------------------------------
rem Subroutine TestFnc
rem ----------------------------------------------------

_TestFnc
  Param (3)
  Local (1)

  Print "This is written from the Function."
  Print "The value of variable <a@> is "; a@
  Print "The value of variable <b@> is "; Show(b@)
  Print "The value of variable <c@> is "; c@
  c@ = 99
  Print "The value of variable <c@> is reset to "; c@
  d@ = a@ + c@
  Print "The Function should return "; d@
Return (d@)

Now, that was 5 mins of work. And don't misunderstand me. I think this interpreter is a great piece of work. It supports a whole slew of BASIC standards and is probably faster than 4tH (I haven't benchmarked). But it consists of almost 20 files, ranging from 20K to 240K. That's more than the *entire* 4tH compiler - let alone uBasic/4tH.

Compared to that amount of code, uBasic/4tH packs a lot of functionality for such a small package.

Let me know what you think.

Hans Bezemer

The Beez

unread,
Jan 19, 2023, 10:13:15 AM1/19/23
to 4tH-compiler
BTW, for those who want to see the output of this program:

$ ubasic bwbasic.bas

CallFunc.BAS -- Test BASIC User-defined Function Statements
The next printed line should be from the Function.

This is written from the Function.
The value of variable <a@> is 5
The value of variable <b@> is Hello
The value of variable <c@> is 17
The value of variable <c@> is reset to 99
The Function should return 104


This is back at the main program.
The value of variable <t> is now 17
The returned value from the function is 104
Did it work?

0 OK, 0:485
Reply all
Reply to author
Forward
0 new messages