Modular Computers the manual GitHub

Reading the keyboard

An OS that reads what you type, a line at a time, and says it back. Type off and it turns the computer off. This is where your OS starts talking with the hardware both ways: it waits for a device, and calls one.

What you need

The whole thing

Replace /init.lua on your OS disk with this:

-- /init.lua: an OS that reads a line from the keyboard and says it back, forever. Typing "off"
-- turns the computer off. It shows the two calls every OS is built on: waiting for a device
-- (board.waitForInterrupt) and calling one of its methods (board.invoke).

-- Writes text to every screen. A terminal moves down a row on \n, but only \r goes back to the
-- left, so each \n is sent as \r\n.
local function write(text)
  board.invoke("console", "write", (text:gsub("\n", "\r\n")))
end

-- Reads a line: shows what's typed, takes Backspace, ends at Enter.
local function readline(prompt)
  write(prompt)
  local line = ""
  local escape = false -- in the middle of a key's escape sequence (an arrow key sends ESC [ A)
  while true do
    -- Sleep until a key is pressed: the console's interrupt goes up when it has keys.
    board.waitForInterrupt(nil, "console")
    -- Take what was typed: a string of bytes, or nil if someone else took it first.
    local ok, keys = board.invoke("console", "read")
    if ok and keys then
      for i = 1, #keys do
        local key = keys:sub(i, i)
        local byte = key:byte()
        if escape then
          escape = not key:match("[%a~]") -- a sequence ends at a letter or ~
        elseif byte == 27 then
          escape = true -- ESC: a key this OS doesn't use (arrows, F-keys): skipped
        elseif key == "\r" then
          write("\n")
          return line
        elseif byte == 127 or byte == 8 then
          if #line > 0 then
            line = line:sub(1, -2)
            write("\b \b") -- back, blank it out, back again
          end
        elseif byte >= 32 then
          line = line .. key
          write(key) -- the screen shows nothing by itself: the OS echoes what you type
        end
      end
    end
  end
end

write("Type something. Type off to turn the computer off.\n")
while true do
  local line = readline("> ")
  if line == "off" then
    board.poweroff()
  end
  write("you typed: " .. line .. "\n")
end

Download os-echo.lua

Boot it (your disk first in the bus order) and type:

Type something. Type off to turn the computer off.
> hello
you typed: hello
> off

The computer turns off. Typing mistakes can be rubbed out with Backspace; the arrow keys do nothing.

How it works

The screen shows nothing by itself

A terminal is two devices in one, like a real VT100 on a serial line: a screen that shows what’s written to it, and a keyboard whose keys wait in the terminal until a program reads them. When you press a key, nothing appears. The letter appears only because the OS writes it back (write(key)). That’s called echo, and every OS does it: in mcos, the kernel’s terminal driver does.

Sleeping until a key comes

board.waitForInterrupt(nil, "console")

The computer stops here and draws only its waiting power, until the console has keys. nil means no time limit. This is an interrupt: a device with news raises its line, and the waiting computer wakes. Interrupts are level-triggered: the line stays up while the keys are waiting, so the call returns at once if you typed while the OS was busy. Nothing is lost.

Taking the keys

local ok, keys = board.invoke("console", "read")

Every call to a device answers first with whether it worked. Then read gives what was typed since the last read, as one string of bytes (several keys, if you typed fast), or nil when there’s nothing. The loop handles each byte:

You press The byte(s) The OS
a letter, a digit, space… the character adds it to the line, and echoes it
Enter "\r" (13) ends the line
Backspace 127 removes the last character, and writes "\b \b": back a column, a space over the letter, back again
an arrow key, F1… ESC (27), then [ and more, ending in a letter or ~ skips the whole sequence

So readline builds the line in a string, and returns it at Enter. The main loop at the bottom reads a line, turns the computer off if it’s off, and otherwise writes it back. It never ends: an OS that returns makes the CPU abort.

write and new lines

write turns every \n into \r\n, so the rest of the code can write "\n" as usual. A terminal moves down a row on \n but goes back to the left only on \r.

When it goes wrong

Next: A command shell.