Is there an intended difference between "command languages" and "programming languages"? Or are we really talking about query-response interfaces?
What are the practical use cases here?For example:
Let's say that we have an input lexer-parser that converts incoming text into tokens stored in a list (or first-in, first-out stack). Each token is either a known name, or a numerical value, and uses the exact same amount of memory (say, 32 bits). Whenever the input is complete (for example, a newline is received), a handler function is called to handle the token list, perhaps selecting the exact function based on the initial token.
This is not a "language" per se, but a query-response interface. If you have a perfect hash function (as I discussed earlier), the input parser does not need to keep a copy of the input string at all. It can be a relatively simple state machine that consumes the input, and poops out a list of tokens. The RAM use is minimal.
The
property=value and
property? interface I described earlier, parses initially the property name, with the following character choosing which function will eventually be called. If the character is
=, then the following numerical value is parsed. Otherwise, there should be nothing on the line except whitespace (or comments) before the newline. This needs only a few bytes of RAM to implement, with almost everything in ROM/Flash, which is very useful for memory-constrained tiny microcontrollers.
The more shell-like form (space-delimited words as tokens), as discussed briefly before, with e.g.
set property value and
get property, is not much more complicated, but does need RAM for the token list. Its state machine can actually be even simpler. It is a lot more versatile while still having a very intuitive action/command interface as well (
command [ arguments...).
Variables are easiest to implement if they always start with a sigil, say
$, used also when assigned to (unlike in Bourne or POSIX shells). Then, each token is either a known name, a numerical value, or references a variable; with variable values stored in a dictionary –– basically a set of token-value or token-token pairs. When the hardware supports 32-bit floating-point (IEEE 754 Binary32, aka "
float" in most situations nowadays), one can use NaNs (over four million distinct values) for non-numeric tokens. This is very useful, because that way all tokens are exactly 32 bits.
In all cases, for known names, one can use a hash table in Flash/ROM for very quick lookup and no RAM used.
However, with this approach, there is no
code listing, just individual commands and assignments and queries, that are acted upon when completely received, and then discarded. You can implement short loops and repeats (as long as they fit in a single command, that is), and even conditionals and aliases, but procedures or functions would require separating the execution from parsing the tokens: you'd need to store the list of token lists, and execute them separately. (A simple option is to use a separator token, like
; in POSIX and Bourne shells, but the big difference is separating the execution from the parser. Technically, it is not difficult, but depending on the implementation, can be quite "tricky". It is better to design the system for the intended model from the ground up.)
So, to get the discussion forward, what exactly do we need from the interface we're talking about?
I do not see myself needing any kind of full language with function support in a tiny microcontroller at all. However, the command/query-response interfaces I describe here
are useful, and definitely nothing new. And they can be made with very frugal resource use, too. Minimal code is needed to access and control any (numerical) variable in RAM. Simple wrapper functions are needed to make commands that call firmware functions. My uses vary from experimentation to debugging. Granted, I do hobby stuff and a lot of personal experiments, not commercial products, so perhaps my own experience is flawed.
(I also like full duplex more than a strict query-response interface, so that even the microcontroller can output similar format statements at any point in time (useful for timed stuff). That is, responses not necessarily being in the order of the queries, but in the order of completion, with additional events possibly reported in between. This means that I also like the way e.g. Gcode lets one specify an identifier for each command or query, with that identifier included in the response.
Looking at "languages" like
G-code and
HPGL is useful, because they were (and still are) parsed and processed by 8-bit microcontrollers. Some of their features are historical baggage, but some are a careful balance between human readability and ease of machine processing.