This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Sora Firestorm's profile picture

Aap Renewal Project Tutorial Series - Term Intermediate : Rewriting Bios Write()

Started by Sora Firestorm, 30 August 2013 - 10:20 PM
Sora Firestorm #1
Posted 31 August 2013 - 12:20 AM
Prerequisites :
Term basics - .write, .clear, .setCursorPos, .getCursorPos (Covered in LBPHacker's tutorial)
Term basics - .getSize, .scroll, .clearLine, .setCursorBlink (Partially covered in LBPHacker's tutorial)
Familiarity with the basic Lua API
Familiarity with the String API

Hello, welcome to the AaP Renewal Project 'Term Intermediate : Rewriting BIOS write()' tutorial. As you may have guessed, in this tutorial, we are going to reimplement the BIOS write() function with our knowledge of the Term API. This tutorial is meant to show that we can use primitive functions to accomplish a more compilcated task.

First task is to write some error-checking code to ensure that the argument is really a string (or number).
All we need to do is use the type() function, which will return a string that is the type of it's argument.
If it's not a string or number, we need to call the error function, and say that we expected a string, but got something else.
If it happens to be a number, convert it into a string with tostring().


function ourWrite(text)
  if not (type(text) == "string" or type(text) == "number") then
	error("String expected, got "..type(text))
  end

  if type(text) == "number" then
	text = tostring(text)
  end

Now is where the real magic will happen. We'll iterate over the string in a for loop and print it one character at a time. Why?
Because unless we print it one character at a time, we're not going to have the fine-grained control that we need to be able to
react to things like newlines in the text or needing to wrap the line. We'll query the terminal for some position and size numbers
so that we'll react appropriately to wraps and newlines.

We need the x value to tell us where in the line, and the y value for telling us what line we're on.

  local x, y = term.getCursorPos()

We also need the height and width of the terminal.

  local width, height = term.getSize()

Loop through the string one character at a time

  for i = 1, #text do


If we encounter a line break, then react appropriately

    if string.sub(text, i, i) == "\n" then
	  x = 1

	  -- Scroll instead of going out of bounds
	  if y == height then
        term.scroll(1)
	  else
        y = y + 1
	  end
	  term.setCursorPos(x, y)

Otherwise, it's nothing special, so we just write it

    else
	  term.write(string.sub(text, i, i))
	  x = x + 1

If x is more then width, then we need to wrap and scroll if we need

	  if x > width then
		x = 1
		-- Same scroll check as before
		if y == height then
		  term.scroll(1)
		else
		  y = y + 1
		end
		term.setCursorPos(x, y)
	  end
	end
  end

Nothing went wrong, so return 0

  return 0
end

Here is the complete function :
Spoiler

function ourWrite(text)
  if not (type(text) == "string" or type(text) == "number") then
    error("String expected, got "..type(text))
  end

  if type(text) == "number" then
    text = tostring(text)
  end

  -- We need the x value to tell us where in the line, and the y value for telling us what line we're on.
  local x, y = term.getCursorPos()

  -- We also need the height and width of the terminal.
  local width, height = term.getSize()

  for i = 1, #text do
    -- If we encounter a line break
    if string.sub(text, i, i) == "\n" then
      x = 1

      -- Scroll instead of going out of bounds
      if y == height then
            term.scroll(1)
      else
            y = y + 1
      end
      term.setCursorPos(x, y)
    else
      -- Nothing special, so just write it
      term.write(string.sub(text, i, i))
      x = x + 1
      -- Uh oh! We need to wrap!
      if x > width then
        x = 1
        -- Same scroll check as before
        if y == height then
          term.scroll(1)
        else
          y = y + 1
        end
        term.setCursorPos(x, y)
      end
    end
  end

  -- Nothing went wrong, so return 0
  return 0
end

And that's it! Now we have our own homebrew write() function. Hopefully you now see how small pieces can interact in big ways!
(Apologies for the code looking so messy, the forum is screwing it up)
Molinko #2
Posted 31 August 2013 - 02:32 PM
I like it. simple but very handy!
theoriginalbit #3
Posted 01 September 2013 - 12:58 AM
Again a large chunk of code… I suggest you split it up, as it is you have comments, so how about you split it up where you have each of those comments, and instead of comments, actually put that explanation before the code snippet. Then if you so choose put a "Now all the code put together"
Sora Firestorm #4
Posted 02 September 2013 - 05:07 PM
Fixed it up with some breaks. Thanks BIT!
MudkipTheEpic #5
Posted 02 September 2013 - 05:38 PM
This is a great tutorial!

I did notice one thing though:

IIRC, returning 0 on success is a C standard. The Lua standard (iirc,) is returning true on success.

Just my two cents.
theoriginalbit #6
Posted 02 September 2013 - 06:41 PM
IIRC, returning 0 on success is a C standard. The Lua standard (iirc,) is returning true on success.
Yes you are correct, returning 0 is a C standard, however in Lua, if not comparing to true, 0 would resolve to true.

One thing is say is that the bios write actually returns the line count, I.e how many lines the text went over.

Secondly, I'm on my phone so correct me if I'm wrong, but wasn't the term intermediate meant to be term.write, not the write in bios' write?
Lyqyd #7
Posted 02 September 2013 - 06:51 PM
No, it was bios' write, though it was intended to use a somewhat different format. I'll write out a better explanation this evening. We may end up moving away from using reimplementation examples altogether eventually, depending on a number of factors.

This is definitely a good start, though!
Sora Firestorm #8
Posted 02 September 2013 - 11:11 PM
To make it clear, I only had this function return a value because I noticed the real write() did too. I went a little overboard on the 're-implementation' part.
It's actually kinda cool that people thought I was being a good C programmer. :)/>
EDIT : wait wait… line count? Huh. Should I really worry about cramming that in to the tutorial at this point?
BigTwisty #9
Posted 13 September 2013 - 12:40 AM
Wouldn't it be simpler if you aren't doing actual word wrapping to just measure the difference between the cursor x position and the width of the screen, write that much of the text, and then remove that much of the text from itself? That would end up with significantly less calls to term.write. To handle \n chars you could even simply split the original text up with:

local count = 0
local w, h = term.getSize()
local firstLine = true
for line in text:gmatch("[^\n]+") do
  while line ~= "" do
    local x, y = term.getCursorPos()
    if not firstLine then
      firstLine = true
      x = 1
      if y == h then term.scroll()
      else y = y + 1 end
      term.setCursorPos(1, y)
      count = count + 1
    end
    local len = w - x + 1
    term.write(line:sub(1, len))
    line = text:sub(len + 1)
  end
end
return count

Correct me if I'm wrong; typing on an iphone.