Jump to content
IGNORED

Beware: Forth in here


Willsy

Recommended Posts

All ye faint of heart turn back and be gone, for here lies The Forth, and The Forth is The Way.

 

Calculate the day of the week for any date since 1/1/1700.

 

Note: BRITISH date format (day month year) is required. Ha!

 

 

variable year
variable month
variable day
 
create years      0 c, 3 c, 3 c, 6 c, 1 c, 4 c, 6 c, 2 c, 5 c, 0 c, 3 c, 5 c,
create leap-years 6 c, 2 c, 3 c, 6 c, 1 c, 4 c, 6 c, 2 c, 5 c, 0 c, 3 c, 5 c,
create centuries  4 c, 2 c, 0 c, 6 c, 
 
: century ( year - n )
    1700 /mod drop 100 / 4 mod centuries + c@ ;
    
: leapYear? ( year - flag )
    dup >r 400 mod 0=  r@ 100 mod 0<> or  r> 4 mod 0= and ;
 
: dow ( day month year -- n )
    dup century >r  100 mod year !  1- month !  day !
    r>  year @ dup 4 /  day @
    year @ leapYear? if leap-years else years then month @ + c@
    + + + +  7 mod ;
 
: ? ( day month year -- )
    dow case
        0 of ." Sunday" endof
        1 of ." Monday" endof
        2 of ." Tuesday" endof
        3 of ." Wednesday" endof
        4 of ." Thursday" endof
        5 of ." Friday" endof
        6 of ." Saturday" endof
    endcase space ;
 
31 1 1883 ?
3 12 1970 ?
25 12 2017 ?
1 1 2018 ?
Edited by Willsy
  • Like 4
Link to comment
Share on other sites

The calendar code is pretty nice.

 

Something I have been playing with is creating a Forth layer that is TI-BASIC user friendly.

Forth is a chameleon so it not too hard to do, but there would still be the RPN aspect of Forth to contend with

and spaces between each token in the source code but it would overlap better with TI-BASIC users

knowledge base and expectations.

 

One easy to fix sticky point is the colours in Forth use the machine values 0 to 15 where TI-BASIC uses 1 to 16.

 

One could also do this kind of thing pretty simply in Forth:

 

VARIABLE A$ 32 DIM

 

A$ =" This is a Forth string"

 

A$ PRINT

This is a Forth String ok

 

If I created such layer on top of Forth with documentation, would it have some value to the 99ers here?

 

BF

Link to comment
Share on other sites

 

I am fascinated by the semantic density between Forth version and the BASIC program. The creator of BASIC found a way to create a pretty high level language.

 

Consider the PHP example Willsy gave us.

function get_century_code($century)
{
	// XVIII
	if (1700 <= $century && $century <= 1799)
		return 4;
 
	// XIX
	if (1800 <= $century && $century <= 1899)
		return 2;
 
	// XX
	if (1900 <= $century && $century <= 1999)
		return 0;
 
	// XXI
	if (2000 <= $century && $century <= 2099)
		return 6;
 
	// XXII
	if (2100 <= $century && $century <= 2199)
		return 4;
 
	// XXIII
	if (2200 <= $century && $century <= 2299)
		return 2;
 
	// XXIV
	if (2300 <= $century && $century <= 2399)
		return 0;
 
	// XXV
	if (2400 <= $century && $century <= 2499)
		return 6;
 
	// XXVI
	if (2500 <= $century && $century <= 2599)
		return 4;
 
	// XXVII
	if (2600 <= $century && $century <= 2699)
		return 2;
}
 
/**
 * Get the day of a given date
 * 
 * @param $date
 */
function get_day_from_date($date) 
{
	$months = array(
		1 => 0,		// January
		2 => 3,		// February
		3 => 3,		// March
		4 => 6,		// April
		5 => 1,		// May
		6 => 4,		// June
		7 => 6,		// July
		8 => 2,		// August
		9 => 5,		// September
		10 => 0,	// October
		11 => 3,	// November
		12 => 5,	// December
	);
 
	$days = array(
		0 => 'Sunday',
		1 => 'Monday',
		2 => 'Tuesday',
		3 => 'Wednesday',
		4 => 'Thursday',
		5 => 'Friday',
		6 => 'Saturday',
	);
 
	// calculate the date
	$dateParts = explode('-', $date);
	$century = substr($dateParts[2], 0, 2);
	$year = substr($dateParts[2], 2);
 
	// 1. Get the number for the corresponding century from the centuries table
	$a = get_century_code($dateParts[2]);
 
	// 2. Get the last two digits from the year
	$b = $year;
 
	// 3. Divide the number from step 2 by 4 and get it without the remainder
	$c = floor($year / 4);
 
	// 4. Get the month number from the month table
	$d = $months[$dateParts[1]];
 
	// 5. Sum the numbers from steps 1 to 4
	$e = $a + $b + $c + $d;
 
	// 6. Divide it by 7 and take the remainder
	$f = $e % 7;
 
	// 7. Find the result of step 6 in the days table
	return $days[$f];
}
 
