1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00

Merge pull request #6 from achilleasa/implement-early-printf

Implement early printf functionality
This commit is contained in:
Achilleas Anagnostopoulos 2017-04-05 08:39:25 +01:00 committed by GitHub
commit 5db83cd3c6
2 changed files with 451 additions and 0 deletions

View File

@ -0,0 +1,270 @@
package early
import "github.com/achilleasa/gopher-os/kernel/hal"
var (
errMissingArg = []byte("(MISSING)")
errWrongArgType = []byte("%!(WRONGTYPE)")
errNoVerb = []byte("%!(NOVERB)")
errExtraArg = []byte("%!(EXTRA)")
padding = []byte{' '}
trueValue = []byte("true")
falseValue = []byte("false")
)
// Printf provides a minimal Printf implementation that can be used before the
// Go runtime has been properly initialized. This version of printf does not
// allocate any memory and uses hal.ActiveTerminal for its output.
//
// Similar to fmt.Printf, this version of printf supports the following subset
// of formatting verbs:
//
// Strings:
// %s the uninterpreted bytes of the string or byte slice
//
// Integers:
// %o base 8
// %d base 10
// %x base 16, with lower-case letters for a-f
//
// Booleans:
// %t "true" or "false"
//
// Width is specified by an optional decimal number immediately preceding the verb.
// If absent, the width is whatever is necessary to represent the value.
//
// String values with length less than the specified width will be left-padded with
// spaces. Integer values formatted as base-10 will also be left-padded with spaces.
// Finally, integer values formatted as base-16 will be left-padded with zeroes.
//
// Printf supports all built-in string and integer types but assumes that the
// Go itables have not been initialized yet so it will not check whether its
// arguments support io.Stringer if they don't match one of the supported tupes.
//
// This function does not provide support for printing pointers (%p) as this
// requires importing the reflect package. By importing reflect, the go compiler
// starts generating calls to runtime.convT2E (which calls runtime.newobject)
// when assembling the argument slice which obviously will crash the kernel since
// memory management is not yet available.
func Printf(format string, args ...interface{}) {
var (
nextCh byte
nextArgIndex int
blockStart, blockEnd, padLen int
fmtLen = len(format)
)
for blockEnd < fmtLen {
nextCh = format[blockEnd]
if nextCh != '%' {
blockEnd++
continue
}
if blockStart < blockEnd {
hal.ActiveTerminal.Write([]byte(format[blockStart:blockEnd]))
}
// Scan til we hit the format character
padLen = 0
blockEnd++
parseFmt:
for ; blockEnd < fmtLen; blockEnd++ {
nextCh = format[blockEnd]
switch {
case nextCh == '%':
hal.ActiveTerminal.Write([]byte{'%'})
break parseFmt
case nextCh >= '0' && nextCh <= '9':
padLen = (padLen * 10) + int(nextCh-'0')
continue
case nextCh == 'd' || nextCh == 'x' || nextCh == 'o' || nextCh == 's' || nextCh == 't':
// Run out of args to print
if nextArgIndex >= len(args) {
hal.ActiveTerminal.Write(errMissingArg)
break parseFmt
}
switch nextCh {
case 'o':
fmtInt(args[nextArgIndex], 8, padLen)
case 'd':
fmtInt(args[nextArgIndex], 10, padLen)
case 'x':
fmtInt(args[nextArgIndex], 16, padLen)
case 's':
fmtString(args[nextArgIndex], padLen)
case 't':
fmtBool(args[nextArgIndex])
}
nextArgIndex++
break parseFmt
}
// reached end of formatting string without finding a verb
hal.ActiveTerminal.Write(errNoVerb)
}
blockStart, blockEnd = blockEnd+1, blockEnd+1
}
if blockStart != blockEnd {
hal.ActiveTerminal.Write([]byte(format[blockStart:blockEnd]))
}
// Check for unused args
for ; nextArgIndex < len(args); nextArgIndex++ {
hal.ActiveTerminal.Write(errExtraArg)
}
}
// fmtBool prints a formatted version of boolean value v using hal.ActiveTerminal
// for its output.
func fmtBool(v interface{}) {
switch bVal := v.(type) {
case bool:
switch bVal {
case true:
hal.ActiveTerminal.Write(trueValue)
case false:
hal.ActiveTerminal.Write(falseValue)
}
default:
hal.ActiveTerminal.Write(errWrongArgType)
return
}
}
// fmtString prints a formatted version of string or []byte value v, applying the
// padding specified by padLen. This function uses hal.ActiveTerminal for its
// output.
func fmtString(v interface{}, padLen int) {
var sval []byte
switch castedVal := v.(type) {
case string:
sval = []byte(castedVal)
case []byte:
sval = castedVal
default:
hal.ActiveTerminal.Write(errWrongArgType)
return
}
for pad := padLen - len(sval); pad > 0; pad-- {
hal.ActiveTerminal.Write(padding)
}
hal.ActiveTerminal.Write(sval)
}
// fmtInt prints out a formatted version of v in the requested base, applying the
// padding specified by padLen. This function uses hal.ActiveTerminal for its
// output, supports all built-in signed and unsigned integer types and supports
// base 8, 10 and 16 output.
func fmtInt(v interface{}, base, padLen int) {
var (
sval int64
uval uint64
divider uint64
remainder uint64
buf [20]byte
padCh byte
left, right, end int
)
switch base {
case 8:
divider = 8
padCh = '0'
case 10:
divider = 10
padCh = ' '
case 16:
divider = 16
padCh = '0'
}
switch v.(type) {
case uint8:
uval = uint64(v.(uint8))
case uint16:
uval = uint64(v.(uint16))
case uint32:
uval = uint64(v.(uint32))
case uint64:
uval = v.(uint64)
case uintptr:
uval = uint64(v.(uintptr))
case int8:
sval = int64(v.(int8))
case int16:
sval = int64(v.(int16))
case int32:
sval = int64(v.(int32))
case int64:
sval = v.(int64)
case int:
sval = int64(v.(int))
default:
hal.ActiveTerminal.Write(errWrongArgType)
return
}
// Handle signs
if sval < 0 {
uval = uint64(-sval)
} else if sval > 0 {
uval = uint64(sval)
}
for {
remainder = uval % divider
if remainder < 10 {
buf[right] = byte(remainder) + '0'
} else {
// map values from 10 to 15 -> a-f
buf[right] = byte(remainder-10) + 'a'
}
right++
uval /= divider
if uval == 0 {
break
}
}
// Apply padding if required
for ; right-left < padLen; right++ {
buf[right] = padCh
}
// Apply hex prefix
if base == 16 {
buf[right] = 'x'
buf[right+1] = '0'
right += 2
}
// Apply negative sign to the rightmost blank character (if using enough padding);
// otherwise append the sign as a new char
if sval < 0 {
for end = right - 1; buf[end] == ' '; end-- {
}
if end == right-1 {
right++
}
buf[end+1] = '-'
}
// Reverse in place
end = right
for right = right - 1; left < right; left, right = left+1, right-1 {
buf[left], buf[right] = buf[right], buf[left]
}
hal.ActiveTerminal.Write(buf[0:end])
}

