| // 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 |
| } |