Skip to content

Commit 55c2f21

Browse files
Extend windowed relex to newline-mode portable lexers (issue #57 S4). (#65)
1 parent a813515 commit 55c2f21

4 files changed

Lines changed: 492 additions & 111 deletions

File tree

src/target-go.ts

Lines changed: 155 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -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

395422
function 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+
` : `
398486
func 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 }
471603
type 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}
484612
func 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 }
518636
func 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

Comments
 (0)