github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/exp/ed/address.go (about)

     1  // Copyright 2019 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // address.go - contains methods for FileBuffer for line address resolution
     6  package main
     7  
     8  import (
     9  	"fmt"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  )
    14  
    15  // regex helpers
    16  func reGroup(s string) string {
    17  	return "(" + s + ")"
    18  }
    19  
    20  func reOr(s ...string) string {
    21  	return strings.Join(s, "|")
    22  }
    23  
    24  func reOpt(s string) string {
    25  	return s + "?"
    26  }
    27  
    28  func reStart(s string) string {
    29  	return "^" + s
    30  }
    31  
    32  // addr regex strings
    33  var (
    34  	reWhitespace   = "(\\s)"
    35  	reSingleSymbol = "([.$])"
    36  	reNumber       = "([0-9]+)"
    37  	reOffset       = reGroup("([-+])" + reOpt(reNumber))
    38  	reMark         = "'([a-z])"
    39  	reRE           = "(\\/((?:\\\\/|[^\\/])*)\\/|\\?((?:\\\\?|[^\\?])*)\\?)"
    40  	reSingle       = reGroup(reOr(reSingleSymbol, reNumber, reOffset, reMark, reRE))
    41  	reOff          = "(?:\\s+" + reGroup(reOr(reNumber, reOffset)) + ")"
    42  )
    43  
    44  // addr compiled regexes
    45  var (
    46  	rxWhitespace   = regexp.MustCompile(reStart(reWhitespace))
    47  	rxSingleSymbol = regexp.MustCompile(reStart(reSingleSymbol))
    48  	rxNumber       = regexp.MustCompile(reStart(reNumber))
    49  	rxOffset       = regexp.MustCompile(reStart(reOffset))
    50  	rxMark         = regexp.MustCompile(reStart(reMark))
    51  	rxRE           = regexp.MustCompile(reStart(reRE))
    52  	rxSingle       = regexp.MustCompile(reStart(reSingle))
    53  	rxOff          = regexp.MustCompile(reStart(reOff))
    54  )
    55  
    56  // ResolveOffset resolves an offset to an addr
    57  func (f *FileBuffer) ResolveOffset(cmd string) (offset, cmdOffset int, e error) {
    58  	ms := rxOff.FindStringSubmatch(cmd)
    59  	// 0: full
    60  	// 1: without whitespace
    61  	// 2: num
    62  	// 3: offset full
    63  	// 4: offset +/-
    64  	// 5: offset num
    65  	if len(ms) == 0 {
    66  		return
    67  	}
    68  	n := 1
    69  	cmdOffset = len(ms[0])
    70  	switch {
    71  	case len(ms[2]) > 0:
    72  		// num
    73  		if n, e = strconv.Atoi(ms[2]); e != nil {
    74  			return
    75  		}
    76  		offset = n
    77  	case len(ms[3]) > 0:
    78  		// offset
    79  		if len(ms[5]) > 0 {
    80  			if n, e = strconv.Atoi(ms[5]); e != nil {
    81  				return
    82  			}
    83  		}
    84  		switch ms[4][0] {
    85  		case '+':
    86  			offset = n
    87  		case '-':
    88  			offset = -n
    89  		}
    90  	}
    91  	return
    92  }
    93  
    94  // ResolveAddr resolves a command address from a cmd string
    95  // - makes no attempt to verify that the resulting addr is valid
    96  func (f *FileBuffer) ResolveAddr(cmd string) (line, cmdOffset int, e error) {
    97  	line = f.GetAddr()
    98  	cmdOffset = 0
    99  	m := rxSingle.FindString(cmd)
   100  	if len(m) == 0 {
   101  		// no match
   102  		return
   103  	}
   104  	cmdOffset = len(m)
   105  	switch {
   106  	case rxSingleSymbol.MatchString(m):
   107  		// no need to rematch; these are all single char
   108  		switch m[0] {
   109  		case '.':
   110  			// current
   111  		case '$':
   112  			// last
   113  			line = f.Len() - 1
   114  		}
   115  	case rxNumber.MatchString(m):
   116  		var n int
   117  		ns := rxNumber.FindString(m)
   118  		if n, e = strconv.Atoi(ns); e != nil {
   119  			return
   120  		}
   121  		line = n - 1
   122  	case rxOffset.MatchString(m):
   123  		n := 1
   124  		ns := rxOffset.FindStringSubmatch(m)
   125  		if len(ns[3]) > 0 {
   126  			if n, e = strconv.Atoi(ns[3]); e != nil {
   127  				return
   128  			}
   129  		}
   130  		switch ns[2][0] {
   131  		case '+':
   132  			line = f.GetAddr() + n
   133  		case '-':
   134  			line = f.GetAddr() - n
   135  		}
   136  	case rxMark.MatchString(m):
   137  		c := m[1] // len should already be verified by regexp
   138  		line, e = buffer.GetMark(c)
   139  	case rxRE.MatchString(m):
   140  		r := rxRE.FindAllStringSubmatch(m, -1)
   141  		// 0: full
   142  		// 1: regexp w/ delim
   143  		// 2: regexp
   144  		if len(r) < 1 || len(r[0]) < 3 {
   145  			e = fmt.Errorf("invalid regexp: %s", m)
   146  			return
   147  		}
   148  		restr := r[0][2]
   149  		sign := 1
   150  		if r[0][0][0] == '?' {
   151  			sign = -1
   152  			restr = r[0][3]
   153  		}
   154  		var re *regexp.Regexp
   155  		if re, e = regexp.Compile(restr); e != nil {
   156  			e = fmt.Errorf("invalid regexp: %v", e)
   157  			return
   158  		}
   159  		var c int
   160  		for i := 0; i < f.Len(); i++ {
   161  			c = (sign*i + f.GetAddr() + f.Len()) % f.Len()
   162  			if re.MatchString(f.GetMust(c, false)) {
   163  				line = c
   164  				return
   165  			}
   166  		}
   167  		e = fmt.Errorf("regexp not found: %s", restr)
   168  	}
   169  	if e != nil {
   170  		return
   171  	}
   172  
   173  	for {
   174  		var off, cmdoff int
   175  		if off, cmdoff, e = f.ResolveOffset(cmd[cmdOffset:]); e != nil {
   176  			return
   177  		}
   178  		if cmdoff > 0 {
   179  			// we got an offset
   180  			line += off
   181  			cmdOffset += cmdoff
   182  		} else {
   183  			// no more offsets
   184  			break
   185  		}
   186  	}
   187  	return
   188  }
   189  
   190  // ResolveAddrs resolves all addrs at the begining of a line
   191  // - makes no attempt to verify that the resulting addrs are valid
   192  // - will always return at least one addr as long as there isn't an error
   193  // - if an error is reached, return value behavior is undefined
   194  func (f *FileBuffer) ResolveAddrs(cmd string) (lines []int, cmdOffset int, e error) {
   195  	var line, off int
   196  
   197  Loop:
   198  	for cmdOffset < len(cmd) {
   199  		cmdOffset += wsOffset(cmd[cmdOffset:])
   200  		if line, off, e = f.ResolveAddr(cmd[cmdOffset:]); e != nil {
   201  			return
   202  		}
   203  		lines = append(lines, line)
   204  		cmdOffset += off
   205  		cmdOffset += wsOffset(cmd[cmdOffset:])
   206  		if len(cmd)-1 <= cmdOffset {
   207  			return
   208  		}
   209  		switch cmd[cmdOffset] { // do we have more addrs?
   210  		case ',':
   211  			cmdOffset++
   212  		case ';':
   213  			// we're  the left side of a ; set the current addr
   214  			if e = f.SetAddr(line); e != nil {
   215  				return
   216  			}
   217  			cmdOffset++
   218  		case '%':
   219  			lines = append(lines, 0, f.Len()-1)
   220  			cmdOffset++
   221  			cmdOffset += wsOffset(cmd[cmdOffset:])
   222  			return
   223  		default:
   224  			break Loop
   225  		}
   226  	}
   227  	return
   228  }
   229  
   230  // wsOffset is a helper to find the offset to skip whitespace
   231  func wsOffset(cmd string) (o int) {
   232  	o = 0
   233  	ws := rxWhitespace.FindStringIndex(cmd)
   234  	if ws != nil {
   235  		o = ws[1]
   236  	}
   237  	return
   238  }
   239  
   240  /*
   241   * The following three functions, AddrValue, AddrRange, AddrRangeOrLine all get the specified
   242   * type of address from an already parsed address.
   243   */
   244  
   245  // AddrValue gets the resolved single-line address
   246  func (f *FileBuffer) AddrValue(addrs []int) (r int, e error) {
   247  	if len(addrs) == 0 {
   248  		e = ErrINV
   249  		return
   250  	}
   251  	r = addrs[len(addrs)-1]
   252  	if f.OOB(r) {
   253  		e = ErrOOB
   254  	}
   255  	return
   256  }
   257  
   258  // AddrRange gets and address range, fails if we don't have a range specified
   259  func (f *FileBuffer) AddrRange(addrs []int) (r [2]int, e error) {
   260  	switch len(addrs) {
   261  	case 0:
   262  		e = ErrINV
   263  		return
   264  	case 1:
   265  		r[0] = f.GetAddr()
   266  		r[1] = addrs[0]
   267  	default:
   268  		r[0] = addrs[len(addrs)-2]
   269  		r[1] = addrs[len(addrs)-1]
   270  	}
   271  	if f.OOB(r[0]) || f.OOB(r[1]) {
   272  		e = ErrOOB
   273  	}
   274  	if r[0] > r[1] {
   275  		e = fmt.Errorf("address out of order")
   276  	}
   277  	return
   278  }
   279  
   280  // AddrRangeOrLine returns a range or single line if no range could be resolved
   281  func (f *FileBuffer) AddrRangeOrLine(addrs []int) (r [2]int, e error) {
   282  	if len(addrs) > 1 {
   283  		// delete a range
   284  		if r, e = buffer.AddrRange(addrs); e != nil {
   285  			return
   286  		}
   287  	} else {
   288  		// delete a line
   289  		if r[0], e = buffer.AddrValue(addrs); e != nil {
   290  			return
   291  		}
   292  		r[1] = r[0]
   293  	}
   294  	return
   295  }