github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/lib/stringlib/format.go (about) 1 package stringlib 2 3 import ( 4 "errors" 5 "fmt" 6 "math" 7 "strconv" 8 "unsafe" 9 10 "github.com/arnodel/golua/lib/base" 11 rt "github.com/arnodel/golua/runtime" 12 ) 13 14 func format(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 15 if err := c.Check1Arg(); err != nil { 16 return nil, err 17 } 18 f, err := c.StringArg(0) 19 if err != nil { 20 return nil, err 21 } 22 s, err := Format(t, f, c.Etc()) 23 if err != nil { 24 return nil, err 25 } 26 t.RequireBytes(len(s)) 27 return c.PushingNext1(t.Runtime, rt.StringValue(s)), nil 28 } 29 30 var errNotEnoughValues = errors.New("not enough values for format string") 31 32 // Format is the base for the implementation of lua string.format() 33 // 34 // It works by scanning the verbs in the format string and converting the 35 // argument corresponding to this verb to the correct type, then calling Go's 36 // fmt.Sprintf(). 37 // 38 // It temporarily requires all the memory needed to store the formatted string, 39 // but releases it before returning so the caller should require memory first 40 // thing after the call. 41 func Format(t *rt.Thread, format string, values []rt.Value) (string, error) { 42 var tmpMem uint64 43 defer t.ReleaseMem(tmpMem) 44 // Temporarily require memory for building the argument list 45 tmpMem += t.RequireArrSize(unsafe.Sizeof(interface{}(nil)), len(values)) 46 args := make([]interface{}, len(values)) 47 j := 0 48 // Temporarily require memory for building the format string 49 tmpMem += t.RequireBytes(len(format)) 50 outFormat := []byte(format) 51 52 // We require an amount of CPU proportional to the format string size 53 t.RequireCPU(uint64(len(format))) 54 OuterLoop: 55 for i := 0; i < len(format); i++ { 56 if format[i] == '%' { 57 var ( 58 start = i + 1 59 arg interface{} 60 length, prec int 61 foundDot bool 62 ) 63 ArgLoop: 64 for i++; i < len(format); i++ { 65 switch format[i] { 66 case '%': 67 continue OuterLoop 68 case 'c': 69 if len(args) <= j { 70 return "", errNotEnoughValues 71 } 72 n, ok := rt.ToInt(values[j]) 73 if !ok { 74 return "", errors.New("invalid value for integer format") 75 } 76 arg = []byte{byte(n)} 77 tmpMem += t.RequireBytes(1) 78 outFormat[i] = 's' 79 break ArgLoop 80 case 'b', 'd', 'o', 'x', 'X', 'U', 'i', 'u': 81 // integer verbs 82 if len(args) <= j { 83 return "", errNotEnoughValues 84 } 85 n, ok := rt.ToInt(values[j]) 86 if !ok { 87 return "", errors.New("invalid value for integer format") 88 } 89 tmpMem += t.RequireBytes(10) 90 switch format[i] { 91 case 'u': 92 // Unsigned int 93 arg = uint64(n) 94 outFormat[i] = 'd' // No 'u' verb in Go 95 case 'i': 96 // Signed int 97 arg = int64(n) 98 outFormat[i] = 'd' // No 'i' verb in Go 99 case 'x', 'X': 100 arg = uint64(n) // Need to convert to unsigned 101 default: 102 arg = int64(n) 103 } 104 break ArgLoop 105 case 'a', 'A': 106 // Hexadecimal float verbs 107 outFormat[i] += 'x' - 'a' 108 fallthrough 109 case 'e', 'E', 'f', 'F', 'g', 'G': 110 // float verbs 111 if len(args) <= j { 112 return "", errNotEnoughValues 113 } 114 f, ok := rt.ToFloat(values[j]) 115 if !ok { 116 return "", errors.New("invalid value for float format") 117 } 118 tmpMem += t.RequireBytes(10) 119 arg = float64(f) 120 break ArgLoop 121 case 's': 122 if len(args) <= j { 123 return "", errNotEnoughValues 124 } 125 s, err := base.ToString(t, values[j]) 126 if err != nil { 127 return "", err 128 } 129 tmpMem += t.RequireBytes(len(s)) 130 arg = string(s) 131 break ArgLoop 132 case 'q': 133 // quote, only for literals I think 134 if start < i { 135 return "", errors.New("specifier '%q' cannot have modifiers") 136 } 137 if len(args) <= j { 138 return "", errNotEnoughValues 139 } 140 v := values[j] 141 if s, ok := quote(v); ok { 142 tmpMem += t.RequireBytes(len(s)) 143 arg = s 144 outFormat[i] = 's' 145 } else { 146 return "", errors.New("no literal") 147 } 148 break ArgLoop 149 case 'p': 150 // Pointer address, new in Lua 5.4 151 switch v := values[j]; v.Type() { 152 case rt.BoolType, rt.FloatType, rt.IntType, rt.NilType: 153 outFormat[i] = 's' 154 arg = "(null)" 155 case rt.StringType: 156 // Here we have a problem. C Lua has one single start 157 // address per string, whereas in Go a string is a 158 // slice, so we can't make the same guarantee. Best 159 // effort: find the address of the start of the string 160 // and format it as a pointer. 161 outFormat[i] = 's' 162 s := v.AsString() 163 ptr := *(*uintptr)(unsafe.Pointer(&s)) 164 arg = fmt.Sprintf("0x%x", ptr) 165 default: 166 arg = v.Interface() 167 } 168 break ArgLoop 169 case 't': 170 // boolean verb 171 if len(args) <= j { 172 return "", errNotEnoughValues 173 } 174 b, ok := values[j].TryBool() 175 if !ok { 176 return "", errors.New("invalid value for boolean format") 177 } 178 tmpMem += t.RequireBytes(5) 179 arg = bool(b) 180 break ArgLoop 181 case '.': 182 foundDot = true 183 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 184 if foundDot { 185 prec = prec*10 + int(format[i]-'0') 186 if prec >= 100 { 187 return "", errors.New("length too long") 188 } 189 } else { 190 length = length*10 + int(format[i]-'0') 191 if length >= 100 { 192 return "", errors.New("precision too long") 193 } 194 } 195 case '+', '-', '#', ' ': 196 // flag characters 197 default: 198 // Unrecognised verbs 199 return "", errors.New("invalid format string") 200 } 201 } 202 args[j] = arg 203 j++ 204 } 205 } 206 if j < len(args) { 207 args = args[:j] 208 } 209 210 // Release temporary memory 211 return fmt.Sprintf(string(outFormat), args...), nil 212 } 213 214 // Quote returns a string representing the value as a valid Lua literal if 215 // possible, the boolean returned indicating success or failure. 216 func quote(v rt.Value) (string, bool) { 217 if v.IsNil() { 218 return "nil", true 219 } 220 switch v.Type() { 221 case rt.IntType: 222 return strconv.Itoa(int(v.AsInt())), true 223 case rt.FloatType: 224 x := v.AsFloat() 225 if math.IsInf(x, 0) { 226 // Use an out of range literal to represent infinity. It works 227 // because that parses to infinity 228 if x > 0 { 229 return "1e9999", true 230 } else { 231 return "-1e9999", true 232 } 233 } 234 if math.IsNaN(x) { 235 return "(0/0)", true 236 } 237 return strconv.FormatFloat(x, 'g', -1, 64), true 238 case rt.BoolType: 239 return strconv.FormatBool(v.AsBool()), true 240 case rt.StringType: 241 return strconv.Quote(v.AsString()), true // An approximation 242 default: 243 return "", false 244 } 245 }