Search the Community
Showing results for tags 'language'.
Found 4 results
Forth: The Cart Before the Horse (#5) This is one is going to be brief. If you've read any of the Forth primers, or any of the Forth proclamations that I and others make here on Atariage from time-to-time, you'll no doubt have read about how versatile and configurable the language is. We're going to have a very brief look at that today. But it won't be the War And Peace tome that I wrote yesterday. Brian Fox recently wrote some fascinating code for Camel99 that gives the language a more Basic-like syntax. As you probably know, in Forth, arguments and parameters to words (functions) come *before* the word/function that you want to call. This is because words/functions take their data, and put their data on the stack, so the stack has to be loaded before the word is called: TI BASIC: CALL HCHAR(ROW,COLUMN,CHAR,REPEATS) Forth: ROW COLUMN CHAR REPEATS HCHAR This tends to put people off when they look at Forth. It just looks like gobbledy-gook; at least until you understand that ROW and COLUMN etc are going on the stack, and HCHAR removes them. However, Forth is supposedly "the most flexible language of them all", "ultra malleable, "if you don't like it you can change it" blah blah blah. So, why don't we put our money where our mouth is and prove it? Well alrighty then! Inspired by Brian's look at HCHAR and VCHAR I thought it might be fun to demonstrate how the language can be changed to suit your preferences. There are some restrictions, sure, but I think you'll be impressed. We're going to change Forth's HCHAR and VCHAR into TI BASIC's HCHAR and VCHAR, where the arguments come after the word. So, again, let's recap: TI BASIC: CALL HCHAR(ROW,COLUMN,CHAR,REPEATS) Forth: ROW COLUMN CHAR REPEATS HCHAR We're going to end up with: CALL HCHAR( ROW COLUMN CHAR REPEATS ) Furthermore, we want it to be sophisticated enough such that the parameters can be numbers (called literals in Forth parlance) or calls to other words, or complex Forth expressions that compute a value etc. What might shock you is how much code is NOT required to do this. I give you: : CALL ( -- ) ; IMMEDIATE \ do absolutely nothing : HCHAR( ASCII ) WORD EVALUATE STATE @ IF COMPILE HCHAR ELSE HCHAR THEN ; IMMEDIATE : VCHAR( ASCII ) WORD EVALUATE STATE @ IF COMPILE VCHAR ELSE VCHAR THEN ; IMMEDIATE Now, you can type this directly on the command line: PAGE CALL HCHAR( 10 10 42 10 ) CALL VCHAR( 10 10 42 10 ) And behold the awesomeness. Note the spaces: The space between the open parenthesis of HCHAR( and VCHAR( is essential. The spaces between the parameters are just normal for Forth (and actually looks much nicer than using commas). The space before the final closing parenthesis is also required. You just changed Forth to be more like BASIC. You don't have to have numbers in the parameter list. They can be words or expressions: 10 CONSTANT TEN 42 CONSTANT FORTY-TWO CALL HCHAR( TEN TEN FORTY-TWO TEN TEN + ) (That last phrase: TEN TEN + puts 10 on the stack, then another 10 on the stack, then + ("add") removes them and replaces them with their sum, thus leaving the repeat count for HCHAR) So how does it work? Let's look at CALL first. CALL does nothing. It's only job is to be there to make those more familiar with BASIC happy. It has no code in it; it's empty. Furthermore, it's what is known as an IMMEDIATE word, meaning that it executes DURING COMPILATION, not during execution. An example: : FRED ( -- ) CR ." I AM FRED" CR ; Type that in. Nothing much happens. The word FRED gets stored in the dictionary ready to be used. Now type FRED and press enter. FRED executes. No big deal. Now, type this: : BOB ( -- ) CR ." BOO! BOB WAS HERE!" CR ; IMMEDIATE Okay, you typed it in. Nothing much happened. Now execute it: type BOB and press enter. Again, no big surprises. Now, try this: : TOM ( -- ) BOB CR ." HELLO! I AM TOM!" CR ; Did you see what happened? While *TOM* was being compiled, BOB got in on the act and ran, rather rudely announcing his presence. So what happens if we run TOM (type TOM and press enter)? HELLO! I AM TOM! Where's BOB? Should BOB not also say BOO!? No. And the reason why is very clever and is the secret sauce that makes Forth so very powerful. Here it is: "Immediate words execute at compile time." That is, immediate words execute when a word is being compiled, *not* when the compiled word is executed! That's possibly a brain-hurting statement. Consider this TI BASIC code: 10 CALL HCHAR(10,10,42,10) Now, when you press enter, the TI BASIC compiler switches on and compiles your code into some internal magic code that will do what you want it to do when you later RUN it. Okay. All normal stuff. But consider this: When the TI BASIC compiler is compiling that line of code, it does so entirely privately. No have no control over what compiler does. Mind your own business, it's nothing to do with you. The compiler privately compiles that code (or doesn't if there's an error), and you are just a bystander. That's not the case in Forth. In Forth, you can use "immediate" words that run when the compiler is compiling. And because they run when the compiler is compiling, you can "hijack" the compiling process, and do something: make a fart sound; say something on the speech synth; load a file; anything you want. You can even compile your own code. Think about that. Code that compiles code. And THAT is what makes Forth so powerful. So, lets get back to TOM and examine what happened. In Forth, the compiler is switched on by : (colon) and switched off by ; (semi-colon): : SOME-WORD <CODE GOES HERE> ; The compiler just walks along the line of text, and when it sees a word it looks for it in its dictionary and if it finds it, it compiles a call (like a GOSUB) to it. Now you can see why spaces are so critical in Forth. They are what separate the words so that they can be found in the dictionary. However, when the compiler is looking for a word, if it finds it, it checks to see if it is immediate or not. If it is not, it just compiles a call/GOSUB to the word. However, if it *is* immediate, it *executes* it, and does not compile it. That means you can put a reference to an immediate word in your definition, and at that point in the compilation process it will call your immediate word, and *you* can do something to the word that is *currently* being compiled, like add some more code to it. When the immediate word ends, the compiler just carries on compiling, totally oblivious to anything you may or may not have done to the word currently being compiled. It's none of its business. It's your business. You are in total control. Thus when TOM was being *compiled* the compiler saw the reference to the word BOB and saw that it was "an immediate word" and so it executed BOB, and BOB did it's thing (in this case, writing a cheeky message to the screen) and then carried on with the compiling. Now you understand why, when TOM was *executed*, there was no message from BOB. BOB did it's thing while TOM was being *compiled*. Yes. In Forth, there are two distinct excecution phases: Run-time: When a word is just plain excecuting, doing its thang; Compile time: When a word is being compiled. And you can do whatever the hell you want in either phase. You might want to go for a little lie down at this point! Now, lets look at HCHAR( and see what it does: : HCHAR( ASCII ) WORD EVALUATE STATE @ IF COMPILE HCHAR ELSE HCHAR THEN ; IMMEDIATE When the compiler sees HCHAR( it sees that it is immediate and so it executes it. The first thing is does is place the ASCII code for a ) (closed parenthesis) on the stack. Then WORD executes. WORD reads the line of text and will stop when it sees the ) character. So, if you typed CALL HCHAR( 1 2 42 4 ) WORD would capture 1 2 42 4 The output of WORD is two numbers: The address and length of the text that it found. This is fed into EVALUATE that simply evaluates the string as if it were a line of code entered at the keyboard. In this case, 1 2 42 4, or TEN TEN 42 TEN etc. are all valid code, so it executes it according to the rules of Forth: If we're compiling (i.e. the compiler was switched on with : (so we're building a word) then it will compile what it sees; If we're not compiling, it will just execute what it sees there and then, just like in BASIC when you enter something without a line number. So, we're using EVALUATE to evaluate the parameters for us between the HCHAR( word and the closing ) character. Note the cheeky use of the open parenthesis in HCHAR( which makes it look like some part of the the syntax of the word, but it isn't: It's just part of the name! And note also the closing parenthesis which again looks like syntax but is in fact nothing more than a marker for WORD to look for to isolate the parameters so that it can feed them into EVALUATE. The magic of Forth. The last bit of HCHAR( is very simple indeed. It just looks to see if we're in compile mode (the variable STATE will be 0 if we're not compiling, and >0 if we are compiling). If we ARE compiling, we compile a call to HCHAR (the original version of HCHAR built into the TurboForth EPROM). See? We're "injecting" code into the definition that is being compiled. However, if we're NOT compiling, we just execute HCHAR right there and then, which uses the parameters that EVALUATE evaluated for us. Thus we can do: CALL HCHAR( 1 2 42 99 ) (i.e. not in a definition, so it will execute immediately, like BASIC code with no line number) Or : LINE ( -- ) CALL HCHAR( 1 2 42 10 ) ; And both will work fine and do what they're supposed/expected to do. So, again, here's what happens when that LINE defintion above is compiled: The compilier sees that CALL is an immediate word, so it runs it. CALL actually does precisely nothing, it compiles nothing and runs nothing. It has 0 impact on run-time speed. It's purely "syntactic sugar" to sweeten things up for BASIC lovers. It's a total sham. You don't need to use it at all. The compiler sees that HCHAR( is immediate so it runs it. HCHAR( temporarily takes over, and reads the input up to the closing parenthesis and evaluates them. Since we're building a definition (LINE) the compiler is ON, so EVALUATE will compile them (by calling a new instance of the compiler and saying "HEY! Compile this! Thanks man!" (How's that for a mind f**k!?). HCHAR( then exits, it's done it's thing. Control now goes back to the compiler. The compiler only sees ; (semi-colon) because the parameters were consumed by WORD and EVALUATE so it completes the definition and we're done. If you were to disassemble the definition of LINE what you would see is this: 1 2 42 10 HCHAR In other words, HCHAR( re-arranged the code so that the parameters went first, then called a reference to the internal (in the EPROM) HCHAR which expects the parameters to be on the stack. The whole HCHAR( definition is nothing more than a trick which allows us to put the parameters *after* HCHAR( but internally it compiles HCHAR after the parameters. And that is the power of Forth. If you don't like: ROW COLUMN CHAR REPEATS HCHAR You can make your own word to give you: CALL HCHAR( ROW COLUMN CHAR REPEATS ) Or any other combination. And there endeth the lesson. This is without a doubt a bit of mind melter when you are new to Forth, so don't worry if you don't understand it all. I just wanted to give you an appreciation of the power and flexibility of Forth. It's not essential to understand this stuff right now. And now a quick demo using our new words. We haven't covered a lot of the code below yet. For now, just sit back and enjoy. : FWD-BOX ( -- ) 30 0 DO 12 0 DO CALL VCHAR( I I I 33 + J + 24 I 2* - ) CALL VCHAR( I 31 I - I 33 + J + 24 I 2* - ) CALL HCHAR( I I I 33 + J + 32 I 2* - ) CALL HCHAR( 23 I - I I 33 + J + 32 I 2* - ) LOOP LOOP ; : REV-BOX ( -- ) 0 29 DO 12 0 DO CALL VCHAR( I I I 33 + J + 24 I 2* - ) CALL VCHAR( I 31 I - I 33 + J + 24 I 2* - ) CALL HCHAR( I I I 33 + J + 32 I 2* - ) CALL HCHAR( 23 I - I I 33 + J + 32 I 2* - ) LOOP -1 +LOOP ; : BOXES ( -- ) \ top-level - run me 1 GMODE 5 0 DO FWD-BOX REV-BOX LOOP 0 GMODE ." Thanks for watching!" CR ; Note the additional spaces in the paremeters so that it's easier to identify each paremeter. References: HCHAR - http://turboforth.net/lang_ref/view_word.asp?ID=220 VCHAR - http://turboforth.net/lang_ref/view_word.asp?ID=232 IMMEDIATE - http://turboforth.net/lang_ref/view_word.asp?ID=163 ASCII - http://turboforth.net/lang_ref/view_word.asp?ID=210 STATE - http://turboforth.net/lang_ref/view_word.asp?ID=189 COMPILE - http://turboforth.net/lang_ref/view_word.asp?ID=156 IF - http://turboforth.net/lang_ref/view_word.asp?ID=81 THEN - http://turboforth.net/lang_ref/view_word.asp?ID=88 ELSE - http://turboforth.net/lang_ref/view_word.asp?id=76 CONSTANT - http://turboforth.net/lang_ref/view_word.asp?ID=157 CR - http://turboforth.net/lang_ref/view_word.asp?ID=129 ." - http://turboforth.net/lang_ref/view_word.asp?ID=206 DO - http://turboforth.net/lang_ref/view_word.asp?ID=75 LOOP - http://turboforth.net/lang_ref/view_word.asp?id=84 +LOOP - http://turboforth.net/lang_ref/view_word.asp?id=69 I - http://turboforth.net/lang_ref/view_word.asp?ID=80 J - http://turboforth.net/lang_ref/view_word.asp?ID=82
I recently picked up a 1040STF with German keyboard and OS, and that got me thinking: What other keyboard language versions/variants did Atari have for the ST/TT/Falcon range of computers? Also, I assume that any difference in keyboard variation will also come with a different, matching OS? I appreciate any information or thoughts on this. Thanks in advance