MINT Tutorial This document assumes that you have loaded it into Freemacs. You should read this document and try the examples that are given. You may use the arrow keys to view the rest of the document. Examples You will be shown an example program, indented one tab stop, and asked to execute it. Rather than force you to type the example in, a MINT program has been written which executes the example directly from the text. This MINT program is appropriately called "try-it". Try it will execute the program that appears on the line that the cursor is on. The result of the program, if any, will be displayed inside quotes at the top of the screen. To try an example, position the cursor anywhere on the line containing the example and type the F2 key. What you have just done is to execute the try it program. Don't try to understand exactly how this works yet, just take for granted that it works. As The Great Wizard of Oz told Dorothy, "Pay no attention to the man behind the curtain." The Basics The basic organizing principle of MINT is this: Everything is a string, and strings are sequences of arbitrary characters. Therefore, programs are strings and data are strings (yes, data really is plural). The following example is a valid MINT program. It doesn't do anything, but it's valid. This is a valid MINT program. Notice that the result of executing the program is just the program itself. This is because MINT didn't find any functions to execute. A Single Function Now, for a more useful program, you need to know how MINT recognizes functions. A MINT function to add two numbers is given below. The two character sequence #( begins the function, ++ is the name of the function, and 5 and 7 are the arguments to the function, separated by commas. The function adds both its arguments and returns the result as its value. Try this program in the same manner as before, that is, place the cursor on the line and press F2. The sum of five and seven is #(++,5,7). Notice that the MINT function is replaced by "12". This is how MINT evaluates its functions. When a function is recognized, the function is executed, and its invocation is replaced by its value. Multiple Functions Functions may be concatenated. Try the following example. #(++,1,1)#(++,2,2) You will discover that the result appears to be, and is, twenty four. This is because you concatenated the results of #(++,1,1) and #(++,2,2) to get a two and a four next to each other. Since MINT deals with strings, a two and a four becomes twenty four. Functions may be nested. Following the logic of the preceding example, nested functions are evaluated in the following manner: the function that is found first is executed first, and its invocation is replaced by its value. The next function to be found is executed, and its invocation is replaced by its value. Try the following example. #(**,3,#(++,5,7)) Notice that the value is 36, which is three multipled by twelve. Recognizing Functions MINT recognizes functions by examining the input string character by character. A # followed by a ( is taken to be the beginning of a function. A , separates arguments from each other. A ) causes a function to be recognized and evaluated. Let's try the following examples. #(an,Hello There) The "an" function causes its first argument to be placed on the bottom line of the screen. #(an,Hello There Leaving off the closing paren causes the function to execute improperly. #an,Hello There) Leaving off the opening paren causes the function to execute improperly. (an,Hello There) Leaving off the sharp has a different, desirable effect. If the MINT scanner encounters an open paren, then all the characters to the next matching close paren are skipped. This is called protecting a string. (#(an,Hello There)) Notice that the "an" function is not executed. This happens because those characters are passed over by the scanner. (#(**,2,#(++,5,7))) Watch what happens when we leave off a close paren. (#(**,2,#(++,5,7)) We end up with the wrong thing being executed. Watch what happens with an extra close paren. (#(**,2,#(++,5,7)))) Now nothing happens. Protection works only if the parentheses are balanced! There are more details concerning scanning, but we'll leave those for later. Strings Not unexpectedly, MINT lets you save strings under a given name. There are no restrictions on what you name a string. Obviously, there are certain characters that you should avoid including in a name, namely open paren, comma, and close paren, and control characters. Let's try an example. Let's define a string called "my-test" and give it the value "this is a silly test string". This is an example of the define string function. #(ds,my-test,this is a silly test string) Wow! You just defined a string! Big, fat, hairy deal. Defining a string is useless until you know how to get it back, which leads us to the next example. This is an example of the get string function. #(gs,my-test) You don't always have to get the entire string. There is a pointer associated with every string. The gs function doesn't change it, but some of the following functions do. You can get the string one character at a time, and the pointer is advanced by one. Try this example several times. #(go,my-test) Notice that you get the characters of "my-test" in sequence, starting at the beginning. You might be wondering how to position the pointer back to the beginning. Well, the following example restores the string pointer. #(rs,my-test) Now that you're back at the beginning, you're ready to get several characters, using the get n characters function. Following this example, the string pointer is positioned to the "s" of "is". #(ds,my-test,this is a silly test string) #(gn,my-test,6) You might be wondering what happens if you try to get more characters than are left following the string pointer. #(ds,my-test,this is a silly test string) #(gn,my-test,1000) You get all of them, and no more. There are no more characters left following the string pointer. What happens if you try to get one when there aren't any to get? #(ds,my-test,this is a silly test string) #(gn,my-test,1000) #(gn,my-test,1,gn says no more) #(go,my-test,go also says no more) The second argument of get one and the third argument of get n is returned if there aren't any characters left to get. The form pointer is used by #(gs) also. #(ds,my-test,this is a silly test string) #(gn,my-test,6) #(gs,my-test) Well, you could probably search a string for a given substring using the above functions, but it would be too much work. A function is provided for just that purpose. The first match function finds the first match of the given string. #(ds,my-test,this is a silly test string) #(fm,my-test,silly) The value of first match is the string up to the found string. The string pointer, however, is positioned after the last character of the found match. This is a nice property. Let's use it to get the contents of "my-test" one word at a time by searching for the space between words. First, we'll restore the string pointer. #(rs,my-test) #(fm,my-test, ) #(fm,my-test, ) #(fm,my-test, ) #(fm,my-test, ) #(fm,my-test, ) #(fm,my-test, ) Aha! We didn't get "string, the last word of "my-test". Well, of course we didn't because there isn't any space following the word "test". We'll use this as an excuse to explain the great lie. The Great Lie I've been leading you down the primrose path. To make things simpler, I've only told you about one type of function invocation. There are really two types of functions, active and neutral. The function you have been using all along is the active type of function. After this function has been scanned, recognized, and evaluated, its result is re-scanned. A neutral function's result is not re-scanned. An example is in order. Let's define a string with a function in it. Remember that we have to protect the function to keep it from being executed too soon. #(ds,my-test,this is a silly test string) #(ds,my-function,(#(go,my-test))) Let's try to retrieve the string the same way that we did earlier. #(gs,my-function) The result of the function is not "#(go,my-test)" like you might imagine, but instead is the result of "#(go,my-test)". This happens because "my-function" is re-scanned, and MINT sees the "go" function. Let's try a neutral function. The double sharp means neutral, not active. ##(gs,my-function) Now we're getting somewhere! What happens if we try a triple sharp? Maybe something else magical will happen? ###(gs,my-function) Nope, no dice. The extra sharp is treated as just another character, just like the period in the next example. .##(gs,my-function) Let's go back to the first match example. What were we doing? Oh yes, we were trying to get "my-test" one word at a time. We ran into the difficulty of not having a blank at the end of "my-test". We couldn't get the following example to work when the string pointer was positioned to the first character of the last word. #(fm,my-test, ) Well, rather than keep you in suspense, I'll give you the solution and explain it afterward. #(fm,my-test, ,(#(gn,my-test,100))) Ahhhh. Success at last. But why? Well, if first match doesn't find the string, it returns its third argument. The third argument in the previous example is the "gn" function, which will get as many as it can, up to the amount specified. This gets the last word in "my-test". But wait... What if I had specified that the fm be a neutral function, i.e. I hadn't wanted to rescan the result of the first match? Wouldn't the result be "#(gn,my-test,100)"? Well, no, it wouldn't, because first match always rescans the third argument if that is what it returns. And the same goes for get one #(go) and get n #(gn). Conditionals So what good would a language be without conditionals? Not much good, so several types of conditionals are supplied. The first, equality, is demonstrated below. string == string: #(==,string,string,yes,no) strin == string: #(==,strin,string,yes,no) string == strin: #(==,string,strin,yes,no) Get the picture? Those examples are simple but not so useful. The following examples are useful but not so simple. #(ds,my-test,this is a silly test string) #(==,string,string,(#(go,my-test)),(#(gn,my-test,2))) #(==,strin,string,(#(go,my-test)),(#(gn,my-test,2))) #(==,string,strin,(#(go,my-test)),(#(gn,my-test,2))) Now you understand why active functions exist. Most of your conditionals functions will be called actively. Other conditionals functions will be explained later. You're 75% of the way to writing real programs. Programs I can see that you're just itching to write your first program. So be it. A program is simply a string that is re-scanned. Let's write a really trivial program. #(ds,trivial,(#(==,a,b,yes,no))) #(gs,trivial) Oh boy! This is getting exciting! The get string function brought trivial in, rescanned it, found the equality, evaluated it, and returned "no". But before you get too pleased with yourself, check out the next example. #(trivial) Confusion! Where is the function name? Well, there is no function name, and MINT recognizes that fact. Rather than throw up its hands in disgust, MINT assumes that you want to execute "trivial" as a program, and essentially performs a get string function on "trivial". Yes, there are some minor differences, but they will be explained in the next section. What happens if I create a string with the same name as a primitive? Well, you simply can't get that string using the latter method. The primitive name list is always searched before the string name list. Now that you know how to write programs, you'll want to pass parameters to them. Parameters Programs need parameters to be at all useful. So let's write a non trivial program, and give it parameters using the make parameter function. The second argument to make parameter is purposefully null. Its meaning will be explained later. #(ds,non trivial,(#(==,a,b,yes,no))) #(mp,non trivial,,a,b) #(non trivial,silly,test) #(non trivial,test,test) The action of make parameter is to search the specified string for each of make parameter's arguments, one at a time. Wherever a match is found, the string is removed and changed into a parameter. The third argument becomes the first parameter, the fourth argument becomes the second parameter, etc. The mysterious second parameter becomes the "zeroth" parameter. When a non-function call is found, such as the "non trivial" example above, the name of the string is substituted for the "zeroth" parameter. The use of this is explained later in the section on looping. Rather than use single characters, such as "a" and "b" used previously, we shall use arg1, arg2, arg3, etc. This helps arguments to stand out from the surrounding text. The "non trivial" program returns yes if its two parameters are equal, and no otherwise. Returning yes or no can be useful, but let's do something even more useful - return a function. Remember that we have to protect the parameters arg2 and arg3, otherwise they both will be evaluated before the equality function is evaluated. We also have to protect the functions that we are passing as parameters to null, otherwise they will get evaluated too soon. #(ds,null,(#(==,arg1,,(arg2),(arg3)))) #(mp,null,,arg1,arg2,arg3) #(null,a,(#(an,Yes)),(#(an,No))) #(null,,(#(an,Yes)),(#(an,No))) Looping MINT doesn't have while loops, or repeat-until loops, or for-next loops, although you could write any of them if you wish. MINT has something which is better, called recursion. Observe the following program, but don't try it yet! #(ds,recurse,(#(an,arg1)#(recurse,#(++,arg1,1)))) #(mp,recurse,,arg1) #(recurse,1) Notice that the program announces its parameter, and then calls itself (recurses) with one more than its argument. There's no provision for stopping! Fortunately, MINT allows you to break out of any running program with the interrupt key (Z-100 uses S-Help, IBM-PC uses C-break). Ok, try the program. When you get bored, use the interrupt key to abort the program. Clearly, we need to exercise more control over looping. Let's rewrite "recurse" so that it stops at 100. #(ds,recurse,(#(an,arg1)#(==,arg1,100,,(#(recurse,#(++,arg1,1)))))) #(mp,recurse,,arg1) #(recurse,1) Recurse announces it argument, and checks for equality with 100. The value of the equality is null if we've gotten to 100, otherwise it's a recursive call to itself with one more than itself. Let's get really tricky and use the "zeroth parameter" referred to in the previous section on parameters. Notice that we are now using the second argument to make parameter. #(ds,recurse,(#(an,arg1)#(==,arg1,100,,(#(SELF,#(++,arg1,1)))))) #(mp,recurse,SELF,arg1) #(recurse,1) When recurse is called, the SELF parameter is replaced by "recurse", so we are effectively doing the same thing as before. By using SELF, we make it obvious that recurse is recursing, and we avoid having to explicitly name it within itself. The advantage of this becomes apparent when you try to change the name of a function. The fewer places that the name appears, the easier it is to change. Math Math in MINT is performed on strings of digits. A number in MINT is considered to be all the digits at the end of a string. Try the following example. #(++,Boeing 707,Lockheed 40) You can see that the two numbers are added and their sum placed at the end of the first argument. The other four math operations work the same way. The only function whose meaning is not obvious is "%%", which means modulo. #(--,Boeing 707,Lockheed 40) #(**,Boeing 707,Lockheed 40) #(//,Boeing 707,Lockheed 40) #(%%,Boeing 707,Lockheed 40) Numeric tests are performed using the greater than function. #(g?,10,20,yes,no) #(g?,10,10,yes,no) #(g?,20,10,yes,no) Numeric conversions from one base to another are possible. The following examples convert 64 decimal to other bases. 64 decimal is #(bc,64,d,a) ASCII 64 decimal is #(bc,64,d,h) hex 64 decimal is #(bc,64,d,o) octal 64 decimal is #(bc,64,d,b) binary The same rules for decimal numbers apply to non-decimal numbers. Converting from one base to the same base is a simple way to remove the non-digit prefix string. #(bc,43210,b,b) #(bc,a9876,o,o) #(bc,cba98,d,d) #(bc,ihgfe,h,h) Base conversion provides a simple way to include a left or right paren in a program. ##(bc,40,d,a) ##(bc,41,d,a) Odds and Ends There are a few more functions to cover, most of which are interesting but not important enough to warrant their own section. The halt function unconditionally exits the MINT interpreter. Usually you want to make sure that the editor, text buffers, etc are saved before you execute this function. #(hl -- I don't think you really want to try this) The number of characters function counts the number of characters in its first argument. #(nc,abcd) One function exists strictly for the use of debugging Freemacs itself. If you execute it, you will be thrown into the debugger. This document only mentions it so that you don't run into trouble by creating a string by that name. #(db -- you don't really want to try this) Ctime returns the current time and date. If it is given an argument, then that argument is considered to be a filename. The time and date for that filename is returned. #(ct) There are several functions pertaining to strings. The list strings function returns a list of the names of the strings that have already been defined. Each name is followed by a copy of the second argument. Only those names that begin with the third argument are listed. You may be surprised by the number of names listed. Remember that the editor that you are using right now is written in MINT. #(ls,/) #(ls,/,r) Suppose we wanted to see if a string existed. We could get the string and see if what we got wasn't null, but that fails for strings that exist and are null. Well, the name test function comes to our rescue. #(n?,some silly name that doesn't exist,yes,no) #(n?,recurse,yes,no) The second example works because we defined recurse earlier. Just as strings may be defined, they may be erased using erase string. Erase string may have any number of arguments. #(ds,foobar,this is foobar) #(n?,foobar,y,n) #(es,foobar) #(n?,foobar,y,n) Just for grins, puzzle out what the following example would do. #(es -- don't do it,#(ls,(,))) Strings may be saved to and loaded from disk using save library and load library. Remember the recurse program we wrote long ago? Let's save it and my-test into a temporary file. Save library may have any number of arguments. #(sl,mine.tmp,recurse,my-test) If we ever want to load them back, we would use load library. All the strings stored in the file are loaded. #(ll,mine.tmp) Strings may be compared for alphabetic ordering. abcd = abce: #(a?,abcd,abce,yes,no) abcd = abcde: #(a?,abcd,abcde,yes,no) abce = abcd: #(a?,abce,abcd,yes,no) abcde = abcd: #(a?,abcde,abcd,yes,no) abcd = abcd: #(a?,abcd,abcd,yes,no) You may also wish to wait a specified time for a character to appear. The next example will wait for two seconds for a key to be pressed. If a key was pressed, then the key will be input. #(it,200) #(==,##(it,200),Timeout,no key pressed,key pressed) Text Buffer The following functions deal with the text buffer. There is a dual representation of text in the text buffer. One is in memory, and the other is on the screen. The function which updates this mapping is called redisplay. You may try the following example if you wish, but since you are using Freemacs to view this text buffer, the screen already matches the memory. #(rd) There are only a few functions which change the text in the buffer. One of them is the read file function, which will be discussed later. Another is the insert string function. Watch what happens when the next example is executed. #(is,Hi!) The string "Hi!" should get inserted at the beginning of the example line. Yet another function to modify the buffer is the delete to mark function. But before we can try it, we need to introduce marks. Marks To solve the problem of moving around in the buffer, the concept of the mark was created. The word mark may be familiar because Freemacs provides a mark ring that remembers where you have been. The Freemacs buffer marks are defined in terms of MINT marks. A mark is a single ASCII character. There are two types of marks - system (predefined) and user (variable). The system marks are given below, and the user marks are discussed later. . The point. < The character to the left of the point. > The character to the right of the point. { The word characters to the left of the mark. } The word characters to the right of the mark. - The non-word characters to the left of the mark. + The non-word characters to the right of the mark. ^ The beginning of this line. $ The end of this line. * The "split" mark. If the current buffer is showing in both windows, this mark is set to the point in the other window. [ The beginning of the file. ] The end of the file. Now let's try an example. Position the cursor to the beginning of the next example line, and try it several times. #(sp,>) The cursor moves to the right once each time that you try it. Position the cursor to the end of the next example line and try it several times. #(sp,<) The cursor moves to the left once each time that you try it. Position the cursor anywhere in the next example and try it. #(sp,^) The cursor always moves to the beginning of the line. Position the cursor anywhere in the next example and try it. #(sp,$) The cursor always moves to the end of the line. We'll skip over the [ and ] marks, since it's tedious to find this spot in the text again. You can imagine how they work, however. Now that you know how to put text into the buffer, you need to know how to read it out of the buffer. The read to mark function will return as its value the contents of the text buffer between the point and the specified mark. Position the cursor to various points on the next example line and try it. Notice that the function is a neutral call (Why?). ##(rm,$) ##(rm,^) ##(rm,>) ##(rm,<) Remember that I deferred talking about deleting text from the buffer? Now you'll learn how. The delete to mark function deletes the contents of the text buffer between the point and the specified mark. #(dm,$) position the cursor here >< and delete this. delete this >< #(dm,^) #(dm,>) position the cursor here >< and delete the left angle. #(dm,<) position the cursor here >< and delete the right angle. User Marks User marks come in two flavors, local and global. They are called local and global because they correspond to the high level language concepts of the same name. Global marks are always available, while only the current set of local marks are available. A local mark is used for saving the point while you go off and do something else. A global mark is used for saving the point to make it available to other functions. Global marks are allocated by using a negative argument to the pm function. This also destroys all local marks. We won't try an example right now, since the example would cause Freemacs great anguish. @ through Z Global user marks. 0 through 9 Local user marks. User marks may be set using the set mark function. The following example sets the global mark @ to the beginning of the file. #(sm,@,[) The next example sets the same mark to the point. If the second argument is missing, it defaults to the point. #(sm,@) Earlier I referred to "words". There are two types of characters - "word" characters and non-"word" characters. Rather than hard code a character type into a program, a table is used. This table is simply a string that is 256 characters long. The "word" bit is bit zero, so that if the 33 character of the syntax table string (which is "!") has an odd value, then "!" is considered to be a word character. This would be unusual. A more usual example would cause "_" to be a word character. The following example sets the string "Fsyntax" to be the syntax table. #(st,Fsyntax) Of course, you need to search the text buffer for strings. There are three primitives that let you do this. Look pattern and look regular set the pattern to be searched for, and Look actually does the searching. To search backwards for the word search, try the following example: #(pm,1) create a single user mark "0". #(lp,search) set the >search