// Sunday
echo get_day_from_date('31-1-1883');
Edited by TheBF
Link to comment
Share on other sites

Something I have been playing with is creating a Forth layer that is TI-BASIC user friendly.

<snip>

 

The thing is, once you learn Forth, you realise it's actually EASIER than BASIC!

 

Especially when you have features such as local variables, which can eliminate stack thrashing completely; especially with my locals implementation, which do not have to be loaded from the stack - they are true local variables.

Link to comment
Share on other sites

The thing is, once you learn Forth, you realise it's actually EASIER than BASIC!

 

Just being curious, as you are obviously skilled in Forth: Can you indeed understand a Forth program from looking at it (of course, if you are not the author)? In that sense, what you just said, is it easier to write a Forth program than a BASIC program, or easier to understand such a program?

 

My programming skills are certainly strongly influenced by object orientation for the last 22 years, so I admit I feel really lost when I look at a Forth program as shown above. (The same would be true for me for functional programming or logic programming.)

Link to comment
Share on other sites

Hi Michael,

 

Chuck Moore (the inventor of the Forth language) describes the Forth language as an "amplifier"; meaning that well designed Forth can look beautiful and almost read like english (or, at least, quite intuitive to understand) whereas badly written Forth can look terrible and be utterly incomprehensible.

 

Forth *can* be hard to understand. The reason is, in most languages you are only directing the flow of the *code* (when certain things are executed, and when they are not). In Forth, in addition to controlling code flow, you are also controlling the flow of *data* because it sits on a FIFO stack. You can of course use variables but the stack is used to manipulate everything.

 

Most of the Forth code that I see that isn't written by me I find hard to understand. There's essentially two reasons:

 

1) It's mostly from Forth nerds on comp.lang.forth and they actually seem to take delight in posting incredibly obtuse, non-idiomatic Forth code. It's like it's some sort of Forth coder cock-measuring context.

 

2) See 1 :-D

 

I find my own code easy to understand, but that's probably because I understand my own style! Having said, I find any code that I didn't write hard to understand. I spend a lot of my day job (at the moment) modifying Structured Text code (similar to Pascal) written for control systems. I find it TERRIBLY difficult, and somewhat terrifying. I find that after a while, I begin to get "into the mind" of the programmer, and *that's* when I begin to understand the code. When I understand where the programmer is coming from, and where he's going. Then it begins to make sense.

 

I'm digressing. Forth is a language for writing other languages. So, normally, when you write a (major) program in Forth, maybe as much as 25-30% of it might be code that is associated with defining a domain specific language which the rest of the program would be written. This low-level, domain specific language definition code will typically be low-loevel nitty-gritty Forth code, and quite dense and difficult to understand. However, when the application is written using the domain specific language, the application is often very simple to understand. The word awesome is oft overused these days, but Forth's ability to extend itself, in terms of itself, is really quite awesome and totally mind blowing when you first experience and understand it.

 

For example, I might be writing a game (space invaders) and to put the invaders on the screen, I could define a DSL such that I can later write:

 

: displayAliens on 20 rows 12 aliens per row ;

 

So, this is a word called displayAliens, and, well, you can understand exactly what it does, because it reads like English, but it isn't English. It's Forth. More specifically, the Forth language has been expanded with words such as rows, aliens, per and row, which are not mere subroutines/functions as they would be in other languages, but are now part of the language itself, just like DUP or SWAP or IF etc.

 

The Forth philosophy is very deep, and it takes time to fully appreciate it. It was the best thing I ever did in my personal journey with computing.

  • Like 1
Link to comment
Share on other sites

Harping on more about local variables, look how code can be simplified by using locals.

 

Here's a program to do exponentiation:

 

