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 }