This documentation is based upon the StrongHelp file. It have been converted to plain ASCII and tidied up, but note that some things may seem a little odd, like you might encounter a "click here to read more about...", which would be a link in the original help. T e l e t e x t S c r i p t ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ( v e r s i o n 1 . 0 5 ) O v e r v i e w ~~~~~~~~~~~~~~~ !Teletext incorporates a script interpreter which allows you to process teletext frames. You may fetch frames, perform various operations on them and then store them back in the cache and/or output them in a variety of formats. For example, one of the scripts provided reads the "Movies at 9pm this week on Channel 5". It pulls out the list of movies, formats the frames to remove advertising and the like, and then outputs the frames to a file in text format. A slightly more complex script reads the temperature in Nantes, from CNN. It reports the temperature in degrees celcius. However, instead of simply reading the farenheit temperature, it calculates it from the celcius temperature. Finally, another example script reads the pound/dollar/euro exchange rates from CNN, and outputs an equivalence chart. These are the sorts of things that can be achieved with the Teletext script language. The script language itself is fairly simple, tailored to exactly what it has been designed for. Thus, it provides a way to pass messages to the user but there is no provision for, say, drawing on the screen. Such a facility would not be useful in processing teletext frames! The script interpreter does not execute quickly. It is designed to poll fairly regularly. The teletext system in use loads the system between 15% and 60% (depending on what it is doing), with an average loading of around twenty-thirty percent. When the script is running, this may go up to around forty percent (again depending on what is happening). Some scripts may take in the order of ten minutes to execute. If you think this is slow, please remember that in the 'movies at 9' script, most of the time taken will be in waiting for the pages to arrive. The system is designed to be used multitasking, where you start a script and then forget about it for a while. Please note that the timings were taken on my RiscPC700 with Ran Mokady's !Usage, and is subjective to the other tasks running on your system, and the system itself. S c r i p t e x e c u t i o n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Running scripts Scripts are run, simply, by clicking MENU in the viewer window, following the Script option, then picking a script to run. As the script executes, a little window will pop up in the lower left corner. It provides two lines of output. The first is a positional line, it tracks the part of the script being executed (as a percentage) and it also provides an approximate line count. The lower line gives you status report on major commands that were executed, to help you follow through the script. The script coder may opt to disable this little status window. Previous versions of !Teletext was able to run the script in a multitasking mode or a single-tasking mode (by holding ALT when selecting which script to execute). This is no longer supported, as the script author has the ability to control how and when the script polls - if permitted in the !Teletext configuration. Because most of the time is likely to be taken waiting for pages to arrive, it is strongly suggested that your scripts multitask while awaiting pages. Polling can then be disabled during calculations, if required. Abandoning scripts Click on the !Teletext iconbar icon. A message will pop up asking if you want to abandon or carry on. If the script is running in single-tasking mode (ie, if the iconbar click has no effect), pressing ESCape will ask if you wish to abandon script processing. Script execution - from the point of view of the script Scripts begin executing at the first line, and then continues until: * A terminate() command is reached. * A script error (or error() command) occurs. * The file ends. Please note that end-of-file is not the proper way to end a script, and a warning will be generated. You have rudimentary control over program execution, you have the if() instruction which can provide conditional tests, you have a way to branch to a previous line, and from that you can build loops. Lines beginning with an egg ('¤') character are control commands (and are ignored by the script interpreter) and lines beginning with a semicolon (';') are for your comments (and, also, are ignored). F r a m e b a s i c s ~~~~~~~~~~~~~~~~~~~~~~~ You have frames 100 to 999, in decimal only. There is no way to access hex. frames. That, pretty much, is all you need to know. If you need to know why you need to know this (!), read on... Those of you with experience in teletext will be aware that 'true' frame numbers are hex, in the form: &PPPSSSS, where PPP is the page number we know (ie, 102) and SSSS is the subframe number. Therefore, the news index on BBC 1 is not frame 102, it is frame &102000x. Okay. Now that you know that, forget it! It simply helps justify this explanation. :-) In the script, frame numbers are always provided in a 'sensible' form. Frame numbers are expressed in the range 100 to 999. No hex frames are allowed. You should NOT try commands like: getframe(&102) As you will end up fetching page 258. You can use the status() command to find out things such as the current frame number (in friendly form) or the current subframe number/count. You can use the frame_number() or frame_subnumber() commands if you want to know the actual frame number, in denary or hex. Again, this is mainly for information. Pages are in the following sequence: 100 - 199, 200 - 299, 300 - 399, etc. You cannot select the out-of-range pages (ie, 29A - 2FF). F r a m e l a y o u t ~~~~~~~~~~~~~~~~~~~~~~~ The top-left of the frame is location 1,1. The bottom right is location 40,25. The first line is the header. Then follow twenty three lines of page data. Finally, the twenty fifth line is usually used for FastText links. Each line is forty characters wide. In the frame, there is no end-of-line marker, everything is padded with spaces. In the script, the notional end-of-line is at horizontal offset forty. V a r i a b l e s ~~~~~~~~~~~~~~~~~ You have sixteen integer variables, A to P. You can set these to any integer values that you like, perform basic maths, or use them in conditional statements and as parameters to commands. You have ten float variables, Q to Z. You can set these to any floating point number you like, so long as there are not more than nine digits (not including decimal point). For example: set Q to 1.12345678901234567890 would set Q to 1.123456789. set Q to 123.123456789012345678 would set Q to 123.1234567. set Q to 1234567.12345678901234 would set Q to 1234567.123. set Q to 123456789.123456789012 would set Q to 123456789. There is sufficient accuracy for all sorts of money calculations. All variables are set to zero when script execution begins. Wherever a command states that it requires a variable as input, then you must give a variable. Wherever a command states that it requires a number as input, you may provide either a constant number or a variable. Your choice. To cast from an integer to a float, or vice versa, simply specify the desired variable as the destination. For example: set A to 14 set B to 15 div(Z, A, B) The result of two integers divided, is written to a float variable, as 0.933333333. You can cast directly, like set Z to A. When casting an integer to a float, the value is simply copied. When casting a float to an integer, anything after the decimal point is discarded. This means the default casting method rounds towards zero. You may prefer to 'round to nearest', in which case you should use the roundcast() command. If you are comparing floating point values in an if() statement, be aware that the following will fail: set A to 14 set B to 15 div(Z, A, B) if (Z = 0.93) message("division successful!") The reason for the failure is simple. 0.933333333 is not the same as 0.93. To make this work, you need to lose a little bit of the accuracy. The flatten() command will do this for you, and so the following will work: set A to 14 set B to 15 div(Z, A, B) flatten(Z, 2) ; flatten Z to two decimal places if (Z = 0.93) message("division successful!") B r a n c h e s ~~~~~~~~~~~~~~~ Simple branches You have the ability to branch. In this version of the script interpreter, you may have up to sixteen branches. You may branch from any part of the script to any part of the script. This makes it simple to construct loops and multi-line IF style structures. Branches are written as a period followed by the branch name, like: .branchpoint and: .herewegoroundthemulberrybush All labels are read when the script begins. You use the go() command to branch to a label. For example: .begin A++ go("begin") This example simply increments A, then goes back to the start. Forever. Please refer to the examples, as branches are used quite a lot in the examples. Procedural branches As of script version 1.05, you can also use a 'procedural' call. In this case, instead of using the go() command, you use the call() command. The syntax is identical. This will, however, record where you called from. This means you can, at the end of your 'procedure', use the return() command to go back to the caller. In this way, you can now create simple 'procedures' and 'functions' that will correctly return, without convoluted go() fanangling. You can nest up to 16 call()s. C o n d i t i o n a l s ~~~~~~~~~~~~~~~~~~~~~~~ In BASIC, you can do things like: IF ( (A = 1) AND (B = 2) AND (C = 3) ) THEN ...do something... You can do this in script by knowing that the if() syntax is: if( ) If the condition evaluates to be true, then the 'command' is executed. There is no reason why the 'command' cannot be another if() command. So, first, the conditions: ! Not equal = Equal < Less than > Greater than [ Less than or equal to ] Greater than or equal to All of the following examples evaluate to TRUE, so the command would be executed. set A to 123 if(A = 123) ... if(A ! 0) ... if(A > 12) ... if(A < 1973) ... if(A [ 123) ... if(A ] 123) ... All of the following evaluate to FALSE. Remember, A is zero at script start. if(A = 123) ... if(A ! 0) ... if(A > 12) ... You get the idea. So, that line of BASIC at the very start could be written as: if(A = 1) if(B = 3) if(C = 3) ...do something... Now let's assume you have read some data from a teletext frame, and you'd like to convert it. We shall invent an algorythm, where: result = ( (((input << 3 ) / 4) >> 1) * 5 ) We only want to do this to numbers over 100, but we may be called with numbers less than 100. What we need is an 'if()' spanning several lines. There are two ways to do this: The wrong way... if(Q ] 100) lsl(Q, 3) if(Q ] 100) div(Q, Q, 4) if(Q ] 100) lsr(Q, 2) if(Q ] 100) mul(Q, Q, 5) The right way... if(Q < 100) go("skipthisstuff") lsl(Q, 3) div(Q, Q, 4) lsr(Q, 2) mul(Q, Q, 5) .skipthisstuff Note that you can compare a float with an integer. 99.4 is less than 100, 103.2 is more... L o o p i n g ~~~~~~~~~~~~~ There are no such things as FOR...NEXT loops or DO...WHILE or the like. Instead, you have an if() clause which can control how you move around the script. For example: FOR a% = 1 TO 20 ...do something... NEXT This can also be expressed, in BASIC, as: a% = 1 REPEAT ...do something... a% += 1 UNTIL a% > 20 So using this concept, we can write it in our script as: set A to 1 .comebackhere ...do something... A++ if (A [ 20) go("comebackhere") F i l e h a n d l i n g ~~~~~~~~~~~~~~~~~~~~~~~~~ In the script, you may output to a file. You open the file with: filewrite(...) or fileupdate(...) The former creates a 'new' file, replacing anything currently there. The latter appends output to an existing file (the file pointer will be set to the end of the file automatically). If there is no previously existing file for fileupdate(), one will be created. Then you use: appendframe(...) or: appendframes(...) to write output to the file. If you only want to output a single line, you can use: appendline(...) You can also use: filewritebyte(...) if you need to write a single byte (such as a newline code) to the file. filewritestring(...) if you need to write a string to the file. filewritevar(...) if you need to write the value of a variable to the file. Finally, you close the file with: fileclose() You can, if you want, set the filetype with: filetype() You can open and use as many files as you like. But only one may be open and in use at any given time. There is no checking to see if the file currently exists. If the file cannot be opened, the script will abort with an error. You may use status() to read the file handle, though there is little real reason to actually know this... Ovation Pro DDL files Scripts may now create OvationPro DDL files. This is a three part process. Firstly, you should output the DDL header, using filewriteovnhead(). Then you output your desired frames, specifying OvationPro DDL type (type 3). You may, if you wish, insert other output; however you will need to understand the basics of OvationPro DDL files in order to do this. Finally, you finish your DDL file by writing the tail, using filewriteovntail(). You should now have a DDL file that'll load right in to OvationPro. O v a t i o n P r o D D L h i n t s ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To output an OvationPro DDL file, you would... filewrite("") filewriteovnhead() ; must be the FIRST thing you output ; output the frames, ie: ; appendframe(x,3) ; in a loop, or whatever filewriteovntail() ; must be the LAST thing you output fileclose() filetype("", &B25) The filewriteovnhead command outputs: // minimal OvationPro DDL script created by Teletext (1.49) COL_00={colour "Ttx Black" {rgb 0x0 0x0 0x0}} COL_01={colour "Ttx Red" {rgb 0x10000 0x0 0x0}} COL_02={colour "Ttx Green" {rgb 0x0 0x10000 0x0}} COL_03={colour "Ttx Yellow" {rgb 0x10000 0x10000 0x0}} COL_04={colour "Ttx Blue" {rgb 0x0 0x0 0x10000}} COL_05={colour "Ttx Magenta" {rgb 0x10000 0x0 0x10000}} COL_06={colour "Ttx Cyan" {rgb 0x0 0x10000 0x10000}} COL_07={colour "Ttx White" {rgb 0x10000 0x10000 0x10000}} STORY_001={story You can replace this with your own header if you wish, however you must define the colours as above, because the output frames will expect them to be present. The append[given]frame[s] commands, with type 3 output, write the frames exactly as for the OvationPro DDL output from the Save dialogue. Please refer to any saved file for an example. The filewriteovntail command outputs: {endoftext} } This tells OvationPro that the DDL file has finished. The minimum you can get away with is a single '}' character, but you might as well call this function and end properly... A suggestion is to 'centre' the output. For this, you should: filewrite("") filewriteovnhead() filewritestring("{align 1}") ; output follows... The alignment types are: left 0 centre 1 right 2 justified 3 You might like to output headings before each group of pages. The example below shows outputting a heading, and then outputs the pages 101 to 110. ; NEWS filewritestring("{newpara}{textsize 16000}{bold 1}{underline 1}") filewritebyte(34) filewritestring("Headlines") filewritebyte(34) filewritestring("{bold 0}{underline 0}{textsize 12000}") filewritebyte(34) filewritestring(" (from CNN)") filewritebyte(34) filewritebyte(10) filewritestring("{textsize}{bold}{underline}{newpara}") set A to 101 .writenews appendframe(A, 3) A++ if (A [ 110) go("writenews") You have various commands at your disposal. A command with no parameter will restore the option to the default for the current style (which will probably be Trinity.Medium, 12pt, left aligned, no bold/italic/underline). {align #} Sets the alignment to that specified (0-left, 1-centre, 2-right, 3-justified). {bold #} Use {bold 1} to switch bold on, and {bold 0} to switch it off. {italic #} Use {italic 1} to switch italics on, and {italic 0} to switch it off. {newpara} If you want a line break, use {newpara}. {textsize #} The text size is 'pointsize * 1000', so 12000 is 12pt, and 24000 is 24pt... {underline #} Use {underline 1} to switch underline on, {underline 0} for off... All text must be enclosed in "quotes", including spaces between items. The following is valid: {bold 1} "this text is in bold" {bold 0} Even the following would work: {bold 1} "this " "text " "is " "in " "bold" {bold 0} This, however, won't work: {bold 1} this text is in bold {bold 0} If you need to include the characters '{', '\', or '"' For: Use: { \{ \ \\ " \" There are all sorts of nifty things possible, such as tab-aligned results from a calculation, but these things require a deeper knowledge of the OvationPro DDL script, and that is outside of the scope of this document. Remember, you can always 'fake' what you'd like to see in OvationPro, then save the document in DDL format to an editor such as !Edit. This will allow you to see how something was achieved, and thus allow you to use it in your own generated output. A useful suggestion is to save a number of 'desired' frames to a directory, and then load them in using the 'undocumented' cache_loaddirectory() command. This will save you waiting for each frame fetch while testing and tweaking your !Teletext script to produce exactly the DDL script that you want. D e b u g g i n g ~~~~~~~~~~~~~~~~~ There are no debugging commands provided in the proper script definition. However, judicial use of if() and two 'undocumented' commands can assist you in debugging your programs. report_var(var) This command will pop up a message box telling you what the value if the specified variable is. lvar() This command will insert a variables list into the debug log. If you have a RAMdisc defined, then the script interpreter will create a file called "ttxscrplog". Everything on line 2 of the report window will be written to this log. Ensure your disc is large enough, as running out of space for the log is classed as a script error (not intentionally, the script error handler picks it up). To give you an idea, "movietest" generated a 66K log. Line one (the upper line) is not logged, so you are spared loads of lines saying "Executing script - 69% (64)". One thing to note, the line number reporting is not totally accurate. I patched this facility into the existing script interpreter to aid me in debugging (as percentages were a pain to figure out). It can get confused by lots of branching. :-) P o l l i n g ~~~~~~~~~~~~~ Early versions of the script interpreter offered two modes of operation. The usual mode was multitasking, where the script interpreter would regularly poll the wimp so the system kept running fairly smoothly; or a singletasking mode where all wimp operations were suspended and full attention was given to the script. These systems are both inefficient. In multitasking, you can take a speed hit while doing things that involve a lot of repetive processing. Likewise with singletasking, waiting for a page to arrive is simply a waste of processor cycles, the script isn't doing anything and neither are you. The way around this is to allow the script to have some control over it's own polling rate... When scripts are started, they are entered in a multitasking mode. If this is acceptable, you need do nothing. Otherwise: poll_disable() Switch to singletasking mode. poll_enable() Switch back to multitasking mode. poll_now() If you are in singletasking mode and you don't want to block the system completely, you can poll yourself at regular points in your script. This command has no effect in multitasking mode. Additionally, you can control that little status window that pops up: poll_nomessages() Disable the output of status messages, and close the window. poll_message( <"message text"> ) Output a message to the status window. If output was disabled, this will cause it to be re-enabled and the window reopened. For example: ; script started - multitasking mode ; stuff to do with frame fetching, much time is spent ; waiting, so we'll poll normally... .getframe_loop ...getframe... go(getframe_loop) ; now we come to do some processing, so we will jump ; to singletasking mode to speed it up poll_nomessages() poll_disable() .process_loop ...do stuff... go(process_loop) terminate() N u m b e r s ~~~~~~~~~~~~~ The script understands only integer numbers, ie whole numbers (1, 2, 3). You cannot specify fractions or decimal points (ie, 2.5). You can set variables to chosen values using: set to Alternatively, you can provide numbers as arguments to many functions. Normally, numbers are expressed in denary, ie base 10, the normal way. Sometimes it is preferable to give numbers in hex (base 16) or binary (base 2). To specify a number in hex, preceed it with '&'. To specify a number in binary, preceed it with '%'. These examples all set the chosen variable to 123: set A to 123 set B to &7B set C to %1111011 Alternatively, you can fetch the news index page 102 on BBC 1 using something like: channel(1) getframe(102) But if you were perverse, the following would work: A++ channel(A) getframe(&66) The above works only as the start of a script, as all variables are set to zero when the script begins. Later on, A might not be zero. You can set a number to be negative simply by prefixing it with a '-'. For example: set A to -123 set B to -&7B set C to -%01111011 R e a l m a t h s ~~~~~~~~~~~~~~~~~~~ Maths plays a part in all scripting when you get beyond the really basic things. Before we start, remember that "" means a variable is required, and "" means either a number OR a variable may be given. You have the following mathematical possibilities with the script: Operation Command(s) Addition = + add(, ) = + 1 add(, 1) [see below] = + set to add(, ) Subtraction = - sub(, ) = - 1 sub(, 1) [see below] = - set to sub(, ) As a shorthand, you can use: ++ instead of add(,1) and -- instead of sub(,1) Multiplication = * mul(, , ) Division If output is to an integer, it is rounded down, so 3 / 2 = 1. = / div(, , ) To calculate a modulus: = MOD mod(, , ) More complicated maths To convert a number to absolute form: = ABS() abs(, ) To determine the sign of a number: = SGN() sign(, ) (returns -1 for negative, 0 for 0, +1 for positive) Square root: = SQR() sqr(, ) L o g i c a l m a t h s ~~~~~~~~~~~~~~~~~~~~~~~~~ Logic = AND and(, , ) = EOR eor(, , ) = NOT not(, ) = OR or(, , ) = << lsl(, , ) = >> lsr(, , ) A brief introduction to logic Logic operations are bitwise operations. That is, they do not treat a number as a number, they treat it as a series of bits which may be manipulated. Binary values are base two. Thus, %100110 is 32 + 4 + 2 which is 38. In binary, the rightmost number is 1, and each number to the left is twice the value of the number before. Typically, you will come across some terms, a byte and a word. The term byte is defined as being eight bits. Thus, it can have the values 0 to 255. Here's how... 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 Thus, %01011010 is 64 + 16 + 8 + 2, so %01011010 is 90. There is a lot of confusion as to the precise meaning of word. Usually, a word is the width of the data path of the processor. Early words were 16 bits (and Acorn assembler supports this with the EQUW instruction), though these are now often referred to as half-word. Now, words are 32 bits (they used to be double-word, hence EQUD in Acorn assembler). No doubt in the future when 64 or 128 bit processors are commonplace, they'll look at our 32bit word with affection and nostalgia. You know, remember those days when people actually bought MicroSoft products and didn't get instantly dismissed! For what it's worth, I consider a word to be 32 bits. But then, I pronounce ADFS as four letters, so who am I to be paid attention to? :-) and AND sets the bit if the source and the corresponding mask are both set. 0 0 0 0 1 0 1 0 0 1 1 1 AND is a way to constrain values to a given boundary. To take any number and make it a byte, simply AND with 255. eor Exclusive OR is the opposite of OR, in that the bit is set if the source and the mask bits are different. 0 0 0 0 1 1 1 0 1 1 1 0 not NOT simply inverts the bits. = %10101100 (172) Result = %01010011 (83) Note that all 32 bits will be inverted, so the number may be something odd (ie, NOT 1 is -2). If you are dealing with bytes, you should then AND the result with 255 to get the correct result, 254. or OR will set the bit if either the source OR the mask are set. 0 0 0 0 1 1 1 0 1 1 1 1 lsl Logical shift left. This shifts the bits places to the left. For example: lsl(A, 12, 2) On entry, the bits are %00001100 (12). They get shifted two places to the left. The result, the value placed in A, is thus %00110000 (48). This could be used as primative (!) multiplication. lsr Logical shift right. This shifts the bits places to the right. For example: lsl(A, 12, 2) On entry, the bits are %00001100 (12). They get shifted two places to the right. The result, the value placed in A, is thus %00000011 (3). This could be used as primative (!) division. Shifts to the right are always treated as unsigned. ========================================================================== ; ~ Syntax: ; <"text"> The semi-colon ';' at the start of any line defines that line as a comment. The line is ignored. You could also put comments on the right hand side of executed lines, as the script interpreter only scans as far as it needs to. . ~ Syntax: .