: ^ ( i n -- i' )
    begin
        dup 1 > while
            -rot
            over
            *
            rot
            1-
    repeat
    drop
    nip ;
It's idiomatic Forth. It doesn't use variables at. It does everything on the stack. Because it does everything on the stack, there's a certain amount of stack-juggling (stacrobatics) going on, just moving things into the right place on the stack for the next operation, etc.
Being truly idiomatic, it would be written horizontally, like this:
: ^ ( i n -- i' ) begin dup 1 > while -rot over * rot 1- repeat drop nip ;
But I find that the indentation helps with understanding. There are endless arguments between ninja black-belt snobby Forthers over this issue, which I won't go into here!
Let's have a look how it looks in TurboForth with locals (full disclosure: TurboForth's locals implementation is unique to TurboForth (I believe them to be superior)):
: ^ ( n r -- n^r )
    locals{ n r }  set r  set n
    n n *
    r 1-  0 do 
        n *
    loop ;
No stack juggling. This version will actually execute faster than the idiomatic version because CPU cycles aren't being burned moving things around on the stack.
  • Like 1
Link to comment
Share on other sites

I am fascinated by the semantic density between Forth version and the BASIC program. The creator of BASIC found a way to create a pretty high level language.

 

Consider the PHP example Willsy gave us.

The PHP example is not exactly the same algorithm. It does not test leap years and the century codes are not in an array but in a switch/case statement.

Consider this JavaScript version.

Forth: 30 lines of code, 913 bytes

JavaScript: 26 lines of code, 778 bytes

 

var years =      [ 0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5 ];
var leap_years = [ 6, 2, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5 ];
var centuries =  [ 4, 2, 0, 6 ];
var days = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ];

function century(year) {
  return centuries[Math.floor(year % 1700 / 100) % 4];
}

function leapYear(year) {
  return !(year % 400) || year % 100 && !(year % 4);
}

function dow(day, month, year) {
  day = +day; month = +month; year = +year; // string to number
  var yy = year % 100;
  return days[
    (century(year) + yy + Math.floor(yy/4) +
    (leapYear(year)? leap_years[month-1] : years[month-1]) +
    day) % 7
  ]
}

function dayOfWeek() {
  output.innerHTML = dow.apply(null, input.value.split(' '));
}
  • Like 1
Link to comment
Share on other sites

 

<SNIP>
Let's have a look how it looks in TurboForth with locals (full disclosure: TurboForth's locals implementation is unique to TurboForth (I believe them to be superior)):
: ^ ( n r -- n^r )
    locals{ n r }  set r  set n
    n n *
    r 1-  0 do 
        n *
    loop ;
No stack juggling. This version will actually execute faster than the idiomatic version because CPU cycles aren't being burned moving things around on the stack.

 

 

Now if those locals took spare registers instead of space on the stack what would happen?

 

(I am still jealous of GCC) :-)

 

Here was some code I thought code be used to allocate register variables in machine Forth

as long as you have the spare registers contiguous from 1 to n.

NEW.REGS would have to RUN by the Forth word LOCALS in order

to free up a new batch of registers for your sub-routine (WORD).

variable  reg#                     \ holds the next available register

: new.regs    ( -- )  6 reg# !  ;  \ INIT the allocations: my system has 6 free registers from R0 to R5

: reg.allot   ( -- n)
              reg# @ 1- dup 0<            \ check if have any left
              abort" no more registers!"  \ abort if we don't
              dup reg# ! ;                \ return the available register and store in REG#

: local       ( -- <text>)                 
              create  reg.allot ,         \ compile time: create a named number that is allotted register
              does> @  ;                  \ run time: fetch the register number when the name is used in a program
Edited by TheBF
  • Like 1
Link to comment
Share on other sites

Hi Michael,

 

Chuck Moore (the inventor of the Forth language) describes the Forth language as an "amplifier"; meaning that well designed Forth can look beautiful and almost read like english (or, at least, quite intuitive to understand) whereas badly written Forth can look terrible and be utterly incomprehensible.

 

Forth *can* be hard to understand. The reason is, in most languages you are only directing the flow of the *code* (when certain things are executed, and when they are not). In Forth, in addition to controlling code flow, you are also controlling the flow of *data* because it sits on a FIFO stack. You can of course use variables but the stack is used to manipulate everything.

 

Most of the Forth code that I see that isn't written by me I find hard to understand. There's essentially two reasons:

 

1) It's mostly from Forth nerds on comp.lang.forth and they actually seem to take delight in posting incredibly obtuse, non-idiomatic Forth code. It's like it's some sort of Forth coder cock-measuring context.

 

2) See 1 :-D

 

I find my own code easy to understand, but that's probably because I understand my own style! Having said, I find any code that I didn't write hard to understand. I spend a lot of my day job (at the moment) modifying Structured Text code (similar to Pascal) written for control systems. I find it TERRIBLY difficult, and somewhat terrifying. I find that after a while, I begin to get "into the mind" of the programmer, and *that's* when I begin to understand the code. When I understand where the programmer is coming from, and where he's going. Then it begins to make sense.

 

