@@ -81,7 +81,7 @@ function scanTok(t: LexTok, defs: string[], stateful: boolean, rxTok?: string, t
8181 return `\t\tif ${ gate ? gate + 'true' : 'true' } { if e := ${ m } (pos); e > pos { ${ push ( 'e' ) } pos = e; continue } }` ;
8282}
8383
84- function newlinePartsGo ( nl : NewlineCfg , pushFn : string ) : { state : string ; boundary : string ; ws : string ; hooks : string } {
84+ function newlinePartsGo ( nl : NewlineCfg , pushFn : string ) : { state : string ; stateFrom : string ; boundary : string ; ws : string ; hooks : string } {
8585 const commentSkip = nl . comment
8686 ? `\t\tif strings.HasPrefix(src[p:], ${ J ( nl . comment ) } ) { e := p; for e < n && src[e] != 10 { e++ }; pos = e; continue }\n`
8787 : '' ;
@@ -90,6 +90,10 @@ function newlinePartsGo(nl: NewlineCfg, pushFn: string): { state: string; bounda
9090\t_flowOpen := map[string]bool{${ nl . flowOpen . map ( ( x ) => `${ J ( x ) } : true` ) . join ( ', ' ) } }
9191\t_flowClose := map[string]bool{${ nl . flowClose . map ( ( x ) => `${ J ( x ) } : true` ) . join ( ', ' ) } }
9292\tconst _nlTok = ${ J ( nl . token ) }
93+ ` ,
94+ stateFrom : `\t_flowOpen := map[string]bool{${ nl . flowOpen . map ( ( x ) => `${ J ( x ) } : true` ) . join ( ', ' ) } }
95+ \t_flowClose := map[string]bool{${ nl . flowClose . map ( ( x ) => `${ J ( x ) } : true` ) . join ( ', ' ) } }
96+ \tconst _nlTok = ${ J ( nl . token ) }
9397` ,
9498 boundary : `\t\tif flowDepth == 0 && lineStart {
9599\t\t\tp := pos
@@ -117,7 +121,7 @@ ${commentSkip}\t\t\tpos = p
117121 ws : `\t\tif c == 32 || c == 9 || c == 11 || c == 12 || c == 160 || c == 5760 || (c >= 8192 && c <= 8202) || c == 8239 || c == 8287 || c == 12288 || c == 65279 { pos++; continue }
118122\t\tif c == 10 || c == 13 {
119123\t\t\tpos++; if c == 13 && pos < n && src[pos] == 10 { pos++ }
120- \t\t\tif flowDepth == 0 { lineStart = true } else { pendingNl = true }
124+ \t\t\tif flowDepth == 0 { lineStart = true }
121125\t\t\tcontinue
122126\t\t}
123127` ,
@@ -132,9 +136,10 @@ function lexer(ir: ParserIR): string {
132136 const rx = ir . regexCtx ;
133137 const tpl = ir . tpl ;
134138 const nl = ir . newlineCfg ;
135- const stateful = ! ! ( rx || tpl || nl ) ;
136- const toks = ir . tokens . map ( ( t ) => scanTok ( t , defs , stateful , rx ?. regexToken , tpl ?. token ) ) . join ( '\n' ) ;
137- const pushPunct = stateful ? ( p : string ) => `emit("", ${ J ( p ) } , pos, pos + ${ p . length } )` : ( p : string ) => `pushTok("", ${ J ( p ) } , pos, pos + ${ p . length } )` ;
139+ const rxOrTpl = ! ! ( rx || tpl ) ;
140+ const newlineOnly = ! ! ( nl && ! rx && ! tpl ) ;
141+ const toks = ir . tokens . map ( ( t ) => scanTok ( t , defs , rxOrTpl , rx ?. regexToken , tpl ?. token ) ) . join ( '\n' ) ;
142+ const pushPunct = rxOrTpl ? ( p : string ) => `emit("", ${ J ( p ) } , pos, pos + ${ p . length } )` : ( p : string ) => `pushTok("", ${ J ( p ) } , pos, pos + ${ p . length } )` ;
138143 const puncts = ir . puncts . map ( ( p ) =>
139144 `\t\tif strings.HasPrefix(src[pos:], ${ J ( p ) } ) { ${ pushPunct ( p ) } ; pos += ${ p . length } ; continue }` ) . join ( '\n' ) ;
140145 const goMap = ( a : string [ ] ) => `map[string]bool{${ a . map ( ( x ) => `${ J ( x ) } : true` ) . join ( ', ' ) } }` ;
@@ -181,7 +186,7 @@ function lexer(ir: ParserIR): string {
181186 nl ? newlinePartsGo ( nl , 'emit' ) . hooks : '' ,
182187 ] . filter ( Boolean ) . join ( '\n' ) ;
183188 const emitTail = rx ? `\n\t\tbpText = prevText; hasPrev2 = hasPrev; prevKind = kind; prevText = text; hasPrev = true` : '' ;
184- const emitFn = stateful ? `\temit := func(kind, text string, off, end int) {
189+ const emitFn = rxOrTpl ? `\temit := func(kind, text string, off, end int) {
185190${ emitHooks }
186191\t\ttoks = append(toks, Tok{kind, text, off, end, pendingNl}); pendingNl = false${ emitTail }
187192\t}
@@ -199,28 +204,35 @@ ${emitHooks}
199204\t\t\tpos = e; continue
200205\t\t}
201206` : '' ;
202- const nlState = nl ? newlinePartsGo ( nl , stateful ? 'emit' : 'pushTok' ) . state : '' ;
203- const nlBoundary = nl ? newlinePartsGo ( nl , stateful ? 'emit' : 'pushTok' ) . boundary : '' ;
204- const nlWs = nl ? newlinePartsGo ( nl , stateful ? 'emit' : 'pushTok' ) . ws : `\t\tif strings.HasPrefix(src[pos:], ${ J ( '\u2028' ) } ) || strings.HasPrefix(src[pos:], ${ J ( '\u2029' ) } ) { pendingNl = true; pos += 3; continue } // LS/PS (UTF-8)
207+ const nlState = nl ? newlinePartsGo ( nl , rxOrTpl ? 'emit' : 'pushTok' ) . state : '' ;
208+ const nlStateFrom = nl ? newlinePartsGo ( nl , 'pushTok' ) . stateFrom : '' ;
209+ const nlBoundary = nl ? newlinePartsGo ( nl , rxOrTpl ? 'emit' : 'pushTok' ) . boundary : '' ;
210+ const nlWs = nl ? newlinePartsGo ( nl , rxOrTpl ? 'emit' : 'pushTok' ) . ws : `\t\tif strings.HasPrefix(src[pos:], ${ J ( '\u2028' ) } ) || strings.HasPrefix(src[pos:], ${ J ( '\u2029' ) } ) { pendingNl = true; pos += 3; continue } // LS/PS (UTF-8)
205211\t\tif c == 10 || c == 13 { pendingNl = true; pos++; continue } // LF/CR
206212\t\tif c == 32 || c == 9 || c == 11 || c == 12 || c == 160 || c == 5760 || (c >= 8192 && c <= 8202) || c == 8239 || c == 8287 || c == 12288 || c == 65279 { pos++; continue }
207213` ;
208- const pushHooks = nl && ! stateful ? newlinePartsGo ( nl , 'pushTok' ) . hooks : '' ;
209- const pushTokFn = stateful ? '' : nl
214+ const pushHooks = nl && ! rxOrTpl ? newlinePartsGo ( nl , 'pushTok' ) . hooks : '' ;
215+ const pushTokFn = rxOrTpl ? '' : nl
210216 ? `\tpushTok := func(kind, text string, off, end int) {
211217${ pushHooks } \t\ttoks = append(toks, Tok{kind, text, off, end, pendingNl}); pendingNl = false
212218\t}
213219\t_ = pushTok
214220`
215221 : `\tpushTok := func(kind, text string, off, end int) { toks = append(toks, Tok{kind, text, off, end, pendingNl}); pendingNl = false }\n\t_ = pushTok\n` ;
216- const pushTokAccFn = `\tpushTok := func(kind, text string, off, end int) { *acc = append(*acc, Tok{kind, text, off, end, pendingNl}); pendingNl = false }
222+ const pushTokAccFn = nl && ! rxOrTpl
223+ ? `\tpushTok := func(kind, text string, off, end int) {
224+ ${ pushHooks } \t\t*acc = append(*acc, Tok{kind, text, off, end, pendingNl}); pendingNl = false
225+ \t}
226+ \t_ = pushTok
227+ `
228+ : `\tpushTok := func(kind, text string, off, end int) { *acc = append(*acc, Tok{kind, text, off, end, pendingNl}); pendingNl = false }
217229\t_ = pushTok
218230` ;
219231 const loopBody = `${ nlBoundary } \t\tc := int(src[pos])
220232${ nlWs } ${ tplDispatch } ${ toks }
221233${ puncts }
222234\t\tpanic(fmt.Sprintf("lex error at %d", pos))` ;
223- if ( stateful ) {
235+ if ( rxOrTpl ) {
224236 return `${ defs . length ? 'var _s string\n' + defs . join ( '\n' ) + '\n' : '' } func lex(src string) []Tok {
225237\ttoks := toks[:0]
226238\tn := len(src)
@@ -231,6 +243,21 @@ ${rxState}${tplState}${nlState}${emitFn}${pushTokFn}${defs.length ? '\t_s = src\
231243${ loopBody }
232244\t}
233245\treturn toks
246+ }` ;
247+ }
248+ if ( newlineOnly ) {
249+ return `${ defs . length ? 'var _s string\n' + defs . join ( '\n' ) + '\n' : '' } func lexFrom(src string, pos int, pendingNl bool, lineStart bool, emittedContent bool, flowDepth int, acc *[]Tok, limit int) (int, bool, bool, bool, int) {
250+ \tn := len(src)
251+ ${ nlStateFrom } ${ pushTokAccFn } ${ defs . length ? '\t_s = src\n' : '' } \tbase := len(*acc)
252+ \tfor pos < n && (limit <= 0 || len(*acc)-base < limit) {
253+ ${ loopBody }
254+ \t}
255+ \treturn pos, pendingNl, lineStart, emittedContent, flowDepth
256+ }
257+ func lex(src string) []Tok {
258+ \tvar out []Tok
259+ \tlexFrom(src, 0, false, true, false, 0, &out, 0)
260+ \treturn out
234261}` ;
235262 }
236263 return `${ defs . length ? 'var _s string\n' + defs . join ( '\n' ) + '\n' : '' } func lexFrom(src string, pos int, pendingNl bool, acc *[]Tok, limit int) (int, bool) {
@@ -393,8 +420,69 @@ ${r.nudSeqs.map((seq) => `\t{ save := pos; sb := len(scratch); nb := len(nodes);
393420}
394421
395422function docEditBlockGo ( ir : ParserIR ) : string {
396- const stateless = ! ( ir . regexCtx || ir . tpl || ir . newlineCfg ) ;
397- const windowHelpers = stateless ? `
423+ const windowLex = ! ( ir . regexCtx || ir . tpl ) ;
424+ const hasNewline = ! ! ir . newlineCfg ;
425+ const windowHelpers = windowLex ? ( hasNewline ? `
426+ func findTokAtOffKind(toks []alignMeta, off int, kind string) int {
427+ \tlo, hi := 0, len(toks)-1
428+ \thit := -1
429+ \tfor lo <= hi {
430+ \t\tmid := (lo + hi) >> 1
431+ \t\tif toks[mid].Off < off { lo = mid + 1 } else if toks[mid].Off > off { hi = mid - 1 } else { hit = mid; break }
432+ \t}
433+ \tif hit < 0 { return -1 }
434+ \tstart := hit
435+ \tfor start > 0 && toks[start-1].Off == off { start-- }
436+ \tfor i := start; i < len(toks) && toks[i].Off == off; i++ {
437+ \t\tif toks[i].Kind == kind { return i }
438+ \t}
439+ \treturn -1
440+ }
441+ func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start, end int, ins string) ([]alignMeta, int) {
442+ \tdelta := len(ins) - (end - start)
443+ \teditEnd := start + len(ins)
444+ \tmaxIdx := -1
445+ \tfor i := 0; i < len(oldToks); i++ {
446+ \t\tif oldToks[i].End < start { maxIdx = i } else { break }
447+ \t}
448+ \trb := -1
449+ \tif maxIdx >= 0 { rb = maxIdx - 1 }
450+ \tvar out []alignMeta
451+ \tif rb >= 0 { out = append(out, oldToks[:rb+1]...) }
452+ \tvar scanOff int
453+ \tvar pendingNl, lineStart, emittedContent bool
454+ \tvar flowDepth int
455+ \tif rb >= 0 {
456+ \t\tscanOff = oldToks[rb].End; pendingNl = false; lineStart = false; emittedContent = true; flowDepth = oldToks[rb].Fd
457+ \t} else {
458+ \t\tscanOff = 0; pendingNl = false; lineStart = true; emittedContent = false; flowDepth = 0
459+ \t}
460+ \tvar scratch []Tok
461+ \trelexed := 0
462+ \tfor scanOff < len(newText) {
463+ \t\tbefore := len(scratch)
464+ \t\tscanOff, pendingNl, lineStart, emittedContent, flowDepth = lexFrom(newText, scanOff, pendingNl, lineStart, emittedContent, flowDepth, &scratch, 1)
465+ \t\tif len(scratch) == before { break }
466+ \t\tt := scratch[len(scratch)-1]
467+ \t\tout = append(out, alignMeta{t.Kind, t.Off, t.End, t.Nl, flowDepth})
468+ \t\trelexed++
469+ \t\tif t.Off >= editEnd {
470+ \t\t\toIdx := findTokAtOffKind(oldToks, t.Off-delta, t.Kind)
471+ \t\t\tif oIdx >= 0 {
472+ \t\t\t\to := oldToks[oIdx]
473+ \t\t\t\tif o.Kind == t.Kind && o.End == t.End-delta && o.Nl == t.Nl && o.Fd == flowDepth && oldText[o.Off:o.End] == newText[t.Off:t.End] {
474+ \t\t\t\t\tfor j := oIdx + 1; j < len(oldToks); j++ {
475+ \t\t\t\t\t\tot := oldToks[j]
476+ \t\t\t\t\t\tout = append(out, alignMeta{ot.Kind, ot.Off + delta, ot.End + delta, ot.Nl, ot.Fd})
477+ \t\t\t\t\t}
478+ \t\t\t\t\treturn out, relexed
479+ \t\t\t\t}
480+ \t\t\t}
481+ \t\t}
482+ \t}
483+ \treturn out, relexed
484+ }
485+ ` : `
398486func findTokAtOff(toks []alignMeta, off int) int {
399487\tlo, hi := 0, len(toks)-1
400488\tfor lo <= hi {
@@ -424,7 +512,7 @@ func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start,
424512\t\tscanOff, pendingNl = lexFrom(newText, scanOff, pendingNl, &scratch, 1)
425513\t\tif len(scratch) == before { break }
426514\t\tt := scratch[len(scratch)-1]
427- \t\tout = append(out, alignMeta{t.Kind, t.Off, t.End, t.Nl})
515+ \t\tout = append(out, alignMeta{t.Kind, t.Off, t.End, t.Nl, 0 })
428516\t\trelexed++
429517\t\tif t.Off >= editEnd {
430518\t\t\toIdx := findTokAtOff(oldToks, t.Off-delta)
@@ -433,7 +521,7 @@ func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start,
433521\t\t\t\tif o.Kind == t.Kind && o.End == t.End-delta && o.Nl == t.Nl && oldText[o.Off:o.End] == newText[t.Off:t.End] {
434522\t\t\t\t\tfor j := oIdx + 1; j < len(oldToks); j++ {
435523\t\t\t\t\t\tot := oldToks[j]
436- \t\t\t\t\t\tout = append(out, alignMeta{ot.Kind, ot.Off + delta, ot.End + delta, ot.Nl})
524+ \t\t\t\t\t\tout = append(out, alignMeta{ot.Kind, ot.Off + delta, ot.End + delta, ot.Nl, 0 })
437525\t\t\t\t\t}
438526\t\t\t\t\treturn out, relexed
439527\t\t\t\t}
@@ -442,8 +530,8 @@ func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start,
442530\t}
443531\treturn out, relexed
444532}
445- ` : '' ;
446- const editBody = stateless
533+ ` ) : '' ;
534+ const editBody = windowLex
447535 ? `\tcurText := d.text
448536\tcurToks := d.toks
449537\tfor _, e := range edits {
@@ -466,8 +554,52 @@ func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start,
466554\tnewToks := tokenize(d.text)
467555\td.toks = toMeta(newToks)
468556\trelexed = len(d.toks)` ;
557+ const toMetaFn = hasNewline ? `
558+ func scanMeta(src string) []alignMeta {
559+ \tvar toks []Tok
560+ \tvar meta []alignMeta
561+ \tpos, pendingNl, lineStart, emittedContent, flowDepth := 0, false, true, false, 0
562+ \tfor pos < len(src) {
563+ \t\tbefore := len(toks)
564+ \t\tpos, pendingNl, lineStart, emittedContent, flowDepth = lexFrom(src, pos, pendingNl, lineStart, emittedContent, flowDepth, &toks, 1)
565+ \t\tif len(toks) == before { break }
566+ \t\tt := toks[len(toks)-1]
567+ \t\tmeta = append(meta, alignMeta{t.Kind, t.Off, t.End, t.Nl, flowDepth})
568+ \t}
569+ \treturn meta
570+ }
571+ func toMeta(toks []Tok) []alignMeta { panic("use scanMeta for newline") }
572+ ` : `func toMeta(toks []Tok) []alignMeta {
573+ \tm := make([]alignMeta, len(toks))
574+ \tfor i, t := range toks { m[i] = alignMeta{t.Kind, t.Off, t.End, t.Nl, 0} }
575+ \treturn m
576+ }` ;
577+ const checkStreamEqFn = hasNewline ? `
578+ func checkStreamEq(text string, meta []alignMeta) bool {
579+ \tfresh := scanMeta(text)
580+ \tif len(fresh) != len(meta) { return false }
581+ \tfor i := range fresh {
582+ \t\tf, m := fresh[i], meta[i]
583+ \t\tif f.Kind != m.Kind || f.Off != m.Off || f.End != m.End || f.Nl != m.Nl || f.Fd != m.Fd { return false }
584+ \t\tif text[f.Off:f.End] != text[m.Off:m.End] { return false }
585+ \t}
586+ \treturn true
587+ }
588+ ` : `
589+ func checkStreamEq(text string, meta []alignMeta) bool {
590+ \tfresh := toMeta(tokenize(text))
591+ \tif len(fresh) != len(meta) { return false }
592+ \tfor i := range fresh {
593+ \t\tf, m := fresh[i], meta[i]
594+ \t\tif f.Kind != m.Kind || f.Off != m.Off || f.End != m.End || f.Nl != m.Nl { return false }
595+ \t\tif text[f.Off:f.End] != text[m.Off:m.End] { return false }
596+ \t}
597+ \treturn true
598+ }
599+ ` ;
600+ const initToks = hasNewline ? 'scanMeta(src)' : 'toMeta(tokenize(src))' ;
469601 return `type Edit struct { Start, End int; Text string }
470- type alignMeta struct { Kind string; Off, End int; Nl bool }
602+ type alignMeta struct { Kind string; Off, End int; Nl bool; Fd int }
471603type Align struct {
472604\tOldN int \`json:"oldN"\`
473605\tNewN int \`json:"newN"\`
@@ -476,11 +608,7 @@ type Align struct {
476608\tRelexed int \`json:"relexed"\`
477609\tStreamEq bool \`json:"streamEq"\`
478610}
479- func toMeta(toks []Tok) []alignMeta {
480- \tm := make([]alignMeta, len(toks))
481- \tfor i, t := range toks { m[i] = alignMeta{t.Kind, t.Off, t.End, t.Nl} }
482- \treturn m
483- }
611+ ${ toMetaFn }
484612func computeAlignCore(oldText string, oldToks []alignMeta, newText string, newToks []alignMeta) (oldN, newN, prefix, suffix int) {
485613\toldN, newN = len(oldToks), len(newToks)
486614\tfor prefix < oldN && prefix < newN {
@@ -504,20 +632,10 @@ func toksFromMeta(text string, meta []alignMeta) []Tok {
504632\tfor i, m := range meta { t[i] = Tok{m.Kind, text[m.Off:m.End], m.Off, m.End, m.Nl} }
505633\treturn t
506634}
507- func checkStreamEq(text string, meta []alignMeta) bool {
508- \tfresh := toMeta(tokenize(text))
509- \tif len(fresh) != len(meta) { return false }
510- \tfor i := range fresh {
511- \t\tf, m := fresh[i], meta[i]
512- \t\tif f.Kind != m.Kind || f.Off != m.Off || f.End != m.End || f.Nl != m.Nl { return false }
513- \t\tif text[f.Off:f.End] != text[m.Off:m.End] { return false }
514- \t}
515- \treturn true
516- }
517- ${ windowHelpers } type Doc struct { text string; root int32; toks []alignMeta; align *Align }
635+ ${ checkStreamEqFn } ${ windowHelpers } type Doc struct { text string; root int32; toks []alignMeta; align *Align }
518636func NewDoc(src string) *Doc {
519637\td := &Doc{text: src}
520- \td.toks = toMeta(tokenize(src))
638+ \td.toks = ${ initToks }
521639\td.root = parse(tokenize(src))
522640\treturn d
523641}
0 commit comments