💾 Archived View for rawtext.club › ~sloum › nimf › guide › 04-quickstart.gmi captured on 2023-05-24 at 18:17:25. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-01-29)
-=-=-=-=-=-=-
____ ____ ____ ____ ||n |||i |||m |||f || ||__|||__|||__|||__|| |/__\|/__\|/__\|/__\|
Welcome to the nimf quickstart! This section of the nimf guide is designed to get you writing nimf code as quickly as possible, but without a lot of the detail that comes in the sections that follow. The things that will be covered in this quickstart include:
Things that are not covered in the quickstart:
You may be thinking: "no strings? no variables? no conditionals or loops? Why not?"
The reason is that it is pretty difficult to use any of those things in nimf without a bit more of a deep dive into those individual concepts. For now we're just going to get used to the stack a bit and get comfortable with some of the more simple concepts.
In nimf anything that is typed into the interpreter (or read from a file or run line) is either a number or a word. If the interpreter does not know a word, it will let you know.
All values in memory that are not words (which have their own storage separate from the rest of nimf's memory) are stored as numbers. Memory addresses, values, strings, characters, etc. are all numbers.
When you enter a number into the interpreter it will push it onto the data stack. If this is your first time working with a stack just know that it is a first in last out situation. So if we push the numbers: 5, 6, 7 onto the stack in that order, they will come off of the stack as 7, 6, 5. You cannot get to 5 without first going through 7 and 6.
Once there are numbers on the stack you can use various words to manipulate the stack.
In the following example we will add some numbers to the stack and view the contents of the stack with the `.s` word. We will then see the stack depth with the `depth` word.
12 57 5 .s
The first line adds the numbers to the stack in the order they are entered (left to right). The next line, containing the `.s` word, will trigger a display of the current stack state. `.s` could have been listed on the same line as the numbers and has been moved to its own line here for clarity.
The above code produces the following output:
<3>[12 57 5]
The `<3>` is the depth of the stack. Everything inside the `[ ... ]` is the contents of the data stack. In nimf there are two stacks: data and return. You will almost always be using the data stack since the return stack is mostly used for internal operation of the nimf interpreter. You can use it, but should be cautious. This will be covered in the stacks section of the nimf guide.
Continuing from above we can see we have these numbers on the stack, but what if we want to take one off of the stack? What if we want it dispalyed?
With the numbers from the example still on the stack we could use the word `drop` to remove an item from the stack:
drop .s
So we dropped a value from the stack, which does not output anything (it just removes the value on the top of the stack), and then called `.s` again to see the contents of the stack.
This would output:
<2>[12 57]
Lets say we wanted to output the numbers instead of looking at the stack. We could use the word `,`. This word will output the top value on the stack and then remove it from the stack. `,` does not provide additional spacing after the value it outputs, so you will likely want to call the word `space` afterward (if you are going to print multiple values you will want to tell them apart and not have them run together). The `std` module has a word, `.`, which does just that: `, space`, which we will cover later. For now we'll do the long way. Just to see what happens we will try to output more values than are present on the stack. We know from our last call of `.s` that we have two numbers on the stack, so lets call `, space` three times and see what happens:
, space , space , space
The above code will output:
57 12 Error: Stack Underflow
It outputs the two values and spaces as requested and then throws an error letting us know we tried to work with a value on the stack, but none was present. When we encounter an error in interpretive mode the stack will be cleared. Since the stack was already empty, we can just continue on in this case.
To see the available words you can use the wordf `words`. The output of this word will vary based on what modules you have inlined and what words you yourself have added to the session.
If you have not inlined any modules the output will look something like this:
! % & * + +! , - .r .s / : ; < << <r = > >> @ allot bye clearstack cr depth do drop dup else emit error file.close file.exists? file.open file.read file.write get get-env get-raw-string get-string halt if inline input key loop module-end over pick r> r@ rdepth roll s! s@ see set set-string sleep space sr@ svar swap tcp.close tcp.connect tcp.read tcp.write then timenow var winsize words xor |
These are all of the current builtins available in nimf. Normally you could use the word `see` with the word you want to get a definition of:
see get-env
However, builtins are coded in Go and are not available as nimf definitions, so you will see output like the following:
get-env is a built-in
For the time being you will need to use the nimf guide's api listing to see how these words work, though many are self explanatory.
You may have noticed some familiar things in the output of `words`, such as `+`, `-`, `*`, and `/`. You may ahve also noticed words we have talked about such as `.s`, `drop`, `,`, and `space`.
Lets introduce some math. In the code below, remember that you can add extra whitespace wherever you see fit, so I have done so to line things up a bit.
3 5 + .s cr 4 - dup , cr 3 * .s cr 2 / , cr .s
The above, very contrived example does the following:
We get the following output from the above example:
<1> [8] 4 <1> [12] 6 <0> []
If you'd like to see it next to the original code, we can put the output into comments inline with the code like so:
3 5 + .s cr ( <1> [8] ) 4 - dup , cr ( 4 ) 3 * .s cr ( <1> [12] ) 2 / , cr ( 6 ) .s ( <0> [] )
Since nimf executes from left to right and the word that functions as an operator always comes after the operands there is no need for PEMDAS or traditional order of operation. Concatenative programming languages lend themselves well to very clear math.
For example the problem: 2 + 3 * 4. As someone reading the problem you have to read ahead and know that there is multiplication coming up and that it must be done before the addition. It could be made more explicitly clear by adding parenthesis: 2 + (3 * 4), but you still have to read ahead and then move backward to do the addition. This is a simple example that of course could be re-ordered to be: 3 * 4 + 2. But this flexibility of expression is also a curse in that there is an inherent lack of clarity and often reading ahead that must occur. This does not happen with math in nimf.
The above problem of: 2 + 3 * 4, would be writtenone way in nimf and it would be read left to right. You would write it like so:
3 4 * 2 +
Thinking of math in this way where everything reads left to right makes a lot of sense, and I often wonder why we don't teach children math in this way.
The following modules come included with nimf as a part of its standard library:
If we want to be able to use code from one of these modules we need to use the word `inline`. We do this in combination with a string literal, which will be covered in more detail in a later section of the nimf guide.
Lets try inlining the `num` module. One thing to note is that inlining is, due to a bug that is currently being worked on, the only word that MUST have a newline entered after it. You can have other code on the line before you call `inline`, but `inline` must be the last word on its line.
To inline `num` you would enter the following:
"num" inline words
As you can see we use double quotes (which themselves are a nimf word amd thus must be surrounded by whitespace) then the contents of the string, in this case 'num', then a closing double quote (again, as a word surrounded by whitespace), then the word `inline`. I then called words so that we can see what words we have available, which produces the following output:
! != % & * + +! ++ +@ , - -- . .r .s / /% 2drop 2dup 2over 2swap 3drop 3dup : ; < << <= <r = > >= >> ? @ ^ allot and bye clearstack cr depth do drop dup else emit error false? file.close file.exists? file.open file.read file.write get get-env get-raw-string get-string halt if inline input key loop module-end ndup num.abs num.between num.digit-length num.max num.min num.negate num.negative? num.positive? num.print-left-pad num.print-right-pad num.within num.zero? or over pick r> r@ rdepth rdrop roll rot s! s@ see set set-false set-string set-true sleep space spaces sr@ std.i std.t std.x std.y svar swap tcp.close tcp.connect tcp.read tcp.write then timenow true? tuck var winsize words xor |
As you can see, there are a LOT more words than there were before. The `num` modules inlines `std` as well, so both end up in the global scope for us to access. But lets focus on words from num, which have a reliable naming scheme (std does not follow the same naming scheme, for reasons given in its api documentation): `num.[something]`.
We can try out one of these new words. Maybe we can try `num.negative?` and `num.min`. First we'll probably want to know what they do. Since the `num` module itself was coded in nimf, we have access to see the exact word definition and comment detailing its usage. To do so, lets use the word `see`:
see num.negative?
Which will output:
: num.negative? ( n -- flag ) 0 < ;
We can see a colon, which is used to define a word, followed by the name of the word. Then is the comment describing its stack effects. Elsewhere in the documentaiton this is refered to as the word's signature. After the comment we can see the values and words called to create the word itself, followed by a semi-colon (which ends the word definition).
So we can see that `num.negative?` takes a value, `n`, from the top of the stack and replaces it with a flag. Flags in nimf function as boolean values and are usually either -1 (true) or 0 (false). In reality, any value other than 0 is a truthy value, but -1 is used for flags by convention.
We can try this out:
-105 num.negative? , cr 42 num.negative? ,
What do you think that will output?
-1 0
The flags that were output tell us that -105 was negative, since our call to `num.negative?` resulted in a truthy flag (-1). It also told us that 42 was not negative, since our call to `num.negative?` produced a falsy flag (0). Keep in mind that we used `,` to output these values, but in actual code they would likely be left on the stack to be used by another word.
We also wanted to see `num.min` in action. I'll combine the code to see it with the output of `see` so we can be a little more brief with this one:
see num.min cr 13 42 num.min .s
The above code would output:
: num.min ( n1 n2 -- min ) 2dup < if drop else swap drop then ; <1> [13]
As you can see, `num.min` takes the top two values from the stack and puts the lower value back on the stack.
Since comments were covered in detail in the last section of the nimf guide (recommended reading if you have not read it yet), I will leave you here. This should give you enough of the basics to play around with the words available to you, to inline modules, to use comments, and to do basic arithmetic and stack operations.
________________________________________________