View File

@ -0,0 +1,181 @@
package early
import (
"bytes"
"testing"
"unsafe"
"github.com/achilleasa/gopher-os/kernel/driver/tty"
"github.com/achilleasa/gopher-os/kernel/driver/video/console"
"github.com/achilleasa/gopher-os/kernel/hal"
)
func TestPrintf(t *testing.T) {
origTerm := hal.ActiveTerminal
defer func() {
hal.ActiveTerminal = origTerm
}()
// mute vet warnings about malformed printf formatting strings
printfn := Printf
ega := &console.Ega{}
fb := make([]uint8, 160*25)
ega.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
vt := &tty.Vt{}
vt.AttachTo(ega)
hal.ActiveTerminal = vt
specs := []struct {
fn func()
expOutput string
}{
{
func() { printfn("no args") },
"no args",
},
// bool values
{
func() { printfn("%t", true) },
"true",
},
{
func() { printfn("%41t", false) },
"false",
},
// strings and byte slices
{
func() { printfn("%s arg", "STRING") },
"STRING arg",
},
{
func() { printfn("%s arg", []byte("BYTE SLICE")) },
"BYTE SLICE arg",
},
{
func() { printfn("'%4s' arg with padding", "ABC") },
"' ABC' arg with padding",
},
{
func() { printfn("'%4s' arg longer than padding", "ABCDE") },
"'ABCDE' arg longer than padding",
},
// uints
{
func() { printfn("uint arg: %d", uint8(10)) },
"uint arg: 10",
},
{
func() { printfn("uint arg: %o", uint16(0777)) },
"uint arg: 777",
},
{
func() { printfn("uint arg: %x", uint32(0xbadf00d)) },
"uint arg: 0xbadf00d",
},
{
func() { printfn("uint arg with padding: '%10d'", uint64(123)) },
"uint arg with padding: ' 123'",
},
{
func() { printfn("uint arg with padding: '%4o'", uint64(0777)) },
"uint arg with padding: '0777'",
},
{
func() { printfn("uint arg with padding: '%10x'", uint64(0xbadf00d)) },
"uint arg with padding: '0x000badf00d'",
},
{
func() { printfn("uint arg longer than padding: '%5x'", int64(0xbadf00d)) },
"uint arg longer than padding: '0xbadf00d'",
},
// pointers
{
func() { printfn("uintptr %x", uintptr(0xb8000)) },
"uintptr 0xb8000",
},
// ints
{
func() { printfn("int arg: %d", int8(-10)) },
"int arg: -10",
},
{
func() { printfn("int arg: %o", int16(0777)) },
"int arg: 777",
},
{
func() { printfn("int arg: %x", int32(-0xbadf00d)) },
"int arg: -0xbadf00d",
},
{
func() { printfn("int arg with padding: '%10d'", int64(-12345678)) },
"int arg with padding: ' -12345678'",
},
{
func() { printfn("int arg with padding: '%10d'", int64(-123456789)) },
"int arg with padding: '-123456789'",
},
{
func() { printfn("int arg with padding: '%10d'", int64(-1234567890)) },
"int arg with padding: '-1234567890'",
},
{
func() { printfn("int arg longer than padding: '%5x'", int(-0xbadf00d)) },
"int arg longer than padding: '-0xbadf00d'",
},
// multiple arguments
{
func() { printfn("%%%s%d%t", "foo", 123, true) },
`%foo123true`,
},
// errors
{
func() { printfn("more args", "foo", "bar", "baz") },
`more args%!(EXTRA)%!(EXTRA)%!(EXTRA)`,
},
{
func() { printfn("missing args %s") },
`missing args (MISSING)`,
},
{
func() { printfn("bad verb %Q") },
`bad verb %!(NOVERB)`,
},
{
func() { printfn("not bool %t", "foo") },
`not bool %!(WRONGTYPE)`,
},
{
func() { printfn("not int %d", "foo") },
`not int %!(WRONGTYPE)`,
},
{
func() { printfn("not string %s", 123) },
`not string %!(WRONGTYPE)`,
},
}
for specIndex, spec := range specs {
for index := 0; index < len(fb); index++ {
fb[index] = 0
}
vt.SetPosition(0, 0)
spec.fn()
var buf bytes.Buffer
for index := 0; ; index += 2 {
if fb[index] == 0 {
break
}
buf.WriteByte(fb[index])
}
if got := buf.String(); got != spec.expOutput {
t.Errorf("[spec %d] expected to get %q; got %q", specIndex, spec.expOutput, got)
}
}
}