github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/parse/parser.go (about)

     1  package parse
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"strings"
     7  	"unicode/utf8"
     8  
     9  	"github.com/elves/elvish/util"
    10  )
    11  
    12  // Parser maintains some mutable states of parsing.
    13  //
    14  // NOTE: The str member is assumed to be valid UF-8.
    15  type Parser struct {
    16  	srcName string
    17  	src     string
    18  	pos     int
    19  	overEOF int
    20  	cutsets []map[rune]int
    21  	errors  Error
    22  }
    23  
    24  // NewParser creates a new parser from a piece of source text and its name.
    25  func NewParser(srcname, src string) *Parser {
    26  	return &Parser{srcname, src, 0, 0, []map[rune]int{{}}, Error{}}
    27  }
    28  
    29  // Done tells the parser that parsing has completed.
    30  func (ps *Parser) Done() {
    31  	if ps.pos != len(ps.src) {
    32  		ps.error(errUnexpectedRune)
    33  	}
    34  }
    35  
    36  // Errors gets the parsing errors after calling one of the parse* functions. If
    37  // the return value is not nil, it is always of type Error.
    38  func (ps *Parser) Errors() error {
    39  	if len(ps.errors.Entries) > 0 {
    40  		return &ps.errors
    41  	}
    42  	return nil
    43  }
    44  
    45  // Source returns the source code that is being parsed.
    46  func (ps *Parser) Source() string {
    47  	return ps.src
    48  }
    49  
    50  const eof rune = -1
    51  
    52  func (ps *Parser) peek() rune {
    53  	if ps.pos == len(ps.src) {
    54  		return eof
    55  	}
    56  	r, _ := utf8.DecodeRuneInString(ps.src[ps.pos:])
    57  	if ps.currentCutset()[r] > 0 {
    58  		return eof
    59  	}
    60  	return r
    61  }
    62  
    63  func (ps *Parser) hasPrefix(prefix string) bool {
    64  	return strings.HasPrefix(ps.src[ps.pos:], prefix)
    65  }
    66  
    67  // findWord looks ahead for [a-z]* that is also a valid compound. If the
    68  // lookahead fails, it returns an empty string. It is useful for looking for
    69  // command leaders.
    70  func (ps *Parser) findPossibleLeader() string {
    71  	rest := ps.src[ps.pos:]
    72  	i := strings.IndexFunc(rest, func(r rune) bool {
    73  		return r < 'a' || r > 'z'
    74  	})
    75  	if i == -1 {
    76  		// The whole rest is just one possible leader.
    77  		return rest
    78  	}
    79  	r, _ := utf8.DecodeRuneInString(rest[i:])
    80  	if startsPrimary(r, false) {
    81  		return ""
    82  	}
    83  	return rest[:i]
    84  }
    85  
    86  func (ps *Parser) next() rune {
    87  	if ps.pos == len(ps.src) {
    88  		ps.overEOF++
    89  		return eof
    90  	}
    91  	r, s := utf8.DecodeRuneInString(ps.src[ps.pos:])
    92  	if ps.currentCutset()[r] > 0 {
    93  		return eof
    94  	}
    95  	ps.pos += s
    96  	return r
    97  }
    98  
    99  func (ps *Parser) backup() {
   100  	if ps.overEOF > 0 {
   101  		ps.overEOF--
   102  		return
   103  	}
   104  	_, s := utf8.DecodeLastRuneInString(ps.src[:ps.pos])
   105  	ps.pos -= s
   106  }
   107  
   108  func (ps *Parser) advance(c int) {
   109  	ps.pos += c
   110  	if ps.pos > len(ps.src) {
   111  		ps.overEOF = ps.pos - len(ps.src)
   112  		ps.pos = len(ps.src)
   113  	}
   114  }
   115  
   116  func (ps *Parser) errorp(begin, end int, e error) {
   117  	ps.errors.Add(e.Error(), util.SourceContext{ps.srcName, ps.src, begin, end, nil})
   118  }
   119  
   120  func (ps *Parser) error(e error) {
   121  	end := ps.pos
   122  	if end < len(ps.src) {
   123  		end++
   124  	}
   125  	ps.errorp(ps.pos, end, e)
   126  }
   127  
   128  func (ps *Parser) pushCutset(rs ...rune) {
   129  	ps.cutsets = append(ps.cutsets, map[rune]int{})
   130  	ps.cut(rs...)
   131  }
   132  
   133  func (ps *Parser) popCutset() {
   134  	n := len(ps.cutsets)
   135  	ps.cutsets[n-1] = nil
   136  	ps.cutsets = ps.cutsets[:n-1]
   137  }
   138  
   139  func (ps *Parser) currentCutset() map[rune]int {
   140  	return ps.cutsets[len(ps.cutsets)-1]
   141  }
   142  
   143  func (ps *Parser) cut(rs ...rune) {
   144  	cutset := ps.currentCutset()
   145  	for _, r := range rs {
   146  		cutset[r]++
   147  	}
   148  }
   149  
   150  func (ps *Parser) uncut(rs ...rune) {
   151  	cutset := ps.currentCutset()
   152  	for _, r := range rs {
   153  		cutset[r]--
   154  	}
   155  }
   156  
   157  func newError(text string, shouldbe ...string) error {
   158  	if len(shouldbe) == 0 {
   159  		return errors.New(text)
   160  	}
   161  	var buf bytes.Buffer
   162  	if len(text) > 0 {
   163  		buf.WriteString(text + ", ")
   164  	}
   165  	buf.WriteString("should be " + shouldbe[0])
   166  	for i, opt := range shouldbe[1:] {
   167  		if i == len(shouldbe)-2 {
   168  			buf.WriteString(" or ")
   169  		} else {
   170  			buf.WriteString(", ")
   171  		}
   172  		buf.WriteString(opt)
   173  	}
   174  	return errors.New(buf.String())
   175  }