# Input/Output ## The input stream Our main loop, through "word", feeds itself from what we call the input stream. It's an endless stream of characters exposed by the "in< ( -- c )" word. This word is a consumer for what we call the "input buffer", which is a range of characters in memory represented by the inptr and INSZ system values. When "in<" is called, the character that inptr points to is read, inptr is increased and INSZ is decreased until it reaches 0. When this happens, we "rotate" the input stream by calling an indirect word pointer NEXTIN< ( -- ?a u ). This word must return replacement values for inptr and INSZ, which just ran out of steam. The originating "in<" call will then yield from that new stream. NEXTIN< can also return u=0, in which case the originating "in<" call will yield EOF (-1), which leads "word expected" abort in a regular interpret loop, but can be used to control the end of other kinds of sub-interpret loops. Overriding the input stream is generally made thus: 1. Save current inptr, INSZ and NEXTIN< values. 2. Set new inptr and INSZ values and NEXTIN< to the "endofstream" word, which simply yields u=0. 3. Run a word that consumes "in<" until EOF is encountered. 4. Restore inptr, INSZ and NEXTIN<. Words like "f<<" temporarily plug NEXTIN< into the opened file's readbuf making it yield u=0 at the end of the file. It then runs "interpret" with that input, which is an interpret loop that runs until it encounters EOF. At that point, the old inptr, INSZ and NEXTIN< values are restored. ### Stepping back It's a common pattern to want to "peek" the next character of the input stream with the possibility to tell the system "alright, that character wasn't mine to process, I wash my hands of it". Having this ability greatly simplifies some parsing patterns. In Dusk, this possibility is realized through "stepback ( -- )". What this does is that it "rewinds" the input stream by one character, which means that the next "in<" will yield the same character it did last time. This can't be called more than once for a given "in<" because it might mess with macros [lib/macro]. When in EOF condition (if the last "in<" yielded -1), calling "stepback" has no effect. We *don't* rewind into our old stream. There is also the companion "peekback ( -- c )" which yields the last result of "in<" without stepping back. ## Accumulating input into words The main loop uses the word "word" to accumulate input coming from "in<" into a string that can then be fed into its interpret logic. Accumulating a word is done by first discarding every whitespace coming from in< and when we reach the first non-whitespace, we accumulate into the string pool [doc/usage/lit] until a whitespace is reached. We discard that whitespace, then yield the accumulated character as a string. ### wordorquote Sometimes, you'd like to read a word from the input stream, but with the possibility for it to contain whitespaces. One such example is "stringlist" from [lib/str]. You could use the '"' word for this, but this means that your input has to be quoted all the time. It's needlessly verbose. There's "wordorquote", which discards in< until the first non-whitespace, then checks whether that character is '"'. If it is, calls the '"' word. If it's not, it stepbacks, then calls "word". This means, for example, that 'wordorquote foo' will yield the string 'foo' and that 'wordorquote "foo bar"' will yield the string 'foo bar'. ### Rest of line and eol? Some words will mention consuming the input's "rest of line". This means that they will consume the input stream until a LF character is met. A notable example is "needs" [doc/usage/unit]. If a "\" character is met, it ignores the character following it. If it's an LF, it means that it inhibits this end of line, thus extending it. This logic is embodied in the word "eol? ( -- f )" [doc/dict]. This is broadly equivalent to "toword?" [doc/dict] except that in addition to stopping to word boundaries and EOF, it also stops on LF. f=1 if LF or EOF were met. This logic includes the whitespace that ended the previous word. Example usage: : printline begin wordorquote stype spc> eol? until ; printline foo \ "ba zooka" baz A "rest of line" word directly followed by a LF will ignore that first LF. Those examples all do the same thing: needs foo bar baz needs foo \ bar \ baz needs foo bar baz There an important caveat to this nice little system: An unquoted word beginning with "\" can't be parsed. Using the previous "printline" example above: printline foo \bar baz will print "foo ar baz" printline foo "\bar" baz will print "foo \bar baz" ### The double quote Dusk diverges from other Forths by adding a special treatment, in "word", to the double quote (") character. When it is encountered, we accumulate it in the buffer, but then we stop the loop immediately. There's an exception upon those exceptions: if the above logic yield '" we peek the next character from the input stream. If it's a quote ('), then we append it to the result so that we can get the '"' character literal. The special treatment of the (") enables us to fix the "wart" that most Forths have with regards to string literals, that is that opening quotes must always be followed by a whitespace. While in regular Forths, whitespace *must* follow a word ending in double quote, in Dusk, it *must not* (unless you want it in your string). The word that has been accumulated will be called with the input stream untouched, so when that word consumes it (any word ending with '"' is extremely likely to consume the input stream in a "string like" manner), it will receive the character directly following the double quote character. This logic follows that in Dusk, you cannot have a word with a (") character in it that is anywhere else than at its very end. One might ask "what happens with compiled words ending with a double quote?". The logic above applies in the same way. Things are a bit weird if you do something like: [compile] "foo because the logic will apply too, that is, the character following the " is not consumed by the [compile]. This means that the above code is the exact equivalent to: [compile] " foo So, it's weird, but it's not much of a problem. It's as if you added an extra whitespace between the quote and the "foo" in the "traditional" word parsing logic. Nothing breaks, everything holds together. ### Debugging input with "echoin" There is a runtime variable named "echoin" which controls whether all input will be echoed. This is different from keyboard input echoing, which is generally enabled. Here we're talking about all input, including loaded file, under all contexts, including the compilation of other code than Forth. You can enable this mode with "1 echoin !" and it can be quite useful to pinpoint exactly when an error occurs. ## Output On the other side of the input stream pond, there's "rtype ( a u -- )" which wraps the RTYPE variable and spits a range of characters at the operator's face (or the screen they're looking at). Once it's set up, it rarely changes. "emit", "stype" and all these types of words all end up calling "rtype". ### Output buffering and "flush." Some outputs such as [io/grid]-based ones are buffered for speed reasons. For example, in the case of grids, refreshing the screen every time the grid changes makes it much slower. This means that for their contents to actually be visible to the operator, that buffer needs to be flushed. The word for this is "flush.". It's a generic alias defined in the core and whatever output system is installed realiases it. [lib/coop] calls it in its runloop, which means that you generally don't need to care about flushing your output unless you write a long running loop.