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:
commit
5db83cd3c6
270
kernel/kfmt/early/early_fmt.go
Normal file
270
kernel/kfmt/early/early_fmt.go
Normal 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])
|
||||
}
|
181
kernel/kfmt/early/early_fmt_test.go
Normal file
181
kernel/kfmt/early/early_fmt_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user