github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/lib/stringlib/packing.go (about)

     1  package stringlib
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"fmt"
     7  	"unsafe"
     8  
     9  	"github.com/arnodel/golua/luastrings"
    10  	rt "github.com/arnodel/golua/runtime"
    11  )
    12  
    13  /*
    14  Notes:
    15  
    16  <: sets little endian
    17  >: sets big endian
    18  
    19  =: sets native endian
    20  
    21  ![n]: sets maximum alignment to n (default is native alignment)
    22     between 1 and 16
    23     Does not need to be a power of 2 but if it's not it seems things break?
    24  
    25  b: a signed byte (char)
    26     int8
    27  
    28  B: an unsigned byte (char)
    29     uint8
    30  
    31  h: a signed short (native size)
    32     int16
    33  
    34  H: an unsigned short (native size)
    35     uint16
    36  
    37  l: a signed long (native size)
    38     int64
    39  
    40  L: an unsigned long (native size)
    41     uint64
    42  
    43  j: a lua_Integer
    44     int64
    45  
    46  J: a lua_Unsigned
    47     uint64
    48  
    49  T: a size_t (native size)
    50     uint64
    51  
    52  i[n]: a signed int with n bytes (default is native size)
    53     default int32
    54     n between 1 and 16 errors if an overflow
    55  
    56  I[n]: an unsigned int with n bytes (default is native size)
    57     default uint32
    58     n between 1 and 16
    59  f: a float (native size)
    60     float32
    61  d: a double (native size)
    62     float64
    63  
    64  n: a lua_Number
    65     float64
    66  
    67  cn: a fixed-sized string with n bytes
    68     Not aligned
    69  
    70  z: a zero-terminated string
    71     Not aligned
    72  
    73  s[n]: a string preceded by its length coded as an unsigned integer with n bytes (default is a size_t)
    74     Aligned like I[n]
    75  
    76  x: one byte of padding
    77     That is one zero byte
    78  
    79  Xop: an empty item that aligns according to option op (which is otherwise ignored)
    80     That is, add padding for alignment but do not add a value
    81  
    82  ' ': (empty space) ignored
    83  */
    84  
    85  func pack(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
    86  	if err := c.Check1Arg(); err != nil {
    87  		return nil, err
    88  	}
    89  	format, err := c.StringArg(0)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	res, used, perr := PackValues(string(format), c.Etc(), t.LinearUnused(10))
    94  	t.LinearRequire(10, used)
    95  	if perr != nil {
    96  		return nil, perr
    97  	}
    98  	return c.PushingNext1(t.Runtime, rt.StringValue(res)), nil
    99  }
   100  
   101  func unpack(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   102  	if err := c.CheckNArgs(2); err != nil {
   103  		return nil, err
   104  	}
   105  	var (
   106  		format, pack string
   107  		n            int64 = 1
   108  		err          error
   109  	)
   110  	format, err = c.StringArg(0)
   111  	if err == nil {
   112  		pack, err = c.StringArg(1)
   113  	}
   114  	if err == nil && c.NArgs() >= 3 {
   115  		n, err = c.IntArg(2)
   116  	}
   117  	i := luastrings.StringNormPos(pack, int(n)) - 1
   118  	if i < 0 || i > len(pack) {
   119  		err = errors.New("#3 out of string")
   120  	}
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	vals, m, used, uerr := UnpackString(string(format), string(pack), i, t.LinearUnused(10))
   125  	t.LinearRequire(10, used)
   126  	if uerr != nil {
   127  		return nil, uerr
   128  	}
   129  	next := c.Next()
   130  	t.Push(next, vals...)
   131  	t.Push1(next, rt.IntValue(int64(m+1)))
   132  	return next, nil
   133  }
   134  
   135  func packsize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   136  	if err := c.Check1Arg(); err != nil {
   137  		return nil, err
   138  	}
   139  	format, err := c.StringArg(0)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	size, serr := PackSize(string(format))
   144  	if serr != nil {
   145  		return nil, serr
   146  	}
   147  	return c.PushingNext1(t.Runtime, rt.IntValue(int64(size))), nil
   148  }
   149  
   150  func isLittleEndian() bool {
   151  	var i int32 = 0x01020304
   152  	u := unsafe.Pointer(&i)
   153  	pb := (*byte)(u)
   154  	b := *pb
   155  	return (b == 0x04)
   156  }
   157  
   158  var nativeEndian binary.ByteOrder
   159  
   160  const defaultMaxAlignement uint = 1
   161  
   162  func init() {
   163  	if isLittleEndian() {
   164  		nativeEndian = binary.LittleEndian
   165  	} else {
   166  		nativeEndian = binary.BigEndian
   167  	}
   168  }
   169  
   170  var (
   171  	errBadOptionArg           = errors.New("arg out of limits [1,16]")
   172  	errMissingSize            = errors.New("missing size")
   173  	errBadType                = errors.New("bad value type") // TODO: better error
   174  	errOutOfBounds            = errors.New("overflow")       // TODO: better error
   175  	errExpectedOption         = errors.New("invalid next option after 'X'")
   176  	errBadAlignment           = errors.New("alignment not power of 2")
   177  	errUnexpectedPackEnd      = errors.New("packed string too short: unexpected end")
   178  	errDoesNotFit             = errors.New("does not fit into Lua integer")
   179  	errStringLongerThanFormat = errors.New("string longer than format spec")
   180  	errStringDoesNotFit       = errors.New("string does not fit")
   181  	errVariableLength         = errors.New("variable-length format") // For packsize only
   182  	errOverflow               = errors.New("invalid format: option size overflow")
   183  	errStringContainsZeros    = errors.New("string contains zeros")
   184  
   185  	errBudgetConsumed = errors.New("Packing memory budget consumed")
   186  )
   187  
   188  func errBadFormatString(c byte) error {
   189  	return fmt.Errorf("invalid format option %q", c)
   190  }