func (f *File) evalAdr(pa padr.Node, reverse bool, start int) (Adr, error) { if reverse { pa.Val = -pa.Val } switch pa.Type { case padr.DOLLAR: return Adr{Start: f.f.Length(), End: f.f.Length()}, nil case padr.DOT: return f.dot, nil case padr.ADRMARK: return adrmark, nil case padr.HASH: no, _ := f.f.OffsetRune(pa.Val, start) return Adr{Start: no, End: no}, nil case padr.NUMBER: if pa.Val == 0 { ind, _ := f.f.OffsetLine(0, start) return Adr{Start: ind, End: ind}, nil } else if pa.Val > 0 { pa.Val-- st, _ := f.f.OffsetLine(pa.Val, start) en, _ := f.f.OffsetLine(1, st) return Adr{Start: st, End: en}, nil } else if pa.Val < 0 { pa.Val++ en, _ := f.f.OffsetLine(pa.Val, start) st, _ := f.f.OffsetLine(-1, en) return Adr{Start: st, End: en}, nil } case padr.REG: if reverse { reg, e := regexp.CompileReverse(pa.Reg) if e != nil { return Adr{}, e } rd := f.f.BackwardsReader(start) ind := reg.FindReaderIndex(rd) if ind == nil { // No match return Adr{Start: f.f.Length(), End: f.f.Length()}, nil } a := Adr{Start: -ind[1] + start, End: -ind[0] + start} return a, nil } reg, e := regexp.Compile(pa.Reg) if e != nil { return Adr{}, e } f.f.Seek(int64(start), 0) ind := reg.FindInputIndex(f.f) if ind == nil { // No match return Adr{Start: f.f.Length(), End: f.f.Length()}, nil } return Adr{Start: ind[0] + start, End: ind[1] + start}, nil case padr.PLUS: la, le := f.evalAdr(*pa.Left, false, start) if le != nil { return Adr{}, le } la = f.sanitiseAdr(la) return f.evalAdr(*pa.Right, false, la.End) case padr.MINUS: la, le := f.evalAdr(*pa.Left, false, start) if le != nil { return Adr{}, le } la = f.sanitiseAdr(la) return f.evalAdr(*pa.Right, true, la.Start) case padr.COMMA: la, le := f.evalAdr(*pa.Left, false, start) if le != nil { return Adr{}, le } ra, re := f.evalAdr(*pa.Right, false, start) if re != nil { return Adr{}, re } return Adr{Start: la.Start, End: ra.End}, nil case padr.SEMI: la, le := f.evalAdr(*pa.Left, false, start) if le != nil { return Adr{}, le } f.dot = f.sanitiseAdr(la) ra, re := f.evalAdr(*pa.Right, false, start) if re != nil { return Adr{}, re } return Adr{Start: la.Start, End: ra.End}, nil default: panic("Unkown adr operator") } spew.Dump(pa) return Adr{}, nil }
// fix is a int that refere to a fixed position in the file, it is returned updated // to keep track for e.g. extract when it does modification func (f *File) run(cmd parser.Command, fix int) (fa int, e error) { var ( adr Adr reg *regexp.Regexp ) f.dot = f.sanitiseAdr(f.dot) // TODO: Update fix locations switch cmd.Type { case parser.C_adr: // Set dot command f.dot, e = f.EvalAdr(cmd.Adr) case parser.C_a: // Append f.f.Change(f.dot.End, f.dot.End, []byte(cmd.Text)) if fix >= f.dot.End { fix += len(cmd.Text) } case parser.C_c: // Change f.f.Change(f.dot.Start, f.dot.End, []byte(cmd.Text)) if fix >= f.dot.End { fix += len(cmd.Text) - (f.dot.End - f.dot.Start) } else if fix > f.dot.Start && fix < f.dot.End { fix = -1 // Fix point not well defined with this change } case parser.C_i: // Insert f.f.Change(f.dot.Start, f.dot.Start, []byte(cmd.Text)) if fix > f.dot.Start { fix += len(cmd.Text) } case parser.C_d: // Delete f.f.Change(f.dot.Start, f.dot.End, nil) if fix >= f.dot.End { fix -= (f.dot.End - f.dot.Start) } else if fix > f.dot.Start && fix < f.dot.End { fix = -1 // Fix point not well defined with this change } case parser.C_m: // Move adr, e = f.EvalAdr(cmd.Adr) if e != nil { break } te, _ := f.f.Get(f.dot.Start, f.dot.End) f.f.Change(adr.End, adr.End, te) f.f.Change(f.dot.Start, f.dot.End, nil) case parser.C_t: // Copy adr, e = f.EvalAdr(cmd.Adr) if e != nil { break } te, _ := f.f.Get(f.dot.Start, f.dot.End) f.f.Change(adr.End, adr.End, te) case parser.C_s: // Substitute reg, e = regexp.Compile(cmd.Text) // |Text|Sub| if e != nil { break } f.f.Seek(int64(f.dot.Start), 0) loc := reg.FindInputSubmatchIndex(f.f) if loc == nil { break } for i := range loc { loc[i] += f.dot.Start } t, _ := f.f.Get(loc[0], loc[1]) re := reg.Expand([]byte{}, []byte(cmd.Sub), t, loc) // Now we have it so now substitute f.f.Change(loc[0], loc[1], re) case parser.C_x: // Extract // TODO: Think about how this should work for after change overlapping etc. ... (need usage to know I think) // Note that it may create eternal loop, if the user is not carefull... We consider this ok... // Do a fast path for the common operation of working on every line if cmd.Text == ".*" { var fp = f.dot.Start for n, e := f.f.OffsetBytes([]byte{'\n'}, fp); fp < f.f.Length(); n, e = f.f.OffsetBytes([]byte{'\n'}, fp) { if e != nil { panic(e) // Should not happen! } fn := File{f: f.f, dot: Adr{Start: fp, End: n}} if n < 0 { // Did not find any, consider everything until eof a line fn = File{f: f.f, dot: Adr{Start: fp, End: f.f.Length()}} } fp = n + 1 fp, e = fn.run(cmd.Cmds[0], fp) if e != nil { panic(e) } else if fp < 0 { e = errors.New("Undefined inner extract change") } if n < 0 { break } } break } reg, e = regexp.Compile(cmd.Text) if e != nil { break } var fp = f.dot.Start f.f.Seek(int64(fp), 0) for loc := reg.FindInputIndex(f.f); loc != nil; loc = reg.FindInputIndex(f.f) { if len(loc) > 1 && loc[0] == loc[1] { // If we have null match we need to check so we don't keep stamping at the // same location if loc[0] == 0 { //lastloc = -1; fp++ // We should advance one rune note one byt _, er := f.f.Seek(int64(fp), 0) if er != nil { // End of file break } continue } } // Create a new file with dot set and run the provided cmd on that fn := File{f: f.f, dot: Adr{Start: loc[0] + fp, End: loc[1] + fp}} fp = fp + loc[1] fp, e = fn.run(cmd.Cmds[0], fp) // TODO: What if this is nill, loop loop? i.e. Run all subcommands not just one! if e != nil { break } else if fp < 0 { e = errors.New("Undefined inner extract change") } // TODO: Maybe we could return negative fix point to indicate that we had an overlapping edit? if fp >= f.f.Length() { break } _, er := f.f.Seek(int64(fp), 0) if er != nil { break } } case parser.C_y: // Extract between matches e = errors.New("y not implemented yet") case parser.C_g: // Guard reg, e = regexp.Compile(cmd.Text) if e != nil { break } f.f.Seek(int64(f.dot.Start), 0) loc := reg.FindInputIndex(f.f) if loc != nil && loc[1]+f.dot.Start <= f.dot.End { return f.run(cmd.Cmds[0], fix) } case parser.C_v: // Guard not reg, e = regexp.Compile(cmd.Text) if e != nil { break } f.f.Seek(int64(f.dot.Start), 0) loc := reg.FindInputIndex(f.f) if loc == nil || loc[1]+f.dot.Start > f.dot.End { return f.run(cmd.Cmds[0], fix) } case parser.C_k: // Store dot in adr mark adrmark = f.dot default: return fix, fmt.Errorf("Unkown command %d", cmd.Type) } return fix, e }