github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/ast/string.go (about)

     1  package ast
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"regexp"
     7  	"strconv"
     8  
     9  	"github.com/arnodel/golua/luastrings"
    10  	"github.com/arnodel/golua/token"
    11  )
    12  
    13  // String is a string literal.
    14  type String struct {
    15  	Location
    16  	Val []byte
    17  }
    18  
    19  var _ ExpNode = String{}
    20  
    21  // NewString returns a String from a token containing a Lua short string
    22  // literal, or an error if it couln't (although perhaps it should panic instead?
    23  // Parsing should ensure well-formed string token).
    24  func NewString(id *token.Token) (ss String, err error) {
    25  	s := luastrings.NormalizeNewLines(id.Lit)
    26  	defer func() {
    27  		if r := recover(); r != nil {
    28  			err2, ok := r.(error)
    29  			if ok {
    30  				err = err2
    31  			}
    32  		}
    33  	}()
    34  	return String{
    35  		Location: LocFromToken(id),
    36  		Val:      escapeSeqs.ReplaceAllFunc(s[1:len(s)-1], replaceEscapeSeq),
    37  	}, nil
    38  }
    39  
    40  // NewLongString returns a String computed from a token containing a Lua long
    41  // string.
    42  func NewLongString(id *token.Token) String {
    43  	s := luastrings.NormalizeNewLines(id.Lit)
    44  	idx := bytes.IndexByte(s[1:], '[') + 2
    45  	contents := s[idx : len(s)-idx]
    46  	if contents[0] == '\n' {
    47  		contents = contents[1:]
    48  	}
    49  	return String{
    50  		Location: LocFromToken(id),
    51  		Val:      contents,
    52  	}
    53  }
    54  
    55  // ProcessExp uses the given ExpProcessor to process the receiver.
    56  func (s String) ProcessExp(p ExpProcessor) {
    57  	p.ProcessStringExp(s)
    58  }
    59  
    60  // HWrite prints a tree representation of the node.
    61  func (s String) HWrite(w HWriter) {
    62  	w.Writef("%q", s.Val)
    63  }
    64  
    65  // This function replaces escape sequences with the values they escape.
    66  func replaceEscapeSeq(e []byte) []byte {
    67  	switch e[1] {
    68  	case 'a':
    69  		return []byte{7}
    70  	case 'b':
    71  		return []byte{8}
    72  	case 't':
    73  		return []byte{9}
    74  	case 'n':
    75  		return []byte{10}
    76  	case 'v':
    77  		return []byte{11}
    78  	case 'f':
    79  		return []byte{12}
    80  	case 'r':
    81  		return []byte{13}
    82  	case 'z':
    83  		return []byte{}
    84  	case 'x', 'X':
    85  		b, err := strconv.ParseInt(string(e[2:]), 16, 64)
    86  		if err != nil {
    87  			panic(err)
    88  		}
    89  		return []byte{byte(b)}
    90  	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
    91  		b, err := strconv.ParseInt(string(e[1:]), 10, 64)
    92  		if err != nil {
    93  			panic(err)
    94  		}
    95  		if b >= 256 {
    96  			panic(fmt.Errorf("decimal escape sequence out of range near '%s'", e))
    97  		}
    98  		return []byte{byte(b)}
    99  	case 'u', 'U':
   100  		i, err := strconv.ParseInt(string(e[3:len(e)-1]), 16, 32)
   101  		if err != nil {
   102  			panic(fmt.Errorf("unicode escape sequence out of range near '%s'", e))
   103  		}
   104  		var p [6]byte
   105  		n := luastrings.UTF8EncodeInt32(p[:], int32(i))
   106  		return p[:n]
   107  	default:
   108  		return e[1:]
   109  	}
   110  }
   111  
   112  // This regex matches all the escape sequences that can be found in a Lua string.
   113  var escapeSeqs = regexp.MustCompile(`(?s)\\\d{1,3}|\\[xX][0-9a-fA-F]{2}|\\[abtnvfr\\]|\\z[\s\v]*|\\[uU]{[0-9a-fA-F]+}|\\.`)