@@ -136,10 +136,12 @@ function lexer(ir: ParserIR): string {
136136 const rx = ir . regexCtx ;
137137 const tpl = ir . tpl ;
138138 const nl = ir . newlineCfg ;
139- const rxOrTpl = ! ! ( rx || tpl ) ;
139+ const rxOnly = ! ! ( rx && ! tpl && ! nl ) ;
140+ const rxOrTpl = ! ! ( rx || tpl ) && ! rxOnly ;
141+ const stateful = ! ! ( rx || tpl ) ;
140142 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 } )` ;
143+ const toks = ir . tokens . map ( ( t ) => scanTok ( t , defs , stateful , rx ?. regexToken , tpl ?. token ) ) . join ( '\n' ) ;
144+ const pushPunct = stateful ? ( p : string ) => `emit("", ${ J ( p ) } , pos, pos + ${ p . length } )` : ( p : string ) => `pushTok("", ${ J ( p ) } , pos, pos + ${ p . length } )` ;
143145 const puncts = ir . puncts . map ( ( p ) =>
144146 `\t\tif strings.HasPrefix(src[pos:], ${ J ( p ) } ) { ${ pushPunct ( p ) } ; pos += ${ p . length } ; continue }` ) . join ( '\n' ) ;
145147 const goMap = ( a : string [ ] ) => `map[string]bool{${ a . map ( ( x ) => `${ J ( x ) } : true` ) . join ( ', ' ) } }` ;
@@ -186,11 +188,38 @@ function lexer(ir: ParserIR): string {
186188 nl ? newlinePartsGo ( nl , 'emit' ) . hooks : '' ,
187189 ] . filter ( Boolean ) . join ( '\n' ) ;
188190 const emitTail = rx ? `\n\t\tbpText = prevText; hasPrev2 = hasPrev; prevKind = kind; prevText = text; hasPrev = true` : '' ;
189- const emitFn = rxOrTpl ? `\temit := func(kind, text string, off, end int) {
191+ const emitFn = stateful ? `\temit := func(kind, text string, off, end int) {
190192${ emitHooks }
191193\t\ttoks = append(toks, Tok{kind, text, off, end, pendingNl}); pendingNl = false${ emitTail }
192194\t}
193195\t_ = emit
196+ ` : '' ;
197+ const rxStateFrom = rx ? `\t_divT := ${ goMap ( rx . divisionTexts ) }
198+ \t_divK := ${ goMap ( rx . divisionTypes ) }
199+ \t_rxT := ${ goMap ( rx . regexTexts ) }
200+ \t_phK := ${ goMap ( rx . parenHeadKw ) }
201+ \t_mem := ${ goMap ( rx . memberAccess ) }
202+ \t_pav := ${ goMap ( rx . postfixAfterValue ) }
203+ \tconst IDENT = ${ J ( rx . identToken ) }
204+ \tprevIsValue := func() bool {
205+ \t\tif !hasPrev { return false }
206+ \t\tif _pav[prevText] { return lastBang }
207+ \t\tisExprKw := prevKind == IDENT && _rxT[prevText]
208+ \t\tisParenHead := prevText == ")" && lastClose
209+ \t\treturn !isExprKw && !isParenHead && (_divK[prevKind] || _divT[prevText])
210+ \t}
211+ \temit := func(kind, text string, off, end int) {
212+ \t\tif text == "(" {
213+ \t\t\tisMember := hasPrev2 && _mem[bpText]
214+ \t\t\tparenHead = append(parenHead, !isMember && prevKind == IDENT && _phK[prevText])
215+ \t\t} else if text == ")" {
216+ \t\t\tif len(parenHead) > 0 { lastClose = parenHead[len(parenHead)-1]; parenHead = parenHead[:len(parenHead)-1] } else { lastClose = false }
217+ \t\t}
218+ \t\tif _pav[text] { lastBang = prevIsValue() }
219+ \t\t*acc = append(*acc, Tok{kind, text, off, end, pendingNl}); pendingNl = false
220+ \t\tbpText = prevText; hasPrev2 = hasPrev; prevKind = kind; prevText = text; hasPrev = true
221+ \t}
222+ \t_ = emit
194223` : '' ;
195224 const tplDispatch = tpl ? `\t\tif len(templateStack) > 0 && strings.HasPrefix(src[pos:], ${ J ( tpl . interpClose ) } ) && templateStack[len(templateStack)-1] == 0 {
196225\t\t\ttemplateStack = templateStack[:len(templateStack)-1]
@@ -204,22 +233,22 @@ ${emitHooks}
204233\t\t\tpos = e; continue
205234\t\t}
206235` : '' ;
207- const nlState = nl ? newlinePartsGo ( nl , rxOrTpl ? 'emit' : 'pushTok' ) . state : '' ;
236+ const nlState = nl ? newlinePartsGo ( nl , stateful ? 'emit' : 'pushTok' ) . state : '' ;
208237 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)
238+ const nlBoundary = nl ? newlinePartsGo ( nl , stateful ? 'emit' : 'pushTok' ) . boundary : '' ;
239+ 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)
211240\t\tif c == 10 || c == 13 { pendingNl = true; pos++; continue } // LF/CR
212241\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 }
213242` ;
214- const pushHooks = nl && ! rxOrTpl ? newlinePartsGo ( nl , 'pushTok' ) . hooks : '' ;
215- const pushTokFn = rxOrTpl ? '' : nl
243+ const pushHooks = nl && ! stateful ? newlinePartsGo ( nl , 'pushTok' ) . hooks : '' ;
244+ const pushTokFn = stateful ? '' : nl
216245 ? `\tpushTok := func(kind, text string, off, end int) {
217246${ pushHooks } \t\ttoks = append(toks, Tok{kind, text, off, end, pendingNl}); pendingNl = false
218247\t}
219248\t_ = pushTok
220249`
221250 : `\tpushTok := func(kind, text string, off, end int) { toks = append(toks, Tok{kind, text, off, end, pendingNl}); pendingNl = false }\n\t_ = pushTok\n` ;
222- const pushTokAccFn = nl && ! rxOrTpl
251+ const pushTokAccFn = nl && ! stateful
223252 ? `\tpushTok := func(kind, text string, off, end int) {
224253${ pushHooks } \t\t*acc = append(*acc, Tok{kind, text, off, end, pendingNl}); pendingNl = false
225254\t}
@@ -232,6 +261,21 @@ ${pushHooks}\t\t*acc = append(*acc, Tok{kind, text, off, end, pendingNl}); pendi
232261${ nlWs } ${ tplDispatch } ${ toks }
233262${ puncts }
234263\t\tpanic(fmt.Sprintf("lex error at %d", pos))` ;
264+ if ( rxOnly ) {
265+ return `${ defs . length ? 'var _s string\n' + defs . join ( '\n' ) + '\n' : '' } func lexFrom(src string, pos int, pendingNl bool, prevText, prevKind, bpText string, hasPrev, hasPrev2 bool, parenHead []bool, lastClose, lastBang bool, acc *[]Tok, limit int) (int, bool, string, string, string, bool, bool, []bool, bool, bool) {
266+ \tn := len(src)
267+ ${ rxStateFrom } ${ defs . length ? '\t_s = src\n' : '' } \tbase := len(*acc)
268+ \tfor pos < n && (limit <= 0 || len(*acc)-base < limit) {
269+ ${ loopBody }
270+ \t}
271+ \treturn pos, pendingNl, prevText, prevKind, bpText, hasPrev, hasPrev2, parenHead, lastClose, lastBang
272+ }
273+ func lex(src string) []Tok {
274+ \tvar out []Tok
275+ \tlexFrom(src, 0, false, "", "", "", false, false, nil, false, false, &out, 0)
276+ \treturn out
277+ }` ;
278+ }
235279 if ( rxOrTpl ) {
236280 return `${ defs . length ? 'var _s string\n' + defs . join ( '\n' ) + '\n' : '' } func lex(src string) []Tok {
237281\ttoks := toks[:0]
@@ -420,8 +464,10 @@ ${r.nudSeqs.map((seq) => `\t{ save := pos; sb := len(scratch); nb := len(nodes);
420464}
421465
422466function docEditBlockGo ( ir : ParserIR ) : string {
423- const windowLex = ! ( ir . regexCtx || ir . tpl ) ;
424- const hasNewline = ! ! ir . newlineCfg ;
467+ const windowLex = ! ir . tpl && ( ! ir . regexCtx || ! ir . newlineCfg ) ;
468+ const hasNewline = ! ! ( ir . newlineCfg && ! ir . regexCtx && ! ir . tpl ) ;
469+ const rxOnly = ! ! ( ir . regexCtx && ! ir . tpl && ! ir . newlineCfg ) ;
470+ const zeroMeta = ', Pd: 0, Lc: false, Lb: false, Hd: false' ;
425471 const windowHelpers = windowLex ? ( hasNewline ? `
426472func findTokAtOffKind(toks []alignMeta, off int, kind string) int {
427473\tlo, hi := 0, len(toks)-1
@@ -464,7 +510,7 @@ func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start,
464510\t\tscanOff, pendingNl, lineStart, emittedContent, flowDepth = lexFrom(newText, scanOff, pendingNl, lineStart, emittedContent, flowDepth, &scratch, 1)
465511\t\tif len(scratch) == before { break }
466512\t\tt := scratch[len(scratch)-1]
467- \t\tout = append(out, alignMeta{t.Kind, t.Off, t.End, t.Nl, flowDepth})
513+ \t\tout = append(out, alignMeta{t.Kind, t.Off, t.End, t.Nl, flowDepth, 0, false, false, false })
468514\t\trelexed++
469515\t\tif t.Off >= editEnd {
470516\t\t\toIdx := findTokAtOffKind(oldToks, t.Off-delta, t.Kind)
@@ -473,7 +519,99 @@ func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start,
473519\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] {
474520\t\t\t\t\tfor j := oIdx + 1; j < len(oldToks); j++ {
475521\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})
522+ \t\t\t\t\t\tout = append(out, alignMeta{ot.Kind, ot.Off + delta, ot.End + delta, ot.Nl, ot.Fd, ot.Pd, ot.Lc, ot.Lb, ot.Hd})
523+ \t\t\t\t\t}
524+ \t\t\t\t\treturn out, relexed
525+ \t\t\t\t}
526+ \t\t\t}
527+ \t\t}
528+ \t}
529+ \treturn out, relexed
530+ }
531+ ` : rxOnly ? `
532+ func findTokAtOff(toks []alignMeta, off int) int {
533+ \tlo, hi := 0, len(toks)-1
534+ \tfor lo <= hi {
535+ \t\tmid := (lo + hi) >> 1
536+ \t\tif toks[mid].Off < off { lo = mid + 1 } else if toks[mid].Off > off { hi = mid - 1 } else { return mid }
537+ \t}
538+ \treturn -1
539+ }
540+ func reconstructParens(toks []alignMeta, text string, b int) []bool {
541+ \tneed := 0
542+ \tif b >= 0 { need = toks[b].Pd }
543+ \tout := make([]bool, 0, need)
544+ \tfor i := b; i >= 0 && need > 0; i-- {
545+ \t\tt := toks[i]
546+ \t\tif text[t.Off:t.End] == "(" && t.Pd == need {
547+ \t\t\tout = append([]bool{t.Hd}, out...)
548+ \t\t\tneed--
549+ \t\t}
550+ \t}
551+ \treturn out
552+ }
553+ func parenStacksEq(a, b []bool) bool {
554+ \tif len(a) != len(b) { return false }
555+ \tfor i := range a { if a[i] != b[i] { return false } }
556+ \treturn true
557+ }
558+ func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start, end int, ins string) ([]alignMeta, int) {
559+ \tdelta := len(ins) - (end - start)
560+ \teditEnd := start + len(ins)
561+ \tmaxIdx := -1
562+ \tfor i := 0; i < len(oldToks); i++ {
563+ \t\tif oldToks[i].End < start { maxIdx = i } else { break }
564+ \t}
565+ \trb := -1
566+ \tif maxIdx >= 0 { rb = maxIdx - 1 }
567+ \tvar out []alignMeta
568+ \tif rb >= 0 { out = append(out, oldToks[:rb+1]...) }
569+ \tvar scanOff int
570+ \tpendingNl := false
571+ \tprevText, prevKind, bpText := "", "", ""
572+ \thasPrev, hasPrev2 := false, false
573+ \tvar parenHead []bool
574+ \tlastClose, lastBang := false, false
575+ \tif rb >= 0 {
576+ \t\tanchor := oldToks[rb]
577+ \t\tscanOff = anchor.End
578+ \t\tprevText = oldText[anchor.Off:anchor.End]
579+ \t\tprevKind = anchor.Kind
580+ \t\thasPrev = true
581+ \t\tif rb >= 1 {
582+ \t\t\tbpText = oldText[oldToks[rb-1].Off:oldToks[rb-1].End]
583+ \t\t\thasPrev2 = true
584+ \t\t}
585+ \t\tlastClose = anchor.Lc
586+ \t\tlastBang = anchor.Lb
587+ \t\tparenHead = reconstructParens(oldToks, oldText, rb)
588+ \t}
589+ \tvar scratch []Tok
590+ \trelexed := 0
591+ \tfor scanOff < len(newText) {
592+ \t\tbefore := len(scratch)
593+ \t\tscanOff, pendingNl, prevText, prevKind, bpText, hasPrev, hasPrev2, parenHead, lastClose, lastBang = lexFrom(newText, scanOff, pendingNl, prevText, prevKind, bpText, hasPrev, hasPrev2, parenHead, lastClose, lastBang, &scratch, 1)
594+ \t\tif len(scratch) == before { break }
595+ \t\tt := scratch[len(scratch)-1]
596+ \t\ttxt := newText[t.Off:t.End]
597+ \t\thd := false
598+ \t\tif txt == "(" && len(parenHead) > 0 { hd = parenHead[len(parenHead)-1] }
599+ \t\tout = append(out, alignMeta{t.Kind, t.Off, t.End, t.Nl, 0, len(parenHead), lastClose, lastBang, hd})
600+ \t\trelexed++
601+ \t\tif t.Off >= editEnd {
602+ \t\t\toIdx := findTokAtOff(oldToks, t.Off-delta)
603+ \t\t\tif oIdx >= 0 {
604+ \t\t\t\to := oldToks[oIdx]
605+ \t\t\t\tnewPrevText := ""
606+ \t\t\t\tif len(out) > 1 { p := out[len(out)-2]; newPrevText = newText[p.Off:p.End] }
607+ \t\t\t\toldPrevText := ""
608+ \t\t\t\tif oIdx >= 1 { p := oldToks[oIdx-1]; oldPrevText = oldText[p.Off:p.End] }
609+ \t\t\t\tbpOk := newPrevText == oldPrevText
610+ \t\t\t\toldStack := reconstructParens(oldToks, oldText, oIdx)
611+ \t\t\t\tif o.Pd == len(parenHead) && parenStacksEq(oldStack, parenHead) && o.Lc == lastClose && o.Lb == lastBang && bpOk && o.Kind == t.Kind && o.End == t.End-delta && o.Nl == t.Nl && oldText[o.Off:o.End] == newText[t.Off:t.End] {
612+ \t\t\t\t\tfor j := oIdx + 1; j < len(oldToks); j++ {
613+ \t\t\t\t\t\tot := oldToks[j]
614+ \t\t\t\t\t\tout = append(out, alignMeta{ot.Kind, ot.Off + delta, ot.End + delta, ot.Nl, ot.Fd, ot.Pd, ot.Lc, ot.Lb, ot.Hd})
477615\t\t\t\t\t}
478616\t\t\t\t\treturn out, relexed
479617\t\t\t\t}
@@ -512,7 +650,7 @@ func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start,
512650\t\tscanOff, pendingNl = lexFrom(newText, scanOff, pendingNl, &scratch, 1)
513651\t\tif len(scratch) == before { break }
514652\t\tt := scratch[len(scratch)-1]
515- \t\tout = append(out, alignMeta{t.Kind, t.Off, t.End, t.Nl, 0})
653+ \t\tout = append(out, alignMeta{t.Kind, t.Off, t.End, t.Nl, 0, 0, false, false, false })
516654\t\trelexed++
517655\t\tif t.Off >= editEnd {
518656\t\t\toIdx := findTokAtOff(oldToks, t.Off-delta)
@@ -521,7 +659,7 @@ func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start,
521659\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] {
522660\t\t\t\t\tfor j := oIdx + 1; j < len(oldToks); j++ {
523661\t\t\t\t\t\tot := oldToks[j]
524- \t\t\t\t\t\tout = append(out, alignMeta{ot.Kind, ot.Off + delta, ot.End + delta, ot.Nl, 0 })
662+ \t\t\t\t\t\tout = append(out, alignMeta{ot.Kind, ot.Off + delta, ot.End + delta, ot.Nl, ot.Fd, ot.Pd, ot.Lc, ot.Lb, ot.Hd })
525663\t\t\t\t\t}
526664\t\t\t\t\treturn out, relexed
527665\t\t\t\t}
@@ -564,14 +702,36 @@ func scanMeta(src string) []alignMeta {
564702\t\tpos, pendingNl, lineStart, emittedContent, flowDepth = lexFrom(src, pos, pendingNl, lineStart, emittedContent, flowDepth, &toks, 1)
565703\t\tif len(toks) == before { break }
566704\t\tt := toks[len(toks)-1]
567- \t\tmeta = append(meta, alignMeta{t.Kind, t.Off, t.End, t.Nl, flowDepth})
705+ \t\tmeta = append(meta, alignMeta{t.Kind, t.Off, t.End, t.Nl, flowDepth, 0, false, false, false })
568706\t}
569707\treturn meta
570708}
571709func toMeta(toks []Tok) []alignMeta { panic("use scanMeta for newline") }
710+ ` : rxOnly ? `
711+ func scanMeta(src string) []alignMeta {
712+ \tvar toks []Tok
713+ \tvar meta []alignMeta
714+ \tpos, pendingNl := 0, false
715+ \tprevText, prevKind, bpText := "", "", ""
716+ \thasPrev, hasPrev2 := false, false
717+ \tvar parenHead []bool
718+ \tlastClose, lastBang := false, false
719+ \tfor pos < len(src) {
720+ \t\tbefore := len(toks)
721+ \t\tpos, pendingNl, prevText, prevKind, bpText, hasPrev, hasPrev2, parenHead, lastClose, lastBang = lexFrom(src, pos, pendingNl, prevText, prevKind, bpText, hasPrev, hasPrev2, parenHead, lastClose, lastBang, &toks, 1)
722+ \t\tif len(toks) == before { break }
723+ \t\tt := toks[len(toks)-1]
724+ \t\ttxt := src[t.Off:t.End]
725+ \t\thd := false
726+ \t\tif txt == "(" && len(parenHead) > 0 { hd = parenHead[len(parenHead)-1] }
727+ \t\tmeta = append(meta, alignMeta{t.Kind, t.Off, t.End, t.Nl, 0, len(parenHead), lastClose, lastBang, hd})
728+ \t}
729+ \treturn meta
730+ }
731+ func toMeta(toks []Tok) []alignMeta { panic("use scanMeta for regex") }
572732` : `func toMeta(toks []Tok) []alignMeta {
573733\tm := make([]alignMeta, len(toks))
574- \tfor i, t := range toks { m[i] = alignMeta{t.Kind, t.Off, t.End, t.Nl, 0} }
734+ \tfor i, t := range toks { m[i] = alignMeta{t.Kind, t.Off, t.End, t.Nl, 0, 0, false, false, false } }
575735\treturn m
576736}` ;
577737 const checkStreamEqFn = hasNewline ? `
@@ -585,6 +745,17 @@ func checkStreamEq(text string, meta []alignMeta) bool {
585745\t}
586746\treturn true
587747}
748+ ` : rxOnly ? `
749+ func checkStreamEq(text string, meta []alignMeta) bool {
750+ \tfresh := scanMeta(text)
751+ \tif len(fresh) != len(meta) { return false }
752+ \tfor i := range fresh {
753+ \t\tf, m := fresh[i], meta[i]
754+ \t\tif f.Kind != m.Kind || f.Off != m.Off || f.End != m.End || f.Nl != m.Nl || f.Pd != m.Pd || f.Lc != m.Lc || f.Lb != m.Lb || f.Hd != m.Hd { return false }
755+ \t\tif text[f.Off:f.End] != text[m.Off:m.End] { return false }
756+ \t}
757+ \treturn true
758+ }
588759` : `
589760func checkStreamEq(text string, meta []alignMeta) bool {
590761\tfresh := toMeta(tokenize(text))
@@ -597,9 +768,9 @@ func checkStreamEq(text string, meta []alignMeta) bool {
597768\treturn true
598769}
599770` ;
600- const initToks = hasNewline ? 'scanMeta(src)' : 'toMeta(tokenize(src))' ;
771+ const initToks = ( hasNewline || rxOnly ) ? 'scanMeta(src)' : 'toMeta(tokenize(src))' ;
601772 return `type Edit struct { Start, End int; Text string }
602- type alignMeta struct { Kind string; Off, End int; Nl bool; Fd int }
773+ type alignMeta struct { Kind string; Off, End int; Nl bool; Fd, Pd int; Lc, Lb, Hd bool }
603774type Align struct {
604775\tOldN int \`json:"oldN"\`
605776\tNewN int \`json:"newN"\`
0 commit comments