blob: cfc7521ba6088cc18df5f24e14a31ef11b59e487 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"
"text/scanner"
"unicode"
)
func parseBytes(input string) ([]byte, scanner.Position, error) {
var s scanner.Scanner
s.Init(strings.NewReader(input))
s.Filename = "fidlbolt.bytes"
s.Mode = scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
var err error
s.Error = func(s *scanner.Scanner, msg string) {
err = errors.New(msg)
}
var result []byte
var token strings.Builder
listMode := false
firstOfLine := true
for tok := s.Scan(); tok != scanner.EOF && err == nil; tok = s.Scan() {
endToken := false
thisListMode := listMode
thisFirstOfLine := firstOfLine
switch tok {
case scanner.String:
var str string
str, err = strconv.Unquote(s.TokenText())
if err != nil {
break
}
result = append(result, []byte(str)...)
case ',':
endToken = true
case '[':
endToken = true
if listMode {
err = errors.New(`unexpected "[" inside list`)
}
listMode = true
case ']':
endToken = true
if !listMode {
err = errors.New(`unexpected character "]"`)
}
listMode = false
default:
token.WriteRune(tok)
switch s.Peek() {
case '\n', '\r':
firstOfLine = true
fallthrough
case '\t', ' ', scanner.EOF:
endToken = true
}
}
if err != nil {
break
}
if endToken && token.Len() > 0 {
str := token.String()
if thisFirstOfLine && str[len(str)-1] == ':' {
token.Reset()
continue
}
var b []byte
if thisListMode {
b, err = parseSingleByteToken(str)
} else {
b, err = parseToken(str)
}
if err != nil {
break
}
token.Reset()
result = append(result, b...)
}
}
if err != nil {
return nil, s.Position, err
}
return result, s.Position, nil
}
func parseSingleByteToken(text string) ([]byte, error) {
num, err := strconv.ParseUint(text, 0, 64)
if err != nil {
return nil, err
}
if num < 0 || num > 255 {
return nil, fmt.Errorf("byte %s out of range (must be 0-255)", text)
}
return []byte{byte(num)}, nil
}
func parseToken(text string) ([]byte, error) {
negative := false
if strings.HasPrefix(text, "-") {
negative = true
text = text[1:]
}
var suffix string
i := strings.LastIndexAny(text, "ui")
if i != -1 {
suffix = text[i:]
text = text[:i]
}
if suffix == "" {
if negative {
return nil, fmt.Errorf("invalid use of negation")
}
if strings.HasPrefix(text, "0x") {
text = text[2:]
}
if len(text)%2 != 0 {
return nil, fmt.Errorf("hex must have an even number of characters")
}
for _, c := range text {
if !(('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f')) {
return nil, fmt.Errorf("invalid character %q", string(c))
}
}
return hex.DecodeString(text)
}
num, err := strconv.ParseUint(text, 0, 64)
if err != nil {
return nil, err
}
var signed bool
switch suffix[0] {
case 'u':
signed = false
if negative {
return nil, fmt.Errorf("cannot have negative unsigned number")
}
case 'i':
signed = true
default:
return nil, fmt.Errorf("invalid number suffix %q", suffix)
}
bits, err := strconv.ParseUint(suffix[1:], 10, 64)
if err != nil {
return nil, err
}
if bits != 8 && bits != 16 && bits != 32 && bits != 64 {
return nil, fmt.Errorf("invalid bit width %d", bits)
}
max := uint64(1) << bits
if signed {
max = uint64(1) << (bits - 1)
if !negative {
max--
}
}
if num >= max && max != 0 {
return nil, fmt.Errorf("%s out of range for %s", text, suffix)
}
if negative {
// Num is uint64, so this will wrap around, storing the two's
// complement. We do this instead of just parsing the full text
// (possibly with "-") as int64 because with int64 we would not
// be able to store the largest u64 values.
num = -num
}
switch bits {
case 8:
return []byte{byte(num)}, nil
case 16:
return []byte{byte(num & 0xff), byte(num >> 8 & 0xff)}, nil
case 32:
return []byte{
byte(num & 0xff), byte(num >> 8 & 0xff), byte(num >> 16 & 0xff), byte(num >> 24 & 0xff),
}, nil
case 64:
return []byte{
byte(num & 0xff), byte(num >> 8 & 0xff), byte(num >> 16 & 0xff), byte(num >> 24 & 0xff),
byte(num >> 32 & 0xff), byte(num >> 40 & 0xff), byte(num >> 48 & 0xff), byte(num >> 56 & 0xff),
}, nil
}
return nil, fmt.Errorf("non-exhaustive")
}
func formatBytes(bytes []byte, options *Options) (string, error) {
byteFmt, offsetFmt := "%02x", "%08x: "
if options.Capital {
byteFmt, offsetFmt = "%02X", "%08X: "
}
group := options.Group
if group < 1 {
group = 1
}
if group > options.Columns {
group = options.Columns
}
for options.Columns%group != 0 {
group--
}
var builder strings.Builder
writeASCII := func(start, end int) {
builder.WriteString(" // ")
for i := start; i < end; i++ {
r := rune(bytes[i])
if r <= unicode.MaxASCII && unicode.IsPrint(r) {
builder.WriteRune(r)
} else {
builder.WriteByte('.')
}
}
}
for i, b := range bytes {
if i%options.Columns == 0 {
if i != 0 {
if options.ASCII {
writeASCII(i-options.Columns, i)
}
builder.WriteByte('\n')
}
if options.Offsets {
builder.WriteString(fmt.Sprintf(offsetFmt, i))
}
} else if i%group == 0 {
builder.WriteByte(' ')
}
builder.WriteString(fmt.Sprintf(byteFmt, b))
}
if options.ASCII {
last := len(bytes) % options.Columns
left := options.Columns - last
if last == 0 {
left = 0
}
spaces := left/group*(group*2+1) + left%group*2
for i := 0; i < spaces; i++ {
builder.WriteByte(' ')
}
writeASCII(len(bytes)-last, len(bytes))
}
return builder.String(), nil
}