💾 Archived View for rawtext.club › ~sloum › nimf › guide › 10-memory.gmi captured on 2023-04-26 at 13:36:45. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-01-29)
-=-=-=-=-=-=-
____ ____ ____ ____ ||n |||i |||m |||f || ||__|||__|||__|||__|| |/__\|/__\|/__\|/__\|
In addition to the stack nimf has persistent memory available for use. The quickstart and string sections of this guide covered referencing memory addresses and basic variable usage. In this section we will go over the different types of variables and how they are used.
Something else that is important is that while nimf has a default memory amount, this can be altered at runtime. So the amount of memory space on a given system or run of the interpreter may vary.
Variables default to being sized to a single cell (the size of a golang int on your system, likely either 32 or 64 bits). From this basic variable you can store numbers, characters, flags, other memory addresses, etc. You can also extend variables for use in more complex structures or easily store and retrieve strings in memory.
Much of this section will show some example code and walk through it to teach the concept.
Variables can be named anything you like with the following exceptions:
As a convention, not enforced at a code level, you may create private words by using the following variable/word naming scheme: `[module-name].private.[word-or-var-name]`. For example: `url.private.port` would be part of the url module and is intended to only be used internally so is marked private, it is then given the name port. Using private in this way excludes any variables and words containing .private. from the word listing produced by the word `words`. In reality you can still call private words if you like, but private is one way a developer can provide intent to other developers.
The following code will be referenced as an example by the rest of this section:
var myvar myvar . ( The address of myvar: 101234 ) myvar get . ( The value of myvar: 0 ) 5 myvar set myvar get . ( The value of myvar: 5 ) 8 myvar +! ( Adds 8 to the value of myvar - not to the address ) myvar get . ( The value of myvar: 13 )
The first line, above, adds the name `myvar` to the dictionary and assigns it a memory address. The second line prints the address of `myvar`. The third line prints the value at that address. All variables can be thought of as pointers, for those familiar with the term. You can get the value stored at an address with `get` or `@` (`get` is an alias of `@`). Since we have not given `myvar` a value yet, the value is `0`. The `set` or `!` builtins update a memory address. The next line shows that the value has been updated. The `+!` subroutine adds to the value held at an address (it adjust the value not the memory address itself) and the last line shows the result of this update.
nimf has the ability to store variables that take up multiple cells. This can be done with the `allot` builtin. Using `allot` will allow you to create something like arrays and is used often for managing strings.
: ? ( addr -- ) @ . ; var myArray ( Reserve a memory address ) 5 allot ( myArray is already 1 cell, '5 allot' adds 5 more for a total of 6) 5 myArray set ( Set myArray to 5, so that the length of the array can be referenced ) 9 myArray 1 + set ( Set the first offset, the first non-length value, to 9 by referencing `myArray 1 +` ) 2 myArray 2 + set ( Set the second offset to 2 by referencing `myArray 2 +` ) myArray ? myArray ++ ? myArray 2 + ? ( 5 9 2 )
In the above example we first create a simple way to print the value at a memory location via the new word `?`.
After creating `?` we create a variable `myArr`. We then `allot` 2 extra cells for this variable. `allot` does not take a memory address, it just expands the most recently created variable. We then update some cells in the "array" to a value, this is done by providing an offset to `myArray`. Lastly we use our newly created `?` to view the value at each offset position.
`allot` opperates in an increasing manner on the next available memory. You cannot define a variable `(A)` then another variable `(B)` and go back an allot more for `(A)`. You must allot before creating any other variables. Note that it is possible that inlining a module may also assign new variables and make it so that you can no longer `allot` for a variable you created, so be aware of this limitation of `allot` when creating additional variables or inlining modules. Best practice is to initialize a `var`, `svar` or `local` and then immediately `allot`.
Strings are covered in their own section of the nimf guide, so this information may be redundant. If you have already read chapter 9, feel free to skip this information. If you hav enot read it and want to do so before continuing, you may do so here:
"text" inline "hello" svar hi ( Puts 'hello' into the temporary string buffer, reserves memory space, copies the string into it, and adds a new word, 'hi' ) hi . ( Memory address: 63214 ) hi str.print ( Outputs: hello ) str.print-buf ( Outputs: hello ) "hola" str.print-buf ( Outputs: hola ) hi str.print ( Outputs: hello ) hi @ . ( Outputs: 5, the length of the string ) hi 2 + @ emit ( Outputs: e, the second char )
We first inline the `text` module so that we have access to the print oriented words (which could easily be defined on your own without the need for `text`, but that is not in the scope of the conversation here). We then create a string `hello`. That puts hello into the temporary string buffer. Calling `str.print-buf` or `str.buf-addr str.print` would print out `hello`. Instead we call `svar hi`, which secures enough memory to hold the string found in the temporary string buffer and copies the string, including its length, to the memory location secured by `svar`. Following the assignment is an example of printing the string as well as printing the temporary string buffer (which still contains the same string). We then add a new string to the temporary string buffer and print it, then print `hi` to show that they now differ. Outputting the value at `hi` gives the length of the string. Lastly we output the value at `hi 2 +`, as a character via `emit`, which will convert an integer to a character and output it. This yields `e`.
The words found in the text module know to treat the first value as the length and the rest of them as characters. For example, calling: `108 emit` would print `l`. Mapping strings in this way allows you to get part of a string based on a simple offset value. The third character of the above example can be acquired very easily: `hi 3 + @`. In other words: add 3 to the address represented by `hi` and get the value stored there via `@` or `get`. In that light, arrays and strings can be thought of as 1 indexed, as opposed to the often more common 0 index found in many other programming languages (though certainly not all, Lua is a good example of a 1 indexed language).
Much like simple variables, strings also have getters and setters. Calling `hi get-string`, referencing the above example, would copy the value of the string that `hi` references into the temporary string buffer. Moving in the other direction we can move a string from the temporary string buffer to a memory address with `set-string`: `"hola" 2300 set-string`. That example would move the string `"hola"` to memory address `2300` (with its length, 4, being at address 2300, 2301 being `h`, etc). This is useful, but dangerous: you need to know that enough memory is writable at that spot to support the string. Otherwise you risk overwriting memory you might be using for something else. So, be careful. It is often useful to allot more memory than you need via `var myStringSpace 500 allot` or the like. Then you can always overwrite up to 500 characters when assigning strings to the variable `myStringSpace`. You can use `var` and `allot` to manage string memory in a more fine grained way and `svar` when you have a string already and just want it moved into memory.
Local variables are the only kind of variable that can be created within a word definition. In fact, they can only be defined inside of a word definition. When the word finishes executing all of the words that it is composed of, the local variables will be cleared from memory automatically.
: local-example local x 5 x set x , space cr ( print the memory address of `x` ) x get ( print the value of `x` ) ; local-example x ,
In the above example we create a word `local-example`. In that word we use the word `local` to create a variable named `x`. The value held at memory address `x` is updated to `5` and that value is printed, along with the value of `x` itself, which is a memory address. We call the word after defining it. After it prints the memory address of `x` it completes the word and clears the memory. When `x` is referenced outside of `local-example`, `x` will not be found in the word dictionary and an error will be thrown (unless there is a global variable named `x`).
Some things to note:
Most of the ways to work with memory are covered by discussing variables. However, there are a few things to know:
Memory can be a murkier and more difficult area of working with nimf as not too much beyond some basics is provided to help you along. As a result you end up writing helper words to get things done. There is both challenge and freedom in this (in the form of being able to shoot your own foot if you aren't careful).
________________________________________________