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  }