mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
The kfmt/early package provides a minimal printf implementation that does not use memory allocations (everything is allocated on the stack). This implementation can be used to emit debug messages before the memory manager is initialized.
271 lines
6.5 KiB
Go
271 lines
6.5 KiB
Go
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])
|
|
}
|