Skip to content

Commit a813515

Browse files
emit-portable: windowed relexing for stateless lexers (S3, #57) (#64)
Extend doc edit resync with windowed relex step and S3 suffix handling across TS, Go, and Rust targets; update portable-targets coverage.
1 parent 860c6c4 commit a813515

4 files changed

Lines changed: 535 additions & 196 deletions

File tree

src/target-go.ts

Lines changed: 178 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,38 @@ ${pushHooks}\t\ttoks = append(toks, Tok{kind, text, off, end, pendingNl}); pendi
213213
\t_ = pushTok
214214
`
215215
: `\tpushTok := func(kind, text string, off, end int) { toks = append(toks, Tok{kind, text, off, end, pendingNl}); pendingNl = false }\n\t_ = pushTok\n`;
216-
return `${defs.length ? 'var _s string\n' + defs.join('\n') + '\n' : ''}func lex(src string) []Tok {
216+
const pushTokAccFn = `\tpushTok := func(kind, text string, off, end int) { *acc = append(*acc, Tok{kind, text, off, end, pendingNl}); pendingNl = false }
217+
\t_ = pushTok
218+
`;
219+
const loopBody = `${nlBoundary}\t\tc := int(src[pos])
220+
${nlWs}${tplDispatch}${toks}
221+
${puncts}
222+
\t\tpanic(fmt.Sprintf("lex error at %d", pos))`;
223+
if (stateful) {
224+
return `${defs.length ? 'var _s string\n' + defs.join('\n') + '\n' : ''}func lex(src string) []Tok {
217225
\ttoks := toks[:0]
218226
\tn := len(src)
219227
\tpos := 0
220228
\tpendingNl := false
221229
\t_ = pendingNl
222230
${rxState}${tplState}${nlState}${emitFn}${pushTokFn}${defs.length ? '\t_s = src\n' : ''}\tfor pos < n {
223-
${nlBoundary}\t\tc := int(src[pos])
224-
${nlWs}${tplDispatch}${toks}
225-
${puncts}
226-
\t\tpanic(fmt.Sprintf("lex error at %d", pos))
231+
${loopBody}
227232
\t}
228233
\treturn toks
234+
}`;
235+
}
236+
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) {
237+
\tn := len(src)
238+
${pushTokAccFn}${defs.length ? '\t_s = src\n' : ''}\tbase := len(*acc)
239+
\tfor pos < n && (limit <= 0 || len(*acc)-base < limit) {
240+
${loopBody}
241+
\t}
242+
\treturn pos, pendingNl
243+
}
244+
func lex(src string) []Tok {
245+
\tvar out []Tok
246+
\tlexFrom(src, 0, false, &out, 0)
247+
\treturn out
229248
}`;
230249
}
231250

@@ -373,6 +392,159 @@ ${r.nudSeqs.map((seq) => `\t{ save := pos; sb := len(scratch); nb := len(nodes);
373392
}`;
374393
}
375394

395+
function docEditBlockGo(ir: ParserIR): string {
396+
const stateless = !(ir.regexCtx || ir.tpl || ir.newlineCfg);
397+
const windowHelpers = stateless ? `
398+
func findTokAtOff(toks []alignMeta, off int) int {
399+
\tlo, hi := 0, len(toks)-1
400+
\tfor lo <= hi {
401+
\t\tmid := (lo + hi) >> 1
402+
\t\tif toks[mid].Off < off { lo = mid + 1 } else if toks[mid].Off > off { hi = mid - 1 } else { return mid }
403+
\t}
404+
\treturn -1
405+
}
406+
func windowRelexStep(oldText string, oldToks []alignMeta, newText string, start, end int, ins string) ([]alignMeta, int) {
407+
\tdelta := len(ins) - (end - start)
408+
\teditEnd := start + len(ins)
409+
\tmaxIdx := -1
410+
\tfor i := 0; i < len(oldToks); i++ {
411+
\t\tif oldToks[i].End < start { maxIdx = i } else { break }
412+
\t}
413+
\trb := -1
414+
\tif maxIdx >= 0 { rb = maxIdx - 1 }
415+
\tvar out []alignMeta
416+
\tif rb >= 0 { out = append(out, oldToks[:rb+1]...) }
417+
\tscanOff := 0
418+
\tif rb >= 0 { scanOff = oldToks[rb].End }
419+
\tpendingNl := false
420+
\tvar scratch []Tok
421+
\trelexed := 0
422+
\tfor scanOff < len(newText) {
423+
\t\tbefore := len(scratch)
424+
\t\tscanOff, pendingNl = lexFrom(newText, scanOff, pendingNl, &scratch, 1)
425+
\t\tif len(scratch) == before { break }
426+
\t\tt := scratch[len(scratch)-1]
427+
\t\tout = append(out, alignMeta{t.Kind, t.Off, t.End, t.Nl})
428+
\t\trelexed++
429+
\t\tif t.Off >= editEnd {
430+
\t\t\toIdx := findTokAtOff(oldToks, t.Off-delta)
431+
\t\t\tif oIdx >= 0 {
432+
\t\t\t\to := oldToks[oIdx]
433+
\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] {
434+
\t\t\t\t\tfor j := oIdx + 1; j < len(oldToks); j++ {
435+
\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})
437+
\t\t\t\t\t}
438+
\t\t\t\t\treturn out, relexed
439+
\t\t\t\t}
440+
\t\t\t}
441+
\t\t}
442+
\t}
443+
\treturn out, relexed
444+
}
445+
` : '';
446+
const editBody = stateless
447+
? `\tcurText := d.text
448+
\tcurToks := d.toks
449+
\tfor _, e := range edits {
450+
\t\tstepOldText, stepOldToks := curText, curToks
451+
\t\tn := len(curText)
452+
\t\tstart, end := e.Start, e.End
453+
\t\tif start < 0 { start = 0 }
454+
\t\tif start > n { start = n }
455+
\t\tif end < start { end = start }
456+
\t\tif end > n { end = n }
457+
\t\tins := e.Text
458+
\t\tcurText = curText[:start] + ins + curText[end:]
459+
\t\tvar stepRelexed int
460+
\t\tcurToks, stepRelexed = windowRelexStep(stepOldText, stepOldToks, curText, start, end, ins)
461+
\t\trelexed += stepRelexed
462+
\t}
463+
\td.text = curText
464+
\td.toks = curToks`
465+
: `\tfor _, e := range edits { d.text = applyEdit(d.text, e) }
466+
\tnewToks := tokenize(d.text)
467+
\td.toks = toMeta(newToks)
468+
\trelexed = len(d.toks)`;
469+
return `type Edit struct { Start, End int; Text string }
470+
type alignMeta struct { Kind string; Off, End int; Nl bool }
471+
type Align struct {
472+
\tOldN int \`json:"oldN"\`
473+
\tNewN int \`json:"newN"\`
474+
\tPrefix int \`json:"prefix"\`
475+
\tSuffix int \`json:"suffix"\`
476+
\tRelexed int \`json:"relexed"\`
477+
\tStreamEq bool \`json:"streamEq"\`
478+
}
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+
}
484+
func computeAlignCore(oldText string, oldToks []alignMeta, newText string, newToks []alignMeta) (oldN, newN, prefix, suffix int) {
485+
\toldN, newN = len(oldToks), len(newToks)
486+
\tfor prefix < oldN && prefix < newN {
487+
\t\to, n := oldToks[prefix], newToks[prefix]
488+
\t\tif o.Kind != n.Kind || o.Off != n.Off || o.End != n.End || o.Nl != n.Nl { break }
489+
\t\tif oldText[o.Off:o.End] != newText[n.Off:n.End] { break }
490+
\t\tprefix++
491+
\t}
492+
\tdelta := len(newText) - len(oldText)
493+
\tminN := oldN; if newN < minN { minN = newN }
494+
\tfor prefix+suffix < minN {
495+
\t\to, n := oldToks[oldN-1-suffix], newToks[newN-1-suffix]
496+
\t\tif o.Kind != n.Kind || o.Nl != n.Nl || n.Off != o.Off+delta || n.End != o.End+delta { break }
497+
\t\tif oldText[o.Off:o.End] != newText[n.Off:n.End] { break }
498+
\t\tsuffix++
499+
\t}
500+
\treturn
501+
}
502+
func toksFromMeta(text string, meta []alignMeta) []Tok {
503+
\tt := make([]Tok, len(meta))
504+
\tfor i, m := range meta { t[i] = Tok{m.Kind, text[m.Off:m.End], m.Off, m.End, m.Nl} }
505+
\treturn t
506+
}
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 }
518+
func NewDoc(src string) *Doc {
519+
\td := &Doc{text: src}
520+
\td.toks = toMeta(tokenize(src))
521+
\td.root = parse(tokenize(src))
522+
\treturn d
523+
}
524+
func (d *Doc) Text() string { return d.text }
525+
func (d *Doc) Root() int32 { return d.root }
526+
func (d *Doc) Align() *Align { return d.align }
527+
func applyEdit(text string, e Edit) string {
528+
\tn := len(text)
529+
\tstart, end := e.Start, e.End
530+
\tif start < 0 { start = 0 }
531+
\tif start > n { start = n }
532+
\tif end < start { end = start }
533+
\tif end > n { end = n }
534+
\treturn text[:start] + e.Text + text[end:]
535+
}
536+
func (d *Doc) Edit(edits []Edit) int32 {
537+
\toldText, oldToks := d.text, d.toks
538+
\trelexed := 0
539+
${editBody}
540+
\tstreamEq := checkStreamEq(d.text, d.toks)
541+
\toldN, newN, prefix, suffix := computeAlignCore(oldText, oldToks, d.text, d.toks)
542+
\td.align = &Align{oldN, newN, prefix, suffix, relexed, streamEq}
543+
\td.root = parse(toksFromMeta(d.text, d.toks))
544+
\treturn d.root
545+
}`;
546+
}
547+
376548
export const goTarget: Target = {
377549
name: 'go',
378550
ext: 'go',
@@ -551,68 +723,7 @@ func parse(t []Tok) int32 {
551723
\treturn parse${ir.entry}()
552724
}
553725
554-
type Edit struct { Start, End int; Text string }
555-
type alignMeta struct { Kind string; Off, End int; Nl bool }
556-
type Align struct {
557-
\tOldN int \`json:"oldN"\`
558-
\tNewN int \`json:"newN"\`
559-
\tPrefix int \`json:"prefix"\`
560-
\tSuffix int \`json:"suffix"\`
561-
}
562-
func toMeta(toks []Tok) []alignMeta {
563-
\tm := make([]alignMeta, len(toks))
564-
\tfor i, t := range toks { m[i] = alignMeta{t.Kind, t.Off, t.End, t.Nl} }
565-
\treturn m
566-
}
567-
func computeAlign(oldText string, oldToks []alignMeta, newText string, newToks []alignMeta) Align {
568-
\toldN, newN := len(oldToks), len(newToks)
569-
\tprefix := 0
570-
\tfor prefix < oldN && prefix < newN {
571-
\t\to, n := oldToks[prefix], newToks[prefix]
572-
\t\tif o.Kind != n.Kind || o.Off != n.Off || o.End != n.End || o.Nl != n.Nl { break }
573-
\t\tif oldText[o.Off:o.End] != newText[n.Off:n.End] { break }
574-
\t\tprefix++
575-
\t}
576-
\tdelta := len(newText) - len(oldText)
577-
\tminN := oldN; if newN < minN { minN = newN }
578-
\tsuffix := 0
579-
\tfor prefix+suffix < minN {
580-
\t\to, n := oldToks[oldN-1-suffix], newToks[newN-1-suffix]
581-
\t\tif o.Kind != n.Kind || o.Nl != n.Nl || n.Off != o.Off+delta || n.End != o.End+delta { break }
582-
\t\tif oldText[o.Off:o.End] != newText[n.Off:n.End] { break }
583-
\t\tsuffix++
584-
\t}
585-
\treturn Align{oldN, newN, prefix, suffix}
586-
}
587-
type Doc struct { text string; root int32; toks []alignMeta; align *Align }
588-
func NewDoc(src string) *Doc {
589-
\td := &Doc{text: src}
590-
\td.toks = toMeta(tokenize(src))
591-
\td.root = parse(tokenize(src))
592-
\treturn d
593-
}
594-
func (d *Doc) Text() string { return d.text }
595-
func (d *Doc) Root() int32 { return d.root }
596-
func (d *Doc) Align() *Align { return d.align }
597-
func applyEdit(text string, e Edit) string {
598-
\tn := len(text)
599-
\tstart, end := e.Start, e.End
600-
\tif start < 0 { start = 0 }
601-
\tif start > n { start = n }
602-
\tif end < start { end = start }
603-
\tif end > n { end = n }
604-
\treturn text[:start] + e.Text + text[end:]
605-
}
606-
func (d *Doc) Edit(edits []Edit) int32 {
607-
\toldText, oldToks := d.text, d.toks
608-
\tfor _, e := range edits { d.text = applyEdit(d.text, e) }
609-
\tnewToks := tokenize(d.text)
610-
\td.toks = toMeta(newToks)
611-
\ta := computeAlign(oldText, oldToks, d.text, d.toks)
612-
\td.align = &a
613-
\td.root = parse(newToks)
614-
\treturn d.root
615-
}
726+
${docEditBlockGo(ir)}
616727
`;
617728
},
618729
emitRunner(): string {

0 commit comments

Comments
 (0)