github.com/hirochachacha/plua@v0.0.0-20170217012138-c82f520cc725/stdlib/string/format.go (about) 1 package string 2 3 import ( 4 "bytes" 5 "fmt" 6 "math" 7 "strings" 8 9 "github.com/hirochachacha/plua/internal/strconv" 10 "github.com/hirochachacha/plua/object" 11 "github.com/hirochachacha/plua/object/fnutil" 12 ) 13 14 func format(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 15 ap := fnutil.NewArgParser(th, args) 16 17 s, err := ap.ToGoString(0) 18 if err != nil { 19 return nil, err 20 } 21 22 b := &buffer{th: th, ap: ap} 23 24 argn := 1 25 26 for i := 0; i < len(s); i++ { 27 c := s[i] 28 29 if c != '%' { 30 e := b.WriteByte(c) 31 if e != nil { 32 return nil, object.NewRuntimeError(e.Error()) 33 } 34 continue 35 } 36 37 i++ 38 if i == len(s) { 39 break 40 } 41 42 c = s[i] 43 44 if c == '%' { 45 e := b.WriteByte(c) 46 if e != nil { 47 return nil, object.NewRuntimeError(e.Error()) 48 } 49 continue 50 } 51 52 if argn == len(args) { 53 return nil, ap.ArgError(argn, "no value") 54 } 55 56 t, j := parseTerm(s[i:]) 57 if t == nil { 58 return nil, ap.ArgError(0, "invalid format") 59 } 60 61 err := b.writeFormat(t, argn) 62 if err != nil { 63 return nil, err 64 } 65 66 i += j - 1 67 68 argn++ 69 } 70 71 return []object.Value{object.String(b.String())}, nil 72 } 73 74 type buffer struct { 75 bytes.Buffer 76 77 th object.Thread 78 ap *fnutil.ArgParser 79 } 80 81 func (b *buffer) writeFormat(t *term, argn int) *object.RuntimeError { 82 prefix, verb, err := b.verb(t, argn) 83 if err != nil { 84 return err 85 } 86 87 padSize := t.width - len(verb) - len(prefix) 88 89 if t.minus { 90 if len(prefix) != 0 { 91 b.WriteString(prefix) 92 } 93 b.WriteString(verb) 94 if t.zero { 95 for i := 0; i < padSize; i++ { 96 b.WriteByte('0') 97 } 98 } else { 99 for i := 0; i < padSize; i++ { 100 b.WriteByte(' ') 101 } 102 } 103 } else { 104 if t.zero { 105 if len(prefix) != 0 { 106 b.WriteString(prefix) 107 } 108 for i := 0; i < padSize; i++ { 109 b.WriteByte('0') 110 } 111 } else { 112 for i := 0; i < padSize; i++ { 113 b.WriteByte(' ') 114 } 115 if len(prefix) != 0 { 116 b.WriteString(prefix) 117 } 118 } 119 b.WriteString(verb) 120 } 121 122 return nil 123 } 124 125 func (b *buffer) verb(t *term, argn int) (string, string, *object.RuntimeError) { 126 switch t.verb { 127 case 'c': 128 return t.byte(b.ap, argn) 129 case 'd', 'i', 'u', 'o', 'x', 'X': 130 return t.int(b.ap, argn) 131 case 'e', 'E', 'f', 'g', 'G': 132 return t.float(b.ap, argn) 133 case 'a', 'A': 134 return t.hexFloat(b.ap, argn) 135 case 'q': 136 return t.quoteString(b.ap, argn) 137 case 's': 138 return t.string(b.th, b.ap, argn) 139 } 140 141 return "", "", b.ap.OptionError(0, string(t.verb)) 142 } 143 144 type term struct { 145 verb byte 146 147 width int 148 prec int 149 150 plus bool 151 minus bool 152 space bool 153 zero bool 154 } 155 156 func parseTerm(s string) (*term, int) { 157 t := &term{ 158 width: -1, 159 prec: -1, 160 } 161 162 var i int 163 164 // parse flags 165 parseFlags: 166 for ; i < len(s); i++ { 167 switch s[i] { 168 case '+': 169 t.plus = true 170 case '-': 171 t.minus = true 172 case ' ': 173 t.space = true 174 case '0': 175 t.zero = true 176 default: 177 break parseFlags 178 } 179 } 180 181 // parse width 182 j := i 183 for ; i < len(s); i++ { 184 if !('0' <= s[i] && s[i] <= '9') { 185 t.width, _ = strconv.Atoi(s[j:i]) 186 187 break 188 } 189 } 190 191 // parse prec 192 if i < len(s) && s[i] == '.' { 193 i++ 194 j = i 195 for ; i < len(s); i++ { 196 if !('0' <= s[i] && s[i] <= '9') { 197 t.prec, _ = strconv.Atoi(s[j:i]) 198 199 break 200 } 201 } 202 } 203 204 if i == len(s) { 205 return nil, 0 206 } 207 208 // parse verb 209 t.verb = s[i] 210 211 i++ 212 213 return t, i 214 } 215 216 func (t *term) byte(ap *fnutil.ArgParser, argn int) (prefix, s string, err *object.RuntimeError) { 217 i, err := ap.ToGoInt64(argn) 218 if err != nil { 219 return "", "", err 220 } 221 return "", string(byte(i)), nil 222 } 223 224 func (t *term) int(ap *fnutil.ArgParser, argn int) (prefix, s string, err *object.RuntimeError) { 225 i, err := ap.ToGoInt64(argn) 226 if err != nil { 227 return "", "", err 228 } 229 230 var base int 231 var toUpper bool 232 var toUint bool 233 234 switch t.verb { 235 case 'i', 'd': 236 base = 10 237 case 'u': 238 base = 10 239 toUint = true 240 case 'o': 241 base = 8 242 toUint = true 243 case 'x': 244 base = 16 245 toUint = true 246 case 'X': 247 base = 16 248 toUpper = true 249 toUint = true 250 default: 251 panic("unreachable") 252 } 253 254 if toUint { 255 s = strconv.FormatUint(uint64(i), base) 256 } else { 257 s = strconv.FormatInt(i, base) 258 259 if s[0] == '-' { 260 s = s[1:] 261 prefix = "-" 262 } else if t.plus { 263 prefix = "+" 264 } 265 } 266 267 if toUpper { 268 s = strings.ToUpper(s) 269 } 270 271 var prec string 272 273 if 0 < t.prec-len(s) { 274 prec = strings.Repeat("0", t.prec-len(s)) 275 } 276 277 return prefix, prec + s, nil 278 } 279 280 func (t *term) float(ap *fnutil.ArgParser, argn int) (prefix, s string, err *object.RuntimeError) { 281 f, err := ap.ToGoFloat64(argn) 282 if err != nil { 283 return "", "", err 284 } 285 286 s = strconv.FormatFloat(f, t.verb, t.prec, 64) 287 288 if s[0] == '-' { 289 s = s[1:] 290 prefix = "-" 291 } else if t.plus { 292 prefix = "+" 293 } 294 295 return prefix, s, nil 296 } 297 298 func (t *term) hexFloat(ap *fnutil.ArgParser, argn int) (prefix, s string, err *object.RuntimeError) { 299 f, err := ap.ToGoFloat64(argn) 300 if err != nil { 301 return "", "", err 302 } 303 304 u := math.Float64bits(f) 305 306 signBit := int(u >> 63) 307 308 if f == 0 { 309 if t.prec > 0 { 310 s = "0." + strings.Repeat("0", t.prec) + "+0" 311 } else { 312 s = "0p+0" 313 } 314 } else { 315 exponent := int64(u>>52&0x7ff) - 1023 316 fraction := u & 0xfffffffffffff 317 318 if t.prec > 0 { 319 // TODO precision support 320 321 s = fmt.Sprintf("1.%xp%+d", fraction, exponent) 322 } else { 323 s = fmt.Sprintf("1.%xp%+d", fraction, exponent) 324 } 325 } 326 327 switch t.verb { 328 case 'a': 329 if signBit == 1 { 330 prefix = "-0x" 331 } else if t.plus { 332 prefix = "+0x" 333 } else { 334 prefix = "0x" 335 } 336 case 'A': 337 if signBit == 1 { 338 prefix = "-0X" 339 } else if t.plus { 340 prefix = "+0X" 341 } else { 342 prefix = "0X" 343 } 344 s = strings.ToUpper(s) 345 default: 346 panic("unreachable") 347 } 348 349 return prefix, s, nil 350 } 351 352 func (t *term) quoteString(ap *fnutil.ArgParser, argn int) (prefix, s string, err *object.RuntimeError) { 353 val, _ := ap.Get(argn) 354 355 switch val := val.(type) { 356 case nil: 357 s = object.Repr(val) 358 case object.Boolean: 359 s = object.Repr(val) 360 case object.String: 361 s = strconv.Quote(string(val)) 362 case object.Integer: 363 s = strconv.FormatInt(int64(val), 10) 364 case object.Number: 365 s = strconv.FormatFloat(float64(val), 'f', -1, 64) 366 default: 367 return "", "", ap.ArgError(argn, "value has no literal form") 368 } 369 370 return "", s, nil 371 } 372 373 func (t *term) string(th object.Thread, ap *fnutil.ArgParser, argn int) (prefix, s string, err *object.RuntimeError) { 374 val, _ := ap.Get(argn) 375 376 if mt := th.GetMetatable(val); mt != nil { 377 if tm := mt.Get(object.TM_TOSTRING); tm != nil { 378 rets, err := th.Call(tm, nil) 379 if err != nil { 380 return "", "", err 381 } 382 383 if len(rets) == 0 { 384 return "", "", object.NewRuntimeError("'tostring' must return a string to 'print'") 385 } 386 387 val = rets[0] 388 } 389 } 390 391 s = object.Repr(val) 392 393 if 0 <= t.prec && t.prec < len(s) { 394 s = s[:t.prec] 395 } 396 397 return "", s, nil 398 }