github.com/jmigpin/editor@v1.6.0/util/parseutil/reslocparser/reslocparser.go (about)

     1  package reslocparser
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/jmigpin/editor/util/parseutil"
     7  )
     8  
     9  type ResLocParser struct {
    10  	parseMu sync.Mutex // allow .Parse() to be used concurrently
    11  
    12  	Escape        rune
    13  	PathSeparator rune
    14  	ParseVolume   bool
    15  
    16  	sc *parseutil.Scanner
    17  	fn struct {
    18  		location ScFn
    19  		reverse  ScFn
    20  	}
    21  	vk struct {
    22  		scheme *parseutil.ScValueKeeper
    23  		volume *parseutil.ScValueKeeper
    24  		path   *parseutil.ScValueKeeper
    25  		line   *parseutil.ScValueKeeper
    26  		column *parseutil.ScValueKeeper
    27  	}
    28  }
    29  
    30  func NewResLocParser() *ResLocParser {
    31  	p := &ResLocParser{}
    32  	p.sc = parseutil.NewScanner()
    33  
    34  	p.Escape = '\\'
    35  	p.PathSeparator = '/'
    36  	p.ParseVolume = false
    37  
    38  	return p
    39  }
    40  func (p *ResLocParser) Init() {
    41  	sc := p.sc
    42  
    43  	p.vk.scheme = sc.NewValueKeeper()
    44  	p.vk.volume = sc.NewValueKeeper()
    45  	p.vk.path = sc.NewValueKeeper()
    46  	p.vk.line = sc.NewValueKeeper()
    47  	p.vk.column = sc.NewValueKeeper()
    48  	resetVks := func() error {
    49  		p.vk.scheme.Reset()
    50  		p.vk.volume.Reset()
    51  		p.vk.path.Reset()
    52  		p.vk.line.Reset()
    53  		p.vk.column.Reset()
    54  		return nil
    55  	}
    56  
    57  	//----------
    58  
    59  	nameSyms := func(except ...rune) ScFn {
    60  		rs := nameRunes(except...)
    61  		return sc.P.RuneAny(rs)
    62  	}
    63  
    64  	volume := func(pathSepFn ScFn) ScFn {
    65  		if p.ParseVolume {
    66  			return sc.P.And(
    67  				p.vk.volume.KeepBytes(sc.P.And(
    68  					sc.M.Letter, sc.P.Rune(':'),
    69  				)),
    70  				pathSepFn,
    71  			)
    72  		} else {
    73  			return nil
    74  		}
    75  	}
    76  
    77  	//----------
    78  
    79  	// ex: "/a/b.txt"
    80  	// ex: "/a/b.txt:12:3"
    81  	cEscRu := p.Escape
    82  	cPathSepRu := p.PathSeparator
    83  	cPathSep := sc.P.Rune(cPathSepRu)
    84  	cName := sc.P.Or(
    85  		sc.P.EscapeAny(cEscRu),
    86  		sc.M.Digit,
    87  		sc.M.Letter,
    88  		nameSyms(cPathSepRu, cEscRu),
    89  	)
    90  	cNames := sc.P.Loop2(sc.P.Or(
    91  		cName,
    92  		cPathSep,
    93  	))
    94  	cPath := sc.P.And(
    95  		sc.P.Optional(volume(cPathSep)),
    96  		cNames,
    97  	)
    98  	cLineCol := sc.P.And(
    99  		sc.P.Rune(':'),
   100  		p.vk.line.KeepBytes(sc.M.Digits), // line
   101  		sc.P.Optional(sc.P.And(
   102  			sc.P.Rune(':'),
   103  			p.vk.column.KeepBytes(sc.M.Digits), // column
   104  		)),
   105  	)
   106  	cFile := sc.P.And(
   107  		p.vk.path.KeepBytes(cPath),
   108  		sc.P.Optional(cLineCol),
   109  	)
   110  
   111  	//----------
   112  
   113  	// ex: "file:///a/b.txt:12"
   114  	// no escape sequence for scheme, used to be '\\' but better to avoid conflicts with platforms that use '\\' as escape; could always use encoding (ex: %20 for ' ')
   115  	schEscRu := '\\'    // fixed
   116  	schPathSepRu := '/' // fixed
   117  	schPathSep := sc.P.Rune(schPathSepRu)
   118  	schName := sc.P.Or(
   119  		sc.P.EscapeAny(schEscRu),
   120  		sc.M.Digit,
   121  		sc.M.Letter,
   122  		nameSyms(schPathSepRu, schEscRu),
   123  	)
   124  	schNames := sc.P.Loop2(sc.P.Or(
   125  		schName,
   126  		schPathSep,
   127  	))
   128  	schPath := sc.P.And(
   129  		schPathSep,
   130  		sc.P.Optional(volume(schPathSep)),
   131  		schNames,
   132  	)
   133  	schFileTagS := "file://"
   134  	schFile := sc.P.And(
   135  		p.vk.scheme.KeepBytes(sc.P.Sequence(schFileTagS)),
   136  		p.vk.path.KeepBytes(schPath),
   137  		sc.P.Optional(cLineCol),
   138  	)
   139  
   140  	//----------
   141  
   142  	// ex: "\"/a/b.txt\""
   143  	dquote := sc.P.Rune('"') // double quote
   144  	dquotedFile := sc.P.And(
   145  		dquote,
   146  		p.vk.path.KeepBytes(cPath),
   147  		dquote,
   148  	)
   149  
   150  	//----------
   151  
   152  	// ex: "\"/a/b.txt\", line 23"
   153  	pyLineTagS := ", line "
   154  	pyFile := sc.P.And(
   155  		dquotedFile,
   156  		sc.P.And(
   157  			sc.P.Sequence(pyLineTagS),
   158  			p.vk.line.KeepBytes(sc.M.Digits),
   159  		),
   160  	)
   161  
   162  	//----------
   163  
   164  	// ex: "/a/b.txt: line 23"
   165  	shellLineTagS := ": line "
   166  	shellFile := sc.P.And(
   167  		p.vk.path.KeepBytes(cPath),
   168  		sc.P.And(
   169  			sc.P.Sequence(shellLineTagS),
   170  			p.vk.line.KeepBytes(sc.M.Digits),
   171  		),
   172  	)
   173  
   174  	//----------
   175  
   176  	p.fn.location = sc.P.Or(
   177  		// ensure values are reset at each attempt
   178  		sc.P.And(resetVks, schFile),
   179  		sc.P.And(resetVks, pyFile),
   180  		sc.P.And(resetVks, dquotedFile),
   181  		sc.P.And(resetVks, shellFile),
   182  		sc.P.And(resetVks, cFile),
   183  	)
   184  
   185  	//----------
   186  	//----------
   187  
   188  	revNames := sc.P.Loop2(
   189  		sc.P.Or(
   190  			cName,
   191  			//schName, // can't reverse, contains fixed '\\' escape that can conflit with platform not considering it an escape
   192  			sc.P.Rune(cEscRu),
   193  			sc.P.Rune(schEscRu),
   194  			cPathSep,
   195  			schPathSep,
   196  		),
   197  	)
   198  	p.fn.reverse = sc.P.And(
   199  		sc.P.Optional(dquote),
   200  		//sc.P.Optional(cVolume),
   201  		//sc.P.Optional(schVolume),
   202  		sc.P.Optional(sc.P.SequenceMid(schFileTagS)),
   203  		sc.P.Optional(sc.P.Loop2(sc.P.Or(
   204  			cPathSep,
   205  			schPathSep,
   206  		))),
   207  		sc.P.Optional(sc.P.And(
   208  			sc.M.Letter, sc.P.Rune(':'), // volume
   209  			//sc.P.Optional(sc.
   210  		)),
   211  		sc.P.Optional(revNames),
   212  		sc.P.Optional(dquote),
   213  		sc.P.Optional(sc.P.SequenceMid(pyLineTagS)),
   214  		sc.P.Optional(sc.P.SequenceMid(shellLineTagS)),
   215  		// c line column
   216  		sc.P.Optional(sc.P.Loop2(
   217  			sc.P.Or(sc.P.Rune(':'), sc.M.Digit),
   218  		)),
   219  	)
   220  }
   221  func (p *ResLocParser) Parse(src []byte, index int) (*ResLoc, error) {
   222  	// only one instance of this parser can parse at each time
   223  	p.parseMu.Lock()
   224  	defer p.parseMu.Unlock()
   225  
   226  	p.sc.SetSrc(src)
   227  	p.sc.Pos = index
   228  
   229  	//fmt.Printf("start pos=%v\n", p.sc.Pos)
   230  
   231  	p.sc.Reverse = true
   232  	_ = p.fn.reverse() // best effort
   233  	p.sc.Reverse = false
   234  	_ = p.sc.Pos
   235  
   236  	//fmt.Printf("reverse pos=%v\n", p.sc.Pos)
   237  
   238  	//p.sc.Debug = true
   239  	pos0 := p.sc.KeepPos()
   240  	if err := p.fn.location(); err != nil {
   241  		return nil, err
   242  	}
   243  	//fmt.Printf("location pos=%v\n", p.sc.Pos)
   244  
   245  	rl := &ResLoc{}
   246  	rl.Scheme = string(p.vk.scheme.BytesOrNil())
   247  	rl.Volume = string(p.vk.volume.BytesOrNil())
   248  	rl.Path = string(p.vk.path.BytesOrNil())
   249  	rl.Line = p.vk.line.IntOrZero()
   250  	rl.Column = p.vk.column.IntOrZero()
   251  	rl.Escape = p.Escape
   252  	rl.PathSep = p.PathSeparator
   253  	rl.Pos = pos0.Pos
   254  	rl.End = p.sc.Pos
   255  
   256  	return rl, nil
   257  }
   258  
   259  //----------
   260  //----------
   261  //----------
   262  
   263  type ScFn = parseutil.ScFn
   264  
   265  //----------
   266  //----------
   267  //----------
   268  
   269  // all syms except letters and digits
   270  var syms = "_-~.%@&?!=#+:^(){}[]<>\\/ "
   271  
   272  // name separator symbols
   273  var nameSepSyms = "" +
   274  	" " + // word separator
   275  	"=" + // usually around filenames (ex: -arg=/a/b.txt)
   276  	"(){}[]<>" + // usually used around filenames in various outputs
   277  	":" + // usually separating lines/cols from filenames
   278  	""
   279  
   280  func nameRunes(except ...rune) []rune {
   281  	out := nameSepSyms
   282  	for _, ru := range except {
   283  		if ru != 0 {
   284  			out += string(ru)
   285  		}
   286  	}
   287  	s := parseutil.RunesExcept(syms, out)
   288  	return []rune(s)
   289  }