I'm digressing. Forth is a language for writing other languages. So, normally, when you write a (major) program in Forth, maybe as much as 25-30% of it might be code that is associated with defining a domain specific language which the rest of the program would be written. This low-level, domain specific language definition code will typically be low-loevel nitty-gritty Forth code, and quite dense and difficult to understand. However, when the application is written using the domain specific language, the application is often very simple to understand. The word awesome is oft overused these days, but Forth's ability to extend itself, in terms of itself, is really quite awesome and totally mind blowing when you first experience and understand it.

 

For example, I might be writing a game (space invaders) and to put the invaders on the screen, I could define a DSL such that I can later write:

 

: displayAliens on 20 rows 12 aliens per row ;

 

So, this is a word called displayAliens, and, well, you can understand exactly what it does, because it reads like English, but it isn't English. It's Forth. More specifically, the Forth language has been expanded with words such as rows, aliens, per and row, which are not mere subroutines/functions as they would be in other languages, but are now part of the language itself, just like DUP or SWAP or IF etc.

 

The Forth philosophy is very deep, and it takes time to fully appreciate it. It was the best thing I ever did in my personal journey with computing.

 

I will echo the language creation concept.

I always say that I stop programming in Forth after this first page of code.

 

Years ago I read an article where a guy wrote code to "Menu Drive the 8250 UART" in Forth.

It was a menu on the screen where you pushed 1 to do X, 2 to do Y,3 to do Z etc.

All this so you could configure the $!%!#$ RS232 ports on a PC.

 

I kind of lost it when I saw it but it prompted me to write a reply article that was published.

 

I created the following Forth words:

 

COM1: COM2: BAUD BITS PARITY ODD EVEN NO STOP-BIT

 

Ya you get it. So instead of pile of BS menus, you could now type at the console -OR- use in your program:

 

COM1: 9600 BAUD NO PARITY 1 STOP-BIT

etc...

 

You don't actually program in Forth. It's too stupid. :-) (primitive)

You make what you need and then program in that.

 

B

Edited by TheBF
  • Like 1
Link to comment
Share on other sites

Harping on more about local variables, look how code can be simplified by using locals.

 

Here's a program to do exponentiation:

: ^ ( i n -- i' )
    begin
        dup 1 > while
            -rot
            over
            *
            rot
            1-
    repeat
    drop
    nip ;
It's idiomatic Forth. It doesn't use variables at. It does everything on the stack. Because it does everything on the stack, there's a certain amount of stack-juggling (stacrobatics) going on, just moving things into the right place on the stack for the next operation, etc.
Being truly idiomatic, it would be written horizontally, like this:
: ^ ( i n -- i' ) begin dup 1 > while -rot over * rot 1- repeat drop nip ;
But I find that the indentation helps with understanding. There are endless arguments between ninja black-belt snobby Forthers over this issue, which I won't go into here!
Let's have a look how it looks in TurboForth with locals (full disclosure: TurboForth's locals implementation is unique to TurboForth (I believe them to be superior)):
: ^ ( n r -- n^r )
    locals{ n r }  set r  set n
    n n *
    r 1-  0 do 
        n *
    loop ;
No stack juggling. This version will actually execute faster than the idiomatic version because CPU cycles aren't being burned moving things around on the stack.

 

These ideas are great, Mark, but your code will not work as is. The “stackrobatic” word is missing over swap before begin . The “locals” example gives n^(r+1) instead of the desired n^r. It can be fixed by removing n * before the loop and enclosing the loop in r 1 > if ... then .

 

Neither example handles r = 0, which can be managed with

 

 

 

: ^ ( i n -- i' )
dup 0= if 1 else over then
swap
begin
dup 1 > while
-rot
over
*
rot
1-
repeat
drop
nip ;
: elseof
compile dup
[compile] of
; immediate
: ^ ( n r -- n^r )
locals{ n r } set r set n
r case
0 of 1 endof
1 of n endof
elseof
n
r 1- 0 do
n *
loop
endof
endcase
;

...lee, the nitpicker!
Link to comment
Share on other sites

Ah I should have checked more closely! I did have code for checking for 0 and 1 but removed it when posting for brevity.

 

Of all nits worth picking this was surely one of them! Thanks :-)

 

Is good to be talking about Forth again.

 

By the way, I made some significant improvements to the locals implementation in TF last night. Have a look at the locals page on the website. Also updated the online help. Need to update the blocks disk.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...