Skip to content

Commit b72941e

Browse files
Extend windowed relex to rx-only portable lexers (issue #57 S5a). (#66)
1 parent 55c2f21 commit b72941e

4 files changed

Lines changed: 544 additions & 55 deletions

File tree

src/target-go.ts

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

422466
function 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 ? `
426472
func 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
}
571709
func 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
` : `
589760
func 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 }
603774
type Align struct {
604775
\tOldN int \`json:"oldN"\`
605776
\tNewN int \`json:"newN"\`

0 commit comments

Comments
 (0)