diff --git a/cmd/garm-cli/cmd/templates.go b/cmd/garm-cli/cmd/templates.go index 752606ac9..ef4ae6222 100644 --- a/cmd/garm-cli/cmd/templates.go +++ b/cmd/garm-cli/cmd/templates.go @@ -435,6 +435,7 @@ var templateEditCmd = &cobra.Command{ } ed := editor.NewEditor() + ed.SetSyntax(editor.SyntaxForOSType(string(response.Payload.OSType))) newContent, saved, err := ed.EditText(string(response.Payload.Data)) if err != nil { diff --git a/cmd/garm-cli/cmd/top.go b/cmd/garm-cli/cmd/top.go index fb064b1f7..008fa8122 100644 --- a/cmd/garm-cli/cmd/top.go +++ b/cmd/garm-cli/cmd/top.go @@ -14,11 +14,15 @@ package cmd import ( + "cmp" "context" "encoding/json" + "errors" "fmt" + "maps" "os/signal" - "sort" + "slices" + "strings" "sync" "time" @@ -31,21 +35,41 @@ import ( garmWs "github.com/cloudbase/garm-provider-common/util/websocket" apiClientInstances "github.com/cloudbase/garm/client/instances" apiClientJobs "github.com/cloudbase/garm/client/jobs" + dbCommon "github.com/cloudbase/garm/database/common" "github.com/cloudbase/garm/params" "github.com/cloudbase/garm/workers/websocket/metrics" ) -const ( - jobQueued = "queued" - jobInProgress = "in_progress" - jobCompleted = "completed" +// changePayload mirrors database/common.ChangePayload, with the payload kept +// raw so it can be decoded based on the entity type. +type changePayload struct { + EntityType dbCommon.DatabaseEntityType `json:"entity-type"` + Operation dbCommon.OperationType `json:"operation"` + Payload json.RawMessage `json:"payload"` +} - opDelete = "delete" +// eventFilter and eventOptions mirror the filter options accepted by the +// events WebSocket endpoint (workers/websocket/events). An empty operations +// list subscribes to all operations for that entity type. +type eventFilter struct { + EntityType dbCommon.DatabaseEntityType `json:"entity-type"` + Operations []dbCommon.OperationType `json:"operations,omitempty"` +} - // Event entity type strings (as used in the events WebSocket). - evtRepository = "repository" - evtOrganization = "organization" -) +type eventOptions struct { + Filters []eventFilter `json:"filters"` +} + +// topEventTypes are the entity types the dashboard subscribes to. +var topEventTypes = []dbCommon.DatabaseEntityType{ + dbCommon.RepositoryEntityType, + dbCommon.OrganizationEntityType, + dbCommon.EnterpriseEntityType, + dbCommon.PoolEntityType, + dbCommon.ScaleSetEntityType, + dbCommon.InstanceEntityType, + dbCommon.JobEntityType, +} // topState holds the mutable state updated by WebSocket handlers. type topState struct { @@ -55,220 +79,313 @@ type topState struct { lastSnapshot *metrics.MetricsSnapshot // latest metrics snapshot, patched by events } -// changePayload mirrors database/common.ChangePayload for JSON decoding. -type changePayload struct { - EntityType string `json:"entity-type"` - Operation string `json:"operation"` - Payload json.RawMessage `json:"payload"` +func newTopState() *topState { + return &topState{ + instances: make(map[string]params.Instance), + jobs: make(map[int64]params.Job), + } } -var topCmd = &cobra.Command{ - Use: "top", - SilenceUsage: true, - Short: "Live dashboard of GARM metrics", - Long: `Interactive terminal UI showing live GARM metrics, refreshed every 5 seconds via WebSocket.`, - RunE: func(_ *cobra.Command, _ []string) error { - if needsInit { - return errNeedsInitError - } - - ctx, stop := signal.NotifyContext(context.Background(), signals...) - defer stop() +// seed populates the initial instance and job lists from the API. The metrics +// snapshot arrives via WebSocket shortly after connecting. +func (s *topState) seed() error { + instResp, err := apiCli.Instances.ListInstances(apiClientInstances.NewListInstancesParams(), authToken) + if err != nil { + return fmt.Errorf("failed to list instances: %w", err) + } + jobsResp, err := apiCli.Jobs.ListJobs(apiClientJobs.NewListJobsParams(), authToken) + if err != nil { + return fmt.Errorf("failed to list jobs: %w", err) + } - app := tview.NewApplication() - state := &topState{ - instances: make(map[string]params.Instance), - jobs: make(map[int64]params.Job), + s.mu.Lock() + defer s.mu.Unlock() + for _, inst := range instResp.Payload { + if inst.ID != "" { + s.instances[inst.ID] = inst + } + } + for _, j := range jobsResp.Payload { + if j.ID != 0 { + s.jobs[j.ID] = j } + } + return nil +} + +// renderData is a self-contained copy of the dashboard state, safe to render +// without holding the state lock. +type renderData struct { + haveSnapshot bool + entities []metrics.MetricsEntity + pools []metrics.MetricsPool + scaleSets []metrics.MetricsScaleSet + instances []params.Instance + jobs []params.Job +} - // --- Seed initial data from API --- +// copyData snapshots the state for rendering. The slices are cloned because +// the event handlers patch the snapshot in place while rendering happens on +// the UI goroutine. +func (s *topState) copyData() renderData { + s.mu.Lock() + defer s.mu.Unlock() + data := renderData{ + haveSnapshot: s.lastSnapshot != nil, + instances: slices.Collect(maps.Values(s.instances)), + jobs: slices.Collect(maps.Values(s.jobs)), + } + if s.lastSnapshot != nil { + data.entities = slices.Clone(s.lastSnapshot.Entities) + data.pools = slices.Clone(s.lastSnapshot.Pools) + data.scaleSets = slices.Clone(s.lastSnapshot.ScaleSets) + } + return data +} - if resp, err := apiCli.Instances.ListInstances(apiClientInstances.NewListInstancesParams(), authToken); err == nil { - for _, inst := range resp.Payload { - if inst.ID != "" { - state.instances[inst.ID] = inst - } - } - } - if resp, err := apiCli.Jobs.ListJobs(apiClientJobs.NewListJobsParams(), authToken); err == nil { - for _, j := range resp.Payload { - if j.ID != 0 { - state.jobs[j.ID] = j - } - } - } +// applyEvent returns the list with item upserted (matched element replaced or +// appended) or, when isDelete is set, with matching elements removed. +func applyEvent[E any](list []E, item E, match func(E) bool, isDelete bool) []E { + if isDelete { + return slices.DeleteFunc(list, match) + } + if i := slices.IndexFunc(list, match); i >= 0 { + list[i] = item + return list + } + return append(list, item) +} - // --- Build TUI layout --- - - // Explicit dark color scheme so the TUI looks consistent - // regardless of light/dark terminal theme. - bgColor := tcell.Color235 // #262626 - dark gray - fgColor := tcell.ColorWhite - borderColor := tcell.ColorLightGray - - tview.Styles.PrimitiveBackgroundColor = bgColor - tview.Styles.ContrastBackgroundColor = bgColor - tview.Styles.PrimaryTextColor = fgColor - tview.Styles.BorderColor = borderColor - tview.Styles.TitleColor = fgColor - - header := tview.NewTextView(). - SetDynamicColors(true). - SetTextAlign(tview.AlignLeft) - header.SetBorder(false).SetBackgroundColor(bgColor) - - summary := tview.NewTextView(). - SetDynamicColors(true). - SetTextAlign(tview.AlignLeft) - summary.SetBorder(true). - SetTitle(" Summary "). - SetTitleAlign(tview.AlignLeft). - SetBackgroundColor(bgColor) +// applyChange folds a single WebSocket event into the state. Pool, scale set +// and entity events patch the latest metrics snapshot; until the first +// snapshot arrives they are dropped, as the snapshot will include them anyway. +func (s *topState) applyChange(cp changePayload) { + s.mu.Lock() + defer s.mu.Unlock() + + isDelete := cp.Operation == dbCommon.DeleteOperation + switch cp.EntityType { + case dbCommon.InstanceEntityType: + var inst params.Instance + if err := json.Unmarshal(cp.Payload, &inst); err != nil || inst.ID == "" { + return + } + if isDelete { + delete(s.instances, inst.ID) + } else { + s.instances[inst.ID] = inst + } + case dbCommon.JobEntityType: + var job params.Job + if err := json.Unmarshal(cp.Payload, &job); err != nil || job.ID == 0 { + return + } + if isDelete { + delete(s.jobs, job.ID) + } else { + s.jobs[job.ID] = job + } + case dbCommon.PoolEntityType: + if s.lastSnapshot == nil { + return + } + var pool params.Pool + if err := json.Unmarshal(cp.Payload, &pool); err != nil || pool.ID == "" { + return + } + s.lastSnapshot.Pools = applyEvent(s.lastSnapshot.Pools, poolToMetrics(pool), + func(p metrics.MetricsPool) bool { return p.ID == pool.ID }, isDelete) + case dbCommon.ScaleSetEntityType: + if s.lastSnapshot == nil { + return + } + var ss params.ScaleSet + if err := json.Unmarshal(cp.Payload, &ss); err != nil || ss.ID == 0 { + return + } + s.lastSnapshot.ScaleSets = applyEvent(s.lastSnapshot.ScaleSets, scaleSetToMetrics(ss), + func(m metrics.MetricsScaleSet) bool { return m.ID == ss.ID }, isDelete) + case dbCommon.RepositoryEntityType, dbCommon.OrganizationEntityType, dbCommon.EnterpriseEntityType: + if s.lastSnapshot == nil { + return + } + entity := entityEventToMetrics(cp.EntityType, cp.Payload) + if entity.ID == "" { + return + } + match := func(e metrics.MetricsEntity) bool { return e.ID == entity.ID } + // Entity events do not carry pool/scale set counts; preserve the + // counts from the snapshot. + if i := slices.IndexFunc(s.lastSnapshot.Entities, match); i >= 0 { + entity.PoolCount = s.lastSnapshot.Entities[i].PoolCount + entity.ScaleSetCount = s.lastSnapshot.Entities[i].ScaleSetCount + } + s.lastSnapshot.Entities = applyEvent(s.lastSnapshot.Entities, entity, match, isDelete) + } +} - entitiesTable := tview.NewTable(). - SetBorders(false). - SetSelectable(true, false). - SetFixed(1, 0) - entitiesTable.SetBorder(true). - SetTitle(" Entities "). - SetTitleAlign(tview.AlignLeft). - SetBackgroundColor(bgColor) +// topUI holds the dashboard widgets. +type topUI struct { + header *tview.TextView + summary *tview.TextView + entities *tview.Table + pools *tview.Table + instances *tview.Table + jobs *tview.Table + root tview.Primitive +} - poolsTable := tview.NewTable(). - SetBorders(false). - SetSelectable(true, false). - SetFixed(1, 0) - poolsTable.SetBorder(true). - SetTitle(" Pools & Scale Sets "). - SetTitleAlign(tview.AlignLeft). - SetBackgroundColor(bgColor) +func newTopUI(app *tview.Application) *topUI { + // Explicit dark color scheme so the TUI looks consistent regardless of + // light/dark terminal theme. + bgColor := tcell.Color235 // #262626 - dark gray + borderColor := tcell.ColorLightGray - instancesTable := tview.NewTable(). - SetBorders(false). - SetSelectable(true, false). - SetFixed(1, 0) - instancesTable.SetBorder(true). - SetTitle(" Instances "). - SetTitleAlign(tview.AlignLeft). - SetBackgroundColor(bgColor) + tview.Styles.PrimitiveBackgroundColor = bgColor + tview.Styles.ContrastBackgroundColor = bgColor + tview.Styles.PrimaryTextColor = tcell.ColorWhite + tview.Styles.BorderColor = borderColor + tview.Styles.TitleColor = tcell.ColorWhite - jobsTable := tview.NewTable(). + newPanelTable := func(title string) *tview.Table { + table := tview.NewTable(). SetBorders(false). SetSelectable(true, false). SetFixed(1, 0) - jobsTable.SetBorder(true). - SetTitle(" Jobs "). + table.SetBorder(true). + SetTitle(title). SetTitleAlign(tview.AlignLeft). SetBackgroundColor(bgColor) - - footer := tview.NewTextView(). - SetDynamicColors(true). - SetTextAlign(tview.AlignCenter) - footer.SetBorder(false).SetBackgroundColor(bgColor) - footer.SetText("[yellow]Tab[white]: switch panel [yellow]↑↓[white]: scroll [yellow]q[white]: quit") - - // Two-column layout: left (entities + pools) | right (instances + jobs) - leftCol := tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(entitiesTable, 0, 1, true). - AddItem(poolsTable, 0, 1, false) - - rightCol := tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(instancesTable, 0, 1, false). - AddItem(jobsTable, 0, 1, false) - - columns := tview.NewFlex().SetDirection(tview.FlexColumn). - AddItem(leftCol, 0, 1, true). - AddItem(rightCol, 0, 1, false) - - layout := tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(header, 1, 0, false). - AddItem(summary, 5, 0, false). - AddItem(columns, 0, 1, true). - AddItem(footer, 1, 0, false) - - // Panel focus cycling - panels := []tview.Primitive{entitiesTable, poolsTable, instancesTable, jobsTable} - panelBorders := []*tview.Table{entitiesTable, poolsTable, instancesTable, jobsTable} - focusIndex := 0 - - setFocus := func(idx int) { - for i, p := range panelBorders { - if i == idx { - p.SetBorderColor(tcell.ColorDodgerBlue) - } else { - p.SetBorderColor(tcell.ColorWhite) - } + return table + } + + ui := &topUI{ + header: tview.NewTextView().SetDynamicColors(true).SetTextAlign(tview.AlignLeft), + summary: tview.NewTextView().SetDynamicColors(true).SetTextAlign(tview.AlignLeft), + entities: newPanelTable(" Entities "), + pools: newPanelTable(" Pools & Scale Sets "), + instances: newPanelTable(" Instances "), + jobs: newPanelTable(" Jobs "), + } + ui.header.SetBackgroundColor(bgColor) + ui.summary.SetBorder(true). + SetTitle(" Summary "). + SetTitleAlign(tview.AlignLeft). + SetBackgroundColor(bgColor) + + footer := tview.NewTextView().SetDynamicColors(true).SetTextAlign(tview.AlignCenter) + footer.SetBackgroundColor(bgColor) + footer.SetText("[yellow]Tab/Shift+Tab[white]: switch panel [yellow]↑↓[white]: scroll [yellow]q[white]: quit") + + // Two-column layout: left (entities + pools) | right (instances + jobs) + leftCol := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(ui.entities, 0, 1, true). + AddItem(ui.pools, 0, 1, false) + + rightCol := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(ui.instances, 0, 1, false). + AddItem(ui.jobs, 0, 1, false) + + columns := tview.NewFlex().SetDirection(tview.FlexColumn). + AddItem(leftCol, 0, 1, true). + AddItem(rightCol, 0, 1, false) + + ui.root = tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(ui.header, 1, 0, false). + AddItem(ui.summary, 5, 0, false). + AddItem(columns, 0, 1, true). + AddItem(footer, 1, 0, false) + + // Panel focus cycling + panels := []*tview.Table{ui.entities, ui.pools, ui.instances, ui.jobs} + focusIndex := 0 + focusPanel := func(idx int) { + focusIndex = idx + for i, p := range panels { + color := borderColor + if i == idx { + color = tcell.ColorDodgerBlue } - app.SetFocus(panels[idx]) + p.SetBorderColor(color) } - setFocus(0) - - updateHeader(header, mgr.BaseURL, "connecting") - - // Keybindings - app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - switch { - case event.Rune() == 'q': - app.Stop() - return nil - case event.Key() == tcell.KeyTab: - focusIndex = (focusIndex + 1) % len(panels) - setFocus(focusIndex) - return nil - case event.Key() == tcell.KeyBacktab: - focusIndex = (focusIndex - 1 + len(panels)) % len(panels) - setFocus(focusIndex) - return nil - } - return event - }) + app.SetFocus(panels[idx]) + } + focusPanel(0) - // --- Metrics WebSocket (5s snapshots) --- + app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch { + case event.Rune() == 'q' || event.Rune() == 'Q': + app.Stop() + return nil + case event.Key() == tcell.KeyTab: + focusPanel((focusIndex + 1) % len(panels)) + return nil + case event.Key() == tcell.KeyBacktab: + focusPanel((focusIndex - 1 + len(panels)) % len(panels)) + return nil + } + return event + }) - metricsConnected := false + return ui +} - // renderAll re-renders the full TUI using the latest snapshot and state. - // Must be called via app.QueueUpdateDraw. - renderAll := func() { - state.mu.Lock() - snap := state.lastSnapshot - if snap == nil { - state.mu.Unlock() - return - } - instances := make([]params.Instance, 0, len(state.instances)) - for _, inst := range state.instances { - instances = append(instances, inst) - } - jobs := make([]params.Job, 0, len(state.jobs)) - for _, j := range state.jobs { - jobs = append(jobs, j) - } - state.mu.Unlock() +// render redraws the whole dashboard. It must run on the UI goroutine (either +// before the application starts or via app.QueueUpdateDraw). +func (ui *topUI) render(data renderData) { + status := "connected" + if !data.haveSnapshot { + status = "connecting" + } + updateHeader(ui.header, mgr.BaseURL, status) + + if data.haveSnapshot { + renderSummary(ui.summary, data) + } else { + ui.summary.SetText(" [gray]Waiting for the first metrics snapshot...") + } + renderEntitiesTable(ui.entities, data.entities) + renderPoolsTable(ui.pools, data.pools, data.scaleSets) + renderInstancesTable(ui.instances, data.instances) + renderJobsTable(ui.jobs, data.jobs) +} + +var topCmd = &cobra.Command{ + Use: "top", + SilenceUsage: true, + Short: "Live dashboard of GARM metrics", + Long: `Interactive terminal UI showing live GARM metrics, refreshed every 5 seconds via WebSocket.`, + RunE: func(_ *cobra.Command, _ []string) error { + if needsInit { + return errNeedsInitError + } - updateHeader(header, mgr.BaseURL, "connected") - renderSummary(summary, snap, len(instances), jobs) - renderEntitiesTable(entitiesTable, snap.Entities) - renderPoolsTable(poolsTable, snap.Pools, snap.ScaleSets) - renderInstancesTable(instancesTable, instances) - renderJobsTable(jobsTable, jobs) + ctx, stop := signal.NotifyContext(context.Background(), signals...) + defer stop() + + state := newTopState() + if err := state.seed(); err != nil { + return err } + app := tview.NewApplication() + ui := newTopUI(app) + renderAll := func() { ui.render(state.copyData()) } + // Initial paint so the seeded data is visible before the first + // metrics snapshot arrives. + renderAll() + metricsHandler := func(_ int, msg []byte) error { var snap metrics.MetricsSnapshot if err := json.Unmarshal(msg, &snap); err != nil { - return nil + return nil // tolerate malformed frames } - state.mu.Lock() state.lastSnapshot = &snap state.mu.Unlock() - - metricsConnected = true app.QueueUpdateDraw(renderAll) return nil } - metricsReader, err := garmWs.NewReader(ctx, mgr.BaseURL, "/api/v1/ws/metrics", mgr.Token, metricsHandler) if err != nil { return fmt.Errorf("failed to connect to metrics WebSocket: %w", err) @@ -276,133 +393,17 @@ var topCmd = &cobra.Command{ if err := metricsReader.Start(); err != nil { return fmt.Errorf("failed to start metrics reader: %w", err) } - - // --- Events WebSocket (all entity types) --- + defer metricsReader.Stop() eventsHandler := func(_ int, msg []byte) error { var cp changePayload if err := json.Unmarshal(msg, &cp); err != nil { - return nil - } - - state.mu.Lock() - switch cp.EntityType { - case "instance": - if cp.Operation == opDelete { - var inst params.Instance - if err := json.Unmarshal(cp.Payload, &inst); err == nil && inst.ID != "" { - delete(state.instances, inst.ID) - } - } else { - var inst params.Instance - if err := json.Unmarshal(cp.Payload, &inst); err == nil && inst.ID != "" { - state.instances[inst.ID] = inst - } - } - case "job": - if cp.Operation == opDelete { - var job params.Job - if err := json.Unmarshal(cp.Payload, &job); err == nil && job.ID != 0 { - delete(state.jobs, job.ID) - } - } else { - var job params.Job - if err := json.Unmarshal(cp.Payload, &job); err == nil && job.ID != 0 { - state.jobs[job.ID] = job - } - } - case "pool": - if state.lastSnapshot != nil { - var pool params.Pool - if err := json.Unmarshal(cp.Payload, &pool); err == nil && pool.ID != "" { - if cp.Operation == opDelete { - filtered := make([]metrics.MetricsPool, 0, len(state.lastSnapshot.Pools)) - for _, p := range state.lastSnapshot.Pools { - if p.ID != pool.ID { - filtered = append(filtered, p) - } - } - state.lastSnapshot.Pools = filtered - } else { - found := false - for i, p := range state.lastSnapshot.Pools { - if p.ID == pool.ID { - state.lastSnapshot.Pools[i] = poolToMetrics(pool) - found = true - break - } - } - if !found { - state.lastSnapshot.Pools = append(state.lastSnapshot.Pools, poolToMetrics(pool)) - } - } - } - } - case "scaleset": - if state.lastSnapshot != nil { - var ss params.ScaleSet - if err := json.Unmarshal(cp.Payload, &ss); err == nil && ss.ID != 0 { - if cp.Operation == opDelete { - filtered := make([]metrics.MetricsScaleSet, 0, len(state.lastSnapshot.ScaleSets)) - for _, s := range state.lastSnapshot.ScaleSets { - if s.ID != ss.ID { - filtered = append(filtered, s) - } - } - state.lastSnapshot.ScaleSets = filtered - } else { - found := false - for i, s := range state.lastSnapshot.ScaleSets { - if s.ID == ss.ID { - state.lastSnapshot.ScaleSets[i] = scaleSetToMetrics(ss) - found = true - break - } - } - if !found { - state.lastSnapshot.ScaleSets = append(state.lastSnapshot.ScaleSets, scaleSetToMetrics(ss)) - } - } - } - } - case evtRepository, evtOrganization, entityTypeEnterprise: - if state.lastSnapshot != nil { - entity := entityEventToMetrics(cp.EntityType, cp.Payload) - if entity.ID != "" { - if cp.Operation == opDelete { - filtered := make([]metrics.MetricsEntity, 0, len(state.lastSnapshot.Entities)) - for _, e := range state.lastSnapshot.Entities { - if e.ID != entity.ID { - filtered = append(filtered, e) - } - } - state.lastSnapshot.Entities = filtered - } else { - found := false - for i, e := range state.lastSnapshot.Entities { - if e.ID == entity.ID { - // Preserve pool/scaleset counts from snapshot, update the rest - entity.PoolCount = e.PoolCount - entity.ScaleSetCount = e.ScaleSetCount - state.lastSnapshot.Entities[i] = entity - found = true - break - } - } - if !found { - state.lastSnapshot.Entities = append(state.lastSnapshot.Entities, entity) - } - } - } - } + return nil // tolerate malformed frames } - state.mu.Unlock() - - // Trigger immediate re-render for any entity type change + state.applyChange(cp) app.QueueUpdateDraw(renderAll) return nil } - eventsReader, err := garmWs.NewReader(ctx, mgr.BaseURL, "/api/v1/ws/events", mgr.Token, eventsHandler) if err != nil { return fmt.Errorf("failed to connect to events WebSocket: %w", err) @@ -410,43 +411,45 @@ var topCmd = &cobra.Command{ if err := eventsReader.Start(); err != nil { return fmt.Errorf("failed to start events reader: %w", err) } + defer eventsReader.Stop() - // Send filter to events WebSocket — subscribe to all entity types relevant to the TUI - eventsFilter := `{"filters":[` + - `{"entity-type":"repository","operations":["create","update","delete"]},` + - `{"entity-type":"organization","operations":["create","update","delete"]},` + - `{"entity-type":"enterprise","operations":["create","update","delete"]},` + - `{"entity-type":"pool","operations":["create","update","delete"]},` + - `{"entity-type":"scaleset","operations":["create","update","delete"]},` + - `{"entity-type":"instance","operations":["create","update","delete"]},` + - `{"entity-type":"job","operations":["create","update","delete"]}]}` - if err := eventsReader.WriteMessage(websocket.TextMessage, []byte(eventsFilter)); err != nil { + // Subscribe to all entity types relevant to the TUI. + filters := make([]eventFilter, 0, len(topEventTypes)) + for _, entityType := range topEventTypes { + filters = append(filters, eventFilter{EntityType: entityType}) + } + filterMsg, err := json.Marshal(eventOptions{Filters: filters}) + if err != nil { + return fmt.Errorf("failed to encode events filter: %w", err) + } + if err := eventsReader.WriteMessage(websocket.TextMessage, filterMsg); err != nil { return fmt.Errorf("failed to send events filter: %w", err) } - // Watch for disconnect + // Stop the TUI when either WebSocket dies or the context is canceled. go func() { - <-metricsReader.Done() - if metricsConnected { - app.QueueUpdateDraw(func() { - updateHeader(header, mgr.BaseURL, "disconnected") - }) + select { + case <-metricsReader.Done(): + case <-eventsReader.Done(): + case <-ctx.Done(): } - time.Sleep(2 * time.Second) - app.Stop() - }() - - go func() { - <-ctx.Done() - metricsReader.Stop() - eventsReader.Stop() app.Stop() }() - if err := app.SetRoot(layout, true).EnableMouse(false).Run(); err != nil { + if err := app.SetRoot(ui.root, true).Run(); err != nil { return fmt.Errorf("TUI error: %w", err) } + // Distinguish a user-initiated quit from a dropped connection. + if ctx.Err() == nil { + select { + case <-metricsReader.Done(): + return errors.New("connection to the GARM metrics stream was lost") + case <-eventsReader.Done(): + return errors.New("connection to the GARM events stream was lost") + default: + } + } return nil }, } @@ -456,40 +459,33 @@ func init() { } func updateHeader(header *tview.TextView, baseURL, status string) { - now := time.Now().Format("15:04:05") statusColor := "[green]" - switch status { - case "connecting": + if status == "connecting" { statusColor = "[yellow]" - case "disconnected": - statusColor = "[red]" } header.SetText(fmt.Sprintf( - " [bold][white]GARM Top[white] │ %s │ %s%s[white] │ %s", - baseURL, statusColor, status, now, + " [::b]GARM Top[::-] │ %s │ %s%s[white] │ %s", + baseURL, statusColor, status, time.Now().Format("15:04:05"), )) } -func renderSummary(view *tview.TextView, snap *metrics.MetricsSnapshot, instanceCount int, jobs []params.Job) { +func renderSummary(view *tview.TextView, data renderData) { repos, orgs, ents := 0, 0, 0 - for _, e := range snap.Entities { - switch e.Type { - case evtRepository: + for _, e := range data.entities { + switch dbCommon.DatabaseEntityType(e.Type) { + case dbCommon.RepositoryEntityType: repos++ - case evtOrganization: + case dbCommon.OrganizationEntityType: orgs++ - case entityTypeEnterprise: + case dbCommon.EnterpriseEntityType: ents++ } } - totalPools := len(snap.Pools) - totalScaleSets := len(snap.ScaleSets) - - // Runner status from metrics snapshot + // Runner status buckets from the metrics snapshot. buckets := map[string]int{} - for _, p := range snap.Pools { - for status, count := range p.RunnerStatusCounts { + countRunners := func(statusCounts map[string]int) { + for status, count := range statusCounts { cat, ok := runnerStatusCategory[params.RunnerStatus(status)] if !ok { cat = "other" @@ -497,50 +493,46 @@ func renderSummary(view *tview.TextView, snap *metrics.MetricsSnapshot, instance buckets[cat] += count } } - for _, ss := range snap.ScaleSets { - for status, count := range ss.RunnerStatusCounts { - cat, ok := runnerStatusCategory[params.RunnerStatus(status)] - if !ok { - cat = "other" - } - buckets[cat] += count - } + for _, p := range data.pools { + countRunners(p.RunnerStatusCounts) + } + for _, ss := range data.scaleSets { + countRunners(ss.RunnerStatusCounts) } - active, idle, offline, pending, other := buckets["active"], buckets["idle"], buckets["offline"], buckets["pending"], buckets["other"] - // Job counts queuedCount, inProgressCount, completedCount := 0, 0, 0 - for _, j := range jobs { - switch j.Status { - case jobQueued: + for _, j := range data.jobs { + switch params.JobStatus(j.Status) { + case params.JobStatusQueued: queuedCount++ - case jobInProgress: + case params.JobStatusInProgress: inProgressCount++ - case jobCompleted: + case params.JobStatusCompleted: completedCount++ } } line1 := fmt.Sprintf( " [blue]Repos:[white] %d [green]Orgs:[white] %d [purple]Enterprises:[white] %d [white]Pools:[white] %d [white]Scale Sets:[white] %d [white]Instances:[white] %d", - repos, orgs, ents, totalPools, totalScaleSets, instanceCount, + repos, orgs, ents, len(data.pools), len(data.scaleSets), len(data.instances), ) runnerLine := " " - if active > 0 { - runnerLine += fmt.Sprintf("[green]Active:[white] %d ", active) - } - if idle > 0 { - runnerLine += fmt.Sprintf("[blue]Idle:[white] %d ", idle) - } - if pending > 0 { - runnerLine += fmt.Sprintf("[yellow]Pending:[white] %d ", pending) - } - if offline > 0 { - runnerLine += fmt.Sprintf("[red]Offline:[white] %d ", offline) + for _, bucket := range []struct { + key, label, color string + }{ + {"active", "Active", "green"}, + {"idle", "Idle", "blue"}, + {"pending", "Pending", "yellow"}, + {"offline", "Offline", "red"}, + {"other", "Other", "gray"}, + } { + if count := buckets[bucket.key]; count > 0 { + runnerLine += fmt.Sprintf("[%s]%s:[white] %d ", bucket.color, bucket.label, count) + } } - if other > 0 { - runnerLine += fmt.Sprintf("[gray]Other:[white] %d ", other) + if runnerLine == " " { + runnerLine = " [gray]No runners" } jobLine := fmt.Sprintf( @@ -551,50 +543,62 @@ func renderSummary(view *tview.TextView, snap *metrics.MetricsSnapshot, instance view.SetText(line1 + "\n" + runnerLine + "\n" + jobLine) } -func renderEntitiesTable(table *tview.Table, entities []metrics.MetricsEntity) { - table.Clear() - - headers := []string{"NAME", "TYPE", "ENDPOINT", "POOLS", "SCALESETS", "HEALTH"} +// setTableHeader sets the header row. Columns from rightAlignFrom on are +// right-aligned; pass a negative value to left-align everything. +func setTableHeader(table *tview.Table, headers []string, rightAlignFrom int) { for i, h := range headers { cell := tview.NewTableCell(h). SetTextColor(tcell.ColorYellow). SetSelectable(false). SetExpansion(1) - if i >= 3 { + if rightAlignFrom >= 0 && i >= rightAlignFrom { cell.SetAlign(tview.AlignRight) } table.SetCell(0, i, cell) } +} - sorted := make([]metrics.MetricsEntity, len(entities)) - copy(sorted, entities) - sort.Slice(sorted, func(i, j int) bool { - ti := sorted[i].PoolCount + sorted[i].ScaleSetCount - tj := sorted[j].PoolCount + sorted[j].ScaleSetCount - return ti > tj +func setEmptyMessage(table *tview.Table, msg string) { + table.SetCell(1, 0, tview.NewTableCell(msg). + SetTextColor(tcell.ColorGray).SetExpansion(1)) +} + +// renderEntitiesTable renders the entities panel. It sorts the slice in place. +func renderEntitiesTable(table *tview.Table, entities []metrics.MetricsEntity) { + table.Clear() + setTableHeader(table, []string{"NAME", "TYPE", "ENDPOINT", "POOLS", "SCALESETS", "HEALTH"}, 3) + + if len(entities) == 0 { + setEmptyMessage(table, "No entities configured") + return + } + + slices.SortFunc(entities, func(a, b metrics.MetricsEntity) int { + return cmp.Or( + cmp.Compare(b.PoolCount+b.ScaleSetCount, a.PoolCount+a.ScaleSetCount), + cmp.Compare(a.Name, b.Name), + ) }) - for row, e := range sorted { + for row, e := range entities { r := row + 1 typeLabel := e.Type typeColor := tcell.ColorWhite - switch e.Type { - case evtRepository: + switch dbCommon.DatabaseEntityType(e.Type) { + case dbCommon.RepositoryEntityType: typeLabel = "repo" typeColor = tcell.ColorDodgerBlue - case evtOrganization: + case dbCommon.OrganizationEntityType: typeLabel = "org" typeColor = tcell.ColorGreen - case entityTypeEnterprise: + case dbCommon.EnterpriseEntityType: typeLabel = "ent" typeColor = tcell.ColorMediumPurple } - healthColor := tcell.ColorGreen - healthStr := "✓" + healthColor, healthStr := tcell.ColorGreen, "✓" if !e.Healthy { - healthColor = tcell.ColorRed - healthStr = "✗" + healthColor, healthStr = tcell.ColorRed, "✗" } table.SetCell(r, 0, tview.NewTableCell(e.Name).SetExpansion(1)) @@ -604,153 +608,114 @@ func renderEntitiesTable(table *tview.Table, entities []metrics.MetricsEntity) { table.SetCell(r, 4, tview.NewTableCell(fmt.Sprintf("%d", e.ScaleSetCount)).SetAlign(tview.AlignRight).SetExpansion(1)) table.SetCell(r, 5, tview.NewTableCell(healthStr).SetTextColor(healthColor).SetAlign(tview.AlignRight).SetExpansion(1)) } +} - if len(entities) == 0 { - table.SetCell(1, 0, tview.NewTableCell("No entities configured"). - SetTextColor(tcell.ColorGray).SetExpansion(1)) +// renderCapacityRow renders one pool or scale set row. +func renderCapacityRow(table *tview.Table, row int, name, provider, osType string, current, maxRunners int, enabled bool) { + utilization := 0 + if maxRunners > 0 { + utilization = current * 100 / maxRunners + } + capColor := tcell.ColorGreen + switch { + case utilization >= 90: + capColor = tcell.ColorRed + case utilization >= 70: + capColor = tcell.ColorYellow } + + status, statusColor, nameColor := "enabled", tcell.ColorGreen, tcell.ColorWhite + if !enabled { + status, statusColor, nameColor = "disabled", tcell.ColorGray, tcell.ColorGray + } + + table.SetCell(row, 0, tview.NewTableCell(name).SetTextColor(nameColor).SetExpansion(1)) + table.SetCell(row, 1, tview.NewTableCell(provider).SetExpansion(1)) + table.SetCell(row, 2, tview.NewTableCell(osType).SetExpansion(1)) + table.SetCell(row, 3, tview.NewTableCell(fmt.Sprintf("%d/%d", current, maxRunners)).SetAlign(tview.AlignRight).SetExpansion(1)) + table.SetCell(row, 4, tview.NewTableCell(fmt.Sprintf("%d%%", utilization)).SetTextColor(capColor).SetAlign(tview.AlignRight).SetExpansion(1)) + table.SetCell(row, 5, tview.NewTableCell(status).SetTextColor(statusColor).SetAlign(tview.AlignRight).SetExpansion(1)) } +// renderPoolsTable renders the pools and scale sets panel. It sorts the +// slices in place. func renderPoolsTable(table *tview.Table, pools []metrics.MetricsPool, scaleSets []metrics.MetricsScaleSet) { table.Clear() + setTableHeader(table, []string{"NAME", "PROVIDER", "OS", "RUNNERS", "CAP", "STATUS"}, 3) - headers := []string{"NAME", "PROVIDER", "OS", "RUNNERS", "CAP", "STATUS"} - for i, h := range headers { - cell := tview.NewTableCell(h). - SetTextColor(tcell.ColorYellow). - SetSelectable(false). - SetExpansion(1) - if i >= 3 { - cell.SetAlign(tview.AlignRight) - } - table.SetCell(0, i, cell) + if len(pools) == 0 && len(scaleSets) == 0 { + setEmptyMessage(table, "No pools or scale sets configured") + return } - row := 1 - - sortedPools := make([]metrics.MetricsPool, len(pools)) - copy(sortedPools, pools) - sort.Slice(sortedPools, func(i, j int) bool { - if sortedPools[i].Enabled != sortedPools[j].Enabled { - return sortedPools[i].Enabled + // Enabled first, then by runner count, with a stable ID/name tiebreaker + // so rows do not jump around between refreshes. + slices.SortFunc(pools, func(a, b metrics.MetricsPool) int { + if a.Enabled != b.Enabled { + if a.Enabled { + return -1 + } + return 1 } - ci := sumCounts(sortedPools[i].RunnerCounts) - cj := sumCounts(sortedPools[j].RunnerCounts) - return ci > cj + return cmp.Or( + cmp.Compare(sumCounts(b.RunnerCounts), sumCounts(a.RunnerCounts)), + cmp.Compare(a.ID, b.ID), + ) }) - - for _, p := range sortedPools { - name := topPoolDisplayName(p) - current := sumCounts(p.RunnerCounts) - maxRunners := int(p.MaxRunners) - utilization := 0 - if maxRunners > 0 { - utilization = current * 100 / maxRunners - } - - runnersStr := fmt.Sprintf("%d/%d", current, maxRunners) - capStr := fmt.Sprintf("%d%%", utilization) - capColor := tcell.ColorGreen - if utilization >= 90 { - capColor = tcell.ColorRed - } else if utilization >= 70 { - capColor = tcell.ColorYellow - } - - statusStr := "enabled" - statusColor := tcell.ColorGreen - nameColor := tcell.ColorWhite - if !p.Enabled { - statusStr = "disabled" - statusColor = tcell.ColorGray - nameColor = tcell.ColorGray + slices.SortFunc(scaleSets, func(a, b metrics.MetricsScaleSet) int { + if a.Enabled != b.Enabled { + if a.Enabled { + return -1 + } + return 1 } + return cmp.Or( + cmp.Compare(sumCounts(b.RunnerCounts), sumCounts(a.RunnerCounts)), + cmp.Compare(a.ID, b.ID), + ) + }) - table.SetCell(row, 0, tview.NewTableCell(name).SetTextColor(nameColor).SetExpansion(1)) - table.SetCell(row, 1, tview.NewTableCell(p.ProviderName).SetExpansion(1)) - table.SetCell(row, 2, tview.NewTableCell(p.OSType).SetExpansion(1)) - table.SetCell(row, 3, tview.NewTableCell(runnersStr).SetAlign(tview.AlignRight).SetExpansion(1)) - table.SetCell(row, 4, tview.NewTableCell(capStr).SetTextColor(capColor).SetAlign(tview.AlignRight).SetExpansion(1)) - table.SetCell(row, 5, tview.NewTableCell(statusStr).SetTextColor(statusColor).SetAlign(tview.AlignRight).SetExpansion(1)) + row := 1 + for _, p := range pools { + renderCapacityRow(table, row, topPoolDisplayName(p), p.ProviderName, p.OSType, + sumCounts(p.RunnerCounts), int(p.MaxRunners), p.Enabled) row++ } - for _, ss := range scaleSets { name := ss.Name if name == "" { name = fmt.Sprintf("scaleset-%d", ss.ID) } - - current := sumCounts(ss.RunnerCounts) - maxRunners := int(ss.MaxRunners) - utilization := 0 - if maxRunners > 0 { - utilization = current * 100 / maxRunners - } - - runnersStr := fmt.Sprintf("%d/%d", current, maxRunners) - capStr := fmt.Sprintf("%d%%", utilization) - capColor := tcell.ColorGreen - if utilization >= 90 { - capColor = tcell.ColorRed - } else if utilization >= 70 { - capColor = tcell.ColorYellow - } - - statusStr := "enabled" - statusColor := tcell.ColorGreen - nameColor := tcell.ColorWhite - if !ss.Enabled { - statusStr = "disabled" - statusColor = tcell.ColorGray - nameColor = tcell.ColorGray - } - - table.SetCell(row, 0, tview.NewTableCell(name).SetTextColor(nameColor).SetExpansion(1)) - table.SetCell(row, 1, tview.NewTableCell(ss.ProviderName).SetExpansion(1)) - table.SetCell(row, 2, tview.NewTableCell(ss.OSType).SetExpansion(1)) - table.SetCell(row, 3, tview.NewTableCell(runnersStr).SetAlign(tview.AlignRight).SetExpansion(1)) - table.SetCell(row, 4, tview.NewTableCell(capStr).SetTextColor(capColor).SetAlign(tview.AlignRight).SetExpansion(1)) - table.SetCell(row, 5, tview.NewTableCell(statusStr).SetTextColor(statusColor).SetAlign(tview.AlignRight).SetExpansion(1)) + renderCapacityRow(table, row, name, ss.ProviderName, ss.OSType, + sumCounts(ss.RunnerCounts), int(ss.MaxRunners), ss.Enabled) row++ } - - if len(pools) == 0 && len(scaleSets) == 0 { - table.SetCell(1, 0, tview.NewTableCell("No pools or scale sets configured"). - SetTextColor(tcell.ColorGray).SetExpansion(1)) - } } +// renderInstancesTable renders the instances panel. It sorts the slice in +// place. func renderInstancesTable(table *tview.Table, instances []params.Instance) { table.Clear() + setTableHeader(table, []string{"NAME", "STATUS", "RUNNER", "PROVIDER", "OS", "POOL/SS", "AGE"}, -1) - headers := []string{"NAME", "STATUS", "RUNNER", "PROVIDER", "OS", "POOL/SS", "AGE"} - for i, h := range headers { - cell := tview.NewTableCell(h). - SetTextColor(tcell.ColorYellow). - SetSelectable(false). - SetExpansion(1) - table.SetCell(0, i, cell) - } - - // Sort: running first, then by creation time desc - sorted := make([]params.Instance, len(instances)) - copy(sorted, instances) - sort.Slice(sorted, func(i, j int) bool { - si := instanceStatusPriorities[sorted[i].Status] - sj := instanceStatusPriorities[sorted[j].Status] - if si != sj { - return si < sj - } - return sorted[i].CreatedAt.After(sorted[j].CreatedAt) + if len(instances) == 0 { + setEmptyMessage(table, "No instances") + return + } + + // Sort: running first, then by creation time desc, then by name so rows + // do not jump around between refreshes. + slices.SortFunc(instances, func(a, b params.Instance) int { + return cmp.Or( + cmp.Compare(instanceStatusPriorities[a.Status], instanceStatusPriorities[b.Status]), + b.CreatedAt.Compare(a.CreatedAt), + cmp.Compare(a.Name, b.Name), + ) }) - for row, inst := range sorted { + for row, inst := range instances { r := row + 1 - statusStr := string(inst.Status) - statusColor := instanceStatusColors[inst.Status] - runnerStr := string(inst.RunnerStatus) runnerColor := runnerStatusColors[inst.RunnerStatus] if runnerStr == "" { @@ -758,73 +723,55 @@ func renderInstancesTable(table *tview.Table, instances []params.Instance) { runnerColor = tcell.ColorGray } - poolRef := inst.PoolID - if len(poolRef) > 8 { - poolRef = poolRef[:8] - } - if inst.ScaleSetID > 0 { + poolRef := "-" + switch { + case inst.ScaleSetID > 0: poolRef = fmt.Sprintf("ss-%d", inst.ScaleSetID) + case inst.PoolID != "": + poolRef = shortID(inst.PoolID, 8) } - if poolRef == "" { - poolRef = "-" - } - - age := time.Since(inst.CreatedAt) - ageStr := formatDuration(age) name := inst.Name if name == "" { - name = inst.ID - if len(name) > 12 { - name = name[:12] - } + name = shortID(inst.ID, 12) } table.SetCell(r, 0, tview.NewTableCell(name).SetExpansion(1)) - table.SetCell(r, 1, tview.NewTableCell(statusStr).SetTextColor(statusColor).SetExpansion(1)) + table.SetCell(r, 1, tview.NewTableCell(string(inst.Status)).SetTextColor(instanceStatusColors[inst.Status]).SetExpansion(1)) table.SetCell(r, 2, tview.NewTableCell(runnerStr).SetTextColor(runnerColor).SetExpansion(1)) table.SetCell(r, 3, tview.NewTableCell(inst.ProviderName).SetExpansion(1)) table.SetCell(r, 4, tview.NewTableCell(string(inst.OSType)).SetExpansion(1)) table.SetCell(r, 5, tview.NewTableCell(poolRef).SetExpansion(1)) - table.SetCell(r, 6, tview.NewTableCell(ageStr).SetExpansion(1)) - } - - if len(instances) == 0 { - table.SetCell(1, 0, tview.NewTableCell("No instances (waiting for events...)"). - SetTextColor(tcell.ColorGray).SetExpansion(1)) + table.SetCell(r, 6, tview.NewTableCell(formatDuration(time.Since(inst.CreatedAt))).SetExpansion(1)) } } +// renderJobsTable renders the jobs panel. It sorts the slice in place. func renderJobsTable(table *tview.Table, jobs []params.Job) { table.Clear() + setTableHeader(table, []string{"NAME", "STATUS", "REPO", "RUNNER", "LABELS", "AGE"}, -1) - headers := []string{"NAME", "STATUS", "REPO", "RUNNER", "LABELS", "AGE"} - for i, h := range headers { - cell := tview.NewTableCell(h). - SetTextColor(tcell.ColorYellow). - SetSelectable(false). - SetExpansion(1) - table.SetCell(0, i, cell) - } - - // Sort: in_progress first, then queued, then completed; within group by time desc - sorted := make([]params.Job, len(jobs)) - copy(sorted, jobs) - sort.Slice(sorted, func(i, j int) bool { - si := jobStatusPriorities[sorted[i].Status] - sj := jobStatusPriorities[sorted[j].Status] - if si != sj { - return si < sj - } - return sorted[i].UpdatedAt.After(sorted[j].UpdatedAt) + if len(jobs) == 0 { + setEmptyMessage(table, "No jobs") + return + } + + // Sort: in_progress first, then queued, then completed; within group by + // update time desc, with the ID as a stable tiebreaker. + slices.SortFunc(jobs, func(a, b params.Job) int { + return cmp.Or( + cmp.Compare(jobStatusPriorities[a.Status], jobStatusPriorities[b.Status]), + b.UpdatedAt.Compare(a.UpdatedAt), + cmp.Compare(a.ID, b.ID), + ) }) - for row, job := range sorted { + for row, job := range jobs { r := row + 1 statusStr := job.Status statusColor := jobStatusColors[statusStr] - if job.Conclusion != "" && job.Status == jobCompleted { + if job.Conclusion != "" && params.JobStatus(job.Status) == params.JobStatusCompleted { statusStr = job.Conclusion statusColor = jobConclusionColors[job.Conclusion] } @@ -839,53 +786,37 @@ func renderJobsTable(table *tview.Table, jobs []params.Job) { runnerStr = "-" } - labelsStr := "" - if len(job.Labels) > 0 { - labelsStr = truncateLabels(job.Labels, 30) - } - - age := time.Since(job.CreatedAt) - ageStr := formatDuration(age) - - name := job.Name - if len(name) > 40 { - name = name[:37] + "..." - } - - table.SetCell(r, 0, tview.NewTableCell(name).SetExpansion(1)) + table.SetCell(r, 0, tview.NewTableCell(truncate(job.Name, 40)).SetExpansion(1)) table.SetCell(r, 1, tview.NewTableCell(statusStr).SetTextColor(statusColor).SetExpansion(1)) table.SetCell(r, 2, tview.NewTableCell(repoStr).SetExpansion(1)) table.SetCell(r, 3, tview.NewTableCell(runnerStr).SetExpansion(1)) - table.SetCell(r, 4, tview.NewTableCell(labelsStr).SetExpansion(1)) - table.SetCell(r, 5, tview.NewTableCell(ageStr).SetExpansion(1)) - } - - if len(jobs) == 0 { - table.SetCell(1, 0, tview.NewTableCell("No jobs (waiting for events...)"). - SetTextColor(tcell.ColorGray).SetExpansion(1)) + table.SetCell(r, 4, tview.NewTableCell(truncate(strings.Join(job.Labels, ","), 30)).SetExpansion(1)) + table.SetCell(r, 5, tview.NewTableCell(formatDuration(time.Since(job.CreatedAt))).SetExpansion(1)) } } // --- Helpers --- func topPoolDisplayName(p metrics.MetricsPool) string { - entityName := p.RepoName - if entityName == "" { - entityName = p.OrgName - } - if entityName == "" { - entityName = p.EnterpriseName + entityName := cmp.Or(p.RepoName, p.OrgName, p.EnterpriseName) + if entityName != "" { + return entityName + " / " + shortID(p.ID, 8) } + return shortID(p.ID, 8) +} - shortID := p.ID - if len(shortID) > 8 { - shortID = shortID[:8] +func shortID(id string, maxLen int) string { + if len(id) > maxLen { + return id[:maxLen] } + return id +} - if entityName != "" { - return entityName + " / " + shortID +func truncate(s string, maxLen int) string { + if len(s) <= maxLen { + return s } - return shortID + return s[:maxLen-3] + "..." } func sumCounts(counts map[string]int) int { @@ -897,16 +828,19 @@ func sumCounts(counts map[string]int) int { } func formatDuration(d time.Duration) string { - if d < time.Minute { - return fmt.Sprintf("%ds", int(d.Seconds())) + if d < 0 { + d = 0 } - if d < time.Hour { + switch { + case d < time.Minute: + return fmt.Sprintf("%ds", int(d.Seconds())) + case d < time.Hour: return fmt.Sprintf("%dm", int(d.Minutes())) - } - if d < 24*time.Hour { + case d < 24*time.Hour: return fmt.Sprintf("%dh%dm", int(d.Hours()), int(d.Minutes())%60) + default: + return fmt.Sprintf("%dd%dh", int(d.Hours())/24, int(d.Hours())%24) } - return fmt.Sprintf("%dd%dh", int(d.Hours())/24, int(d.Hours())%24) } // runnerStatusCategory maps runner statuses to summary bucket names. @@ -955,15 +889,15 @@ var runnerStatusColors = map[params.RunnerStatus]tcell.Color{ } var jobStatusPriorities = map[string]int{ - jobInProgress: 0, - jobQueued: 1, - jobCompleted: 2, + string(params.JobStatusInProgress): 0, + string(params.JobStatusQueued): 1, + string(params.JobStatusCompleted): 2, } var jobStatusColors = map[string]tcell.Color{ - jobInProgress: tcell.ColorGreen, - jobQueued: tcell.ColorYellow, - jobCompleted: tcell.ColorGray, + string(params.JobStatusInProgress): tcell.ColorGreen, + string(params.JobStatusQueued): tcell.ColorYellow, + string(params.JobStatusCompleted): tcell.ColorGray, } var jobConclusionColors = map[string]tcell.Color{ @@ -1004,60 +938,48 @@ func scaleSetToMetrics(ss params.ScaleSet) metrics.MetricsScaleSet { } } -func entityEventToMetrics(entityType string, payload json.RawMessage) metrics.MetricsEntity { +func entityEventToMetrics(entityType dbCommon.DatabaseEntityType, payload json.RawMessage) metrics.MetricsEntity { switch entityType { - case evtRepository: + case dbCommon.RepositoryEntityType: var r params.Repository - if err := json.Unmarshal(payload, &r); err == nil && r.ID != "" { - name := r.Name - if r.Owner != "" { - name = r.Owner + "/" + r.Name - } - return metrics.MetricsEntity{ - ID: r.ID, - Name: name, - Type: evtRepository, - Endpoint: r.Endpoint.Name, - Healthy: r.PoolManagerStatus.IsRunning, - } + if err := json.Unmarshal(payload, &r); err != nil || r.ID == "" { + return metrics.MetricsEntity{} + } + name := r.Name + if r.Owner != "" { + name = r.Owner + "/" + r.Name } - case evtOrganization: + return metrics.MetricsEntity{ + ID: r.ID, + Name: name, + Type: string(entityType), + Endpoint: r.Endpoint.Name, + Healthy: r.PoolManagerStatus.IsRunning, + } + case dbCommon.OrganizationEntityType: var o params.Organization - if err := json.Unmarshal(payload, &o); err == nil && o.ID != "" { - return metrics.MetricsEntity{ - ID: o.ID, - Name: o.Name, - Type: evtOrganization, - Endpoint: o.Endpoint.Name, - Healthy: o.PoolManagerStatus.IsRunning, - } + if err := json.Unmarshal(payload, &o); err != nil || o.ID == "" { + return metrics.MetricsEntity{} } - case entityTypeEnterprise: - var e params.Enterprise - if err := json.Unmarshal(payload, &e); err == nil && e.ID != "" { - return metrics.MetricsEntity{ - ID: e.ID, - Name: e.Name, - Type: entityTypeEnterprise, - Endpoint: e.Endpoint.Name, - Healthy: e.PoolManagerStatus.IsRunning, - } + return metrics.MetricsEntity{ + ID: o.ID, + Name: o.Name, + Type: string(entityType), + Endpoint: o.Endpoint.Name, + Healthy: o.PoolManagerStatus.IsRunning, } - } - return metrics.MetricsEntity{} -} - -func truncateLabels(labels []string, maxLen int) string { - result := "" - for i, l := range labels { - if i > 0 { - result += "," + case dbCommon.EnterpriseEntityType: + var e params.Enterprise + if err := json.Unmarshal(payload, &e); err != nil || e.ID == "" { + return metrics.MetricsEntity{} } - if len(result)+len(l) > maxLen { - result += "..." - break + return metrics.MetricsEntity{ + ID: e.ID, + Name: e.Name, + Type: string(entityType), + Endpoint: e.Endpoint.Name, + Healthy: e.PoolManagerStatus.IsRunning, } - result += l } - return result + return metrics.MetricsEntity{} } diff --git a/cmd/garm-cli/editor/editor.go b/cmd/garm-cli/editor/editor.go index a87ce88d5..a351791df 100644 --- a/cmd/garm-cli/editor/editor.go +++ b/cmd/garm-cli/editor/editor.go @@ -2,6 +2,7 @@ package editor import ( "fmt" + "strconv" "strings" "github.com/gdamore/tcell/v2" @@ -17,124 +18,211 @@ const ( VimSearch ) -// Editor represents a text editor that can be launched with initial content +// Editor is a terminal text editor with optional vim-style keybindings. type Editor struct { - app *tview.Application - pages *tview.Pages - editor *tview.TextArea - footer *tview.TextView - result string - saved bool - useVim bool - vimMode VimMode - waitingForSecondG bool - searchTerm string - searchInput *tview.InputField + app *tview.Application + pages *tview.Pages + layout *tview.Grid + frame *editorFrame + editor *tview.TextArea + gutter *lineNumberGutter + footer *tview.TextView + searchBar *tview.InputField + quitModal *tview.Modal + + initialContent string + result string + saved bool + + syntax string + highlighter *syntaxHighlighter + + useVim bool + vimMode VimMode + pendingKey rune // first key of a two-key vim command (gg, dd) + searchTerm string } // NewEditor creates a new editor instance func NewEditor() *Editor { - e := &Editor{ - app: tview.NewApplication(), - pages: tview.NewPages(), - useVim: false, - vimMode: VimNormal, + return &Editor{ + app: tview.NewApplication(), + pages: tview.NewPages(), } - return e +} + +// SetSyntax sets the language used for syntax highlighting (a chroma lexer +// name, e.g. "bash" or "powershell"). An empty or unknown language disables +// highlighting. It must be called before EditText. +func (e *Editor) SetSyntax(language string) { + e.syntax = language } // SetVimMode enables or disables vim modal editing func (e *Editor) SetVimMode(enabled bool) { e.useVim = enabled - if enabled { - e.vimMode = VimNormal - } + e.vimMode = VimNormal + e.pendingKey = 0 } -// toggleVimMode toggles vim mode on/off and updates the UI -func (e *Editor) toggleVimMode() { - e.useVim = !e.useVim - if e.useVim { - e.vimMode = VimNormal +// EditText launches the editor with initial content. It returns the edited +// text and whether the user saved the changes. +func (e *Editor) EditText(initialContent string) (string, bool, error) { + e.setup(initialContent) + if err := e.app.SetRoot(e.pages, true).EnableMouse(true).EnablePaste(true).Run(); err != nil { + return "", false, err } - e.updateTitle() - e.updateFooter() + return e.result, e.saved, nil } -// updateTitle updates the editor title to show current vim mode -func (e *Editor) updateTitle() { - if e.useVim { - var modeStr string - switch e.vimMode { - case VimNormal: - modeStr = "NORMAL" - case VimInsert: - modeStr = "INSERT" - case VimSearch: - modeStr = "SEARCH" +// setup builds the editor UI for the given content. +func (e *Editor) setup(initialContent string) { + e.initialContent = initialContent + e.result = initialContent + e.saved = false + + e.editor = tview.NewTextArea(). + SetText(initialContent, false). + SetWrap(false) + e.editor.SetInputCapture(e.handleEditorKey) + // A mouse click back into the editor closes the search bar if it is open. + e.editor.SetFocusFunc(e.closeSearchBar) + e.editor.SetChangedFunc(e.onTextChanged) + + e.highlighter = newSyntaxHighlighter(e.syntax) + e.gutter = newLineNumberGutter(e.editor) + e.frame = &editorFrame{ + Flex: tview.NewFlex(). + AddItem(e.gutter, 0, 0, false). + AddItem(e.editor, 0, 1, true), + editor: e.editor, + gutter: e.gutter, + highlighter: e.highlighter, + } + e.frame.SetBorder(true).SetTitleAlign(tview.AlignCenter) + e.onTextChanged() + + e.footer = tview.NewTextView(). + SetDynamicColors(true). + SetTextAlign(tview.AlignCenter) + + e.layout = tview.NewGrid(). + SetRows(0, 1). + AddItem(e.frame, 0, 0, 1, 1, 0, 0, true). + AddItem(e.footer, 1, 0, 1, 1, 0, 0, false) + + e.pages.AddPage("main", e.layout, true, true) + e.createHelpPages() + e.createQuitModal() + e.refreshChrome() + + // By default tview stops the application on Ctrl+C, which would silently + // discard all changes. Route it through the quit confirmation instead. + e.app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyCtrlC { + e.requestQuit() + return nil } - e.editor.SetTitle(fmt.Sprintf("Text Editor (VIM - %s)", modeStr)) - } else { - e.editor.SetTitle("Text Editor") - } + return event + }) } -// resetGCommand resets the gg command state -func (e *Editor) resetGCommand() { - e.waitingForSecondG = false +// Gutter colors: a subtly lighter background sets the line number strip apart +// from the text area, dimmed numbers keep the focus on the text, and the +// cursor line is shown in the primary text color. On terminals without 256 +// colors (e.g. TERM=xterm-color) the background would map to black, so the +// strip falls back to vim-style colored numbers instead. +var ( + gutterBackground = tcell.Color238 // #444444, clearly lighter than dark terminal themes + gutterNumberColor = tcell.ColorSilver + gutterFallbackNumber = tcell.ColorOlive // ANSI dark yellow, vim's default LineNr +) + +// lineNumberGutter renders line numbers next to the text area. It reads the +// text area's scroll offset and cursor at draw time, so it stays in sync with +// scrolling without any event plumbing. Screen rows equal text lines because +// wrapping is disabled. +type lineNumberGutter struct { + *tview.Box + editor *tview.TextArea + lines int } -// updateFooter updates the footer text based on current mode -func (e *Editor) updateFooter() { - if e.footer == nil { - return - } +func newLineNumberGutter(editor *tview.TextArea) *lineNumberGutter { + return &lineNumberGutter{Box: tview.NewBox(), editor: editor} +} - footerText := "[yellow]Ctrl+S[white]: Save & Exit | [yellow]Ctrl+Q[white]: Quit | [yellow]Alt+H[white]: Help | [yellow]Alt+V[white]: Toggle VIM" - if e.useVim { - switch e.vimMode { - case VimNormal: - footerText += " | [green]VIM NORMAL[white]: i/a/o for insert, h/j/k/l/arrows nav, G/gg, / search, n/N find next/prev, x del" - case VimInsert: - footerText += " | [green]VIM INSERT[white]: Esc for normal mode" - case VimSearch: - footerText += " | [green]VIM SEARCH[white]: Enter search term, Enter to find, Esc to cancel" +// width returns the columns needed for the highest line number plus a blank +// separator column on each side. +func (g *lineNumberGutter) width() int { + return len(strconv.Itoa(max(g.lines, 1))) + 2 +} + +func (g *lineNumberGutter) Draw(screen tcell.Screen) { + g.DrawForSubclass(screen, g) + x, y, width, height := g.GetInnerRect() + rowOffset, _ := g.editor.GetOffset() + cursorRow, _, _, _ := g.editor.GetCursor() + base := tcell.StyleDefault.Foreground(gutterFallbackNumber) + if screen.Colors() >= 256 { + base = tcell.StyleDefault.Background(gutterBackground).Foreground(gutterNumberColor) + } + for i := range height { + line := rowOffset + i + 1 + style := base + if line-1 == cursorRow { + style = style.Foreground(tview.Styles.PrimaryTextColor) + } + var number string + if line <= g.lines { + number = strconv.Itoa(line) + } + // The number right-aligned with one separator column, the rest of the + // strip (including rows past the end of the text) just background. + text := fmt.Sprintf("%*s ", width-1, number) + for j, r := range text { + screen.SetContent(x+j, y+i, r, nil, style) } } - e.footer.SetText(footerText) } -// handleVimInput manages vim modal input handling -func (e *Editor) handleVimInput(event *tcell.EventKey) *tcell.EventKey { - switch e.vimMode { - case VimNormal: - return e.handleVimNormalMode(event) - case VimInsert: - return e.handleVimInsertMode(event) - case VimSearch: - return e.handleVimSearchMode(event) - } - return event +// editorFrame is the bordered container holding the gutter and the text area. +// Flex defers drawing the focused item (the text area) until last, and +// TextArea.Draw may still adjust the scroll offset while drawing, so the +// syntax recoloring pass and the gutter run afterwards to pick up the settled +// offset. +type editorFrame struct { + *tview.Flex + editor *tview.TextArea + gutter *lineNumberGutter + highlighter *syntaxHighlighter // nil when highlighting is disabled } -// handleVimNormalMode handles input in vim normal mode -func (e *Editor) handleVimNormalMode(event *tcell.EventKey) *tcell.EventKey { - // Handle global commands first - if result := e.handleGlobalCommands(event); result != event { - return result +func (f *editorFrame) Draw(screen tcell.Screen) { + f.Flex.Draw(screen) + if f.highlighter != nil { + f.highlighter.recolor(screen, f.editor) } + f.gutter.Draw(screen) +} - // Handle vim character-based commands - if result := e.handleVimCharCommands(event); result != event { - return result +// onTextChanged recounts the lines for the gutter (resizing it to fit the +// digits of the highest line number) and re-tokenizes the text for syntax +// highlighting. +func (e *Editor) onTextChanged() { + text := e.editor.GetText() + e.gutter.lines = strings.Count(text, "\n") + 1 + e.frame.ResizeItem(e.gutter, e.gutter.width(), 0) + if e.highlighter != nil { + e.highlighter.update(text) } - - // Handle key-based navigation - return e.handleKeyNavigation(event) } -// handleGlobalCommands handles global commands available in all modes -func (e *Editor) handleGlobalCommands(event *tcell.EventKey) *tcell.EventKey { +// handleEditorKey is the input capture of the text area. Global shortcuts are +// handled first; everything else goes through the vim handler when vim mode +// is enabled. +func (e *Editor) handleEditorKey(event *tcell.EventKey) *tcell.EventKey { + alt := event.Modifiers()&tcell.ModAlt != 0 switch { case event.Key() == tcell.KeyCtrlS: e.result = e.editor.GetText() @@ -142,573 +230,387 @@ func (e *Editor) handleGlobalCommands(event *tcell.EventKey) *tcell.EventKey { e.app.Stop() return nil case event.Key() == tcell.KeyCtrlQ: - e.app.Stop() + e.requestQuit() return nil - case event.Rune() == 'h' && event.Modifiers()&tcell.ModAlt != 0: + case event.Key() == tcell.KeyCtrlUnderscore, alt && event.Rune() == 'h': e.pages.SwitchToPage("help") return nil - case event.Key() == tcell.KeyCtrlUnderscore: - e.pages.SwitchToPage("help") + case alt && event.Rune() == 'v': + e.toggleVimMode() return nil - } - return event // Continue processing -} - -// handleVimCharCommands handles vim character-based commands -func (e *Editor) handleVimCharCommands(event *tcell.EventKey) *tcell.EventKey { - // Handle mode switching commands - if result := e.handleModeSwitching(event); result != event { - return result + case alt && event.Rune() == 'c': + // Forward as Ctrl+Q, the text area's native "copy selection" binding + // (the Ctrl+Q key itself is taken by "quit" above). + return tcell.NewEventKey(tcell.KeyCtrlQ, 0, tcell.ModNone) } - // Handle navigation commands - if result := e.handleCharNavigation(event); result != event { - return result + if !e.useVim { + return event } - - // Handle editing commands - return e.handleEditingCommands(event) -} - -// handleModeSwitching handles commands that switch vim modes -func (e *Editor) handleModeSwitching(event *tcell.EventKey) *tcell.EventKey { - switch event.Rune() { - case 'i': - e.resetGCommand() - e.enterInsertMode() - return nil - case 'a': - e.resetGCommand() - e.enterInsertMode() - return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone) - case 'A': - e.resetGCommand() - e.enterInsertMode() - return tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone) - case 'I': - e.resetGCommand() - e.enterInsertMode() - return tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone) - case 'o': - e.resetGCommand() - e.enterInsertMode() - e.insertNewLineBelow() - return nil - case 'O': - e.resetGCommand() - e.enterInsertMode() - e.insertNewLineAbove() - return nil + if e.vimMode == VimInsert { + if event.Key() == tcell.KeyEscape { + e.setVimState(VimNormal) + return nil + } + return event } - return event // Continue processing + return e.handleVimNormalKey(event) } -// enterInsertMode switches to insert mode -func (e *Editor) enterInsertMode() { - e.vimMode = VimInsert - e.updateTitle() - e.updateFooter() -} +// handleVimNormalKey handles input in vim normal mode. +func (e *Editor) handleVimNormalKey(event *tcell.EventKey) *tcell.EventKey { + pending := e.pendingKey + e.pendingKey = 0 -// exitInsertMode switches to normal mode -func (e *Editor) exitInsertMode() { - if e.vimMode != VimInsert { - return + if event.Key() != tcell.KeyRune { + switch event.Key() { + case tcell.KeyLeft, tcell.KeyRight, tcell.KeyUp, tcell.KeyDown, + tcell.KeyHome, tcell.KeyEnd, tcell.KeyPgUp, tcell.KeyPgDn, + tcell.KeyDelete, tcell.KeyCtrlZ, tcell.KeyCtrlY: + return event + case tcell.KeyEnter: + return tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone) + case tcell.KeyBackspace, tcell.KeyBackspace2: + return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone) + } + // Block everything else so the text cannot be modified in normal mode. + return nil } - - e.vimMode = VimNormal - e.updateTitle() - e.updateFooter() + return e.handleVimNormalRune(pending, event.Rune()) } -// handleCharNavigation handles character-based navigation commands -func (e *Editor) handleCharNavigation(event *tcell.EventKey) *tcell.EventKey { - switch event.Rune() { - case 'h': - e.resetGCommand() +// handleVimNormalRune handles character commands in vim normal mode. The +// pending rune is the first key of a two-key command ("gg", "dd"), or 0. +func (e *Editor) handleVimNormalRune(pending, r rune) *tcell.EventKey { + switch { + case pending == 'g' && r == 'g': + e.moveTo(0) + case pending == 'd' && r == 'd': + e.deleteCurrentLine() + case r == 'g', r == 'd': + e.pendingKey = r + case r == 'i': + e.setVimState(VimInsert) + case r == 'a': + e.setVimState(VimInsert) + return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone) + case r == 'A': + e.setVimState(VimInsert) + return tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone) + case r == 'I': + e.setVimState(VimInsert) + return tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone) + case r == 'o': + e.setVimState(VimInsert) + e.forwardKeys(tcell.KeyEnd, tcell.KeyEnter) + case r == 'O': + e.setVimState(VimInsert) + e.forwardKeys(tcell.KeyHome, tcell.KeyEnter, tcell.KeyUp) + case r == 'h': return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone) - case 'j': - e.resetGCommand() + case r == 'j': return tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone) - case 'k': - e.resetGCommand() + case r == 'k': return tcell.NewEventKey(tcell.KeyUp, 0, tcell.ModNone) - case 'l': - e.resetGCommand() + case r == 'l': return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone) - case '0': - e.resetGCommand() + case r == '0': return tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone) - case '$': - e.resetGCommand() + case r == '$': return tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone) - case 'w': - e.resetGCommand() + case r == 'w': return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModCtrl) - case 'b': - e.resetGCommand() + case r == 'b': return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModCtrl) - case 'G': - e.resetGCommand() - e.goToEnd() - return nil - case 'g': - return e.handleGCommand(event) - } - return event // Continue processing -} - -// handleEditingCommands handles editing and search commands -func (e *Editor) handleEditingCommands(event *tcell.EventKey) *tcell.EventKey { - switch event.Rune() { - case 'x': - e.resetGCommand() + case r == 'G': + e.moveTo(e.editor.GetTextLength()) + case r == 'x': return tcell.NewEventKey(tcell.KeyDelete, 0, tcell.ModNone) - case 'X': - e.resetGCommand() - return tcell.NewEventKey(tcell.KeyBackspace, 0, tcell.ModNone) - case 'd': - e.resetGCommand() - return e.handleDeleteCommand(event) - case '/': - e.resetGCommand() - e.startSearch() - return nil - case 'n': - e.resetGCommand() + case r == 'X': + return tcell.NewEventKey(tcell.KeyBackspace2, 0, tcell.ModNone) + case r == 'u': + return tcell.NewEventKey(tcell.KeyCtrlZ, 0, tcell.ModNone) + case r == '/': + e.openSearchBar() + case r == 'n': e.findNext() - return nil - case 'N': - e.resetGCommand() + case r == 'N': e.findPrevious() - return nil } - - return event // Continue processing -} - -// handleKeyNavigation handles key-based navigation (arrow keys, etc.) -func (e *Editor) handleKeyNavigation(event *tcell.EventKey) *tcell.EventKey { - switch event.Key() { - case tcell.KeyEscape: - e.resetGCommand() - return nil - case tcell.KeyLeft: - e.resetGCommand() - return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone) - case tcell.KeyDown: - e.resetGCommand() - return tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone) - case tcell.KeyUp: - e.resetGCommand() - return tcell.NewEventKey(tcell.KeyUp, 0, tcell.ModNone) - case tcell.KeyRight: - e.resetGCommand() - return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone) - case tcell.KeyHome: - e.resetGCommand() - return tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone) - case tcell.KeyEnd: - e.resetGCommand() - return tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone) - case tcell.KeyPgUp: - e.resetGCommand() - return tcell.NewEventKey(tcell.KeyPgUp, 0, tcell.ModNone) - case tcell.KeyPgDn: - e.resetGCommand() - return tcell.NewEventKey(tcell.KeyPgDn, 0, tcell.ModNone) - } - - // Block all other text input in normal mode + // Block all other text input in normal mode. return nil } -// handleVimInsertMode handles input in vim insert mode -func (e *Editor) handleVimInsertMode(event *tcell.EventKey) *tcell.EventKey { - switch event.Key() { - case tcell.KeyEscape: - e.exitInsertMode() - return nil - case tcell.KeyCtrlS: - e.result = e.editor.GetText() - e.saved = true - e.app.Stop() - return nil - case tcell.KeyCtrlQ: - e.app.Stop() - return nil +// forwardKeys feeds key events to the text area. The events pass through the +// input capture again, so callers must make sure the current mode lets them +// through. +func (e *Editor) forwardKeys(keys ...tcell.Key) { + handler := e.editor.InputHandler() + for _, key := range keys { + handler(tcell.NewEventKey(key, 0, tcell.ModNone), nil) } - - // Pass through all other keys in insert mode - return event -} - -// Helper functions for vim operations -func (e *Editor) insertNewLineBelow() { - // Move to end of current line and insert newline - e.editor.InputHandler()(tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone), nil) - e.editor.InputHandler()(tcell.NewEventKey(tcell.KeyEnter, 0, tcell.ModNone), nil) } -func (e *Editor) insertNewLineAbove() { - // Move to beginning of line, insert newline, move up - e.editor.InputHandler()(tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone), nil) - e.editor.InputHandler()(tcell.NewEventKey(tcell.KeyEnter, 0, tcell.ModNone), nil) - e.editor.InputHandler()(tcell.NewEventKey(tcell.KeyUp, 0, tcell.ModNone), nil) +// setVimState switches the vim mode and updates title and footer. +func (e *Editor) setVimState(mode VimMode) { + e.vimMode = mode + e.pendingKey = 0 + e.refreshChrome() } -func (e *Editor) goToEnd() { - // Go to end of document by setting text with cursor at end - text := e.editor.GetText() - e.editor.SetText(text, true) // true = cursor at end -} - -func (e *Editor) goToBeginning() { - // Go to beginning of document by setting text with cursor at beginning - text := e.editor.GetText() - e.editor.SetText(text, false) // false = cursor at beginning +// toggleVimMode toggles vim mode on/off and updates the UI. +func (e *Editor) toggleVimMode() { + e.useVim = !e.useVim + e.setVimState(VimNormal) + // The navigation help includes a vim section only while vim mode is on. + e.pages.RemovePage("help") + e.createHelpPages() } -func (e *Editor) handleGCommand(_ *tcell.EventKey) *tcell.EventKey { - // For now, let's implement a simpler approach - // In vim, single 'g' usually requires a second command - // But let's make gg work by tracking the state in the editor instance - if e.waitingForSecondG { - // This is the second 'g' - execute gg (go to beginning) - e.waitingForSecondG = false - e.goToBeginning() - return nil +// requestQuit exits the editor, asking for confirmation first if there are +// unsaved changes. +func (e *Editor) requestQuit() { + if e.editor.GetText() == e.initialContent { + e.app.Stop() + return } - // First 'g' press - wait for second one - e.waitingForSecondG = true - return nil + e.quitModal.SetFocus(1) // default to "Cancel" + e.pages.ShowPage("confirm-quit") + e.app.SetFocus(e.quitModal) } -func (e *Editor) handleDeleteCommand(_ *tcell.EventKey) *tcell.EventKey { - // For now, just implement x (delete character) - // A full implementation would handle dd, dw, etc. - return tcell.NewEventKey(tcell.KeyDelete, 0, tcell.ModNone) -} - -// handleVimSearchMode handles input in vim search mode -func (e *Editor) handleVimSearchMode(event *tcell.EventKey) *tcell.EventKey { - // Search mode is handled by the modal dialog, so this shouldn't be called - // But keep it for consistency - switch event.Key() { - case tcell.KeyEscape: - e.cancelSearch() - return nil - case tcell.KeyCtrlS: - e.result = e.editor.GetText() - e.saved = true - e.app.Stop() - return nil - case tcell.KeyCtrlQ: - e.app.Stop() - return nil +// createQuitModal builds the unsaved-changes confirmation dialog. +func (e *Editor) createQuitModal() { + cancel := func() { + e.pages.SwitchToPage("main") + e.app.SetFocus(e.editor) } - - return event + e.quitModal = tview.NewModal(). + SetText("You have unsaved changes. Quit without saving?"). + AddButtons([]string{"Quit without saving", "Cancel"}). + SetDoneFunc(func(buttonIndex int, _ string) { + if buttonIndex == 0 { + e.app.Stop() + return + } + cancel() + }) + e.quitModal.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEscape { + cancel() + return nil + } + return event + }) + e.pages.AddPage("confirm-quit", e.quitModal, true, false) } -// startSearch enters search mode and shows search input -func (e *Editor) startSearch() { - e.vimMode = VimSearch - e.updateTitle() - e.updateFooter() - e.showSearchInput() +// openSearchBar replaces the footer with a vim-style search input. +func (e *Editor) openSearchBar() { + bar := tview.NewInputField().SetLabel("/") + bar.SetDoneFunc(func(key tcell.Key) { + e.closeSearchBar() + if key == tcell.KeyEnter { + if term := bar.GetText(); term != "" { + e.searchTerm = term + e.findNext() + } + } + }) + e.searchBar = bar + e.setVimState(VimSearch) + e.layout.RemoveItem(e.footer) + e.layout.AddItem(bar, 1, 0, 1, 1, 0, 0, true) + e.app.SetFocus(bar) } -// cancelSearch exits search mode without searching -func (e *Editor) cancelSearch() { - e.vimMode = VimNormal - e.updateTitle() - e.updateFooter() - e.hideSearchInput() +// closeSearchBar restores the footer and returns focus to the editor. It is a +// no-op when the search bar is not open. +func (e *Editor) closeSearchBar() { + if e.searchBar == nil { + return + } + bar := e.searchBar + e.searchBar = nil + e.layout.RemoveItem(bar) + e.layout.AddItem(e.footer, 1, 0, 1, 1, 0, 0, false) + e.setVimState(VimNormal) + e.app.SetFocus(e.editor) } -// findNext finds the next occurrence of the search term +// findNext selects the next occurrence of the search term, wrapping around at +// the end of the text. func (e *Editor) findNext() { if e.searchTerm == "" { return } - text := e.editor.GetText() - fromRow, fromCol, _, _ := e.editor.GetCursor() - - // Convert current position to linear position - lines := strings.Split(text, "\n") - currentPos := 0 - for i := 0; i < fromRow && i < len(lines); i++ { - currentPos += len(lines[i]) + 1 // +1 for newline - } - currentPos += fromCol - - // Search from current position + 1 - searchFrom := currentPos + 1 - if searchFrom < len(text) { - index := strings.Index(text[searchFrom:], e.searchTerm) - if index != -1 { - e.goToPosition(searchFrom + index) + _, selStart, selEnd := e.editor.GetSelection() + from := selEnd + if from == selStart { + from++ // No active selection: skip the character under the cursor. + } + if from < len(text) { + if idx := strings.Index(text[from:], e.searchTerm); idx >= 0 { + e.selectMatch(from + idx) return } } - - // Wrap around search from beginning - index := strings.Index(text, e.searchTerm) - if index != -1 && index < currentPos { - e.goToPosition(index) + if idx := strings.Index(text, e.searchTerm); idx >= 0 { + e.selectMatch(idx) } } -// findPrevious finds the previous occurrence of the search term +// findPrevious selects the previous occurrence of the search term, wrapping +// around at the beginning of the text. func (e *Editor) findPrevious() { if e.searchTerm == "" { return } - text := e.editor.GetText() - fromRow, fromCol, _, _ := e.editor.GetCursor() - - // Convert current position to linear position - lines := strings.Split(text, "\n") - currentPos := 0 - for i := 0; i < fromRow && i < len(lines); i++ { - currentPos += len(lines[i]) + 1 // +1 for newline + _, selStart, _ := e.editor.GetSelection() + if idx := strings.LastIndex(text[:selStart], e.searchTerm); idx >= 0 { + e.selectMatch(idx) + return + } + if idx := strings.LastIndex(text, e.searchTerm); idx >= 0 { + e.selectMatch(idx) } - currentPos += fromCol +} - // Search backwards from current position - if currentPos > 0 { - index := strings.LastIndex(text[:currentPos], e.searchTerm) - if index != -1 { - e.goToPosition(index) +// selectMatch highlights the match at the given byte offset and scrolls it +// into view. +func (e *Editor) selectMatch(pos int) { + e.materializeThrough(pos) + e.editor.Select(pos, pos+len(e.searchTerm)) + e.scrollCursorIntoView() +} + +// moveTo places the cursor at the given byte offset. +func (e *Editor) moveTo(pos int) { + e.materializeThrough(pos) + e.editor.Select(pos, pos) + e.scrollCursorIntoView() +} + +// materializeThrough makes the text area build its internal line index at +// least up to the line containing the given byte offset. TextArea.Select +// mislocates offsets that lie beyond the lines materialized so far (it +// attributes the entire remaining text to the last known line, yielding a +// bogus cursor column far off screen), so we page the cursor down first — +// keyboard navigation extends the index correctly. Rows equal text lines +// here because wrapping is disabled. +func (e *Editor) materializeThrough(pos int) { + targetRow := strings.Count(e.editor.GetText()[:pos], "\n") + handler := e.editor.InputHandler() + lastRow := -1 + for { + row, _, _, _ := e.editor.GetCursor() + if row >= targetRow || row == lastRow { return } + lastRow = row + handler(tcell.NewEventKey(tcell.KeyPgDn, 0, tcell.ModNone), nil) } +} - // Wrap around search from end - index := strings.LastIndex(text, e.searchTerm) - if index != -1 && index > currentPos { - e.goToPosition(index) +// scrollCursorIntoView adjusts the scroll offsets so the cursor is visible. +// The text area only scrolls automatically on keyboard navigation, not when +// the cursor is moved programmatically via Select. +func (e *Editor) scrollCursorIntoView() { + row, column, _, _ := e.editor.GetCursor() + rowOffset, columnOffset := e.editor.GetOffset() + _, _, width, height := e.editor.GetInnerRect() + switch { + case row < rowOffset: + rowOffset = row + case row >= rowOffset+height: + rowOffset = row - height + 1 + } + switch { + case column < columnOffset: + columnOffset = column + case column >= columnOffset+width: + columnOffset = column - width + 1 } + e.editor.SetOffset(rowOffset, columnOffset) } -// goToPosition moves cursor to a specific character position in the text -func (e *Editor) goToPosition(pos int) { +// deleteCurrentLine implements vim's "dd". +func (e *Editor) deleteCurrentLine() { text := e.editor.GetText() - if pos >= len(text) { - return + _, start, _ := e.editor.GetSelection() + if start > len(text) { + start = len(text) } - - // Simple approach: reset text with cursor at beginning, then move right - e.editor.SetText(text, false) - - // Move cursor to the right position by simulating key presses - for range pos { - e.editor.InputHandler()(tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone), nil) + lineStart := strings.LastIndexByte(text[:start], '\n') + 1 + lineEnd := len(text) + if idx := strings.IndexByte(text[start:], '\n'); idx >= 0 { + lineEnd = start + idx + 1 } + e.editor.Replace(lineStart, lineEnd, "") } -// showSearchInput displays the search input field -func (e *Editor) showSearchInput() { - // Create search input field with proper handling - e.searchInput = tview.NewInputField(). - SetLabel("Search: "). - SetFieldWidth(30). - SetText("") - - // Set input capture for the search input - e.searchInput.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - if event.Key() == tcell.KeyEnter { - // Get search term and perform search - e.searchTerm = e.searchInput.GetText() - if e.searchTerm != "" { - e.findNext() - } - e.vimMode = VimNormal - e.updateTitle() - e.updateFooter() - e.pages.RemovePage("search") - e.pages.SwitchToPage("main") - e.app.SetFocus(e.editor) - return nil - } else if event.Key() == tcell.KeyEscape { - // Cancel search - e.vimMode = VimNormal - e.updateTitle() - e.updateFooter() - e.pages.RemovePage("search") - e.pages.SwitchToPage("main") - e.app.SetFocus(e.editor) - return nil - } - return event - }) - - // Create a container with border and title - flex := tview.NewFlex(). - SetDirection(tview.FlexRow). - AddItem(nil, 0, 1, false). - AddItem(e.searchInput, 1, 1, true). - AddItem(nil, 0, 1, false) - - container := tview.NewFlex(). - AddItem(nil, 0, 1, false). - AddItem(flex, 40, 1, true). - AddItem(nil, 0, 1, false) - - // Create bordered frame - frame := tview.NewFrame(container). - SetBorders(1, 1, 1, 1, 2, 2). - AddText("Search", true, tview.AlignCenter, tcell.ColorWhite) - - // Add to pages and switch - e.pages.AddPage("search", frame, true, true) - e.app.SetFocus(e.searchInput) -} - -// hideSearchInput hides the search input field -func (e *Editor) hideSearchInput() { - e.pages.RemovePage("search") - e.pages.SwitchToPage("main") - e.app.SetFocus(e.editor) -} - -// EditText launches the editor with initial content and returns the edited text -func (e *Editor) EditText(initialContent string) (string, bool, error) { - e.result = initialContent - e.saved = false - - e.editor = tview.NewTextArea(). - SetText(initialContent, false). - SetWrap(false) - - e.editor.SetBorder(true). - SetTitleAlign(tview.AlignCenter) - - e.updateTitle() - - e.editor.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - // Handle global commands first (available in all modes) - switch { - case event.Key() == tcell.KeyCtrlS: - e.result = e.editor.GetText() - e.saved = true - e.app.Stop() - return nil - case event.Key() == tcell.KeyCtrlQ: - e.app.Stop() - return nil - case event.Rune() == 'h' && event.Modifiers()&tcell.ModAlt != 0: - e.pages.SwitchToPage("help") - return nil - case event.Key() == tcell.KeyCtrlUnderscore: - e.pages.SwitchToPage("help") - return nil - case event.Rune() == 'v' && event.Modifiers()&tcell.ModAlt != 0: - // Toggle vim mode - e.toggleVimMode() - return nil - } - - // Handle vim keybindings if enabled - if e.useVim { - return e.handleVimInput(event) +// refreshChrome updates the editor title and footer for the current mode. +func (e *Editor) refreshChrome() { + title := "Text Editor" + footer := "[yellow]Ctrl+S[white]: Save & Exit | [yellow]Ctrl+Q[white]: Quit | [yellow]Alt+H[white]: Help | [yellow]Alt+V[white]: Toggle VIM" + if e.useVim { + var mode string + switch e.vimMode { + case VimInsert: + mode = "INSERT" + footer += " | [green]INSERT[white]: Esc for normal mode" + case VimSearch: + mode = "SEARCH" + default: + mode = "NORMAL" + footer += " | [green]NORMAL[white]: i insert, h/j/k/l move, gg/G, dd, x del, u undo, / n N search" } - - return event - }) - - // Create footer with shortcuts - e.footer = tview.NewTextView(). - SetDynamicColors(true). - SetTextAlign(tview.AlignCenter) - - e.updateFooter() - - // Create layout with editor and footer - grid := tview.NewGrid(). - SetRows(0, 1). - AddItem(e.editor, 0, 0, 1, 1, 0, 0, true). - AddItem(e.footer, 1, 0, 1, 1, 0, 0, false) - - // Create help pages - e.createHelpPages() - - // Add main editor page - e.pages.AddPage("main", grid, true, true) - - e.app.SetRoot(e.pages, true) - - // Run the editor - err := e.app.Run() - if err != nil { - return "", false, err + title = fmt.Sprintf("Text Editor (VIM - %s)", mode) } - - return e.result, e.saved, nil + e.frame.SetTitle(title) + e.footer.SetText(footer) } -// createHelpPages creates the help system with multiple pages -func (e *Editor) createHelpPages() { - // Create three separate help pages - help1 := tview.NewTextView() - help1.SetDynamicColors(true) - navText := `[green]Navigation[white] +// navigationHelp returns the navigation help text, including the vim section +// when vim mode is enabled. +func (e *Editor) navigationHelp() string { + text := `[green]Navigation[white] [yellow]Arrow Keys[white]: Move cursor around [yellow]Ctrl-A, Home[white]: Move to beginning of line [yellow]Ctrl-E, End[white]: Move to end of line [yellow]Ctrl-F, Page Down[white]: Move down by one page [yellow]Ctrl-B, Page Up[white]: Move up by one page -[yellow]Alt-Up[white]: Scroll page up -[yellow]Alt-Down[white]: Scroll page down -[yellow]Alt-Left[white]: Scroll page left -[yellow]Alt-Right[white]: Scroll page right +[yellow]Alt-Up/Down/Left/Right[white]: Scroll the page [yellow]Alt-B, Ctrl-Left[white]: Move back by one word [yellow]Alt-F, Ctrl-Right[white]: Move forward by one word` if e.useVim { - navText += ` + text += ` -[green]VIM Navigation (Normal Mode)[white] +[green]VIM Normal Mode[white] [yellow]h/j/k/l or Arrow Keys[white]: Move left/down/up/right [yellow]0/$ or Home/End[white]: Beginning/end of line [yellow]w/b[white]: Word forward/backward [yellow]gg/G[white]: Beginning/end of document -[yellow]Page Up/Down[white]: Page navigation - -[green]VIM Mode Switching[white] -[yellow]i[white]: Insert mode at cursor -[yellow]a[white]: Insert mode after cursor -[yellow]A[white]: Insert mode at end of line -[yellow]I[white]: Insert mode at beginning of line -[yellow]o[white]: New line below + insert mode -[yellow]O[white]: New line above + insert mode - -[green]VIM Editing[white] +[yellow]i/a[white]: Insert mode at/after cursor +[yellow]I/A[white]: Insert mode at beginning/end of line +[yellow]o/O[white]: New line below/above + insert mode [yellow]x/X[white]: Delete character right/left -[yellow]Esc[white]: Return to normal mode -[yellow]Alt+V[white]: Toggle VIM mode on/off - -[green]VIM Search[white] -[yellow]/[white]: Search for text (opens dialog) -[yellow]n[white]: Find next occurrence -[yellow]N[white]: Find previous occurrence` +[yellow]dd[white]: Delete current line +[yellow]u[white]: Undo +[yellow]/[white]: Search, then [yellow]n/N[white]: next/previous match +[yellow]Esc[white]: Return to normal mode` } - navText += ` + return text + ` [blue]Press Enter for more help, Escape to return to editor[white]` +} - help1.SetText(navText) - help1.SetBorder(true) - help1.SetTitle("Help - Navigation") - - help2 := tview.NewTextView() - help2.SetDynamicColors(true) - help2.SetText(`[green]Editing[white] +const editingHelp = `[green]Editing[white] Type to enter text. [yellow]Ctrl-H, Backspace[white]: Delete left character @@ -716,26 +618,23 @@ Type to enter text. [yellow]Ctrl-K[white]: Delete to end of line [yellow]Ctrl-W[white]: Delete rest of word [yellow]Ctrl-U[white]: Delete current line +[yellow]Ctrl-Z[white]: Undo +[yellow]Ctrl-Y[white]: Redo [green]Selection & Clipboard[white] Hold [yellow]Shift[white] + movement keys to select -Double-click to select a word [yellow]Ctrl-L[white]: Select entire text -[yellow]Ctrl-Q[white]: Copy selection +[yellow]Alt-C[white]: Copy selection [yellow]Ctrl-X[white]: Cut selection [yellow]Ctrl-V[white]: Paste -[blue]Press Enter for more help, Escape to return to editor[white]`) - help2.SetBorder(true) - help2.SetTitle("Help - Editing") +[blue]Press Enter for more help, Escape to return to editor[white]` - help3 := tview.NewTextView() - help3.SetDynamicColors(true) - help3.SetText(`[green]Editor Commands[white] +const commandsHelp = `[green]Editor Commands[white] [yellow]Ctrl-S[white]: Save changes and exit editor -[yellow]Ctrl-Q[white]: Quit without saving changes +[yellow]Ctrl-Q, Ctrl-C[white]: Quit (asks for confirmation on unsaved changes) [yellow]Alt+H[white]: Show this help [yellow]Alt+V[white]: Toggle VIM mode on/off @@ -746,34 +645,43 @@ Drag to select text Double-click to select word Mouse wheel to scroll -[blue]Press Enter to cycle back, Escape to return to editor[white]`) - help3.SetBorder(true) - help3.SetTitle("Help - Commands") +[blue]Press Enter to cycle back, Escape to return to editor[white]` + +// createHelpPages creates the help system with multiple pages. +func (e *Editor) createHelpPages() { + helpContent := []struct { + title string + text string + }{ + {"Help - Navigation", e.navigationHelp()}, + {"Help - Editing", editingHelp}, + {"Help - Commands", commandsHelp}, + } helpPages := tview.NewPages() - helpPages.AddPage("help1", help1, true, true) - helpPages.AddPage("help2", help2, true, false) - helpPages.AddPage("help3", help3, true, false) - - currentHelpPage := 0 - helpPageNames := []string{"help1", "help2", "help3"} - - // Set input capture for all help pages - for _, helpView := range []*tview.TextView{help1, help2, help3} { - helpView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - if event.Key() == tcell.KeyEscape { - e.pages.SwitchToPage("main") - return nil - } else if event.Key() == tcell.KeyEnter { - currentHelpPage = (currentHelpPage + 1) % len(helpPageNames) - helpPages.SwitchToPage(helpPageNames[currentHelpPage]) - return nil - } - return event - }) + current := 0 + cycle := func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyEscape: + e.pages.SwitchToPage("main") + return nil + case tcell.KeyEnter: + current = (current + 1) % len(helpContent) + helpPages.SwitchToPage(helpContent[current].title) + return nil + } + return event + } + + for i, page := range helpContent { + view := tview.NewTextView().SetDynamicColors(true).SetText(page.text) + view.SetBorder(true) + view.SetTitle(page.title) + view.SetInputCapture(cycle) + helpPages.AddPage(page.title, view, true, i == 0) } - // Center the help dialog + // Center the help dialog. helpGrid := tview.NewGrid(). SetColumns(0, 80, 0). SetRows(0, 25, 0). diff --git a/cmd/garm-cli/editor/editor_test.go b/cmd/garm-cli/editor/editor_test.go new file mode 100644 index 000000000..faa58bf64 --- /dev/null +++ b/cmd/garm-cli/editor/editor_test.go @@ -0,0 +1,345 @@ +package editor + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + "github.com/stretchr/testify/require" +) + +// newTestEditor builds an editor around content and draws it once on a +// simulation screen. The text area computes its layout during drawing, which +// programmatic cursor positioning (Select) depends on. +func newTestEditor(t *testing.T, content string) *Editor { + t.Helper() + e := NewEditor() + e.setup(content) + sim := tcell.NewSimulationScreen("UTF-8") + require.NoError(t, sim.Init()) + t.Cleanup(sim.Fini) + sim.SetSize(80, 24) + e.editor.SetRect(0, 0, 80, 24) + e.editor.Draw(sim) + return e +} + +func selection(e *Editor) (string, int, int) { + return e.editor.GetSelection() +} + +func TestFindNextSelectsMatchesAndWraps(t *testing.T) { + // Multi-byte characters and a tab before the first match make sure the + // search operates on byte offsets, not screen columns. + content := "héllo\twörld\nfirst foo\nsecond foo\nlast" + e := newTestEditor(t, content) + e.searchTerm = "foo" + + first := strings.Index(content, "foo") + second := strings.LastIndex(content, "foo") + + e.findNext() + text, start, end := selection(e) + require.Equal(t, "foo", text) + require.Equal(t, first, start) + require.Equal(t, first+3, end) + + e.findNext() + _, start, _ = selection(e) + require.Equal(t, second, start) + + // No further match: wrap around to the first one. + e.findNext() + _, start, _ = selection(e) + require.Equal(t, first, start) +} + +func TestFindPreviousWrapsAround(t *testing.T) { + content := "first foo\nsecond foo\n" + e := newTestEditor(t, content) + e.searchTerm = "foo" + + // Cursor is at the beginning: wrap to the last occurrence. + e.findPrevious() + text, start, _ := selection(e) + require.Equal(t, "foo", text) + require.Equal(t, strings.LastIndex(content, "foo"), start) + + e.findPrevious() + _, start, _ = selection(e) + require.Equal(t, strings.Index(content, "foo"), start) +} + +func TestSearchBeyondVisibleAreaScrollsCorrectly(t *testing.T) { + // Regression test: TextArea.Select mislocates byte offsets beyond the + // lines it has materialized so far (everything past the last known line + // is attributed to that line, producing a huge cursor column). Searching + // for a match far below the visible area then panned the view far to the + // right, hiding the text. + var sb strings.Builder + for range 500 { + sb.WriteString("\tsome padding line with a tab\n") + } + sb.WriteString("\tneedle here\n") + content := sb.String() + + e := newTestEditor(t, content) + e.searchTerm = "needle" + e.findNext() + + row, col, _, _ := e.editor.GetCursor() + require.Equal(t, 500, row) + // One tab (rendered as TabSize columns) precedes the match. + require.Equal(t, tview.TabSize, col) + + rowOffset, colOffset := e.editor.GetOffset() + require.Equal(t, 0, colOffset, "view must not pan horizontally") + _, _, _, height := e.editor.GetInnerRect() + require.GreaterOrEqual(t, row, rowOffset) + require.Less(t, row, rowOffset+height, "match row must be inside the viewport") + + // Jumping back to the start must restore the viewport. + e.moveTo(0) + rowOffset, colOffset = e.editor.GetOffset() + require.Equal(t, 0, rowOffset) + require.Equal(t, 0, colOffset) +} + +func TestFindNextWithoutTermIsNoop(t *testing.T) { + e := newTestEditor(t, "some text") + e.findNext() + e.findPrevious() + _, start, end := selection(e) + require.Equal(t, 0, start) + require.Equal(t, 0, end) +} + +func TestMoveTo(t *testing.T) { + content := "one\ntwo\nthree" + e := newTestEditor(t, content) + + e.moveTo(e.editor.GetTextLength()) // vim "G" + _, start, end := selection(e) + require.Equal(t, len(content), start) + require.Equal(t, len(content), end) + + e.moveTo(0) // vim "gg" + _, start, _ = selection(e) + require.Equal(t, 0, start) +} + +func TestDeleteCurrentLine(t *testing.T) { + tests := []struct { + name string + content string + cursor int + expected string + }{ + {"middle line", "one\ntwo\nthree\n", 5, "one\nthree\n"}, + {"first line", "one\ntwo\n", 0, "two\n"}, + {"last line without newline", "one\ntwo", 5, "one\n"}, + {"single line", "only", 2, ""}, + {"empty text", "", 0, ""}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + e := newTestEditor(t, tc.content) + e.moveTo(tc.cursor) + e.deleteCurrentLine() + require.Equal(t, tc.expected, e.editor.GetText()) + }) + } +} + +func TestVimNormalModeKeyTranslation(t *testing.T) { + e := newTestEditor(t, "some text") + e.SetVimMode(true) + + // Movement and editing commands translate to text area key events. + for r, key := range map[rune]tcell.Key{ + 'h': tcell.KeyLeft, + 'j': tcell.KeyDown, + 'k': tcell.KeyUp, + 'l': tcell.KeyRight, + '0': tcell.KeyHome, + '$': tcell.KeyEnd, + 'x': tcell.KeyDelete, + 'u': tcell.KeyCtrlZ, + } { + event := e.handleVimNormalRune(0, r) + require.NotNil(t, event, "rune %q", r) + require.Equal(t, key, event.Key(), "rune %q", r) + } + + // Regular text input is blocked in normal mode. + require.Nil(t, e.handleVimNormalRune(0, 'z')) + require.Nil(t, e.handleVimNormalRune(0, 'q')) +} + +func TestVimTwoKeySequences(t *testing.T) { + content := "one\ntwo\nthree" + e := newTestEditor(t, content) + e.SetVimMode(true) + e.moveTo(5) + + // "gg" moves to the beginning of the document. + event := tcell.NewEventKey(tcell.KeyRune, 'g', tcell.ModNone) + require.Nil(t, e.handleVimNormalKey(event)) + require.Equal(t, 'g', e.pendingKey) + require.Nil(t, e.handleVimNormalKey(event)) + require.Equal(t, rune(0), e.pendingKey) + _, start, _ := selection(e) + require.Equal(t, 0, start) + + // "dd" deletes the current line. + e.moveTo(5) + dKey := tcell.NewEventKey(tcell.KeyRune, 'd', tcell.ModNone) + require.Nil(t, e.handleVimNormalKey(dKey)) + require.Nil(t, e.handleVimNormalKey(dKey)) + require.Equal(t, "one\nthree", e.editor.GetText()) + + // A different key in between cancels the pending command. + require.Nil(t, e.handleVimNormalKey(dKey)) + e.handleVimNormalKey(tcell.NewEventKey(tcell.KeyRune, 'j', tcell.ModNone)) + require.Equal(t, rune(0), e.pendingKey) +} + +func TestModeSwitching(t *testing.T) { + e := newTestEditor(t, "some text") + e.SetVimMode(true) + require.Equal(t, VimNormal, e.vimMode) + + require.Nil(t, e.handleVimNormalRune(0, 'i')) + require.Equal(t, VimInsert, e.vimMode) + + // In insert mode, Escape returns to normal mode and typing passes through. + typed := tcell.NewEventKey(tcell.KeyRune, 'z', tcell.ModNone) + require.Equal(t, typed, e.handleEditorKey(typed)) + require.Nil(t, e.handleEditorKey(tcell.NewEventKey(tcell.KeyEscape, 0, tcell.ModNone))) + require.Equal(t, VimNormal, e.vimMode) + + // "a" enters insert mode and moves the cursor right. + event := e.handleVimNormalRune(0, 'a') + require.Equal(t, VimInsert, e.vimMode) + require.Equal(t, tcell.KeyRight, event.Key()) +} + +func TestSaveShortcut(t *testing.T) { + e := newTestEditor(t, "initial") + e.editor.SetText("modified", true) + + require.Nil(t, e.handleEditorKey(tcell.NewEventKey(tcell.KeyCtrlS, 0, tcell.ModNone))) + require.True(t, e.saved) + require.Equal(t, "modified", e.result) +} + +func TestQuitWithoutChangesDoesNotConfirm(t *testing.T) { + e := newTestEditor(t, "initial") + + // Unchanged content: quit immediately, no confirmation page. + require.Nil(t, e.handleEditorKey(tcell.NewEventKey(tcell.KeyCtrlQ, 0, tcell.ModNone))) + name, _ := e.pages.GetFrontPage() + require.Equal(t, "main", name) + require.False(t, e.saved) +} + +func TestQuitWithChangesAsksForConfirmation(t *testing.T) { + e := newTestEditor(t, "initial") + e.editor.SetText("modified", true) + + require.Nil(t, e.handleEditorKey(tcell.NewEventKey(tcell.KeyCtrlQ, 0, tcell.ModNone))) + name, _ := e.pages.GetFrontPage() + require.Equal(t, "confirm-quit", name) + require.False(t, e.saved) +} + +// screenRow returns the text of one row of a simulation screen. +func screenRow(t *testing.T, sim tcell.SimulationScreen, y int) string { + t.Helper() + cells, width, _ := sim.GetContents() + var sb strings.Builder + for x := range width { + r := ' ' + if runes := cells[y*width+x].Runes; len(runes) > 0 { + r = runes[0] + } + sb.WriteRune(r) + } + return sb.String() +} + +func TestLineNumberGutterRendersAndFollowsScroll(t *testing.T) { + var sb strings.Builder + for i := 1; i <= 120; i++ { + fmt.Fprintf(&sb, "line %d\n", i) + } + content := sb.String() + e := newTestEditor(t, content) + + // 120 newlines yield 121 addressable rows; 3 digits + 2 separator columns. + require.Equal(t, 121, e.gutter.lines) + require.Equal(t, 5, e.gutter.width()) + + sim := tcell.NewSimulationScreen("UTF-8") + require.NoError(t, sim.Init()) + t.Cleanup(sim.Fini) + sim.SetSize(e.gutter.width(), 24) + e.gutter.SetRect(0, 0, e.gutter.width(), 24) + + e.gutter.Draw(sim) + sim.Show() + require.Equal(t, "1", strings.TrimSpace(screenRow(t, sim, 0))) + require.Equal(t, "24", strings.TrimSpace(screenRow(t, sim, 23))) + + // The whole strip carries the gutter background shade. + cells, width, _ := sim.GetContents() + for _, idx := range []int{0, width - 1, 23*width + width - 1} { + _, bg, _ := cells[idx].Style.Decompose() + require.Equal(t, gutterBackground, bg, "cell %d", idx) + } + + // Jump near the bottom: the gutter follows the text area's scroll offset. + e.moveTo(strings.Index(content, "line 100")) + rowOffset, _ := e.editor.GetOffset() + require.Positive(t, rowOffset) + e.gutter.Draw(sim) + sim.Show() + require.Equal(t, strconv.Itoa(rowOffset+1), strings.TrimSpace(screenRow(t, sim, 0))) +} + +func TestGutterTracksLineCount(t *testing.T) { + e := newTestEditor(t, "one\ntwo\nthree") + require.Equal(t, 3, e.gutter.lines) + require.Equal(t, 3, e.gutter.width()) + + e.deleteCurrentLine() + require.Equal(t, 2, e.gutter.lines) + + // Inserting text with newlines goes through the changed callback too. + e.editor.Replace(0, 0, strings.Repeat("x\n", 100)) + require.Equal(t, 102, e.gutter.lines) + require.Equal(t, 5, e.gutter.width()) +} + +func TestSearchBarOpensAndCloses(t *testing.T) { + e := newTestEditor(t, "first foo\nsecond foo\n") + e.SetVimMode(true) + + require.Nil(t, e.handleVimNormalRune(0, '/')) + require.NotNil(t, e.searchBar) + require.Equal(t, VimSearch, e.vimMode) + + // Type a term and submit it via the done func. + e.searchBar.SetText("foo") + handler := e.searchBar.InputHandler() + handler(tcell.NewEventKey(tcell.KeyEnter, 0, tcell.ModNone), func(tview.Primitive) {}) + require.Nil(t, e.searchBar) + require.Equal(t, VimNormal, e.vimMode) + require.Equal(t, "foo", e.searchTerm) + text, start, _ := selection(e) + require.Equal(t, "foo", text) + require.Equal(t, 6, start) +} diff --git a/cmd/garm-cli/editor/highlight.go b/cmd/garm-cli/editor/highlight.go new file mode 100644 index 000000000..4978c18d2 --- /dev/null +++ b/cmd/garm-cli/editor/highlight.go @@ -0,0 +1,171 @@ +// Copyright 2026 Cloudbase Solutions SRL +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package editor + +import ( + "strings" + + "github.com/alecthomas/chroma/v2" + "github.com/alecthomas/chroma/v2/lexers" + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + "github.com/rivo/uniseg" +) + +// SyntaxForOSType maps a runner OS type to the syntax language used for +// highlighting its install templates. Bash is the fallback for anything that +// is not Windows. +func SyntaxForOSType(osType string) string { + if strings.EqualFold(osType, "windows") { + return "powershell" + } + return "bash" +} + +// lineSpan is a colored byte range within a single line. +type lineSpan struct { + start, end int + color tcell.Color +} + +// syntaxHighlighter tokenizes the full text on every change and keeps a list +// of colored byte spans per line. Highlighting is applied as a post-draw pass +// that recolors the foreground of visible cells, since tview's TextArea has +// no per-token styling of its own. +type syntaxHighlighter struct { + lexer chroma.Lexer + lines []string + spans [][]lineSpan +} + +// newSyntaxHighlighter returns a highlighter for the given chroma language +// name, or nil if the language is unknown or empty. +func newSyntaxHighlighter(language string) *syntaxHighlighter { + lexer := lexers.Get(language) + if lexer == nil { + return nil + } + return &syntaxHighlighter{lexer: chroma.Coalesce(lexer)} +} + +// tokenColor maps chroma token types to terminal colors. The palette sticks +// to the basic ANSI colors so it renders on 8-color terminals too. Error +// tokens (e.g. half-typed constructs the lexer cannot place) keep the default +// color to avoid flagging code while it is being written. +func tokenColor(t chroma.TokenType) tcell.Color { + switch { + case t.InCategory(chroma.Comment): + return tcell.ColorTeal + case t.InCategory(chroma.Keyword): + return tcell.ColorOlive + case t.InSubCategory(chroma.LiteralString): + return tcell.ColorGreen + case t.InSubCategory(chroma.LiteralNumber): + return tcell.ColorPurple + case t == chroma.NameVariable, t == chroma.NameVariableGlobal, + t == chroma.NameVariableInstance, t == chroma.NameVariableClass, + t == chroma.NameBuiltin, t == chroma.NameBuiltinPseudo, + t == chroma.NameAttribute: + return tcell.ColorMaroon + } + return tcell.ColorDefault +} + +// update re-tokenizes the text and rebuilds the per-line span lists. +func (h *syntaxHighlighter) update(text string) { + h.lines = strings.Split(text, "\n") + h.spans = make([][]lineSpan, len(h.lines)) + iterator, err := h.lexer.Tokenise(nil, text) + if err != nil { + return // leave the text unhighlighted + } + + line, col := 0, 0 // current line index and byte offset within it + for _, token := range iterator.Tokens() { + color := tokenColor(token.Type) + value := token.Value + for value != "" && line < len(h.lines) { + segment := value + newline := false + if idx := strings.IndexByte(value, '\n'); idx >= 0 { + segment, value = value[:idx], value[idx+1:] + newline = true + } else { + value = "" + } + if color != tcell.ColorDefault && segment != "" { + h.spans[line] = append(h.spans[line], lineSpan{col, col + len(segment), color}) + } + col += len(segment) + if newline { + line++ + col = 0 + } + } + } +} + +// recolor applies the syntax colors to the visible cells of the text area. +// It walks each visible line with the same width rules the text area uses +// (tabs render as TabSize cells, everything else uses grapheme cluster +// widths). Only cells whose foreground still is the default text color are +// touched, so the selection keeps its inverted styling. +func (h *syntaxHighlighter) recolor(screen tcell.Screen, area *tview.TextArea) { + x, y, width, height := area.GetInnerRect() + rowOffset, columnOffset := area.GetOffset() + for row := range height { + lineIdx := rowOffset + row + if lineIdx >= len(h.lines) { + break + } + if len(h.spans[lineIdx]) > 0 { + h.recolorLine(screen, x, y+row, width, columnOffset, h.lines[lineIdx], h.spans[lineIdx]) + } + } +} + +func (h *syntaxHighlighter) recolorLine(screen tcell.Screen, x, y, width, columnOffset int, line string, spans []lineSpan) { + defaultFg := tview.Styles.PrimaryTextColor + pos, col, state, spanIdx := 0, 0, -1, 0 + for line != "" && col-columnOffset < width { + var cluster string + var boundaries int + cluster, line, boundaries, state = uniseg.StepString(line, state) + clusterWidth := boundaries >> uniseg.ShiftWidth + if cluster == "\t" { + clusterWidth = tview.TabSize + } + for spanIdx < len(spans) && spans[spanIdx].end <= pos { + spanIdx++ + } + if spanIdx < len(spans) && spans[spanIdx].start <= pos { + for i := range clusterWidth { + cx := col + i - columnOffset + if cx < 0 || cx >= width { + continue + } + str, style, _ := screen.Get(x+cx, y) + if str == "" { + continue // continuation cell of a wide character + } + if fg, _, _ := style.Decompose(); fg == defaultFg { + screen.Put(x+cx, y, str, style.Foreground(spans[spanIdx].color)) + } + } + } + pos += len(cluster) + col += clusterWidth + } +} diff --git a/cmd/garm-cli/editor/highlight_test.go b/cmd/garm-cli/editor/highlight_test.go new file mode 100644 index 000000000..90f195679 --- /dev/null +++ b/cmd/garm-cli/editor/highlight_test.go @@ -0,0 +1,91 @@ +package editor + +import ( + "strings" + "testing" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + "github.com/stretchr/testify/require" +) + +func TestSyntaxForOSType(t *testing.T) { + require.Equal(t, "powershell", SyntaxForOSType("windows")) + require.Equal(t, "powershell", SyntaxForOSType("Windows")) + require.Equal(t, "bash", SyntaxForOSType("linux")) + require.Equal(t, "bash", SyntaxForOSType("")) + require.Equal(t, "bash", SyntaxForOSType("freebsd")) +} + +func TestNewSyntaxHighlighter(t *testing.T) { + require.NotNil(t, newSyntaxHighlighter("bash")) + require.NotNil(t, newSyntaxHighlighter("powershell")) + require.Nil(t, newSyntaxHighlighter("")) + require.Nil(t, newSyntaxHighlighter("no-such-language")) +} + +// spanAt returns the color of the span covering the given byte offset on the +// given line, or ColorDefault if none does. +func spanAt(h *syntaxHighlighter, line, pos int) tcell.Color { + for _, s := range h.spans[line] { + if s.start <= pos && pos < s.end { + return s.color + } + } + return tcell.ColorDefault +} + +func TestHighlighterUpdateBash(t *testing.T) { + h := newSyntaxHighlighter("bash") + content := "#!/bin/bash\n# a comment\nif true; then\nMSG=\"hello\"\nfi\n" + h.update(content) + require.Len(t, h.spans, strings.Count(content, "\n")+1) + + require.Equal(t, tcell.ColorTeal, spanAt(h, 0, 0), "shebang is a comment") + require.Equal(t, tcell.ColorTeal, spanAt(h, 1, 0), "comment") + require.Equal(t, tcell.ColorOlive, spanAt(h, 2, 0), "if keyword") + require.Equal(t, tcell.ColorGreen, spanAt(h, 3, len("MSG=")), "string") + require.Equal(t, tcell.ColorOlive, spanAt(h, 4, 0), "fi keyword") + // Whitespace stays uncolored. + require.Equal(t, tcell.ColorDefault, spanAt(h, 2, len("if"))) +} + +func TestHighlighterMultilineToken(t *testing.T) { + // A double-quoted string spanning two lines must produce a span on each. + h := newSyntaxHighlighter("bash") + h.update("X=\"first\nsecond\"\n") + require.Equal(t, tcell.ColorGreen, spanAt(h, 0, len("X=\"fir"))) + require.Equal(t, tcell.ColorGreen, spanAt(h, 1, 0)) +} + +func TestHighlighterRecolorOnScreen(t *testing.T) { + e := NewEditor() + e.SetSyntax("bash") + e.setup("# comment\nMSG=\"hi\"\nplain\n") + + sim := tcell.NewSimulationScreen("UTF-8") + require.NoError(t, sim.Init()) + t.Cleanup(sim.Fini) + sim.SetSize(40, 10) + e.frame.SetRect(0, 0, 40, 10) + e.frame.Draw(sim) + sim.Show() + + cells, width, _ := sim.GetContents() + fgAt := func(x, y int) tcell.Color { + fg, _, _ := cells[y*width+x].Style.Decompose() + return fg + } + // Find where the text starts: after the border and the gutter. + textX := 1 + e.gutter.width() + + require.Equal(t, tcell.ColorTeal, fgAt(textX, 1), "comment on line 1") + require.Equal(t, tcell.ColorGreen, fgAt(textX+len("MSG="), 2), "string on line 2") + require.Equal(t, tview.Styles.PrimaryTextColor, fgAt(textX, 3), "plain text keeps default color") +} + +func TestEditorWithoutSyntaxHasNoHighlighter(t *testing.T) { + e := newTestEditor(t, "# not highlighted") + require.Nil(t, e.highlighter) + require.Nil(t, e.frame.highlighter) +} diff --git a/go.mod b/go.mod index a215d19c0..f9c572d2c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.26.2 require ( github.com/BurntSushi/toml v1.6.0 + github.com/alecthomas/chroma/v2 v2.26.1 github.com/bradleyfalzon/ghinstallation/v2 v2.18.0 github.com/cloudbase/garm-provider-common v0.1.9 github.com/felixge/httpsnoop v1.0.4 @@ -27,6 +28,7 @@ require ( github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 github.com/prometheus/client_golang v1.23.2 github.com/rivo/tview v0.42.0 + github.com/rivo/uniseg v0.4.7 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 golang.org/x/crypto v0.52.0 @@ -52,6 +54,7 @@ require ( github.com/chzyer/readline v1.5.1 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2/v2 v2.1.1 // indirect github.com/gdamore/encoding v1.0.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -94,7 +97,6 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.68.1 // indirect github.com/prometheus/procfs v0.20.1 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 // indirect diff --git a/go.sum b/go.sum index 94b9f6a50..5f1f3bfbc 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,12 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.26.1 h1:2X21EdxGZNv5GF9mG5u+uzc02GCFyGxbcBm3Grd9A78= +github.com/alecthomas/chroma/v2 v2.26.1/go.mod h1:lxhRRa9H4hPmRLOOdYga4zkQIQjq3dtrrdwQeCfu78Y= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradleyfalzon/ghinstallation/v2 v2.18.0 h1:WPqnN6NS9XvYlOgZQAIseN7Z1uAiE+UxgDKlW7FvFuU= @@ -25,6 +31,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2/v2 v2.1.1 h1:LCUGyd9Wf+r+VVOl8Ny38JTpWJcAsdVnCIuhhtthmKw= +github.com/dlclark/regexp2/v2 v2.1.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-samfira/go-sqlite3 v0.0.0-20251005121134-bc61ecf9b4c7 h1:+r9O7HrPI4OpkdFsZ9l5sjRD99KOl9uw4XpYJiP2HV4= @@ -119,6 +127,8 @@ github.com/gorilla/websocket v1.5.4-0.20240702125206-a62d9d2a8413 h1:0Zn/h+BUQg6 github.com/gorilla/websocket v1.5.4-0.20240702125206-a62d9d2a8413/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= diff --git a/vendor/github.com/alecthomas/chroma/v2/.editorconfig b/vendor/github.com/alecthomas/chroma/v2/.editorconfig new file mode 100644 index 000000000..cfb2c669e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.xml] +indent_style = space +indent_size = 2 +insert_final_newline = false + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/vendor/github.com/alecthomas/chroma/v2/.gitignore b/vendor/github.com/alecthomas/chroma/v2/.gitignore new file mode 100644 index 000000000..aedf83d99 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/.gitignore @@ -0,0 +1,28 @@ +# Binaries for programs and plugins +.git +.idea +.vscode +.hermit +*.exe +*.dll +*.so +*.dylib +/cmd/chroma/chroma + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +_models/ + +_examples/ +*.min.* +build/ + +cmd/chromad/static/chroma.wasm +cmd/chromad/static/wasm_exec.js diff --git a/vendor/github.com/alecthomas/chroma/v2/.golangci.yml b/vendor/github.com/alecthomas/chroma/v2/.golangci.yml new file mode 100644 index 000000000..a345ac652 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/.golangci.yml @@ -0,0 +1,122 @@ +version: "2" +run: + tests: true +output: + show-stats: false + formats: + text: + print-issued-lines: false + colors: true +linters: + default: all + disable: + - exhaustive + - prealloc + - dupl + - godoclint + - cyclop + - depguard + - dupword + - err113 + - errname + - errorlint + - exhaustruct + - forbidigo + - forcetypeassert + - funlen + - gochecknoglobals + - gocognit + - gocritic + - gocyclo + - godot + - godox + - gomoddirectives + - ireturn + - lll + - maintidx + - mnd + - nakedret + - nestif + - nilnil + - nlreturn + - nolintlint + - nonamedreturns + - paralleltest + - perfsprint + - predeclared + - recvcheck + - revive + - testpackage + - varnamelen + - wastedassign + - whitespace + - wsl + - wsl_v5 + - funcorder + - noinlineerr + - tagalign + - goconst + - gochecknoinits + - durationcheck + - embeddedstructfieldcheck + - wrapcheck + - gomodguard + settings: + dupl: + threshold: 100 + exhaustive: + default-signifies-exhaustive: true + goconst: + min-len: 8 + min-occurrences: 3 + gocyclo: + min-complexity: 10 + wrapcheck: + report-internal-errors: false + ignore-package-globs: + - github.com/alecthomas/errors + exclusions: + generated: lax + rules: + - path: (.+)\.go$ + text: "^(G104|G204|G307|G304):" + - path: (.+)\.go$ + text: Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked + - path: (.+)\.go$ + text: exported method `(.*\.MarshalJSON|.*\.UnmarshalJSON|.*\.EntityURN|.*\.GoString|.*\.Pos)` should have comment or be unexported + - path: (.+)\.go$ + text: uses unkeyed fields + - path: (.+)\.go$ + text: declaration of "err" shadows declaration + - path: (.+)\.go$ + text: bad syntax for struct tag key + - path: (.+)\.go$ + text: bad syntax for struct tag pair + - path: (.+)\.go$ + text: ^ST1012 + - path: (.+)\.go$ + text: log/slog.Logger.*must not be called + - path: (.+)_test\.go$ + text: error returned from external package is unwrapped + - linters: [staticcheck] + text: QF1008 + - text: "Error return value of `.*.Write` is not checked" + linters: [errcheck] + path: (.+)_test\.go$ + paths: + - third_party$ + - builtin$ + - examples$ +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/vendor/github.com/alecthomas/chroma/v2/.goreleaser.yml b/vendor/github.com/alecthomas/chroma/v2/.goreleaser.yml new file mode 100644 index 000000000..f7c4f7d2d --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/.goreleaser.yml @@ -0,0 +1,34 @@ +project_name: chroma +release: + github: + owner: alecthomas + name: chroma +brews: + - install: bin.install "chroma" +env: + - CGO_ENABLED=0 +builds: + - goos: + - linux + - darwin + - windows + goarch: + - arm64 + - amd64 + - "386" + goarm: + - "6" + dir: ./cmd/chroma + main: . + ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} + binary: chroma +archives: + - format: tar.gz + name_template: "{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" + files: + - COPYING + - README* +snapshot: + name_template: SNAPSHOT-{{ .Commit }} +checksum: + name_template: "{{ .ProjectName }}-{{ .Version }}-checksums.txt" diff --git a/vendor/github.com/alecthomas/chroma/v2/AGENTS.md b/vendor/github.com/alecthomas/chroma/v2/AGENTS.md new file mode 100644 index 000000000..d15d770ab --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/AGENTS.md @@ -0,0 +1,11 @@ +Chroma is a syntax highlighting library, tool and web playground for Go. It is based on Pygments and includes importers for it, so most of the same concepts from Pygments apply to Chroma. + +This project is written in Go, uses Hermit to manage tooling, and Just for helper commands. Helper tooling is primarily in ./_tools. + +Language definitions are XML files defined in ./lexers/embedded/*.xml. + +Styles/themes are defined in ./styles/*.xml. + +The CLI can be run with `chroma`. + +The web playground can be run with `chromad --csrf-key=moo`. It blocks, so should generally be run in the background. It also does not hot reload, so has to be manually restarted. The playground has two modes - for local development it uses the server itself to render, while for production running `just chromad` will compile ./cmd/libchromawasm into a WASM module that is bundled into `chromad`. diff --git a/vendor/github.com/alecthomas/chroma/v2/BUILD.bit b/vendor/github.com/alecthomas/chroma/v2/BUILD.bit new file mode 100644 index 000000000..7f3b5ef83 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/BUILD.bit @@ -0,0 +1,84 @@ +let version = exec("git describe --tags --dirty --always") | trim +# TinyGo's installation root; used to source `wasm_exec.js`. +let tinygoroot = exec("tinygo env TINYGOROOT") | trim + + +# Generate tokentype_enumer.go from types.go via `//go:generate`. +tokentype = go.generate { + package = "." + inputs = ["types.go"] + outputs = ["tokentype_enumer.go"] +} + +# Regenerate the lexer table in README.md by invoking the host `chroma` binary. +# GOOS/GOARCH are cleared so cross-compile env vars don't break the local run. +protected readme = exec { + command = "./table.py" + inputs = ["table.py", "lexers/**/*.go", "lexers/**/*.xml"] + output = "README.md" +} + +# Format frontend JS sources in place. Runs as a sub-step of `index-min-js`, +# so bundling always sees formatted sources. +format-js = exec { + command = "biome format --write cmd/chromad/static/index.js cmd/chromad/static/chroma.js" + inputs = ["biome.js", "cmd/chromad/static/index.js", "cmd/chromad/static/chroma.js"] +} + +# Copy TinyGo's wasm_exec.js into the chromad static assets. +wasm-exec = exec { + command = "install -m644 '#{tinygoroot}/targets/wasm_exec.js' cmd/chromad/static/wasm_exec.js" + resolve = "sha256 '#{tinygoroot}/targets/wasm_exec.js'" + output = "cmd/chromad/static/wasm_exec.js" +} + +# Build the chroma WASM module via tinygo (installed via hermit) for the +# smaller output binary. +chroma-wasm = exec { + command = "tinygo build -no-debug -target wasm -o cmd/chromad/static/chroma.wasm cmd/libchromawasm/main.go" + inputs = ["cmd/libchromawasm/**/*.go", "*.go", "lexers/**/*.go", "lexers/**/*.xml", "formatters/**/*.go", "styles/**/*.go"] + output = "cmd/chromad/static/chroma.wasm" +} + +# Bundle and minify the frontend JS. Depends on `format-js` so the bundle +# always reflects formatted sources. +index-min-js = exec { + command = "esbuild --platform=browser --format=esm --bundle cmd/chromad/static/index.js --minify --external:./wasm_exec.js --outfile=cmd/chromad/static/index.min.js" + inputs = ["cmd/chromad/static/index.js", "cmd/chromad/static/chroma.js"] + output = "cmd/chromad/static/index.min.js" + depends_on = [format-js] +} + +# Bundle and minify the frontend CSS. +index-min-css = exec { + command = "esbuild --bundle cmd/chromad/static/index.css --minify --outfile=cmd/chromad/static/index.min.css" + inputs = ["cmd/chromad/static/index.css", "cmd/chromad/static/bulma.css"] + output = "cmd/chromad/static/index.min.css" +} + +# Build the chromad server binary. cmd/chromad is a separate Go module, so +# `dir` puts the build in there and `package = "."` resolves against that +# module. `output` stays project-root-relative; bit absolutises it before +# passing to `go build -o`. Defaults to linux/amd64 to match the deploy +# target; override with GOOS/GOARCH env vars for local builds. +chromad = go.exe { + dir = "cmd/chromad" + package = "." + output = "build/chromad" + flags = ["-ldflags", "-X 'main.version=#{version}'"] + goos = env("GOOS", "linux") + goarch = env("GOARCH", "amd64") + cgo = false + depends_on = [wasm-exec, chroma-wasm, index-min-js, index-min-css] +} + +# Deploy chromad to swapoff.org. Must be explicitly selected. +explicit upload = exec { + command = <<-EOF + scp #{chromad.path} root@swapoff.org: + ssh root@swapoff.org 'install -m755 ./chromad /srv/http/swapoff.org/bin && service chromad restart' + EOF + depends_on = [chromad] +} + +target default = [chromad, readme, tokentype] diff --git a/vendor/github.com/alecthomas/chroma/v2/COPYING b/vendor/github.com/alecthomas/chroma/v2/COPYING new file mode 100644 index 000000000..33da48981 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/COPYING @@ -0,0 +1,118 @@ +Copyright (C) 2017 Alec Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +// formatters/svg/font_liberation_mono.go + +Digitized data copyright (c) 2010 Google Corporation +with Reserved Font Arimo, Tinos and Cousine. +Copyright (c) 2012 Red Hat, Inc. +with Reserved Font Name Liberation. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/vendor/github.com/alecthomas/chroma/v2/Dockerfile b/vendor/github.com/alecthomas/chroma/v2/Dockerfile new file mode 100644 index 000000000..8a706766d --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/Dockerfile @@ -0,0 +1,64 @@ +# Multi-stage Dockerfile for chromad Go application using Hermit-managed tools + +# Build stage +FROM ubuntu:26.04 AS builder + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + curl \ + git \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /app + +# Copy the entire project (including bin directory with Hermit tools) +COPY . . + +# Make Hermit tools executable and add to PATH +ENV PATH="/app/bin:${PATH}" + +# Set Go environment variables for static compilation +ENV CGO_ENABLED=0 +ENV GOOS=linux +ENV GOARCH=amd64 + +# Build the application using just +RUN just chromad + +# Runtime stage +FROM alpine:3.23 AS runtime + +# Install ca-certificates for HTTPS requests +RUN apk --no-cache add ca-certificates curl + +# Create a non-root user +RUN addgroup -g 1001 chromad && \ + adduser -D -s /bin/sh -u 1001 -G chromad chromad + +# Set working directory +WORKDIR /app + +# Copy the binary from build stage +COPY --from=builder /app/build/chromad /app/chromad + +# Change ownership to non-root user +RUN chown chromad:chromad /app/chromad + +# Switch to non-root user +USER chromad + +# Expose port (default is 8080, but can be overridden via PORT env var) +EXPOSE 8080 + +# Set default environment variables +ENV PORT=8080 +ENV CHROMA_CSRF_KEY="testtest" + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -fsSL http://127.0.0.1:8080/ > /dev/null + +# Run the application +CMD ["sh", "-c", "./chromad --csrf-key=$CHROMA_CSRF_KEY --bind=0.0.0.0:$PORT"] diff --git a/vendor/github.com/alecthomas/chroma/v2/Justfile b/vendor/github.com/alecthomas/chroma/v2/Justfile new file mode 100644 index 000000000..4db3dc319 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/Justfile @@ -0,0 +1,59 @@ +set positional-arguments +set shell := ["bash", "-c"] + +version := `git describe --tags --dirty --always` +export GOOS := env("GOOS", "linux") +export GOARCH := env("GOARCH", "amd64") + +_help: + @just -l + +# Generate README.md from lexer definitions +readme: + #!/usr/bin/env bash + GOOS= GOARCH= ./table.py + +# Generate tokentype_string.go +tokentype-string: + go generate + +# Format JavaScript files +format-js: + biome format --write cmd/chromad/static/index.js cmd/chromad/static/chroma.js + +# Tidy Go modules +tidy: + find . -name 'go.mod' -execdir go mod tidy \; + +# Build chromad binary +chromad: wasm-exec chroma-wasm + #!/usr/bin/env bash + rm -rf build + mk cmd/chromad/static/index.min.js : cmd/chromad/static/{index,chroma}.js -- \ + esbuild --platform=browser --format=esm --bundle cmd/chromad/static/index.js --minify --external:./wasm_exec.js --outfile=cmd/chromad/static/index.min.js + mk cmd/chromad/static/index.min.css : cmd/chromad/static/index.css -- \ + esbuild --bundle cmd/chromad/static/index.css --minify --outfile=cmd/chromad/static/index.min.css + cd cmd/chromad && CGOENABLED=0 go build -ldflags="-X 'main.version={{ version }}'" -o ../../build/chromad . + +# Copy wasm_exec.js from TinyGo +wasm-exec: + #!/usr/bin/env bash + tinygoroot=$(tinygo env TINYGOROOT) + mk cmd/chromad/static/wasm_exec.js : "$tinygoroot/targets/wasm_exec.js" -- \ + install -m644 "$tinygoroot/targets/wasm_exec.js" cmd/chromad/static/wasm_exec.js + +# Build WASM binary +chroma-wasm: + #!/usr/bin/env bash + if type tinygo > /dev/null 2>&1; then + mk cmd/chromad/static/chroma.wasm : cmd/libchromawasm/main.go -- \ + tinygo build -no-debug -target wasm -o cmd/chromad/static/chroma.wasm cmd/libchromawasm/main.go + else + mk cmd/chromad/static/chroma.wasm : cmd/libchromawasm/main.go -- \ + GOOS=js GOARCH=wasm go build -o cmd/chromad/static/chroma.wasm cmd/libchromawasm/main.go + fi + +# Upload chromad to server +upload: chromad + scp build/chromad root@swapoff.org: + ssh root@swapoff.org 'install -m755 ./chromad /srv/http/swapoff.org/bin && service chromad restart' diff --git a/vendor/github.com/alecthomas/chroma/v2/README.md b/vendor/github.com/alecthomas/chroma/v2/README.md new file mode 100644 index 000000000..df962da7b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/README.md @@ -0,0 +1,309 @@ +![Chroma](chroma.jpg) + +# A general purpose syntax highlighter in pure Go + +[![Go Reference](https://pkg.go.dev/badge/github.com/alecthomas/chroma/v2.svg)](https://pkg.go.dev/github.com/alecthomas/chroma/v2) [![CI](https://github.com/alecthomas/chroma/actions/workflows/ci.yml/badge.svg)](https://github.com/alecthomas/chroma/actions/workflows/ci.yml) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://invite.slack.golangbridge.org/) + + +Chroma takes source code and other structured text and converts it into syntax +highlighted HTML, ANSI-coloured text, etc. + +Chroma is based heavily on [Pygments](http://pygments.org/), and includes +translators for Pygments lexers and styles. + +## Table of Contents + + + +1. [Supported languages](#supported-languages) +2. [Try it](#try-it) +3. [Using the library](#using-the-library) + 1. [Quick start](#quick-start) + 2. [Identifying the language](#identifying-the-language) + 3. [Formatting the output](#formatting-the-output) + 4. [The HTML formatter](#the-html-formatter) +4. [More detail](#more-detail) + 1. [Lexers](#lexers) + 2. [Formatters](#formatters) + 3. [Styles](#styles) +5. [Command-line interface](#command-line-interface) +6. [Testing lexers](#testing-lexers) +7. [What's missing compared to Pygments?](#whats-missing-compared-to-pygments) + + + +## Supported languages + +| Prefix | Language +| :----: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +| A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Agda, AL, Alloy, AMPL, Angular2, ANTLR, ApacheConf, APL, AppleScript, ArangoDB AQL, Arduino, ArmAsm, Arturo, ATL, AutoHotkey, AutoIt, Awk +| B | Ballerina, Bash, Bash Session, Batchfile, Beef, BibTeX, Bicep, BlitzBasic, BNF, BQN, Brainfuck +| C | C, C#, C++, C3, Caddyfile, Caddyfile Directives, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Chapel, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Core, Crystal, CSS, CSV, CUE, Cython +| D | D, Dart, Dax, Desktop file, Devicetree, Diff, Django/Jinja, dns, Docker, DTD, Dylan +| E | EBNF, Elixir, Elm, EmacsLisp, ERB, Erlang +| F | Factor, Fennel, Fish, Forth, Fortran, FortranFixed, FSharp +| G | GAS, GDScript, GDScript3, Gemtext, Genshi, Genshi HTML, Genshi Text, Gettext, Gherkin, Gleam, GLSL, Gnuplot, Go, Go HTML Template, Go Template, Go Text Template, GraphQL, Groff, Groovy +| H | Handlebars, Hare, Haskell, Haxe, HCL, Hexdump, HLB, HLSL, HolyC, HTML, HTTP, Hy +| I | Idris, Igor, INI, Io, ISCdhcpd +| J | J, Janet, Java, JavaScript, JSON, JSONata, Jsonnet, Julia, Jungle +| K | Kakoune, KDL, Kotlin +| L | Lateralus, Lean4, Lighttpd configuration file, LilyPond, LLVM, lox, Lua, Luau +| M | Makefile, Mako, markdown, Markless, Mason, Materialize SQL dialect, Mathematica, Matlab, MCFunction, Meson, Metal, microcad, MiniZinc, MLIR, Modelica, Modula-2, Mojo, MonkeyC, MoonBit, MoonScript, MorrowindScript, Myghty, MySQL +| N | NASM, Natural, NDISASM, Newspeak, Nginx configuration file, Nim, Nix, NSIS, Nu +| O | Objective-C, ObjectPascal, OCaml, Octave, Odin, OnesEnterprise, OpenEdge ABL, OpenSCAD, Org Mode +| P | PacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Plutus Core, Pony, PostgreSQL SQL dialect, PostScript, POVRay, PowerQuery, PowerShell, Prolog, Promela, PromQL, properties, Protocol Buffer, Protocol Buffer Text Format, PRQL, PSL, Puppet, Python, Python 2 +| Q | QBasic, QML +| R | R, Racket, Ragel, Raku, react, ReasonML, reg, Rego, reStructuredText, Rexx, RGBDS Assembly, Ring, RPGLE, RPMSpec, Ruby, Rust +| S | SAS, Sass, Scala, scdoc, Scheme, Scilab, SCSS, Sed, Sieve, Smali, Smalltalk, Smarty, SNBT, Snobol, Solidity, SourcePawn, Spade, SPARQL, SQL, SquidConf, Standard ML, stas, Stylus, Svelte, Swift, SYSTEMD, systemverilog +| T | TableGen, Tal, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData, Typst +| U | ucode +| V | V, V shell, Vala, VB.net, verilog, VHDL, VHS, VimL, vue +| W | WDTE, WebAssembly Text Format, WebGPU Shading Language, WebVTT, Whiley +| X | XML, Xorg +| Y | YAML, YANG +| Z | Z80 Assembly, Zed, Zig + +_I will attempt to keep this section up to date, but an authoritative list can be +displayed with `chroma --list`._ + +## Try it + +Try out various languages and styles on the [Chroma Playground](https://swapoff.org/chroma/playground/). + +## Using the library + +This is version 2 of Chroma, use the import path: + +```go +import "github.com/alecthomas/chroma/v2" +``` + +Chroma, like Pygments, has the concepts of +[lexers](https://github.com/alecthomas/chroma/tree/master/lexers), +[formatters](https://github.com/alecthomas/chroma/tree/master/formatters) and +[styles](https://github.com/alecthomas/chroma/tree/master/styles). + +Lexers convert source text into a stream of tokens, styles specify how token +types are mapped to colours, and formatters convert tokens and styles into +formatted output. + +A package exists for each of these, containing a global `Registry` variable +with all of the registered implementations. There are also helper functions +for using the registry in each package, such as looking up lexers by name or +matching filenames, etc. + +In all cases, if a lexer, formatter or style can not be determined, `nil` will +be returned. In this situation you may want to default to the `Fallback` +value in each respective package, which provides sane defaults. + +### Quick start + +A convenience function exists that can be used to simply format some source +text, without any effort: + +```go +err := quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai") +``` + +### Identifying the language + +To highlight code, you'll first have to identify what language the code is +written in. There are three primary ways to do that: + +1. Detect the language from its filename. + + ```go + lexer := lexers.Match("foo.go") + ``` + +2. Explicitly specify the language by its Chroma syntax ID (a full list is available from `lexers.Names()`). + + ```go + lexer := lexers.Get("go") + ``` + +3. Detect the language from its content. + + ```go + lexer := lexers.Analyse("package main\n\nfunc main()\n{\n}\n") + ``` + +In all cases, `nil` will be returned if the language can not be identified. + +```go +if lexer == nil { + lexer = lexers.Fallback +} +``` + +At this point, it should be noted that some lexers can be extremely chatty. To +mitigate this, you can use the coalescing lexer to coalesce runs of identical +token types into a single token: + +```go +lexer = chroma.Coalesce(lexer) +``` + +### Formatting the output + +Once a language is identified you will need to pick a formatter and a style (theme). + +```go +style := styles.Get("swapoff") +if style == nil { + style = styles.Fallback +} +formatter := formatters.Get("html") +if formatter == nil { + formatter = formatters.Fallback +} +``` + +Then obtain an iterator over the tokens: + +```go +contents, err := ioutil.ReadAll(r) +iterator, err := lexer.Tokenise(nil, string(contents)) +``` + +And finally, format the tokens from the iterator: + +```go +err := formatter.Format(w, style, iterator) +``` + +### The HTML formatter + +By default the `html` registered formatter generates standalone HTML with +embedded CSS. More flexibility is available through the `formatters/html` package. + +Firstly, the output generated by the formatter can be customised with the +following constructor options: + +- `Standalone()` - generate standalone HTML with embedded CSS. +- `WithClasses()` - use classes rather than inlined style attributes. +- `ClassPrefix(prefix)` - prefix each generated CSS class. +- `TabWidth(width)` - Set the rendered tab width, in characters. +- `WithLineNumbers()` - Render line numbers (style with `LineNumbers`). +- `WithLinkableLineNumbers()` - Make the line numbers linkable and be a link to themselves. +- `HighlightLines(ranges)` - Highlight lines in these ranges (style with `LineHighlight`). +- `LineNumbersInTable()` - Use a table for formatting line numbers and code, rather than spans. + +If `WithClasses()` is used, the corresponding CSS can be obtained from the formatter with: + +```go +formatter := html.New(html.WithClasses(true)) +err := formatter.WriteCSS(w, style) +``` + +## More detail + +### Lexers + +See the [Pygments documentation](http://pygments.org/docs/lexerdevelopment/) +for details on implementing lexers. Most concepts apply directly to Chroma, +but see existing lexer implementations for real examples. + +In many cases lexers can be automatically converted directly from Pygments by +using the included Python 3 script `pygments2chroma_xml.py`. I use something like +the following: + +```sh +uv run --script _tools/pygments2chroma_xml.py \ + pygments.lexers.jvm.KotlinLexer \ + > lexers/embedded/kotlin.xml +``` + +A list of all lexers available in Pygments can be found in [pygments-lexers.txt](https://github.com/alecthomas/chroma/blob/master/pygments-lexers.txt). + +### Formatters + +Chroma supports HTML output, as well as terminal output in 8 colour, 256 colour, and true-colour. + +A `noop` formatter is included that outputs the token text only, and a `tokens` +formatter outputs raw tokens. The latter is useful for debugging lexers. + +### Styles + +Chroma styles are defined in XML. The style entries use the +[same syntax](http://pygments.org/docs/styles/) as Pygments. All Pygments styles have been converted to Chroma using the `_tools/style.py` +script. + +Style names are case-insensitive. For example, `monokai` and `Monokai` are treated as the same style. + +When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles), +know that the `Background` token type provides the default style for tokens. It does so +by defining a foreground color and background color. + +For example, this gives each token name not defined in the style a default color +of `#f8f8f8` and uses `#000000` for the highlighted code block's background: + +```xml + +``` + +Also, token types in a style file are hierarchical. For instance, when `CommentSpecial` is not defined, Chroma uses the token style from `Comment`. So when several comment tokens use the same color, you'll only need to define `Comment` and override the one that has a different color. + +For a quick overview of the available styles and how they look, check out the [Chroma Style Gallery](https://xyproto.github.io/splash/docs/). + +## Command-line interface + +A command-line interface to Chroma is included. + +Binaries are available to install from [the releases page](https://github.com/alecthomas/chroma/releases). + +The CLI can be used as a preprocessor to colorise output of `less(1)`, +see documentation for the `LESSOPEN` environment variable. + +The `--fail` flag can be used to suppress output and return with exit status +1 to facilitate falling back to some other preprocessor in case chroma +does not resolve a specific lexer to use for the given file. For example: + +```shell +export LESSOPEN='| p() { chroma --fail "$1" || cat "$1"; }; p "%s"' +``` + +Replace `cat` with your favourite fallback preprocessor. + +When invoked as `.lessfilter`, the `--fail` flag is automatically turned +on under the hood for easy integration with [lesspipe shipping with +Debian and derivatives](https://manpages.debian.org/lesspipe#USER_DEFINED_FILTERS); +for that setup the `chroma` executable can be just symlinked to `~/.lessfilter`. + +## Projects using Chroma + +* [`moor`](https://github.com/walles/moor) is a full-blown pager that colorizes + its input using Chroma +* [Hugo](https://gohugo.io/) is a static site generator that [uses Chroma for syntax + highlighting code examples](https://gohugo.io/content-management/syntax-highlighting/) +* [f4](https://github.com/unxed/f4) is asynchronious cross platform Far Manager clone in Go + that uses Chroma for syntax highlighting in built-in editor + +## Testing lexers + +If you edit some lexers and want to try it, open a shell in `cmd/chromad` and run: + +```shell +go run . --csrf-key=securekey +``` + +A Link will be printed. Open it in your Browser. Now you can test on the Playground with your local changes. + +If you want to run the tests and the lexers, open a shell in the root directory and run: + +```shell +go test ./lexers +``` + +When updating or adding a lexer, please add tests. See [lexers/README.md](lexers/README.md) for more. + +## What's missing compared to Pygments? + +- Quite a few lexers, for various reasons (pull-requests welcome): + - Pygments lexers for complex languages often include custom code to + handle certain aspects, such as Raku's ability to nest code inside + regular expressions. These require time and effort to convert. + - I mostly only converted languages I had heard of, to reduce the porting cost. +- Some more esoteric features of Pygments are omitted for simplicity. +- Though the Chroma API supports content detection, very few languages support them. + I have plans to implement a statistical analyser at some point, but not enough time. diff --git a/vendor/github.com/alecthomas/chroma/v2/biome.json b/vendor/github.com/alecthomas/chroma/v2/biome.json new file mode 100644 index 000000000..a5bec2e19 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/biome.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.0.5/schema.json", + "formatter": { + "indentStyle": "space" + } +} diff --git a/vendor/github.com/alecthomas/chroma/v2/chroma.jpg b/vendor/github.com/alecthomas/chroma/v2/chroma.jpg new file mode 100644 index 000000000..a747dbf80 Binary files /dev/null and b/vendor/github.com/alecthomas/chroma/v2/chroma.jpg differ diff --git a/vendor/github.com/alecthomas/chroma/v2/coalesce.go b/vendor/github.com/alecthomas/chroma/v2/coalesce.go new file mode 100644 index 000000000..f5048951a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/coalesce.go @@ -0,0 +1,35 @@ +package chroma + +// Coalesce is a Lexer interceptor that collapses runs of common types into a single token. +func Coalesce(lexer Lexer) Lexer { return &coalescer{lexer} } + +type coalescer struct{ Lexer } + +func (d *coalescer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { + var prev Token + it, err := d.Lexer.Tokenise(options, text) + if err != nil { + return nil, err + } + return func() Token { + for token := it(); token != (EOF); token = it() { + if len(token.Value) == 0 { + continue + } + if prev == EOF { + prev = token + } else { + if prev.Type == token.Type && len(prev.Value) < 8192 { + prev.Value += token.Value + } else { + out := prev + prev = token + return out + } + } + } + out := prev + prev = EOF + return out + }, nil +} diff --git a/vendor/github.com/alecthomas/chroma/v2/colour.go b/vendor/github.com/alecthomas/chroma/v2/colour.go new file mode 100644 index 000000000..8add068dc --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/colour.go @@ -0,0 +1,192 @@ +package chroma + +import ( + "fmt" + "math" + "strconv" + "strings" +) + +// ANSI2RGB maps ANSI colour names, as supported by Chroma, to hex RGB values. +var ANSI2RGB = map[string]string{ + "#ansiblack": "000000", + "#ansidarkred": "7f0000", + "#ansidarkgreen": "007f00", + "#ansibrown": "7f7fe0", + "#ansidarkblue": "00007f", + "#ansipurple": "7f007f", + "#ansiteal": "007f7f", + "#ansilightgray": "e5e5e5", + // Normal + "#ansidarkgray": "555555", + "#ansired": "ff0000", + "#ansigreen": "00ff00", + "#ansiyellow": "ffff00", + "#ansiblue": "0000ff", + "#ansifuchsia": "ff00ff", + "#ansiturquoise": "00ffff", + "#ansiwhite": "ffffff", + + // Aliases without the "ansi" prefix, because...why? + "#black": "000000", + "#darkred": "7f0000", + "#darkgreen": "007f00", + "#brown": "7f7fe0", + "#darkblue": "00007f", + "#purple": "7f007f", + "#teal": "007f7f", + "#lightgray": "e5e5e5", + // Normal + "#darkgray": "555555", + "#red": "ff0000", + "#green": "00ff00", + "#yellow": "ffff00", + "#blue": "0000ff", + "#fuchsia": "ff00ff", + "#turquoise": "00ffff", + "#white": "ffffff", +} + +// Colour represents an RGB colour. +type Colour int32 + +// NewColour creates a Colour directly from RGB values. +func NewColour(r, g, b uint8) Colour { + return Colour(int32(r)<<16|int32(g)<<8|int32(b)) + 1 +} + +// Distance between this colour and another. +// +// This uses the approach described here (https://www.compuphase.com/cmetric.htm). +// This is not as accurate as LAB, et. al. but is *vastly* simpler and sufficient for our needs. +func (c Colour) Distance(e2 Colour) float64 { + ar, ag, ab := int64(c.Red()), int64(c.Green()), int64(c.Blue()) + br, bg, bb := int64(e2.Red()), int64(e2.Green()), int64(e2.Blue()) + rmean := (ar + br) / 2 + r := ar - br + g := ag - bg + b := ab - bb + return math.Sqrt(float64((((512 + rmean) * r * r) >> 8) + 4*g*g + (((767 - rmean) * b * b) >> 8))) +} + +// Brighten returns a copy of this colour with its brightness adjusted. +// +// If factor is negative, the colour is darkened. +// +// Uses approach described here (http://www.pvladov.com/2012/09/make-color-lighter-or-darker.html). +func (c Colour) Brighten(factor float64) Colour { + r := float64(c.Red()) + g := float64(c.Green()) + b := float64(c.Blue()) + + if factor < 0 { + factor++ + r *= factor + g *= factor + b *= factor + } else { + r = (255-r)*factor + r + g = (255-g)*factor + g + b = (255-b)*factor + b + } + return NewColour(uint8(r), uint8(g), uint8(b)) +} + +// BrightenOrDarken brightens a colour if it is < 0.5 brightness or darkens if > 0.5 brightness. +func (c Colour) BrightenOrDarken(factor float64) Colour { + if c.Brightness() < 0.5 { + return c.Brighten(factor) + } + return c.Brighten(-factor) +} + +// ClampBrightness returns a copy of this colour with its brightness adjusted such that +// it falls within the range [min, max] (or very close to it due to rounding errors). +// The supplied values use the same [0.0, 1.0] range as Brightness. +func (c Colour) ClampBrightness(min, max float64) Colour { + if !c.IsSet() { + return c + } + + min = math.Max(min, 0) + max = math.Min(max, 1) + current := c.Brightness() + target := math.Min(math.Max(current, min), max) + if current == target { + return c + } + + r := float64(c.Red()) + g := float64(c.Green()) + b := float64(c.Blue()) + rgb := r + g + b + if target > current { + // Solve for x: target == ((255-r)*x + r + (255-g)*x + g + (255-b)*x + b) / 255 / 3 + return c.Brighten((target*255*3 - rgb) / (255*3 - rgb)) + } + // Solve for x: target == (r*(x+1) + g*(x+1) + b*(x+1)) / 255 / 3 + return c.Brighten((target*255*3)/rgb - 1) +} + +// Brightness of the colour (roughly) in the range 0.0 to 1.0. +func (c Colour) Brightness() float64 { + return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0 +} + +// ParseColour in the forms #rgb, #rrggbb, #ansi, or #. +// Will return an "unset" colour if invalid. +func ParseColour(colour string) Colour { + colour = normaliseColour(colour) + n, err := strconv.ParseUint(colour, 16, 32) + if err != nil { + return 0 + } + return Colour(n + 1) //nolint:gosec +} + +// MustParseColour is like ParseColour except it panics if the colour is invalid. +// +// Will panic if colour is in an invalid format. +func MustParseColour(colour string) Colour { + parsed := ParseColour(colour) + if !parsed.IsSet() { + panic(fmt.Errorf("invalid colour %q", colour)) + } + return parsed +} + +// IsSet returns true if the colour is set. +func (c Colour) IsSet() bool { return c != 0 } + +func (c Colour) String() string { return fmt.Sprintf("#%06x", int(c-1)) } +func (c Colour) GoString() string { return fmt.Sprintf("Colour(0x%06x)", int(c-1)) } + +// Red component of colour. +func (c Colour) Red() uint8 { return uint8(((c - 1) >> 16) & 0xff) } //nolint:gosec + +// Green component of colour. +func (c Colour) Green() uint8 { return uint8(((c - 1) >> 8) & 0xff) } //nolint:gosec + +// Blue component of colour. +func (c Colour) Blue() uint8 { return uint8((c - 1) & 0xff) } //nolint:gosec + +// Colours is an orderable set of colours. +type Colours []Colour + +func (c Colours) Len() int { return len(c) } +func (c Colours) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c Colours) Less(i, j int) bool { return c[i] < c[j] } + +// Convert colours to #rrggbb. +func normaliseColour(colour string) string { + if ansi, ok := ANSI2RGB[colour]; ok { + return ansi + } + if strings.HasPrefix(colour, "#") { + colour = colour[1:] + if len(colour) == 3 { + return colour[0:1] + colour[0:1] + colour[1:2] + colour[1:2] + colour[2:3] + colour[2:3] + } + } + return colour +} diff --git a/vendor/github.com/alecthomas/chroma/v2/delegate.go b/vendor/github.com/alecthomas/chroma/v2/delegate.go new file mode 100644 index 000000000..298f2dbbd --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/delegate.go @@ -0,0 +1,161 @@ +package chroma + +import ( + "bytes" +) + +type delegatingLexer struct { + root Lexer + language Lexer +} + +// DelegatingLexer combines two lexers to handle the common case of a language embedded inside another, such as PHP +// inside HTML or PHP inside plain text. +// +// It takes two lexer as arguments: a root lexer and a language lexer. First everything is scanned using the language +// lexer, which must return "Other" for unrecognised tokens. Then all "Other" tokens are lexed using the root lexer. +// Finally, these two sets of tokens are merged. +// +// The lexers from the template lexer package use this base lexer. +func DelegatingLexer(root Lexer, language Lexer) Lexer { + return &delegatingLexer{ + root: root, + language: language, + } +} + +func (d *delegatingLexer) SetTracing(enable bool) { + if l, ok := d.language.(TracingLexer); ok { + l.SetTracing(enable) + } + if l, ok := d.root.(TracingLexer); ok { + l.SetTracing(enable) + } +} + +func (d *delegatingLexer) AnalyseText(text string) float32 { + return d.root.AnalyseText(text) +} + +func (d *delegatingLexer) SetAnalyser(analyser func(text string) float32) Lexer { + d.root.SetAnalyser(analyser) + return d +} + +func (d *delegatingLexer) SetRegistry(r *LexerRegistry) Lexer { + d.root.SetRegistry(r) + d.language.SetRegistry(r) + return d +} + +func (d *delegatingLexer) Config() *Config { + return d.language.Config() +} + +// An insertion is the character range where language tokens should be inserted. +type insertion struct { + start, end int + tokens []Token +} + +func (d *delegatingLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { // nolint: gocognit + tokens, err := Tokenise(Coalesce(d.language), options, text) + if err != nil { + return nil, err + } + // Compute insertions and gather "Other" tokens. + others := &bytes.Buffer{} + insertions := []*insertion{} + var insert *insertion + offset := 0 + var last Token + for _, t := range tokens { + if t.Type == Other { + if last != EOF && insert != nil && last.Type != Other { + insert.end = offset + } + others.WriteString(t.Value) + } else { + if last == EOF || last.Type == Other { + insert = &insertion{start: offset} + insertions = append(insertions, insert) + } + insert.tokens = append(insert.tokens, t) + } + last = t + offset += len(t.Value) + } + + if len(insertions) == 0 { + return d.root.Tokenise(options, text) + } + + // Lex the other tokens. + rootTokens, err := Tokenise(Coalesce(d.root), options, others.String()) + if err != nil { + return nil, err + } + + // Interleave the two sets of tokens. + var out []Token + offset = 0 // Offset into text. + tokenIndex := 0 + nextToken := func() Token { + if tokenIndex >= len(rootTokens) { + return EOF + } + t := rootTokens[tokenIndex] + tokenIndex++ + return t + } + insertionIndex := 0 + nextInsertion := func() *insertion { + if insertionIndex >= len(insertions) { + return nil + } + i := insertions[insertionIndex] + insertionIndex++ + return i + } + t := nextToken() + i := nextInsertion() + for t != EOF || i != nil { + // fmt.Printf("%d->%d:%q %d->%d:%q\n", offset, offset+len(t.Value), t.Value, i.start, i.end, Stringify(i.tokens...)) + if t == EOF || (i != nil && i.start < offset+len(t.Value)) { + var l Token + l, t = splitToken(t, i.start-offset) + if l != EOF { + out = append(out, l) + offset += len(l.Value) + } + out = append(out, i.tokens...) + offset += i.end - i.start + if t == EOF { + t = nextToken() + } + i = nextInsertion() + } else { + out = append(out, t) + offset += len(t.Value) + t = nextToken() + } + } + return Literator(out...), nil +} + +func splitToken(t Token, offset int) (l Token, r Token) { + if t == EOF { + return EOF, EOF + } + if offset == 0 { + return EOF, t + } + if offset == len(t.Value) { + return t, EOF + } + l = t.Clone() + r = t.Clone() + l.Value = l.Value[:offset] + r.Value = r.Value[offset:] + return +} diff --git a/vendor/github.com/alecthomas/chroma/v2/doc.go b/vendor/github.com/alecthomas/chroma/v2/doc.go new file mode 100644 index 000000000..4dde77c81 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/doc.go @@ -0,0 +1,7 @@ +// Package chroma takes source code and other structured text and converts it into syntax highlighted HTML, ANSI- +// coloured text, etc. +// +// Chroma is based heavily on Pygments, and includes translators for Pygments lexers and styles. +// +// For more information, go here: https://github.com/alecthomas/chroma +package chroma diff --git a/vendor/github.com/alecthomas/chroma/v2/emitters.go b/vendor/github.com/alecthomas/chroma/v2/emitters.go new file mode 100644 index 000000000..1097a7576 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/emitters.go @@ -0,0 +1,233 @@ +package chroma + +import ( + "fmt" +) + +// An Emitter takes group matches and returns tokens. +type Emitter interface { + // Emit tokens for the given regex groups. + Emit(groups []string, state *LexerState) Iterator +} + +// ValidatingEmitter is an Emitter that can validate against a compiled rule. +type ValidatingEmitter interface { + Emitter + ValidateEmitter(rule *CompiledRule) error +} + +// SerialisableEmitter is an Emitter that can be serialised and deserialised to/from JSON. +type SerialisableEmitter interface { + Emitter + EmitterKind() string +} + +// EmitterFunc is a function that is an Emitter. +type EmitterFunc func(groups []string, state *LexerState) Iterator + +// Emit tokens for groups. +func (e EmitterFunc) Emit(groups []string, state *LexerState) Iterator { + return e(groups, state) +} + +type Emitters []Emitter + +type byGroupsEmitter struct { + Emitters +} + +var _ ValidatingEmitter = (*byGroupsEmitter)(nil) + +// ByGroups emits a token for each matching group in the rule's regex. +func ByGroups(emitters ...Emitter) Emitter { + return &byGroupsEmitter{Emitters: emitters} +} + +func (b *byGroupsEmitter) EmitterKind() string { return "bygroups" } + +func (b *byGroupsEmitter) ValidateEmitter(rule *CompiledRule) error { + if len(rule.Regexp.GetGroupNumbers())-1 != len(b.Emitters) { + return fmt.Errorf("number of groups %d does not match number of emitters %d", len(rule.Regexp.GetGroupNumbers())-1, len(b.Emitters)) + } + return nil +} + +func (b *byGroupsEmitter) Emit(groups []string, state *LexerState) Iterator { + iterators := make([]Iterator, 0, len(groups)-1) + if len(b.Emitters) != len(groups)-1 { + iterators = append(iterators, Error.Emit(groups, state)) + // panic(errors.Errorf("number of groups %q does not match number of emitters %v", groups, emitters)) + } else { + for i, group := range groups[1:] { + if b.Emitters[i] != nil { + iterators = append(iterators, b.Emitters[i].Emit([]string{group}, state)) + } + } + } + return Concaterator(iterators...) +} + +// ByGroupNames emits a token for each named matching group in the rule's regex. +func ByGroupNames(emitters map[string]Emitter) Emitter { + return EmitterFunc(func(groups []string, state *LexerState) Iterator { + iterators := make([]Iterator, 0, len(state.NamedGroups)-1) + if len(state.NamedGroups)-1 == 0 { + if emitter, ok := emitters[`0`]; ok { + iterators = append(iterators, emitter.Emit(groups, state)) + } else { + iterators = append(iterators, Error.Emit(groups, state)) + } + } else { + ruleRegex := state.Rules[state.State][state.Rule].Regexp + for i := 1; i < len(state.NamedGroups); i++ { + groupName := ruleRegex.GroupNameFromNumber(i) + group := state.NamedGroups[groupName] + if emitter, ok := emitters[groupName]; ok { + if emitter != nil { + iterators = append(iterators, emitter.Emit([]string{group}, state)) + } + } else { + iterators = append(iterators, Error.Emit([]string{group}, state)) + } + } + } + return Concaterator(iterators...) + }) +} + +// UsingByGroup emits tokens for the matched groups in the regex using a +// sublexer. Used when lexing code blocks where the name of a sublexer is +// contained within the block, for example on a Markdown text block or SQL +// language block. +// +// An attempt to load the sublexer will be made using the captured value from +// the text of the matched sublexerNameGroup. If a sublexer matching the +// sublexerNameGroup is available, then tokens for the matched codeGroup will +// be emitted using the sublexer. Otherwise, if no sublexer is available, then +// tokens will be emitted from the passed emitter. +// +// Example: +// +// var Markdown = internal.Register(MustNewLexer( +// &Config{ +// Name: "markdown", +// Aliases: []string{"md", "mkd"}, +// Filenames: []string{"*.md", "*.mkd", "*.markdown"}, +// MimeTypes: []string{"text/x-markdown"}, +// }, +// Rules{ +// "root": { +// {"^(```)(\\w+)(\\n)([\\w\\W]*?)(^```$)", +// UsingByGroup( +// 2, 4, +// String, String, String, Text, String, +// ), +// nil, +// }, +// }, +// }, +// )) +// +// See the lexers/markdown.go for the complete example. +// +// Note: panic's if the number of emitters does not equal the number of matched +// groups in the regex. +func UsingByGroup(sublexerNameGroup, codeGroup int, emitters ...Emitter) Emitter { + return &usingByGroup{ + SublexerNameGroup: sublexerNameGroup, + CodeGroup: codeGroup, + Emitters: emitters, + } +} + +type usingByGroup struct { + SublexerNameGroup int `xml:"sublexer_name_group"` + CodeGroup int `xml:"code_group"` + Emitters Emitters `xml:"emitters"` +} + +func (u *usingByGroup) EmitterKind() string { return "usingbygroup" } +func (u *usingByGroup) Emit(groups []string, state *LexerState) Iterator { + // bounds check + if len(u.Emitters) != len(groups)-1 { + panic("UsingByGroup expects number of emitters to be the same as len(groups)-1") + } + + // grab sublexer + sublexer := state.Registry.Get(groups[u.SublexerNameGroup]) + + // build iterators + iterators := make([]Iterator, len(groups)-1) + for i, group := range groups[1:] { + if i == u.CodeGroup-1 && sublexer != nil { + var err error + iterators[i], err = sublexer.Tokenise(nil, groups[u.CodeGroup]) + if err != nil { + panic(err) + } + } else if u.Emitters[i] != nil { + iterators[i] = u.Emitters[i].Emit([]string{group}, state) + } + } + return Concaterator(iterators...) +} + +// UsingLexer returns an Emitter that uses a given Lexer for parsing and emitting. +// +// This Emitter is not serialisable. +func UsingLexer(lexer Lexer) Emitter { + return EmitterFunc(func(groups []string, _ *LexerState) Iterator { + it, err := lexer.Tokenise(&TokeniseOptions{State: "root", Nested: true}, groups[0]) + if err != nil { + panic(err) + } + return it + }) +} + +type usingEmitter struct { + Lexer string `xml:"lexer,attr"` +} + +func (u *usingEmitter) EmitterKind() string { return "using" } + +func (u *usingEmitter) Emit(groups []string, state *LexerState) Iterator { + if state.Registry == nil { + panic(fmt.Sprintf("no LexerRegistry available for Using(%q)", u.Lexer)) + } + lexer := state.Registry.Get(u.Lexer) + if lexer == nil { + panic(fmt.Sprintf("no such lexer %q", u.Lexer)) + } + it, err := lexer.Tokenise(&TokeniseOptions{State: "root", Nested: true}, groups[0]) + if err != nil { + panic(err) + } + return it +} + +// Using returns an Emitter that uses a given Lexer reference for parsing and emitting. +// +// The referenced lexer must be stored in the same LexerRegistry. +func Using(lexer string) Emitter { + return &usingEmitter{Lexer: lexer} +} + +type usingSelfEmitter struct { + State string `xml:"state,attr"` +} + +func (u *usingSelfEmitter) EmitterKind() string { return "usingself" } + +func (u *usingSelfEmitter) Emit(groups []string, state *LexerState) Iterator { + it, err := state.Lexer.Tokenise(&TokeniseOptions{State: u.State, Nested: true}, groups[0]) + if err != nil { + panic(err) + } + return it +} + +// UsingSelf is like Using, but uses the current Lexer. +func UsingSelf(stateName string) Emitter { + return &usingSelfEmitter{stateName} +} diff --git a/vendor/github.com/alecthomas/chroma/v2/formatter.go b/vendor/github.com/alecthomas/chroma/v2/formatter.go new file mode 100644 index 000000000..00dd5d8df --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/formatter.go @@ -0,0 +1,43 @@ +package chroma + +import ( + "io" +) + +// A Formatter for Chroma lexers. +type Formatter interface { + // Format returns a formatting function for tokens. + // + // If the iterator panics, the Formatter should recover. + Format(w io.Writer, style *Style, iterator Iterator) error +} + +// A FormatterFunc is a Formatter implemented as a function. +// +// Guards against iterator panics. +type FormatterFunc func(w io.Writer, style *Style, iterator Iterator) error + +func (f FormatterFunc) Format(w io.Writer, s *Style, it Iterator) (err error) { // nolint + defer func() { + if perr := recover(); perr != nil { + err = perr.(error) + } + }() + return f(w, s, it) +} + +type recoveringFormatter struct { + Formatter +} + +func (r recoveringFormatter) Format(w io.Writer, s *Style, it Iterator) (err error) { + defer func() { + if perr := recover(); perr != nil { + err = perr.(error) + } + }() + return r.Formatter.Format(w, s, it) +} + +// RecoveringFormatter wraps a formatter with panic recovery. +func RecoveringFormatter(formatter Formatter) Formatter { return recoveringFormatter{formatter} } diff --git a/vendor/github.com/alecthomas/chroma/v2/iterator.go b/vendor/github.com/alecthomas/chroma/v2/iterator.go new file mode 100644 index 000000000..cf39bb577 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/iterator.go @@ -0,0 +1,93 @@ +package chroma + +import "strings" + +// An Iterator across tokens. +// +// EOF will be returned at the end of the Token stream. +// +// If an error occurs within an Iterator, it may propagate this in a panic. Formatters should recover. +type Iterator func() Token + +// Tokens consumes all tokens from the iterator and returns them as a slice. +func (i Iterator) Tokens() []Token { + var out []Token + for t := i(); t != EOF; t = i() { + out = append(out, t) + } + return out +} + +// Stdlib converts a Chroma iterator to a Go 1.23-compatible iterator. +func (i Iterator) Stdlib() func(yield func(Token) bool) { + return func(yield func(Token) bool) { + for t := i(); t != EOF; t = i() { + if !yield(t) { + return + } + } + } +} + +// Concaterator concatenates tokens from a series of iterators. +func Concaterator(iterators ...Iterator) Iterator { + return func() Token { + for len(iterators) > 0 { + t := iterators[0]() + if t != EOF { + return t + } + iterators = iterators[1:] + } + return EOF + } +} + +// Literator converts a sequence of literal Tokens into an Iterator. +func Literator(tokens ...Token) Iterator { + return func() Token { + if len(tokens) == 0 { + return EOF + } + token := tokens[0] + tokens = tokens[1:] + return token + } +} + +// SplitTokensIntoLines splits tokens containing newlines in two. +func SplitTokensIntoLines(tokens []Token) (out [][]Token) { + var line []Token // nolint: prealloc +tokenLoop: + for _, token := range tokens { + for strings.Contains(token.Value, "\n") { + parts := strings.SplitAfterN(token.Value, "\n", 2) + // Token becomes the tail. + token.Value = parts[1] + + // Append the head to the line and flush the line. + clone := token.Clone() + clone.Value = parts[0] + line = append(line, clone) + out = append(out, line) + line = nil + + // If the tail token is empty, don't emit it. + if len(token.Value) == 0 { + continue tokenLoop + } + } + line = append(line, token) + } + if len(line) > 0 { + out = append(out, line) + } + // Strip empty trailing token line. + if len(out) > 0 { + last := out[len(out)-1] + if len(last) == 1 && last[0].Value == "" { + out = out[:len(out)-1] + } + } + return out +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexer.go b/vendor/github.com/alecthomas/chroma/v2/lexer.go new file mode 100644 index 000000000..602db1c4f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexer.go @@ -0,0 +1,179 @@ +package chroma + +import ( + "fmt" + "strings" +) + +var ( + defaultOptions = &TokeniseOptions{ + State: "root", + EnsureLF: true, + } +) + +// Config for a lexer. +type Config struct { + // Name of the lexer. + Name string `xml:"name,omitempty"` + + // Shortcuts for the lexer + Aliases []string `xml:"alias,omitempty"` + + // File name globs + Filenames []string `xml:"filename,omitempty"` + + // Secondary file name globs + AliasFilenames []string `xml:"alias_filename,omitempty"` + + // MIME types + MimeTypes []string `xml:"mime_type,omitempty"` + + // Regex matching is case-insensitive. + CaseInsensitive bool `xml:"case_insensitive,omitempty"` + + // Regex matches all characters. + DotAll bool `xml:"dot_all,omitempty"` + + // Regex does not match across lines ($ matches EOL). + // + // Defaults to multiline. + NotMultiline bool `xml:"not_multiline,omitempty"` + + // Don't strip leading and trailing newlines from the input. + // DontStripNL bool + + // Strip all leading and trailing whitespace from the input + // StripAll bool + + // Make sure that the input ends with a newline. This + // is required for some lexers that consume input linewise. + EnsureNL bool `xml:"ensure_nl,omitempty"` + + // If given and greater than 0, expand tabs in the input. + // TabSize int + + // Priority of lexer. + // + // If this is 0 it will be treated as a default of 1. + Priority float32 `xml:"priority,omitempty"` + + // Analyse is a list of regexes to match against the input. + // + // If a match is found, the score is returned if single attribute is set to true, + // otherwise the sum of all the score of matching patterns will be + // used as the final score. + Analyse *AnalyseConfig `xml:"analyse,omitempty"` +} + +// AnalyseConfig defines the list of regexes analysers. +type AnalyseConfig struct { + Regexes []RegexConfig `xml:"regex,omitempty"` + // If true, the first matching score is returned. + First bool `xml:"first,attr"` +} + +// RegexConfig defines a single regex pattern and its score in case of match. +type RegexConfig struct { + Pattern string `xml:"pattern,attr"` + Score float32 `xml:"score,attr"` +} + +// Token output to formatter. +type Token struct { + Type TokenType `json:"type"` + Value string `json:"value"` +} + +func (t *Token) String() string { return t.Value } +func (t *Token) GoString() string { return fmt.Sprintf("&Token{%s, %q}", t.Type, t.Value) } + +// Clone returns a clone of the Token. +func (t *Token) Clone() Token { + return *t +} + +// EOF is returned by lexers at the end of input. +var EOF Token + +// TokeniseOptions contains options for tokenisers. +type TokeniseOptions struct { + // State to start tokenisation in. Defaults to "root". + State string + // Nested tokenisation. + Nested bool + + // If true, all EOLs are converted into LF + // by replacing CRLF and CR + EnsureLF bool +} + +// A Lexer for tokenising source code. +type Lexer interface { + // Config describing the features of the Lexer. + Config() *Config + // Tokenise returns an Iterator over tokens in text. + Tokenise(options *TokeniseOptions, text string) (Iterator, error) + // SetRegistry sets the registry this Lexer is associated with. + // + // The registry should be used by the Lexer if it needs to look up other + // lexers. + SetRegistry(registry *LexerRegistry) Lexer + // SetAnalyser sets a function the Lexer should use for scoring how + // likely a fragment of text is to match this lexer, between 0.0 and 1.0. + // A value of 1 indicates high confidence. + // + // Lexers may ignore this if they implement their own analysers. + SetAnalyser(analyser func(text string) float32) Lexer + // AnalyseText scores how likely a fragment of text is to match + // this lexer, between 0.0 and 1.0. A value of 1 indicates high confidence. + AnalyseText(text string) float32 +} + +// Trace is the trace of a tokenisation process. +type Trace struct { + Lexer string `json:"lexer"` + State string `json:"state"` + Rule int `json:"rule"` + Pattern string `json:"pattern"` + Pos int `json:"pos"` + Length int `json:"length"` + Elapsed float64 `json:"elapsedMs"` // Elapsed time spent matching for this rule. +} + +// TracingLexer is a Lexer that can trace its tokenisation process. +type TracingLexer interface { + Lexer + SetTracing(enable bool) +} + +// Lexers is a slice of lexers sortable by name. +type Lexers []Lexer + +func (l Lexers) Len() int { return len(l) } +func (l Lexers) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l Lexers) Less(i, j int) bool { + return strings.ToLower(l[i].Config().Name) < strings.ToLower(l[j].Config().Name) +} + +// PrioritisedLexers is a slice of lexers sortable by priority. +type PrioritisedLexers []Lexer + +func (l PrioritisedLexers) Len() int { return len(l) } +func (l PrioritisedLexers) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l PrioritisedLexers) Less(i, j int) bool { + ip := l[i].Config().Priority + if ip == 0 { + ip = 1 + } + jp := l[j].Config().Priority + if jp == 0 { + jp = 1 + } + return ip > jp +} + +// Analyser determines how appropriate this lexer is for the given text. +type Analyser interface { + AnalyseText(text string) float32 +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/README.md b/vendor/github.com/alecthomas/chroma/v2/lexers/README.md new file mode 100644 index 000000000..60a0055ad --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/README.md @@ -0,0 +1,46 @@ +# Chroma lexers + +All lexers in Chroma should now be defined in XML unless they require custom code. + +## Lexer tests + +The tests in this directory feed a known input `testdata/.actual` into the parser for `` and check +that its output matches `.expected`. + +It is also possible to perform several tests on a same parser ``, by placing know inputs `*.actual` into a +directory `testdata//`. + +### Running the tests + +Run the tests as normal: +```go +go test ./lexers +``` + +### Update existing tests + +When you add a new test data file (`*.actual`), you need to regenerate all tests. That's how Chroma creates the `*.expected` test file based on the corresponding lexer. + +To regenerate all tests, type in your terminal: + +```go +RECORD=true go test ./lexers +``` + +This first sets the `RECORD` environment variable to `true`. Then it runs `go test` on the `./lexers` directory of the Chroma project. + +(That environment variable tells Chroma it needs to output test data. After running `go test ./lexers` you can remove or reset that variable.) + +#### Windows users + +Windows users will find that the `RECORD=true go test ./lexers` command fails in both the standard command prompt terminal and in PowerShell. + +Instead we have to perform both steps separately: + +- Set the `RECORD` environment variable to `true`. + + In the regular command prompt window, the `set` command sets an environment variable for the current session: `set RECORD=true`. See [this page](https://superuser.com/questions/212150/how-to-set-env-variable-in-windows-cmd-line) for more. + + In PowerShell, you can use the `$env:RECORD = 'true'` command for that. See [this article](https://mcpmag.com/articles/2019/03/28/environment-variables-in-powershell.aspx) for more. + + You can also make a persistent environment variable by hand in the Windows computer settings. See [this article](https://www.computerhope.com/issues/ch000549.htm) for how. +- When the environment variable is set, run `go test ./lexers`. + +Chroma will now regenerate the test files and print its results to the console window. diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/caddyfile.go b/vendor/github.com/alecthomas/chroma/v2/lexers/caddyfile.go new file mode 100644 index 000000000..82a7efa48 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/caddyfile.go @@ -0,0 +1,275 @@ +package lexers + +import ( + . "github.com/alecthomas/chroma/v2" // nolint +) + +// Matcher token stub for docs, or +// Named matcher: @name, or +// Path matcher: /foo, or +// Wildcard path matcher: * +// nolint: gosec +var caddyfileMatcherTokenRegexp = `(\[\\]|@[^\s]+|/[^\s]+|\*)` + +// Comment at start of line, or +// Comment preceded by whitespace +var caddyfileCommentRegexp = `(^|\s+)#.*\n` + +// caddyfileCommon are the rules common to both of the lexer variants +func caddyfileCommonRules() Rules { + return Rules{ + "site_block_common": { + Include("site_body"), + // Any other directive + {`[^\s#]+`, Keyword, Push("directive")}, + Include("base"), + }, + "site_body": { + // Import keyword + {`\b(import|invoke)\b( [^\s#]+)`, ByGroups(Keyword, Text), Push("subdirective")}, + // Matcher definition + {`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")}, + // Matcher token stub for docs + {`\[\\]`, NameDecorator, Push("matcher")}, + // These cannot have matchers but may have things that look like + // matchers in their arguments, so we just parse as a subdirective. + {`\b(try_files|tls|log|bind)\b`, Keyword, Push("subdirective")}, + // These are special, they can nest more directives + {`\b(handle_errors|handle_path|handle_response|replace_status|handle|route)\b`, Keyword, Push("nested_directive")}, + // uri directive has special syntax + {`\b(uri)\b`, Keyword, Push("uri_directive")}, + }, + "matcher": { + {`\{`, Punctuation, Push("block")}, + // Not can be one-liner + {`not`, Keyword, Push("deep_not_matcher")}, + // Heredoc for CEL expression + Include("heredoc"), + // Backtick for CEL expression + {"`", StringBacktick, Push("backticks")}, + // Any other same-line matcher + {`[^\s#]+`, Keyword, Push("arguments")}, + // Terminators + {`\s*\n`, Text, Pop(1)}, + {`\}`, Punctuation, Pop(1)}, + Include("base"), + }, + "block": { + {`\}`, Punctuation, Pop(2)}, + // Using double quotes doesn't stop at spaces + {`"`, StringDouble, Push("double_quotes")}, + // Using backticks doesn't stop at spaces + {"`", StringBacktick, Push("backticks")}, + // Not can be one-liner + {`not`, Keyword, Push("not_matcher")}, + // Directives & matcher definitions + Include("site_body"), + // Any directive + {`[^\s#]+`, Keyword, Push("subdirective")}, + Include("base"), + }, + "nested_block": { + {`\}`, Punctuation, Pop(2)}, + // Using double quotes doesn't stop at spaces + {`"`, StringDouble, Push("double_quotes")}, + // Using backticks doesn't stop at spaces + {"`", StringBacktick, Push("backticks")}, + // Not can be one-liner + {`not`, Keyword, Push("not_matcher")}, + // Directives & matcher definitions + Include("site_body"), + // Any other subdirective + {`[^\s#]+`, Keyword, Push("directive")}, + Include("base"), + }, + "not_matcher": { + {`\}`, Punctuation, Pop(2)}, + {`\{(?=\s)`, Punctuation, Push("block")}, + {`[^\s#]+`, Keyword, Push("arguments")}, + {`\s+`, Text, nil}, + }, + "deep_not_matcher": { + {`\}`, Punctuation, Pop(2)}, + {`\{(?=\s)`, Punctuation, Push("block")}, + {`[^\s#]+`, Keyword, Push("deep_subdirective")}, + {`\s+`, Text, nil}, + }, + "directive": { + {`\{(?=\s)`, Punctuation, Push("block")}, + {caddyfileMatcherTokenRegexp, NameDecorator, Push("arguments")}, + {caddyfileCommentRegexp, CommentSingle, Pop(1)}, + {`\s*\n`, Text, Pop(1)}, + Include("base"), + }, + "nested_directive": { + {`\{(?=\s)`, Punctuation, Push("nested_block")}, + {caddyfileMatcherTokenRegexp, NameDecorator, Push("nested_arguments")}, + {caddyfileCommentRegexp, CommentSingle, Pop(1)}, + {`\s*\n`, Text, Pop(1)}, + Include("base"), + }, + "subdirective": { + {`\{(?=\s)`, Punctuation, Push("block")}, + {caddyfileCommentRegexp, CommentSingle, Pop(1)}, + {`\s*\n`, Text, Pop(1)}, + Include("base"), + }, + "arguments": { + {`\{(?=\s)`, Punctuation, Push("block")}, + {caddyfileCommentRegexp, CommentSingle, Pop(2)}, + {`\\\n`, Text, nil}, // Skip escaped newlines + {`\s*\n`, Text, Pop(2)}, + Include("base"), + }, + "nested_arguments": { + {`\{(?=\s)`, Punctuation, Push("nested_block")}, + {caddyfileCommentRegexp, CommentSingle, Pop(2)}, + {`\\\n`, Text, nil}, // Skip escaped newlines + {`\s*\n`, Text, Pop(2)}, + Include("base"), + }, + "deep_subdirective": { + {`\{(?=\s)`, Punctuation, Push("block")}, + {caddyfileCommentRegexp, CommentSingle, Pop(3)}, + {`\s*\n`, Text, Pop(3)}, + Include("base"), + }, + "uri_directive": { + {`\{(?=\s)`, Punctuation, Push("block")}, + {caddyfileMatcherTokenRegexp, NameDecorator, nil}, + {`(strip_prefix|strip_suffix|replace|path_regexp)`, NameConstant, Push("arguments")}, + {caddyfileCommentRegexp, CommentSingle, Pop(1)}, + {`\s*\n`, Text, Pop(1)}, + Include("base"), + }, + "double_quotes": { + Include("placeholder"), + {`\\"`, StringDouble, nil}, + {`[^"]`, StringDouble, nil}, + {`"`, StringDouble, Pop(1)}, + }, + "backticks": { + Include("placeholder"), + {"\\\\`", StringBacktick, nil}, + {"[^`]", StringBacktick, nil}, + {"`", StringBacktick, Pop(1)}, + }, + "optional": { + // Docs syntax for showing optional parts with [ ] + {`\[`, Punctuation, Push("optional")}, + Include("name_constants"), + {`\|`, Punctuation, nil}, + {`[^\[\]\|]+`, String, nil}, + {`\]`, Punctuation, Pop(1)}, + }, + "heredoc": { + {`(<<([a-zA-Z0-9_-]+))(\n(.*|\n)*)(\s*)(\2)`, ByGroups(StringHeredoc, nil, String, String, String, StringHeredoc), nil}, + }, + "name_constants": { + {`\b(most_recently_modified|largest_size|smallest_size|first_exist|internal|disable_redirects|ignore_loaded_certs|disable_certs|private_ranges|first|last|before|after|on|off)\b(\||(?=\]|\s|$))`, ByGroups(NameConstant, Punctuation), nil}, + }, + "placeholder": { + // Placeholder with dots, colon for default value, brackets for args[0:] + {`\{[\w+.\[\]\:\$-]+\}`, StringEscape, nil}, + // Handle opening brackets with no matching closing one + {`\{[^\}\s]*\b`, String, nil}, + }, + "base": { + {caddyfileCommentRegexp, CommentSingle, nil}, + {`\[\\]`, NameDecorator, nil}, + Include("name_constants"), + Include("heredoc"), + {`(https?://)?([a-z0-9.-]+)(:)([0-9]+)([^\s]*)`, ByGroups(Name, Name, Punctuation, NumberInteger, Name), nil}, + {`\[`, Punctuation, Push("optional")}, + {"`", StringBacktick, Push("backticks")}, + {`"`, StringDouble, Push("double_quotes")}, + Include("placeholder"), + {`[a-z-]+/[a-z-+]+`, String, nil}, + {`[0-9]+([smhdk]|ns|us|µs|ms)?\b`, NumberInteger, nil}, + {`[^\s\n#\{]+`, String, nil}, + {`/[^\s#]*`, Name, nil}, + {`\s+`, Text, nil}, + }, + } +} + +// Caddyfile lexer. +var Caddyfile = Register(MustNewLexer( + &Config{ + Name: "Caddyfile", + Aliases: []string{"caddyfile", "caddy"}, + Filenames: []string{"Caddyfile*"}, + MimeTypes: []string{}, + }, + caddyfileRules, +)) + +func caddyfileRules() Rules { + return Rules{ + "root": { + {caddyfileCommentRegexp, CommentSingle, nil}, + // Global options block + {`^\s*(\{)\s*$`, ByGroups(Punctuation), Push("globals")}, + // Top level import + {`(import)(\s+)([^\s]+)`, ByGroups(Keyword, Text, NameVariableMagic), nil}, + // Snippets + {`(&?\([^\s#]+\))(\s*)(\{)`, ByGroups(NameVariableAnonymous, Text, Punctuation), Push("snippet")}, + // Site label + {`[^#{(\s,]+`, GenericHeading, Push("label")}, + // Site label with placeholder + {`\{[\w+.\[\]\:\$-]+\}`, StringEscape, Push("label")}, + {`\s+`, Text, nil}, + }, + "globals": { + {`\}`, Punctuation, Pop(1)}, + // Global options are parsed as subdirectives (no matcher) + {`[^\s#]+`, Keyword, Push("subdirective")}, + Include("base"), + }, + "snippet": { + {`\}`, Punctuation, Pop(1)}, + Include("site_body"), + // Any other directive + {`[^\s#]+`, Keyword, Push("directive")}, + Include("base"), + }, + "label": { + // Allow multiple labels, comma separated, newlines after + // a comma means another label is coming + {`,\s*\n?`, Text, nil}, + {` `, Text, nil}, + // Site label with placeholder + Include("placeholder"), + // Site label + {`[^#{(\s,]+`, GenericHeading, nil}, + // Comment after non-block label (hack because comments end in \n) + {`#.*\n`, CommentSingle, Push("site_block")}, + // Note: if \n, we'll never pop out of the site_block, it's valid + {`\{(?=\s)|\n`, Punctuation, Push("site_block")}, + }, + "site_block": { + {`\}`, Punctuation, Pop(2)}, + Include("site_block_common"), + }, + }.Merge(caddyfileCommonRules()) +} + +// Caddyfile directive-only lexer. +var CaddyfileDirectives = Register(MustNewLexer( + &Config{ + Name: "Caddyfile Directives", + Aliases: []string{"caddyfile-directives", "caddyfile-d", "caddy-d"}, + Filenames: []string{}, + MimeTypes: []string{}, + }, + caddyfileDirectivesRules, +)) + +func caddyfileDirectivesRules() Rules { + return Rules{ + // Same as "site_block" in Caddyfile + "root": { + Include("site_block_common"), + }, + }.Merge(caddyfileCommonRules()) +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/cl.go b/vendor/github.com/alecthomas/chroma/v2/lexers/cl.go new file mode 100644 index 000000000..3eb0c2307 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/cl.go @@ -0,0 +1,243 @@ +package lexers + +import ( + . "github.com/alecthomas/chroma/v2" // nolint +) + +var ( + clBuiltinFunctions = []string{ + "<", "<=", "=", ">", ">=", "-", "/", "/=", "*", "+", "1-", "1+", + "abort", "abs", "acons", "acos", "acosh", "add-method", "adjoin", + "adjustable-array-p", "adjust-array", "allocate-instance", + "alpha-char-p", "alphanumericp", "append", "apply", "apropos", + "apropos-list", "aref", "arithmetic-error-operands", + "arithmetic-error-operation", "array-dimension", "array-dimensions", + "array-displacement", "array-element-type", "array-has-fill-pointer-p", + "array-in-bounds-p", "arrayp", "array-rank", "array-row-major-index", + "array-total-size", "ash", "asin", "asinh", "assoc", "assoc-if", + "assoc-if-not", "atan", "atanh", "atom", "bit", "bit-and", "bit-andc1", + "bit-andc2", "bit-eqv", "bit-ior", "bit-nand", "bit-nor", "bit-not", + "bit-orc1", "bit-orc2", "bit-vector-p", "bit-xor", "boole", + "both-case-p", "boundp", "break", "broadcast-stream-streams", + "butlast", "byte", "byte-position", "byte-size", "caaaar", "caaadr", + "caaar", "caadar", "caaddr", "caadr", "caar", "cadaar", "cadadr", + "cadar", "caddar", "cadddr", "caddr", "cadr", "call-next-method", "car", + "cdaaar", "cdaadr", "cdaar", "cdadar", "cdaddr", "cdadr", "cdar", + "cddaar", "cddadr", "cddar", "cdddar", "cddddr", "cdddr", "cddr", "cdr", + "ceiling", "cell-error-name", "cerror", "change-class", "char", "char<", + "char<=", "char=", "char>", "char>=", "char/=", "character", + "characterp", "char-code", "char-downcase", "char-equal", + "char-greaterp", "char-int", "char-lessp", "char-name", + "char-not-equal", "char-not-greaterp", "char-not-lessp", "char-upcase", + "cis", "class-name", "class-of", "clear-input", "clear-output", + "close", "clrhash", "code-char", "coerce", "compile", + "compiled-function-p", "compile-file", "compile-file-pathname", + "compiler-macro-function", "complement", "complex", "complexp", + "compute-applicable-methods", "compute-restarts", "concatenate", + "concatenated-stream-streams", "conjugate", "cons", "consp", + "constantly", "constantp", "continue", "copy-alist", "copy-list", + "copy-pprint-dispatch", "copy-readtable", "copy-seq", "copy-structure", + "copy-symbol", "copy-tree", "cos", "cosh", "count", "count-if", + "count-if-not", "decode-float", "decode-universal-time", "delete", + "delete-duplicates", "delete-file", "delete-if", "delete-if-not", + "delete-package", "denominator", "deposit-field", "describe", + "describe-object", "digit-char", "digit-char-p", "directory", + "directory-namestring", "disassemble", "documentation", "dpb", + "dribble", "echo-stream-input-stream", "echo-stream-output-stream", + "ed", "eighth", "elt", "encode-universal-time", "endp", + "enough-namestring", "ensure-directories-exist", + "ensure-generic-function", "eq", "eql", "equal", "equalp", "error", + "eval", "evenp", "every", "exp", "export", "expt", "fboundp", + "fceiling", "fdefinition", "ffloor", "fifth", "file-author", + "file-error-pathname", "file-length", "file-namestring", + "file-position", "file-string-length", "file-write-date", + "fill", "fill-pointer", "find", "find-all-symbols", "find-class", + "find-if", "find-if-not", "find-method", "find-package", "find-restart", + "find-symbol", "finish-output", "first", "float", "float-digits", + "floatp", "float-precision", "float-radix", "float-sign", "floor", + "fmakunbound", "force-output", "format", "fourth", "fresh-line", + "fround", "ftruncate", "funcall", "function-keywords", + "function-lambda-expression", "functionp", "gcd", "gensym", "gentemp", + "get", "get-decoded-time", "get-dispatch-macro-character", "getf", + "gethash", "get-internal-real-time", "get-internal-run-time", + "get-macro-character", "get-output-stream-string", "get-properties", + "get-setf-expansion", "get-universal-time", "graphic-char-p", + "hash-table-count", "hash-table-p", "hash-table-rehash-size", + "hash-table-rehash-threshold", "hash-table-size", "hash-table-test", + "host-namestring", "identity", "imagpart", "import", + "initialize-instance", "input-stream-p", "inspect", + "integer-decode-float", "integer-length", "integerp", + "interactive-stream-p", "intern", "intersection", + "invalid-method-error", "invoke-debugger", "invoke-restart", + "invoke-restart-interactively", "isqrt", "keywordp", "last", "lcm", + "ldb", "ldb-test", "ldiff", "length", "lisp-implementation-type", + "lisp-implementation-version", "list", "list*", "list-all-packages", + "listen", "list-length", "listp", "load", + "load-logical-pathname-translations", "log", "logand", "logandc1", + "logandc2", "logbitp", "logcount", "logeqv", "logical-pathname", + "logical-pathname-translations", "logior", "lognand", "lognor", + "lognot", "logorc1", "logorc2", "logtest", "logxor", "long-site-name", + "lower-case-p", "machine-instance", "machine-type", "machine-version", + "macroexpand", "macroexpand-1", "macro-function", "make-array", + "make-broadcast-stream", "make-concatenated-stream", "make-condition", + "make-dispatch-macro-character", "make-echo-stream", "make-hash-table", + "make-instance", "make-instances-obsolete", "make-list", + "make-load-form", "make-load-form-saving-slots", "make-package", + "make-pathname", "make-random-state", "make-sequence", "make-string", + "make-string-input-stream", "make-string-output-stream", "make-symbol", + "make-synonym-stream", "make-two-way-stream", "makunbound", "map", + "mapc", "mapcan", "mapcar", "mapcon", "maphash", "map-into", "mapl", + "maplist", "mask-field", "max", "member", "member-if", "member-if-not", + "merge", "merge-pathnames", "method-combination-error", + "method-qualifiers", "min", "minusp", "mismatch", "mod", + "muffle-warning", "name-char", "namestring", "nbutlast", "nconc", + "next-method-p", "nintersection", "ninth", "no-applicable-method", + "no-next-method", "not", "notany", "notevery", "nreconc", "nreverse", + "nset-difference", "nset-exclusive-or", "nstring-capitalize", + "nstring-downcase", "nstring-upcase", "nsublis", "nsubst", "nsubst-if", + "nsubst-if-not", "nsubstitute", "nsubstitute-if", "nsubstitute-if-not", + "nth", "nthcdr", "null", "numberp", "numerator", "nunion", "oddp", + "open", "open-stream-p", "output-stream-p", "package-error-package", + "package-name", "package-nicknames", "packagep", + "package-shadowing-symbols", "package-used-by-list", "package-use-list", + "pairlis", "parse-integer", "parse-namestring", "pathname", + "pathname-device", "pathname-directory", "pathname-host", + "pathname-match-p", "pathname-name", "pathnamep", "pathname-type", + "pathname-version", "peek-char", "phase", "plusp", "position", + "position-if", "position-if-not", "pprint", "pprint-dispatch", + "pprint-fill", "pprint-indent", "pprint-linear", "pprint-newline", + "pprint-tab", "pprint-tabular", "prin1", "prin1-to-string", "princ", + "princ-to-string", "print", "print-object", "probe-file", "proclaim", + "provide", "random", "random-state-p", "rassoc", "rassoc-if", + "rassoc-if-not", "rational", "rationalize", "rationalp", "read", + "read-byte", "read-char", "read-char-no-hang", "read-delimited-list", + "read-from-string", "read-line", "read-preserving-whitespace", + "read-sequence", "readtable-case", "readtablep", "realp", "realpart", + "reduce", "reinitialize-instance", "rem", "remhash", "remove", + "remove-duplicates", "remove-if", "remove-if-not", "remove-method", + "remprop", "rename-file", "rename-package", "replace", "require", + "rest", "restart-name", "revappend", "reverse", "room", "round", + "row-major-aref", "rplaca", "rplacd", "sbit", "scale-float", "schar", + "search", "second", "set", "set-difference", + "set-dispatch-macro-character", "set-exclusive-or", + "set-macro-character", "set-pprint-dispatch", "set-syntax-from-char", + "seventh", "shadow", "shadowing-import", "shared-initialize", + "short-site-name", "signal", "signum", "simple-bit-vector-p", + "simple-condition-format-arguments", "simple-condition-format-control", + "simple-string-p", "simple-vector-p", "sin", "sinh", "sixth", "sleep", + "slot-boundp", "slot-exists-p", "slot-makunbound", "slot-missing", + "slot-unbound", "slot-value", "software-type", "software-version", + "some", "sort", "special-operator-p", "sqrt", "stable-sort", + "standard-char-p", "store-value", "stream-element-type", + "stream-error-stream", "stream-external-format", "streamp", "string", + "string<", "string<=", "string=", "string>", "string>=", "string/=", + "string-capitalize", "string-downcase", "string-equal", + "string-greaterp", "string-left-trim", "string-lessp", + "string-not-equal", "string-not-greaterp", "string-not-lessp", + "stringp", "string-right-trim", "string-trim", "string-upcase", + "sublis", "subseq", "subsetp", "subst", "subst-if", "subst-if-not", + "substitute", "substitute-if", "substitute-if-not", "subtypep", "svref", + "sxhash", "symbol-function", "symbol-name", "symbolp", "symbol-package", + "symbol-plist", "symbol-value", "synonym-stream-symbol", "syntax:", + "tailp", "tan", "tanh", "tenth", "terpri", "third", + "translate-logical-pathname", "translate-pathname", "tree-equal", + "truename", "truncate", "two-way-stream-input-stream", + "two-way-stream-output-stream", "type-error-datum", + "type-error-expected-type", "type-of", "typep", "unbound-slot-instance", + "unexport", "unintern", "union", "unread-char", "unuse-package", + "update-instance-for-different-class", + "update-instance-for-redefined-class", "upgraded-array-element-type", + "upgraded-complex-part-type", "upper-case-p", "use-package", + "user-homedir-pathname", "use-value", "values", "values-list", "vector", + "vectorp", "vector-pop", "vector-push", "vector-push-extend", "warn", + "wild-pathname-p", "write", "write-byte", "write-char", "write-line", + "write-sequence", "write-string", "write-to-string", "yes-or-no-p", + "y-or-n-p", "zerop", + } + + clSpecialForms = []string{ + "block", "catch", "declare", "eval-when", "flet", "function", "go", "if", + "labels", "lambda", "let", "let*", "load-time-value", "locally", "macrolet", + "multiple-value-call", "multiple-value-prog1", "progn", "progv", "quote", + "return-from", "setq", "symbol-macrolet", "tagbody", "the", "throw", + "unwind-protect", + } + + clMacros = []string{ + "and", "assert", "call-method", "case", "ccase", "check-type", "cond", + "ctypecase", "decf", "declaim", "defclass", "defconstant", "defgeneric", + "define-compiler-macro", "define-condition", "define-method-combination", + "define-modify-macro", "define-setf-expander", "define-symbol-macro", + "defmacro", "defmethod", "defpackage", "defparameter", "defsetf", + "defstruct", "deftype", "defun", "defvar", "destructuring-bind", "do", + "do*", "do-all-symbols", "do-external-symbols", "dolist", "do-symbols", + "dotimes", "ecase", "etypecase", "formatter", "handler-bind", + "handler-case", "ignore-errors", "incf", "in-package", "lambda", "loop", + "loop-finish", "make-method", "multiple-value-bind", "multiple-value-list", + "multiple-value-setq", "nth-value", "or", "pop", + "pprint-exit-if-list-exhausted", "pprint-logical-block", "pprint-pop", + "print-unreadable-object", "prog", "prog*", "prog1", "prog2", "psetf", + "psetq", "push", "pushnew", "remf", "restart-bind", "restart-case", + "return", "rotatef", "setf", "shiftf", "step", "time", "trace", "typecase", + "unless", "untrace", "when", "with-accessors", "with-compilation-unit", + "with-condition-restarts", "with-hash-table-iterator", + "with-input-from-string", "with-open-file", "with-open-stream", + "with-output-to-string", "with-package-iterator", "with-simple-restart", + "with-slots", "with-standard-io-syntax", + } + + clLambdaListKeywords = []string{ + "&allow-other-keys", "&aux", "&body", "&environment", "&key", "&optional", + "&rest", "&whole", + } + + clDeclarations = []string{ + "dynamic-extent", "ignore", "optimize", "ftype", "inline", "special", + "ignorable", "notinline", "type", + } + + clBuiltinTypes = []string{ + "atom", "boolean", "base-char", "base-string", "bignum", "bit", + "compiled-function", "extended-char", "fixnum", "keyword", "nil", + "signed-byte", "short-float", "single-float", "double-float", "long-float", + "simple-array", "simple-base-string", "simple-bit-vector", "simple-string", + "simple-vector", "standard-char", "unsigned-byte", + + // Condition Types + "arithmetic-error", "cell-error", "condition", "control-error", + "division-by-zero", "end-of-file", "error", "file-error", + "floating-point-inexact", "floating-point-overflow", + "floating-point-underflow", "floating-point-invalid-operation", + "parse-error", "package-error", "print-not-readable", "program-error", + "reader-error", "serious-condition", "simple-condition", "simple-error", + "simple-type-error", "simple-warning", "stream-error", "storage-condition", + "style-warning", "type-error", "unbound-variable", "unbound-slot", + "undefined-function", "warning", + } + + clBuiltinClasses = []string{ + "array", "broadcast-stream", "bit-vector", "built-in-class", "character", + "class", "complex", "concatenated-stream", "cons", "echo-stream", + "file-stream", "float", "function", "generic-function", "hash-table", + "integer", "list", "logical-pathname", "method-combination", "method", + "null", "number", "package", "pathname", "ratio", "rational", "readtable", + "real", "random-state", "restart", "sequence", "standard-class", + "standard-generic-function", "standard-method", "standard-object", + "string-stream", "stream", "string", "structure-class", "structure-object", + "symbol", "synonym-stream", "t", "two-way-stream", "vector", + } +) + +// Common Lisp lexer. +var CommonLisp = Register(TypeRemappingLexer(MustNewXMLLexer( + embedded, + "embedded/common_lisp.xml", +), TypeMapping{ + {NameVariable, NameFunction, clBuiltinFunctions}, + {NameVariable, Keyword, clSpecialForms}, + {NameVariable, NameBuiltin, clMacros}, + {NameVariable, Keyword, clLambdaListKeywords}, + {NameVariable, Keyword, clDeclarations}, + {NameVariable, KeywordType, clBuiltinTypes}, + {NameVariable, NameClass, clBuiltinClasses}, +})) diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/dns.go b/vendor/github.com/alecthomas/chroma/v2/lexers/dns.go new file mode 100644 index 000000000..7e699622a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/dns.go @@ -0,0 +1,17 @@ +package lexers + +import ( + "regexp" +) + +// TODO(moorereason): can this be factored away? +var zoneAnalyserRe = regexp.MustCompile(`(?m)^@\s+IN\s+SOA\s+`) + +func init() { // nolint: gochecknoinits + Get("dns").SetAnalyser(func(text string) float32 { + if zoneAnalyserRe.FindString(text) != "" { + return 1.0 + } + return 0.0 + }) +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/emacs.go b/vendor/github.com/alecthomas/chroma/v2/lexers/emacs.go new file mode 100644 index 000000000..869b0f3f4 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/emacs.go @@ -0,0 +1,533 @@ +package lexers + +import ( + . "github.com/alecthomas/chroma/v2" // nolint +) + +var ( + emacsMacros = []string{ + "atomic-change-group", "case", "block", "cl-block", "cl-callf", "cl-callf2", + "cl-case", "cl-decf", "cl-declaim", "cl-declare", + "cl-define-compiler-macro", "cl-defmacro", "cl-defstruct", + "cl-defsubst", "cl-deftype", "cl-defun", "cl-destructuring-bind", + "cl-do", "cl-do*", "cl-do-all-symbols", "cl-do-symbols", "cl-dolist", + "cl-dotimes", "cl-ecase", "cl-etypecase", "eval-when", "cl-eval-when", "cl-flet", + "cl-flet*", "cl-function", "cl-incf", "cl-labels", "cl-letf", + "cl-letf*", "cl-load-time-value", "cl-locally", "cl-loop", + "cl-macrolet", "cl-multiple-value-bind", "cl-multiple-value-setq", + "cl-progv", "cl-psetf", "cl-psetq", "cl-pushnew", "cl-remf", + "cl-return", "cl-return-from", "cl-rotatef", "cl-shiftf", + "cl-symbol-macrolet", "cl-tagbody", "cl-the", "cl-typecase", + "combine-after-change-calls", "condition-case-unless-debug", "decf", + "declaim", "declare", "declare-function", "def-edebug-spec", + "defadvice", "defclass", "defcustom", "defface", "defgeneric", + "defgroup", "define-advice", "define-alternatives", + "define-compiler-macro", "define-derived-mode", "define-generic-mode", + "define-global-minor-mode", "define-globalized-minor-mode", + "define-minor-mode", "define-modify-macro", + "define-obsolete-face-alias", "define-obsolete-function-alias", + "define-obsolete-variable-alias", "define-setf-expander", + "define-skeleton", "defmacro", "defmethod", "defsetf", "defstruct", + "defsubst", "deftheme", "deftype", "defun", "defvar-local", + "delay-mode-hooks", "destructuring-bind", "do", "do*", + "do-all-symbols", "do-symbols", "dolist", "dont-compile", "dotimes", + "dotimes-with-progress-reporter", "ecase", "ert-deftest", "etypecase", + "eval-and-compile", "eval-when-compile", "flet", "ignore-errors", + "incf", "labels", "lambda", "letrec", "lexical-let", "lexical-let*", + "loop", "multiple-value-bind", "multiple-value-setq", "noreturn", + "oref", "oref-default", "oset", "oset-default", "pcase", + "pcase-defmacro", "pcase-dolist", "pcase-exhaustive", "pcase-let", + "pcase-let*", "pop", "psetf", "psetq", "push", "pushnew", "remf", + "return", "rotatef", "rx", "save-match-data", "save-selected-window", + "save-window-excursion", "setf", "setq-local", "shiftf", + "track-mouse", "typecase", "unless", "use-package", "when", + "while-no-input", "with-case-table", "with-category-table", + "with-coding-priority", "with-current-buffer", "with-demoted-errors", + "with-eval-after-load", "with-file-modes", "with-local-quit", + "with-output-to-string", "with-output-to-temp-buffer", + "with-parsed-tramp-file-name", "with-selected-frame", + "with-selected-window", "with-silent-modifications", "with-slots", + "with-syntax-table", "with-temp-buffer", "with-temp-file", + "with-temp-message", "with-timeout", "with-tramp-connection-property", + "with-tramp-file-property", "with-tramp-progress-reporter", + "with-wrapper-hook", "load-time-value", "locally", "macrolet", "progv", + "return-from", + } + + emacsSpecialForms = []string{ + "and", "catch", "cond", "condition-case", "defconst", "defvar", + "function", "if", "interactive", "let", "let*", "or", "prog1", + "prog2", "progn", "quote", "save-current-buffer", "save-excursion", + "save-restriction", "setq", "setq-default", "subr-arity", + "unwind-protect", "while", + } + + emacsBuiltinFunction = []string{ + "%", "*", "+", "-", "/", "/=", "1+", "1-", "<", "<=", "=", ">", ">=", + "Snarf-documentation", "abort-recursive-edit", "abs", + "accept-process-output", "access-file", "accessible-keymaps", "acos", + "active-minibuffer-window", "add-face-text-property", + "add-name-to-file", "add-text-properties", "all-completions", + "append", "apply", "apropos-internal", "aref", "arrayp", "aset", + "ash", "asin", "assoc", "assoc-string", "assq", "atan", "atom", + "autoload", "autoload-do-load", "backtrace", "backtrace--locals", + "backtrace-debug", "backtrace-eval", "backtrace-frame", + "backward-char", "backward-prefix-chars", "barf-if-buffer-read-only", + "base64-decode-region", "base64-decode-string", + "base64-encode-region", "base64-encode-string", "beginning-of-line", + "bidi-find-overridden-directionality", "bidi-resolved-levels", + "bitmap-spec-p", "bobp", "bolp", "bool-vector", + "bool-vector-count-consecutive", "bool-vector-count-population", + "bool-vector-exclusive-or", "bool-vector-intersection", + "bool-vector-not", "bool-vector-p", "bool-vector-set-difference", + "bool-vector-subsetp", "bool-vector-union", "boundp", + "buffer-base-buffer", "buffer-chars-modified-tick", + "buffer-enable-undo", "buffer-file-name", "buffer-has-markers-at", + "buffer-list", "buffer-live-p", "buffer-local-value", + "buffer-local-variables", "buffer-modified-p", "buffer-modified-tick", + "buffer-name", "buffer-size", "buffer-string", "buffer-substring", + "buffer-substring-no-properties", "buffer-swap-text", "bufferp", + "bury-buffer-internal", "byte-code", "byte-code-function-p", + "byte-to-position", "byte-to-string", "byteorder", + "call-interactively", "call-last-kbd-macro", "call-process", + "call-process-region", "cancel-kbd-macro-events", "capitalize", + "capitalize-region", "capitalize-word", "car", "car-less-than-car", + "car-safe", "case-table-p", "category-docstring", + "category-set-mnemonics", "category-table", "category-table-p", + "ccl-execute", "ccl-execute-on-string", "ccl-program-p", "cdr", + "cdr-safe", "ceiling", "char-after", "char-before", + "char-category-set", "char-charset", "char-equal", "char-or-string-p", + "char-resolve-modifiers", "char-syntax", "char-table-extra-slot", + "char-table-p", "char-table-parent", "char-table-range", + "char-table-subtype", "char-to-string", "char-width", "characterp", + "charset-after", "charset-id-internal", "charset-plist", + "charset-priority-list", "charsetp", "check-coding-system", + "check-coding-systems-region", "clear-buffer-auto-save-failure", + "clear-charset-maps", "clear-face-cache", "clear-font-cache", + "clear-image-cache", "clear-string", "clear-this-command-keys", + "close-font", "clrhash", "coding-system-aliases", + "coding-system-base", "coding-system-eol-type", "coding-system-p", + "coding-system-plist", "coding-system-priority-list", + "coding-system-put", "color-distance", "color-gray-p", + "color-supported-p", "combine-after-change-execute", + "command-error-default-function", "command-remapping", "commandp", + "compare-buffer-substrings", "compare-strings", + "compare-window-configurations", "completing-read", + "compose-region-internal", "compose-string-internal", + "composition-get-gstring", "compute-motion", "concat", "cons", + "consp", "constrain-to-field", "continue-process", + "controlling-tty-p", "coordinates-in-window-p", "copy-alist", + "copy-category-table", "copy-file", "copy-hash-table", "copy-keymap", + "copy-marker", "copy-sequence", "copy-syntax-table", "copysign", + "cos", "current-active-maps", "current-bidi-paragraph-direction", + "current-buffer", "current-case-table", "current-column", + "current-global-map", "current-idle-time", "current-indentation", + "current-input-mode", "current-local-map", "current-message", + "current-minor-mode-maps", "current-time", "current-time-string", + "current-time-zone", "current-window-configuration", + "cygwin-convert-file-name-from-windows", + "cygwin-convert-file-name-to-windows", "daemon-initialized", + "daemonp", "dbus--init-bus", "dbus-get-unique-name", + "dbus-message-internal", "debug-timer-check", "declare-equiv-charset", + "decode-big5-char", "decode-char", "decode-coding-region", + "decode-coding-string", "decode-sjis-char", "decode-time", + "default-boundp", "default-file-modes", "default-printer-name", + "default-toplevel-value", "default-value", "define-category", + "define-charset-alias", "define-charset-internal", + "define-coding-system-alias", "define-coding-system-internal", + "define-fringe-bitmap", "define-hash-table-test", "define-key", + "define-prefix-command", "delete", + "delete-all-overlays", "delete-and-extract-region", "delete-char", + "delete-directory-internal", "delete-field", "delete-file", + "delete-frame", "delete-other-windows-internal", "delete-overlay", + "delete-process", "delete-region", "delete-terminal", + "delete-window-internal", "delq", "describe-buffer-bindings", + "describe-vector", "destroy-fringe-bitmap", "detect-coding-region", + "detect-coding-string", "ding", "directory-file-name", + "directory-files", "directory-files-and-attributes", "discard-input", + "display-supports-face-attributes-p", "do-auto-save", "documentation", + "documentation-property", "downcase", "downcase-region", + "downcase-word", "draw-string", "dump-colors", "dump-emacs", + "dump-face", "dump-frame-glyph-matrix", "dump-glyph-matrix", + "dump-glyph-row", "dump-redisplay-history", "dump-tool-bar-row", + "elt", "emacs-pid", "encode-big5-char", "encode-char", + "encode-coding-region", "encode-coding-string", "encode-sjis-char", + "encode-time", "end-kbd-macro", "end-of-line", "eobp", "eolp", "eq", + "eql", "equal", "equal-including-properties", "erase-buffer", + "error-message-string", "eval", "eval-buffer", "eval-region", + "event-convert-list", "execute-kbd-macro", "exit-recursive-edit", + "exp", "expand-file-name", "expt", "external-debugging-output", + "face-attribute-relative-p", "face-attributes-as-vector", "face-font", + "fboundp", "fceiling", "fetch-bytecode", "ffloor", + "field-beginning", "field-end", "field-string", + "field-string-no-properties", "file-accessible-directory-p", + "file-acl", "file-attributes", "file-attributes-lessp", + "file-directory-p", "file-executable-p", "file-exists-p", + "file-locked-p", "file-modes", "file-name-absolute-p", + "file-name-all-completions", "file-name-as-directory", + "file-name-completion", "file-name-directory", + "file-name-nondirectory", "file-newer-than-file-p", "file-readable-p", + "file-regular-p", "file-selinux-context", "file-symlink-p", + "file-system-info", "file-system-info", "file-writable-p", + "fillarray", "find-charset-region", "find-charset-string", + "find-coding-systems-region-internal", "find-composition-internal", + "find-file-name-handler", "find-font", "find-operation-coding-system", + "float", "float-time", "floatp", "floor", "fmakunbound", + "following-char", "font-at", "font-drive-otf", "font-face-attributes", + "font-family-list", "font-get", "font-get-glyphs", + "font-get-system-font", "font-get-system-normal-font", "font-info", + "font-match-p", "font-otf-alternates", "font-put", + "font-shape-gstring", "font-spec", "font-variation-glyphs", + "font-xlfd-name", "fontp", "fontset-font", "fontset-info", + "fontset-list", "fontset-list-all", "force-mode-line-update", + "force-window-update", "format", "format-mode-line", + "format-network-address", "format-time-string", "forward-char", + "forward-comment", "forward-line", "forward-word", + "frame-border-width", "frame-bottom-divider-width", + "frame-can-run-window-configuration-change-hook", "frame-char-height", + "frame-char-width", "frame-face-alist", "frame-first-window", + "frame-focus", "frame-font-cache", "frame-fringe-width", "frame-list", + "frame-live-p", "frame-or-buffer-changed-p", "frame-parameter", + "frame-parameters", "frame-pixel-height", "frame-pixel-width", + "frame-pointer-visible-p", "frame-right-divider-width", + "frame-root-window", "frame-scroll-bar-height", + "frame-scroll-bar-width", "frame-selected-window", "frame-terminal", + "frame-text-cols", "frame-text-height", "frame-text-lines", + "frame-text-width", "frame-total-cols", "frame-total-lines", + "frame-visible-p", "framep", "frexp", "fringe-bitmaps-at-pos", + "fround", "fset", "ftruncate", "funcall", "funcall-interactively", + "function-equal", "functionp", "gap-position", "gap-size", + "garbage-collect", "gc-status", "generate-new-buffer-name", "get", + "get-buffer", "get-buffer-create", "get-buffer-process", + "get-buffer-window", "get-byte", "get-char-property", + "get-char-property-and-overlay", "get-file-buffer", "get-file-char", + "get-internal-run-time", "get-load-suffixes", "get-pos-property", + "get-process", "get-screen-color", "get-text-property", + "get-unicode-property-internal", "get-unused-category", + "get-unused-iso-final-char", "getenv-internal", "gethash", + "gfile-add-watch", "gfile-rm-watch", "global-key-binding", + "gnutls-available-p", "gnutls-boot", "gnutls-bye", "gnutls-deinit", + "gnutls-error-fatalp", "gnutls-error-string", "gnutls-errorp", + "gnutls-get-initstage", "gnutls-peer-status", + "gnutls-peer-status-warning-describe", "goto-char", "gpm-mouse-start", + "gpm-mouse-stop", "group-gid", "group-real-gid", + "handle-save-session", "handle-switch-frame", "hash-table-count", + "hash-table-p", "hash-table-rehash-size", + "hash-table-rehash-threshold", "hash-table-size", "hash-table-test", + "hash-table-weakness", "iconify-frame", "identity", "image-flush", + "image-mask-p", "image-metadata", "image-size", "imagemagick-types", + "imagep", "indent-to", "indirect-function", "indirect-variable", + "init-image-library", "inotify-add-watch", "inotify-rm-watch", + "input-pending-p", "insert", "insert-and-inherit", + "insert-before-markers", "insert-before-markers-and-inherit", + "insert-buffer-substring", "insert-byte", "insert-char", + "insert-file-contents", "insert-startup-screen", "int86", + "integer-or-marker-p", "integerp", "interactive-form", "intern", + "intern-soft", "internal--track-mouse", "internal-char-font", + "internal-complete-buffer", "internal-copy-lisp-face", + "internal-default-process-filter", + "internal-default-process-sentinel", "internal-describe-syntax-value", + "internal-event-symbol-parse-modifiers", + "internal-face-x-get-resource", "internal-get-lisp-face-attribute", + "internal-lisp-face-attribute-values", "internal-lisp-face-empty-p", + "internal-lisp-face-equal-p", "internal-lisp-face-p", + "internal-make-lisp-face", "internal-make-var-non-special", + "internal-merge-in-global-face", + "internal-set-alternative-font-family-alist", + "internal-set-alternative-font-registry-alist", + "internal-set-font-selection-order", + "internal-set-lisp-face-attribute", + "internal-set-lisp-face-attribute-from-resource", + "internal-show-cursor", "internal-show-cursor-p", "interrupt-process", + "invisible-p", "invocation-directory", "invocation-name", "isnan", + "iso-charset", "key-binding", "key-description", + "keyboard-coding-system", "keymap-parent", "keymap-prompt", "keymapp", + "keywordp", "kill-all-local-variables", "kill-buffer", "kill-emacs", + "kill-local-variable", "kill-process", "last-nonminibuffer-frame", + "lax-plist-get", "lax-plist-put", "ldexp", "length", + "libxml-parse-html-region", "libxml-parse-xml-region", + "line-beginning-position", "line-end-position", "line-pixel-height", + "list", "list-fonts", "list-system-processes", "listp", "load", + "load-average", "local-key-binding", "local-variable-if-set-p", + "local-variable-p", "locale-info", "locate-file-internal", + "lock-buffer", "log", "logand", "logb", "logior", "lognot", "logxor", + "looking-at", "lookup-image", "lookup-image-map", "lookup-key", + "lower-frame", "lsh", "macroexpand", "make-bool-vector", + "make-byte-code", "make-category-set", "make-category-table", + "make-char", "make-char-table", "make-directory-internal", + "make-frame-invisible", "make-frame-visible", "make-hash-table", + "make-indirect-buffer", "make-keymap", "make-list", + "make-local-variable", "make-marker", "make-network-process", + "make-overlay", "make-serial-process", "make-sparse-keymap", + "make-string", "make-symbol", "make-symbolic-link", "make-temp-name", + "make-terminal-frame", "make-variable-buffer-local", + "make-variable-frame-local", "make-vector", "makunbound", + "map-char-table", "map-charset-chars", "map-keymap", + "map-keymap-internal", "mapatoms", "mapc", "mapcar", "mapconcat", + "maphash", "mark-marker", "marker-buffer", "marker-insertion-type", + "marker-position", "markerp", "match-beginning", "match-data", + "match-end", "matching-paren", "max", "max-char", "md5", "member", + "memory-info", "memory-limit", "memory-use-counts", "memq", "memql", + "menu-bar-menu-at-x-y", "menu-or-popup-active-p", + "menu-or-popup-active-p", "merge-face-attribute", "message", + "message-box", "message-or-box", "min", + "minibuffer-completion-contents", "minibuffer-contents", + "minibuffer-contents-no-properties", "minibuffer-depth", + "minibuffer-prompt", "minibuffer-prompt-end", + "minibuffer-selected-window", "minibuffer-window", "minibufferp", + "minor-mode-key-binding", "mod", "modify-category-entry", + "modify-frame-parameters", "modify-syntax-entry", + "mouse-pixel-position", "mouse-position", "move-overlay", + "move-point-visually", "move-to-column", "move-to-window-line", + "msdos-downcase-filename", "msdos-long-file-names", "msdos-memget", + "msdos-memput", "msdos-mouse-disable", "msdos-mouse-enable", + "msdos-mouse-init", "msdos-mouse-p", "msdos-remember-default-colors", + "msdos-set-keyboard", "msdos-set-mouse-buttons", + "multibyte-char-to-unibyte", "multibyte-string-p", "narrow-to-region", + "natnump", "nconc", "network-interface-info", + "network-interface-list", "new-fontset", "newline-cache-check", + "next-char-property-change", "next-frame", "next-overlay-change", + "next-property-change", "next-read-file-uses-dialog-p", + "next-single-char-property-change", "next-single-property-change", + "next-window", "nlistp", "nreverse", "nth", "nthcdr", "null", + "number-or-marker-p", "number-to-string", "numberp", + "open-dribble-file", "open-font", "open-termscript", + "optimize-char-table", "other-buffer", "other-window-for-scrolling", + "overlay-buffer", "overlay-end", "overlay-get", "overlay-lists", + "overlay-properties", "overlay-put", "overlay-recenter", + "overlay-start", "overlayp", "overlays-at", "overlays-in", + "parse-partial-sexp", "play-sound-internal", "plist-get", + "plist-member", "plist-put", "point", "point-marker", "point-max", + "point-max-marker", "point-min", "point-min-marker", + "pos-visible-in-window-p", "position-bytes", "posix-looking-at", + "posix-search-backward", "posix-search-forward", "posix-string-match", + "posn-at-point", "posn-at-x-y", "preceding-char", + "prefix-numeric-value", "previous-char-property-change", + "previous-frame", "previous-overlay-change", + "previous-property-change", "previous-single-char-property-change", + "previous-single-property-change", "previous-window", "prin1", + "prin1-to-string", "princ", "print", "process-attributes", + "process-buffer", "process-coding-system", "process-command", + "process-connection", "process-contact", "process-datagram-address", + "process-exit-status", "process-filter", "process-filter-multibyte-p", + "process-id", "process-inherit-coding-system-flag", "process-list", + "process-mark", "process-name", "process-plist", + "process-query-on-exit-flag", "process-running-child-p", + "process-send-eof", "process-send-region", "process-send-string", + "process-sentinel", "process-status", "process-tty-name", + "process-type", "processp", "profiler-cpu-log", + "profiler-cpu-running-p", "profiler-cpu-start", "profiler-cpu-stop", + "profiler-memory-log", "profiler-memory-running-p", + "profiler-memory-start", "profiler-memory-stop", "propertize", + "purecopy", "put", "put-text-property", + "put-unicode-property-internal", "puthash", "query-font", + "query-fontset", "quit-process", "raise-frame", "random", "rassoc", + "rassq", "re-search-backward", "re-search-forward", "read", + "read-buffer", "read-char", "read-char-exclusive", + "read-coding-system", "read-command", "read-event", + "read-from-minibuffer", "read-from-string", "read-function", + "read-key-sequence", "read-key-sequence-vector", + "read-no-blanks-input", "read-non-nil-coding-system", "read-string", + "read-variable", "recent-auto-save-p", "recent-doskeys", + "recent-keys", "recenter", "recursion-depth", "recursive-edit", + "redirect-debugging-output", "redirect-frame-focus", "redisplay", + "redraw-display", "redraw-frame", "regexp-quote", "region-beginning", + "region-end", "register-ccl-program", "register-code-conversion-map", + "remhash", "remove-list-of-text-properties", "remove-text-properties", + "rename-buffer", "rename-file", "replace-match", + "reset-this-command-lengths", "resize-mini-window-internal", + "restore-buffer-modified-p", "resume-tty", "reverse", "round", + "run-hook-with-args", "run-hook-with-args-until-failure", + "run-hook-with-args-until-success", "run-hook-wrapped", "run-hooks", + "run-window-configuration-change-hook", "run-window-scroll-functions", + "safe-length", "scan-lists", "scan-sexps", "scroll-down", + "scroll-left", "scroll-other-window", "scroll-right", "scroll-up", + "search-backward", "search-forward", "secure-hash", "select-frame", + "select-window", "selected-frame", "selected-window", + "self-insert-command", "send-string-to-terminal", "sequencep", + "serial-process-configure", "set", "set-buffer", + "set-buffer-auto-saved", "set-buffer-major-mode", + "set-buffer-modified-p", "set-buffer-multibyte", "set-case-table", + "set-category-table", "set-char-table-extra-slot", + "set-char-table-parent", "set-char-table-range", "set-charset-plist", + "set-charset-priority", "set-coding-system-priority", + "set-cursor-size", "set-default", "set-default-file-modes", + "set-default-toplevel-value", "set-file-acl", "set-file-modes", + "set-file-selinux-context", "set-file-times", "set-fontset-font", + "set-frame-height", "set-frame-position", "set-frame-selected-window", + "set-frame-size", "set-frame-width", "set-fringe-bitmap-face", + "set-input-interrupt-mode", "set-input-meta-mode", "set-input-mode", + "set-keyboard-coding-system-internal", "set-keymap-parent", + "set-marker", "set-marker-insertion-type", "set-match-data", + "set-message-beep", "set-minibuffer-window", + "set-mouse-pixel-position", "set-mouse-position", + "set-network-process-option", "set-output-flow-control", + "set-process-buffer", "set-process-coding-system", + "set-process-datagram-address", "set-process-filter", + "set-process-filter-multibyte", + "set-process-inherit-coding-system-flag", "set-process-plist", + "set-process-query-on-exit-flag", "set-process-sentinel", + "set-process-window-size", "set-quit-char", + "set-safe-terminal-coding-system-internal", "set-screen-color", + "set-standard-case-table", "set-syntax-table", + "set-terminal-coding-system-internal", "set-terminal-local-value", + "set-terminal-parameter", "set-text-properties", "set-time-zone-rule", + "set-visited-file-modtime", "set-window-buffer", + "set-window-combination-limit", "set-window-configuration", + "set-window-dedicated-p", "set-window-display-table", + "set-window-fringes", "set-window-hscroll", "set-window-margins", + "set-window-new-normal", "set-window-new-pixel", + "set-window-new-total", "set-window-next-buffers", + "set-window-parameter", "set-window-point", "set-window-prev-buffers", + "set-window-redisplay-end-trigger", "set-window-scroll-bars", + "set-window-start", "set-window-vscroll", "setcar", "setcdr", + "setplist", "show-face-resources", "signal", "signal-process", "sin", + "single-key-description", "skip-chars-backward", "skip-chars-forward", + "skip-syntax-backward", "skip-syntax-forward", "sleep-for", "sort", + "sort-charsets", "special-variable-p", "split-char", + "split-window-internal", "sqrt", "standard-case-table", + "standard-category-table", "standard-syntax-table", "start-kbd-macro", + "start-process", "stop-process", "store-kbd-macro-event", "string", + "string-as-multibyte", "string-as-unibyte", "string-bytes", + "string-collate-equalp", "string-collate-lessp", "string-equal", + "string-lessp", "string-make-multibyte", "string-make-unibyte", + "string-match", "string-to-char", "string-to-multibyte", + "string-to-number", "string-to-syntax", "string-to-unibyte", + "string-width", "stringp", "subr-name", "subrp", + "subst-char-in-region", "substitute-command-keys", + "substitute-in-file-name", "substring", "substring-no-properties", + "suspend-emacs", "suspend-tty", "suspicious-object", "sxhash", + "symbol-function", "symbol-name", "symbol-plist", "symbol-value", + "symbolp", "syntax-table", "syntax-table-p", "system-groups", + "system-move-file-to-trash", "system-name", "system-users", "tan", + "terminal-coding-system", "terminal-list", "terminal-live-p", + "terminal-local-value", "terminal-name", "terminal-parameter", + "terminal-parameters", "terpri", "test-completion", + "text-char-description", "text-properties-at", "text-property-any", + "text-property-not-all", "this-command-keys", + "this-command-keys-vector", "this-single-command-keys", + "this-single-command-raw-keys", "time-add", "time-less-p", + "time-subtract", "tool-bar-get-system-style", "tool-bar-height", + "tool-bar-pixel-width", "top-level", "trace-redisplay", + "trace-to-stderr", "translate-region-internal", "transpose-regions", + "truncate", "try-completion", "tty-display-color-cells", + "tty-display-color-p", "tty-no-underline", + "tty-suppress-bold-inverse-default-colors", "tty-top-frame", + "tty-type", "type-of", "undo-boundary", "unencodable-char-position", + "unhandled-file-name-directory", "unibyte-char-to-multibyte", + "unibyte-string", "unicode-property-table-internal", "unify-charset", + "unintern", "unix-sync", "unlock-buffer", "upcase", "upcase-initials", + "upcase-initials-region", "upcase-region", "upcase-word", + "use-global-map", "use-local-map", "user-full-name", + "user-login-name", "user-real-login-name", "user-real-uid", + "user-uid", "variable-binding-locus", "vconcat", "vector", + "vector-or-char-table-p", "vectorp", "verify-visited-file-modtime", + "vertical-motion", "visible-frame-list", "visited-file-modtime", + "w16-get-clipboard-data", "w16-selection-exists-p", + "w16-set-clipboard-data", "w32-battery-status", + "w32-default-color-map", "w32-define-rgb-color", + "w32-display-monitor-attributes-list", "w32-frame-menu-bar-size", + "w32-frame-rect", "w32-get-clipboard-data", + "w32-get-codepage-charset", "w32-get-console-codepage", + "w32-get-console-output-codepage", "w32-get-current-locale-id", + "w32-get-default-locale-id", "w32-get-keyboard-layout", + "w32-get-locale-info", "w32-get-valid-codepages", + "w32-get-valid-keyboard-layouts", "w32-get-valid-locale-ids", + "w32-has-winsock", "w32-long-file-name", "w32-reconstruct-hot-key", + "w32-register-hot-key", "w32-registered-hot-keys", + "w32-selection-exists-p", "w32-send-sys-command", + "w32-set-clipboard-data", "w32-set-console-codepage", + "w32-set-console-output-codepage", "w32-set-current-locale", + "w32-set-keyboard-layout", "w32-set-process-priority", + "w32-shell-execute", "w32-short-file-name", "w32-toggle-lock-key", + "w32-unload-winsock", "w32-unregister-hot-key", "w32-window-exists-p", + "w32notify-add-watch", "w32notify-rm-watch", + "waiting-for-user-input-p", "where-is-internal", "widen", + "widget-apply", "widget-get", "widget-put", + "window-absolute-pixel-edges", "window-at", "window-body-height", + "window-body-width", "window-bottom-divider-width", "window-buffer", + "window-combination-limit", "window-configuration-frame", + "window-configuration-p", "window-dedicated-p", + "window-display-table", "window-edges", "window-end", "window-frame", + "window-fringes", "window-header-line-height", "window-hscroll", + "window-inside-absolute-pixel-edges", "window-inside-edges", + "window-inside-pixel-edges", "window-left-child", + "window-left-column", "window-line-height", "window-list", + "window-list-1", "window-live-p", "window-margins", + "window-minibuffer-p", "window-mode-line-height", "window-new-normal", + "window-new-pixel", "window-new-total", "window-next-buffers", + "window-next-sibling", "window-normal-size", "window-old-point", + "window-parameter", "window-parameters", "window-parent", + "window-pixel-edges", "window-pixel-height", "window-pixel-left", + "window-pixel-top", "window-pixel-width", "window-point", + "window-prev-buffers", "window-prev-sibling", + "window-redisplay-end-trigger", "window-resize-apply", + "window-resize-apply-total", "window-right-divider-width", + "window-scroll-bar-height", "window-scroll-bar-width", + "window-scroll-bars", "window-start", "window-system", + "window-text-height", "window-text-pixel-size", "window-text-width", + "window-top-child", "window-top-line", "window-total-height", + "window-total-width", "window-use-time", "window-valid-p", + "window-vscroll", "windowp", "write-char", "write-region", + "x-backspace-delete-keys-p", "x-change-window-property", + "x-change-window-property", "x-close-connection", + "x-close-connection", "x-create-frame", "x-create-frame", + "x-delete-window-property", "x-delete-window-property", + "x-disown-selection-internal", "x-display-backing-store", + "x-display-backing-store", "x-display-color-cells", + "x-display-color-cells", "x-display-grayscale-p", + "x-display-grayscale-p", "x-display-list", "x-display-list", + "x-display-mm-height", "x-display-mm-height", "x-display-mm-width", + "x-display-mm-width", "x-display-monitor-attributes-list", + "x-display-pixel-height", "x-display-pixel-height", + "x-display-pixel-width", "x-display-pixel-width", "x-display-planes", + "x-display-planes", "x-display-save-under", "x-display-save-under", + "x-display-screens", "x-display-screens", "x-display-visual-class", + "x-display-visual-class", "x-family-fonts", "x-file-dialog", + "x-file-dialog", "x-file-dialog", "x-focus-frame", "x-frame-geometry", + "x-frame-geometry", "x-get-atom-name", "x-get-resource", + "x-get-selection-internal", "x-hide-tip", "x-hide-tip", + "x-list-fonts", "x-load-color-file", "x-menu-bar-open-internal", + "x-menu-bar-open-internal", "x-open-connection", "x-open-connection", + "x-own-selection-internal", "x-parse-geometry", "x-popup-dialog", + "x-popup-menu", "x-register-dnd-atom", "x-select-font", + "x-select-font", "x-selection-exists-p", "x-selection-owner-p", + "x-send-client-message", "x-server-max-request-size", + "x-server-max-request-size", "x-server-vendor", "x-server-vendor", + "x-server-version", "x-server-version", "x-show-tip", "x-show-tip", + "x-synchronize", "x-synchronize", "x-uses-old-gtk-dialog", + "x-window-property", "x-window-property", "x-wm-set-size-hint", + "xw-color-defined-p", "xw-color-defined-p", "xw-color-values", + "xw-color-values", "xw-display-color-p", "xw-display-color-p", + "yes-or-no-p", "zlib-available-p", "zlib-decompress-region", + "forward-point", + } + + emacsBuiltinFunctionHighlighted = []string{ + "defvaralias", "provide", "require", + "with-no-warnings", "define-widget", "with-electric-help", + "throw", "defalias", "featurep", + } + + emacsLambdaListKeywords = []string{ + "&allow-other-keys", "&aux", "&body", "&environment", "&key", "&optional", + "&rest", "&whole", + } + + emacsErrorKeywords = []string{ + "cl-assert", "cl-check-type", "error", "signal", + "user-error", "warn", + } +) + +// EmacsLisp lexer. +var EmacsLisp = Register(TypeRemappingLexer(MustNewXMLLexer( + embedded, + "embedded/emacslisp.xml", +), TypeMapping{ + {NameVariable, NameFunction, emacsBuiltinFunction}, + {NameVariable, NameBuiltin, emacsSpecialForms}, + {NameVariable, NameException, emacsErrorKeywords}, + {NameVariable, NameBuiltin, append(emacsBuiltinFunctionHighlighted, emacsMacros...)}, + {NameVariable, KeywordPseudo, emacsLambdaListKeywords}, +})) diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abap.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abap.xml new file mode 100644 index 000000000..e8140b738 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abap.xml @@ -0,0 +1,154 @@ + + + ABAP + abap + *.abap + *.ABAP + text/x-abap + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abnf.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abnf.xml new file mode 100644 index 000000000..3ffd51c6c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abnf.xml @@ -0,0 +1,66 @@ + + + ABNF + abnf + *.abnf + text/x-abnf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript.xml new file mode 100644 index 000000000..d6727a103 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript.xml @@ -0,0 +1,68 @@ + + + ActionScript + as + actionscript + *.as + application/x-actionscript + text/x-actionscript + text/actionscript + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript_3.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript_3.xml new file mode 100644 index 000000000..e5f653848 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript_3.xml @@ -0,0 +1,163 @@ + + + ActionScript 3 + as3 + actionscript3 + *.as + application/x-actionscript3 + text/x-actionscript3 + text/actionscript3 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ada.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ada.xml new file mode 100644 index 000000000..5854a20e9 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ada.xml @@ -0,0 +1,321 @@ + + + Ada + ada + ada95 + ada2005 + *.adb + *.ads + *.ada + text/x-ada + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/agda.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/agda.xml new file mode 100644 index 000000000..6f2b2d508 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/agda.xml @@ -0,0 +1,66 @@ + + + Agda + agda + *.agda + text/x-agda + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/al.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/al.xml new file mode 100644 index 000000000..30bad5ae9 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/al.xml @@ -0,0 +1,75 @@ + + + AL + al + *.al + *.dal + text/x-al + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/alloy.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/alloy.xml new file mode 100644 index 000000000..1de9ea6cc --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/alloy.xml @@ -0,0 +1,58 @@ + + + + Alloy + alloy + *.als + text/x-alloy + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ampl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ampl.xml new file mode 100644 index 000000000..8c2479e50 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ampl.xml @@ -0,0 +1,98 @@ + + + AMPL + ampl + *.mod + *.run + text/x-ampl + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/angular2.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/angular2.xml new file mode 100644 index 000000000..230ef868c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/angular2.xml @@ -0,0 +1,109 @@ + + + Angular2 + ng2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/antlr.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/antlr.xml new file mode 100644 index 000000000..e57edd404 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/antlr.xml @@ -0,0 +1,317 @@ + + + ANTLR + antlr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apacheconf.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apacheconf.xml new file mode 100644 index 000000000..7643541c1 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apacheconf.xml @@ -0,0 +1,74 @@ + + + ApacheConf + apacheconf + aconf + apache + .htaccess + apache.conf + apache2.conf + text/x-apacheconf + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apl.xml new file mode 100644 index 000000000..959448ca4 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apl.xml @@ -0,0 +1,59 @@ + + + APL + apl + *.apl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/applescript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/applescript.xml new file mode 100644 index 000000000..5a6224ac0 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/applescript.xml @@ -0,0 +1,151 @@ + + + AppleScript + applescript + *.applescript + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arangodb_aql.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arangodb_aql.xml new file mode 100644 index 000000000..434b395a1 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arangodb_aql.xml @@ -0,0 +1,174 @@ + + + ArangoDB AQL + aql + *.aql + text/x-aql + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arduino.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arduino.xml new file mode 100644 index 000000000..6a75df5b9 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arduino.xml @@ -0,0 +1,322 @@ + + + Arduino + arduino + *.ino + text/x-arduino + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/armasm.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/armasm.xml new file mode 100644 index 000000000..340278d37 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/armasm.xml @@ -0,0 +1,126 @@ + + + ArmAsm + armasm + *.s + *.S + text/x-armasm + text/x-asm + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arturo.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arturo.xml new file mode 100644 index 000000000..541dc46e5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arturo.xml @@ -0,0 +1,119 @@ + + + + Arturo + arturo + art + *.art + + + + + + + + + + + + + + + + + + + + + + + + + 3 + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/atl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/atl.xml new file mode 100644 index 000000000..623dc205e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/atl.xml @@ -0,0 +1,165 @@ + + + ATL + atl + *.atl + text/x-atl + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autohotkey.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autohotkey.xml new file mode 100644 index 000000000..6ec94edeb --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autohotkey.xml @@ -0,0 +1,78 @@ + + + + AutoHotkey + autohotkey + ahk + *.ahk + *.ahkl + text/x-autohotkey + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autoit.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autoit.xml new file mode 100644 index 000000000..1f7e15df3 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autoit.xml @@ -0,0 +1,70 @@ + + + + AutoIt + autoit + *.au3 + text/x-autoit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/awk.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/awk.xml new file mode 100644 index 000000000..07476ff74 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/awk.xml @@ -0,0 +1,95 @@ + + + Awk + awk + gawk + mawk + nawk + *.awk + application/x-awk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ballerina.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ballerina.xml new file mode 100644 index 000000000..d13c12319 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ballerina.xml @@ -0,0 +1,97 @@ + + + Ballerina + ballerina + *.bal + text/x-ballerina + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash.xml new file mode 100644 index 000000000..6163cc613 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash.xml @@ -0,0 +1,222 @@ + + + Bash + bash + sh + ksh + zsh + shell + *.sh + *.ksh + *.bash + *.ebuild + *.eclass + .env + .env.* + *.env + *.exheres-0 + *.exlib + *.zsh + *.zshrc + .bashrc + bashrc + .bash_* + bash_* + zshrc + .zshrc + APKBUILD + PKGBUILD + application/x-sh + application/x-shellscript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash_session.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash_session.xml new file mode 100644 index 000000000..82c5fd6d0 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash_session.xml @@ -0,0 +1,25 @@ + + + Bash Session + bash-session + console + shell-session + *.sh-session + text/x-sh + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/batchfile.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/batchfile.xml new file mode 100644 index 000000000..d3e062728 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/batchfile.xml @@ -0,0 +1,660 @@ + + + Batchfile + bat + batch + dosbatch + winbatch + *.bat + *.cmd + application/x-dos-batch + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/beef.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/beef.xml new file mode 100644 index 000000000..031a220f2 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/beef.xml @@ -0,0 +1,120 @@ + + + Beef + beef + *.bf + text/x-beef + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bibtex.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bibtex.xml new file mode 100644 index 000000000..8fde161b3 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bibtex.xml @@ -0,0 +1,152 @@ + + + BibTeX + bib + bibtex + *.bib + text/x-bibtex + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bicep.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bicep.xml new file mode 100644 index 000000000..db90f31b3 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bicep.xml @@ -0,0 +1,84 @@ + + + Bicep + bicep + *.bicep + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/blitzbasic.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/blitzbasic.xml new file mode 100644 index 000000000..591b1ad0f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/blitzbasic.xml @@ -0,0 +1,141 @@ + + + BlitzBasic + blitzbasic + b3d + bplus + *.bb + *.decls + text/x-bb + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bnf.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bnf.xml new file mode 100644 index 000000000..5c9842477 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bnf.xml @@ -0,0 +1,28 @@ + + + BNF + bnf + *.bnf + text/x-bnf + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bqn.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bqn.xml new file mode 100644 index 000000000..c1090ea5a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bqn.xml @@ -0,0 +1,83 @@ + + + BQN + bqn + *.bqn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/brainfuck.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/brainfuck.xml new file mode 100644 index 000000000..4c84c3308 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/brainfuck.xml @@ -0,0 +1,51 @@ + + + Brainfuck + brainfuck + bf + *.bf + *.b + application/x-brainfuck + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c#.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c#.xml new file mode 100644 index 000000000..f1e21db03 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c#.xml @@ -0,0 +1,121 @@ + + + C# + csharp + c# + *.cs + text/x-csharp + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c++.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c++.xml new file mode 100644 index 000000000..680a19afb --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c++.xml @@ -0,0 +1,331 @@ + + + C++ + cpp + c++ + *.cpp + *.hpp + *.c++ + *.h++ + *.cc + *.hh + *.cxx + *.hxx + *.C + *.H + *.cp + *.CPP + *.tpp + text/x-c++hdr + text/x-c++src + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c.xml new file mode 100644 index 000000000..35ee32dce --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c.xml @@ -0,0 +1,260 @@ + + + C + c + *.c + *.h + *.idc + *.x[bp]m + text/x-chdr + text/x-csrc + image/x-xbitmap + image/x-xpixmap + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c3.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c3.xml new file mode 100644 index 000000000..8094ce4b8 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c3.xml @@ -0,0 +1,374 @@ + + + C3 + c3 + *.c3 + *.c3i + *.c3t + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cap_n_proto.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cap_n_proto.xml new file mode 100644 index 000000000..3e7d1470b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cap_n_proto.xml @@ -0,0 +1,122 @@ + + + Cap'n Proto + capnp + *.capnp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cassandra_cql.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cassandra_cql.xml new file mode 100644 index 000000000..1a78f99a1 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cassandra_cql.xml @@ -0,0 +1,137 @@ + + + Cassandra CQL + cassandra + cql + *.cql + text/x-cql + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ceylon.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ceylon.xml new file mode 100644 index 000000000..4c4121835 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ceylon.xml @@ -0,0 +1,151 @@ + + + Ceylon + ceylon + *.ceylon + text/x-ceylon + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfengine3.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfengine3.xml new file mode 100644 index 000000000..495030505 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfengine3.xml @@ -0,0 +1,197 @@ + + + CFEngine3 + cfengine3 + cf3 + *.cf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfstatement.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfstatement.xml new file mode 100644 index 000000000..46a84cf6a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfstatement.xml @@ -0,0 +1,92 @@ + + + cfstatement + cfs + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chaiscript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chaiscript.xml new file mode 100644 index 000000000..860439aa8 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chaiscript.xml @@ -0,0 +1,134 @@ + + + ChaiScript + chai + chaiscript + *.chai + text/x-chaiscript + application/x-chaiscript + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chapel.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chapel.xml new file mode 100644 index 000000000..c89cafc6a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chapel.xml @@ -0,0 +1,143 @@ + + + Chapel + chapel + chpl + *.chpl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cheetah.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cheetah.xml new file mode 100644 index 000000000..284457c6c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cheetah.xml @@ -0,0 +1,55 @@ + + + Cheetah + cheetah + spitfire + *.tmpl + *.spt + application/x-cheetah + application/x-spitfire + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/clojure.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/clojure.xml new file mode 100644 index 000000000..967ba399c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/clojure.xml @@ -0,0 +1,71 @@ + + + Clojure + clojure + clj + edn + *.clj + *.edn + text/x-clojure + application/x-clojure + application/edn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cmake.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cmake.xml new file mode 100644 index 000000000..b041cfd01 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cmake.xml @@ -0,0 +1,90 @@ + + + CMake + cmake + *.cmake + CMakeLists.txt + text/x-cmake + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cobol.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cobol.xml new file mode 100644 index 000000000..a8a80291d --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cobol.xml @@ -0,0 +1,90 @@ + + + COBOL + cobol + *.cob + *.COB + *.cpy + *.CPY + text/x-cobol + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coffeescript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coffeescript.xml new file mode 100644 index 000000000..e29722fb8 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coffeescript.xml @@ -0,0 +1,210 @@ + + + CoffeeScript + coffee-script + coffeescript + coffee + *.coffee + text/coffeescript + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/common_lisp.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/common_lisp.xml new file mode 100644 index 000000000..0fb9a7a4b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/common_lisp.xml @@ -0,0 +1,184 @@ + + + Common Lisp + common-lisp + cl + lisp + *.cl + *.lisp + text/x-common-lisp + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coq.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coq.xml new file mode 100644 index 000000000..62f64ff99 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coq.xml @@ -0,0 +1,136 @@ + + + Coq + coq + *.v + text/x-coq + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/core.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/core.xml new file mode 100644 index 000000000..f0a9d2905 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/core.xml @@ -0,0 +1,79 @@ + + + Core + core + *.core + text/x-core + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/crystal.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/crystal.xml new file mode 100644 index 000000000..94853db33 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/crystal.xml @@ -0,0 +1,762 @@ + + + Crystal + cr + crystal + *.cr + text/x-crystal + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/css.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/css.xml new file mode 100644 index 000000000..adc13c75a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/css.xml @@ -0,0 +1,323 @@ + + + CSS + css + *.css + text/css + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/csv.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/csv.xml new file mode 100644 index 000000000..b70c2f8b1 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/csv.xml @@ -0,0 +1,53 @@ + + + + + CSV + csv + *.csv + text/csv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cue.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cue.xml new file mode 100644 index 000000000..2a12f3953 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cue.xml @@ -0,0 +1,85 @@ + + + CUE + cue + *.cue + text/x-cue + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cython.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cython.xml new file mode 100644 index 000000000..15dfe4d4f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cython.xml @@ -0,0 +1,372 @@ + + + Cython + cython + pyx + pyrex + *.pyx + *.pxd + *.pxi + text/x-cython + application/x-cython + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/d.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/d.xml new file mode 100644 index 000000000..19c85e2c8 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/d.xml @@ -0,0 +1,133 @@ + + + D + d + *.d + *.di + text/x-d + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dart.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dart.xml new file mode 100644 index 000000000..4044f4914 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dart.xml @@ -0,0 +1,213 @@ + + + Dart + dart + *.dart + text/x-dart + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dax.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dax.xml new file mode 100644 index 000000000..2bb3a1aa7 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dax.xml @@ -0,0 +1,39 @@ + + + Dax + dax + *.dax + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/desktop_entry.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/desktop_entry.xml new file mode 100644 index 000000000..ad71ad471 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/desktop_entry.xml @@ -0,0 +1,17 @@ + + + Desktop file + desktop + desktop_entry + *.desktop + application/x-desktop + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/devicetree.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/devicetree.xml new file mode 100644 index 000000000..2db54ddc5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/devicetree.xml @@ -0,0 +1,251 @@ + + + Devicetree + devicetree + dts + *.dts + *.dtsi + text/x-devicetree + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/diff.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/diff.xml new file mode 100644 index 000000000..dc0beb7fd --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/diff.xml @@ -0,0 +1,52 @@ + + + Diff + diff + udiff + *.diff + *.patch + text/x-diff + text/x-patch + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/django_jinja.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/django_jinja.xml new file mode 100644 index 000000000..3c97c222e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/django_jinja.xml @@ -0,0 +1,153 @@ + + + Django/Jinja + django + jinja + application/x-django-templating + application/x-jinja + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dns.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dns.xml new file mode 100644 index 000000000..ef8f663f9 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dns.xml @@ -0,0 +1,44 @@ + + + + dns + zone + bind + *.zone + text/dns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/docker.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/docker.xml new file mode 100644 index 000000000..261834f42 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/docker.xml @@ -0,0 +1,68 @@ + + + Docker + docker + dockerfile + containerfile + Dockerfile + Dockerfile.* + *.Dockerfile + *.docker + Containerfile + Containerfile.* + *.Containerfile + text/x-dockerfile-config + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dtd.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dtd.xml new file mode 100644 index 000000000..0edbbdeac --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dtd.xml @@ -0,0 +1,168 @@ + + + DTD + dtd + *.dtd + application/xml-dtd + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dylan.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dylan.xml new file mode 100644 index 000000000..3660d1440 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dylan.xml @@ -0,0 +1,176 @@ + + + Dylan + dylan + *.dylan + *.dyl + *.intr + text/x-dylan + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ebnf.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ebnf.xml new file mode 100644 index 000000000..df5d62ffa --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ebnf.xml @@ -0,0 +1,90 @@ + + + EBNF + ebnf + *.ebnf + text/x-ebnf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elixir.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elixir.xml new file mode 100644 index 000000000..286f53a24 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elixir.xml @@ -0,0 +1,744 @@ + + + Elixir + elixir + ex + exs + *.ex + *.eex + *.exs + text/x-elixir + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elm.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elm.xml new file mode 100644 index 000000000..ed65efc63 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elm.xml @@ -0,0 +1,119 @@ + + + Elm + elm + *.elm + text/x-elm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/emacslisp.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/emacslisp.xml new file mode 100644 index 000000000..668bc621e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/emacslisp.xml @@ -0,0 +1,132 @@ + + + EmacsLisp + emacs + elisp + emacs-lisp + *.el + text/x-elisp + application/x-elisp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erb.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erb.xml new file mode 100644 index 000000000..e597cd8a9 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erb.xml @@ -0,0 +1,37 @@ + + + ERB + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erlang.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erlang.xml new file mode 100644 index 000000000..b18658868 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erlang.xml @@ -0,0 +1,166 @@ + + + Erlang + erlang + *.erl + *.hrl + *.es + *.escript + text/x-erlang + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/factor.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/factor.xml new file mode 100644 index 000000000..4743b9a2e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/factor.xml @@ -0,0 +1,412 @@ + + + Factor + factor + *.factor + text/x-factor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fennel.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fennel.xml new file mode 100644 index 000000000..b9b6d5950 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fennel.xml @@ -0,0 +1,68 @@ + + + Fennel + fennel + fnl + *.fennel + text/x-fennel + application/x-fennel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fish.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fish.xml new file mode 100644 index 000000000..deb78145e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fish.xml @@ -0,0 +1,159 @@ + + + Fish + fish + fishshell + *.fish + *.load + application/x-fish + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/forth.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/forth.xml new file mode 100644 index 000000000..31096a225 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/forth.xml @@ -0,0 +1,78 @@ + + + Forth + forth + *.frt + *.fth + *.fs + application/x-forth + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortran.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortran.xml new file mode 100644 index 000000000..6140e7041 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortran.xml @@ -0,0 +1,102 @@ + + + Fortran + fortran + f90 + *.f03 + *.f90 + *.f95 + *.F03 + *.F90 + *.F95 + text/x-fortran + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortranfixed.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortranfixed.xml new file mode 100644 index 000000000..11343c0e7 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortranfixed.xml @@ -0,0 +1,71 @@ + + + FortranFixed + fortranfixed + *.f + *.F + text/x-fortran + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fsharp.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fsharp.xml new file mode 100644 index 000000000..e1c19ffb4 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fsharp.xml @@ -0,0 +1,245 @@ + + + FSharp + fsharp + *.fs + *.fsi + text/x-fsharp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gas.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gas.xml new file mode 100644 index 000000000..399cdd008 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gas.xml @@ -0,0 +1,150 @@ + + + GAS + gas + asm + *.s + *.S + text/x-gas + 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript.xml new file mode 100644 index 000000000..811f38d02 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript.xml @@ -0,0 +1,259 @@ + + + GDScript + gdscript + gd + *.gd + text/x-gdscript + application/x-gdscript + 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript3.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript3.xml new file mode 100644 index 000000000..b50c9dd7b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript3.xml @@ -0,0 +1,270 @@ + + + GDScript3 + gdscript3 + gd3 + *.gd + text/x-gdscript + application/x-gdscript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gemfile_lock.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gemfile_lock.xml new file mode 100644 index 000000000..83fc47b74 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gemfile_lock.xml @@ -0,0 +1,81 @@ + + + Gemfile.lock + gemfile-lock + gemfilelock + Gemfile.lock + *.gemfile.lock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gettext.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gettext.xml new file mode 100644 index 000000000..38c0c21b4 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gettext.xml @@ -0,0 +1,24 @@ + + + + Gettext + pot + po + *.pot + *.po + application/x-gettext + text/x-gettext + text/gettext + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gherkin.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gherkin.xml new file mode 100644 index 000000000..c53a2cbb5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gherkin.xml @@ -0,0 +1,263 @@ + + + Gherkin + cucumber + Cucumber + gherkin + Gherkin + *.feature + *.FEATURE + text/x-gherkin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gleam.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gleam.xml new file mode 100644 index 000000000..396632203 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gleam.xml @@ -0,0 +1,100 @@ + + + Gleam + gleam + *.gleam + text/x-gleam + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/glsl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/glsl.xml new file mode 100644 index 000000000..ca0b696de --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/glsl.xml @@ -0,0 +1,65 @@ + + + GLSL + glsl + *.vert + *.frag + *.geo + text/x-glslsrc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gnuplot.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gnuplot.xml new file mode 100644 index 000000000..ee6a245f3 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gnuplot.xml @@ -0,0 +1,289 @@ + + + Gnuplot + gnuplot + *.plot + *.plt + text/x-gnuplot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/go_template.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/go_template.xml new file mode 100644 index 000000000..36f737b93 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/go_template.xml @@ -0,0 +1,114 @@ + + + Go Template + go-template + *.gotmpl + *.go.tmpl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/graphql.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/graphql.xml new file mode 100644 index 000000000..b40422f22 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/graphql.xml @@ -0,0 +1,92 @@ + + + GraphQL + graphql + graphqls + gql + *.graphql + *.graphqls + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groff.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groff.xml new file mode 100644 index 000000000..3af0a43e7 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groff.xml @@ -0,0 +1,90 @@ + + + Groff + groff + nroff + man + *.[1-9] + *.1p + *.3pm + *.man + application/x-troff + text/troff + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groovy.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groovy.xml new file mode 100644 index 000000000..3cca2e9a5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groovy.xml @@ -0,0 +1,135 @@ + + + Groovy + groovy + *.groovy + *.gradle + text/x-groovy + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/handlebars.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/handlebars.xml new file mode 100644 index 000000000..7cf2a648a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/handlebars.xml @@ -0,0 +1,147 @@ + + + Handlebars + handlebars + hbs + *.handlebars + *.hbs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hare.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hare.xml new file mode 100644 index 000000000..c1f7e9433 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hare.xml @@ -0,0 +1,98 @@ + + + Hare + hare + *.ha + text/x-hare + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/haskell.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/haskell.xml new file mode 100644 index 000000000..1fad08231 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/haskell.xml @@ -0,0 +1,275 @@ + + + Haskell + haskell + hs + *.hs + text/x-haskell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hcl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hcl.xml new file mode 100644 index 000000000..d3ed208af --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hcl.xml @@ -0,0 +1,143 @@ + + + HCL + hcl + *.hcl + application/x-hcl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hexdump.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hexdump.xml new file mode 100644 index 000000000..a6f28eab1 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hexdump.xml @@ -0,0 +1,189 @@ + + + Hexdump + hexdump + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlb.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlb.xml new file mode 100644 index 000000000..9723fd9c2 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlb.xml @@ -0,0 +1,131 @@ + + + HLB + hlb + *.hlb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlsl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlsl.xml new file mode 100644 index 000000000..41ab32395 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlsl.xml @@ -0,0 +1,110 @@ + + + HLSL + hlsl + *.hlsl + *.hlsli + *.cginc + *.fx + *.fxh + text/x-hlsl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/holyc.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/holyc.xml new file mode 100644 index 000000000..cd2d9d16b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/holyc.xml @@ -0,0 +1,252 @@ + + + HolyC + holyc + *.HC + *.hc + *.HH + *.hh + *.hc.z + *.HC.Z + text/x-chdr + text/x-csrc + image/x-xbitmap + image/x-xpixmap + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/html.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/html.xml new file mode 100644 index 000000000..2f1a8a979 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/html.xml @@ -0,0 +1,159 @@ + + + HTML + html + *.html + *.htm + *.xhtml + *.xslt + text/html + application/xhtml+xml + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hy.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hy.xml new file mode 100644 index 000000000..a0dae46ae --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hy.xml @@ -0,0 +1,104 @@ + + + Hy + hylang + *.hy + text/x-hy + application/x-hy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/idris.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/idris.xml new file mode 100644 index 000000000..9592d8822 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/idris.xml @@ -0,0 +1,216 @@ + + + Idris + idris + idr + *.idr + text/x-idris + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/igor.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/igor.xml new file mode 100644 index 000000000..1cc020570 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/igor.xml @@ -0,0 +1,47 @@ + + + Igor + igor + igorpro + *.ipf + text/ipf + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ini.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ini.xml new file mode 100644 index 000000000..3f1de09cb --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ini.xml @@ -0,0 +1,52 @@ + + + INI + ini + cfg + dosini + *.ini + *.cfg + *.inf + *.service + *.socket + *.container + *.network + *.build + *.pod + *.kube + *.volume + *.image + .gitconfig + .editorconfig + pylintrc + .pylintrc + text/x-ini + text/inf + 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/io.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/io.xml new file mode 100644 index 000000000..9ad94fa52 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/io.xml @@ -0,0 +1,71 @@ + + + Io + io + *.io + text/x-iosrc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/iscdhcpd.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/iscdhcpd.xml new file mode 100644 index 000000000..645cb05d5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/iscdhcpd.xml @@ -0,0 +1,96 @@ + + + ISCdhcpd + iscdhcpd + dhcpd.conf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/j.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/j.xml new file mode 100644 index 000000000..872d08122 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/j.xml @@ -0,0 +1,157 @@ + + + J + j + *.ijs + text/x-j + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/janet.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/janet.xml new file mode 100644 index 000000000..fe139e856 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/janet.xml @@ -0,0 +1,48 @@ + + + + Janet + janet + *.janet + *.jdn + text/x-janet + application/x-janet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/java.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/java.xml new file mode 100644 index 000000000..4ef5fd267 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/java.xml @@ -0,0 +1,206 @@ + + + Java + java + *.java + text/x-java + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/javascript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/javascript.xml new file mode 100644 index 000000000..0e475c53e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/javascript.xml @@ -0,0 +1,160 @@ + + + JavaScript + js + javascript + *.js + *.jsm + *.mjs + *.cjs + application/javascript + application/x-javascript + text/x-javascript + text/javascript + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/json.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/json.xml new file mode 100644 index 000000000..f65d7103c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/json.xml @@ -0,0 +1,117 @@ + + + JSON + json + jsonl + *.json + *.jsonl + *.jsonc + *.json5 + *.avsc + .luaurc + application/json + application/jsonl + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonata.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonata.xml new file mode 100644 index 000000000..c0eafabe3 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonata.xml @@ -0,0 +1,83 @@ + + + JSONata + jsonata + *.jsonata + true + + + + + + + + + + // Spread operator + + + // Sort operator + + + // Descendant | Wildcard | Multiplication + + + // Division + + + // Comparison operators + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonnet.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonnet.xml new file mode 100644 index 000000000..1633a5e40 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonnet.xml @@ -0,0 +1,138 @@ + + + + Jsonnet + jsonnet + *.jsonnet + *.libsonnet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/julia.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/julia.xml new file mode 100644 index 000000000..776dcdbcb --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/julia.xml @@ -0,0 +1,400 @@ + + + Julia + julia + jl + *.jl + text/x-julia + application/x-julia + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jungle.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jungle.xml new file mode 100644 index 000000000..92c785d0c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jungle.xml @@ -0,0 +1,98 @@ + + + Jungle + jungle + *.jungle + text/x-jungle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kakoune.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kakoune.xml new file mode 100644 index 000000000..afc048960 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kakoune.xml @@ -0,0 +1,96 @@ + + + + Kakoune + kak + kakoune + kakrc + kakscript + *.kak + kakrc + application/x-sh + application/x-shellscript + text/x-shellscript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kdl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kdl.xml new file mode 100644 index 000000000..bc6ebfb80 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kdl.xml @@ -0,0 +1,75 @@ + + + KDL + kdl + *.kdl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kotlin.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kotlin.xml new file mode 100644 index 000000000..28bf2d8ba --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kotlin.xml @@ -0,0 +1,234 @@ + + + Kotlin + kotlin + *.kt + *.kts + text/x-kotlin + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lateralus.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lateralus.xml new file mode 100644 index 000000000..cea10eba7 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lateralus.xml @@ -0,0 +1,184 @@ + + + Lateralus + lateralus + ltl + *.ltl + text/x-lateralus + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lean.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lean.xml new file mode 100644 index 000000000..6ac5151bb --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lean.xml @@ -0,0 +1,56 @@ + + + Lean4 + lean4 + lean + *.lean + text/x-lean4 + text/x-lean + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lighttpd_configuration_file.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lighttpd_configuration_file.xml new file mode 100644 index 000000000..1319e5c83 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lighttpd_configuration_file.xml @@ -0,0 +1,42 @@ + + + Lighttpd configuration file + lighty + lighttpd + text/x-lighttpd-conf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lilypond.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lilypond.xml new file mode 100644 index 000000000..fe4ac8ed3 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lilypond.xml @@ -0,0 +1,133 @@ + + + + LilyPond + lilypond + *.ly + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/llvm.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/llvm.xml new file mode 100644 index 000000000..f24f1522b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/llvm.xml @@ -0,0 +1,73 @@ + + + LLVM + llvm + *.ll + text/x-llvm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lox.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lox.xml new file mode 100644 index 000000000..8dec0a856 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lox.xml @@ -0,0 +1,78 @@ + + + lox + *.lox + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lua.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lua.xml new file mode 100644 index 000000000..903d4581f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lua.xml @@ -0,0 +1,158 @@ + + + Lua + lua + *.lua + *.wlua + text/x-lua + application/x-lua + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/luau.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/luau.xml new file mode 100644 index 000000000..79a60949a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/luau.xml @@ -0,0 +1,173 @@ + + + Luau + luau + *.luau + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/makefile.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/makefile.xml new file mode 100644 index 000000000..a82a7f8d5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/makefile.xml @@ -0,0 +1,131 @@ + + + Makefile + make + makefile + mf + bsdmake + *.mak + *.mk + Makefile + makefile + Makefile.* + GNUmakefile + BSDmakefile + Justfile + justfile + .justfile + text/x-makefile + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mako.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mako.xml new file mode 100644 index 000000000..782414087 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mako.xml @@ -0,0 +1,120 @@ + + + Mako + mako + *.mao + application/x-mako + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mason.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mason.xml new file mode 100644 index 000000000..5873f2afc --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mason.xml @@ -0,0 +1,89 @@ + + + Mason + mason + *.m + *.mhtml + *.mc + *.mi + autohandler + dhandler + application/x-mason + 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/materialize_sql_dialect.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/materialize_sql_dialect.xml new file mode 100644 index 000000000..616d7ae4c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/materialize_sql_dialect.xml @@ -0,0 +1,155 @@ + + + Materialize SQL dialect + text/x-materializesql + true + true + materialize + mzsql + + + + + + + + + + + + + + + + + + + 6 + 12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 12 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mathematica.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mathematica.xml new file mode 100644 index 000000000..0b8dfb617 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mathematica.xml @@ -0,0 +1,60 @@ + + + Mathematica + mathematica + mma + nb + *.cdf + *.m + *.ma + *.mt + *.mx + *.nb + *.nbp + *.wl + application/mathematica + application/vnd.wolfram.mathematica + application/vnd.wolfram.mathematica.package + application/vnd.wolfram.cdf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/matlab.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/matlab.xml new file mode 100644 index 000000000..ebb4e2c33 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/matlab.xml @@ -0,0 +1,114 @@ + + + Matlab + matlab + *.m + text/matlab + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mcfunction.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mcfunction.xml new file mode 100644 index 000000000..a6aa6dbe8 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mcfunction.xml @@ -0,0 +1,138 @@ + + + + MCFunction + mcfunction + mcf + *.mcfunction + text/mcfunction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/meson.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/meson.xml new file mode 100644 index 000000000..fcfbda115 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/meson.xml @@ -0,0 +1,86 @@ + + + Meson + meson + meson.build + meson.build + meson.options + meson_options.txt + text/x-meson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/metal.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/metal.xml new file mode 100644 index 000000000..62d04ba84 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/metal.xml @@ -0,0 +1,270 @@ + + + Metal + metal + *.metal + text/x-metal + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/microcad.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/microcad.xml new file mode 100644 index 000000000..6de71ef0a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/microcad.xml @@ -0,0 +1,139 @@ + + + microcad + µcad + *.µcad + *.ucad + *.mcad + text/microcad + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/minizinc.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/minizinc.xml new file mode 100644 index 000000000..1ad6860f4 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/minizinc.xml @@ -0,0 +1,82 @@ + + + MiniZinc + minizinc + MZN + mzn + *.mzn + *.dzn + *.fzn + text/minizinc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mlir.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mlir.xml new file mode 100644 index 000000000..025c3dc56 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mlir.xml @@ -0,0 +1,73 @@ + + + MLIR + mlir + *.mlir + text/x-mlir + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modelica.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modelica.xml new file mode 100644 index 000000000..e5fa60f99 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modelica.xml @@ -0,0 +1,106 @@ + + + Modelica + modelica + *.mo + text/x-modelica + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modula-2.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modula-2.xml new file mode 100644 index 000000000..0bf37bcc3 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modula-2.xml @@ -0,0 +1,245 @@ + + + Modula-2 + modula2 + m2 + *.def + *.mod + text/x-modula2 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mojo.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mojo.xml new file mode 100644 index 000000000..677811eb1 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mojo.xml @@ -0,0 +1,228 @@ + + + Mojo + mojo + 🔥 + *.mojo + *.🔥 + text/x-mojo + application/x-mojo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/monkeyc.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/monkeyc.xml new file mode 100644 index 000000000..7445a639d --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/monkeyc.xml @@ -0,0 +1,153 @@ + + + MonkeyC + monkeyc + *.mc + text/x-monkeyc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/moonbit.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/moonbit.xml new file mode 100644 index 000000000..846e724dc --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/moonbit.xml @@ -0,0 +1,75 @@ + + + MoonBit + moonbit + mbt + *.mbt + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/moonscript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/moonscript.xml new file mode 100644 index 000000000..293f538cd --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/moonscript.xml @@ -0,0 +1,83 @@ + + + + MoonScript + moonscript + moon + *.moon + text/x-moonscript + application/x-moonscript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/morrowindscript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/morrowindscript.xml new file mode 100644 index 000000000..724a19fc6 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/morrowindscript.xml @@ -0,0 +1,90 @@ + + + MorrowindScript + morrowind + mwscript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/myghty.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/myghty.xml new file mode 100644 index 000000000..6d03917e5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/myghty.xml @@ -0,0 +1,77 @@ + + + Myghty + myghty + *.myt + autodelegate + application/x-myghty + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mysql.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mysql.xml new file mode 100644 index 000000000..0517ec8f7 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mysql.xml @@ -0,0 +1,121 @@ + + + MySQL + mysql + mariadb + *.sql + text/x-mysql + text/x-mariadb + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nasm.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nasm.xml new file mode 100644 index 000000000..defe65b3c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nasm.xml @@ -0,0 +1,126 @@ + + + NASM + nasm + *.asm + *.ASM + *.nasm + text/x-nasm + true + 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/natural.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/natural.xml new file mode 100644 index 000000000..707252b4c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/natural.xml @@ -0,0 +1,143 @@ + + + Natural + natural + *.NSN + *.NSP + *.NSS + *.NSH + *.NSG + *.NSL + *.NSA + *.NSM + *.NSC + *.NS7 + text/x-natural + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ndisasm.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ndisasm.xml new file mode 100644 index 000000000..74d443b64 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ndisasm.xml @@ -0,0 +1,123 @@ + + + NDISASM + ndisasm + text/x-disasm + true + 0.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/newspeak.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/newspeak.xml new file mode 100644 index 000000000..b93265706 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/newspeak.xml @@ -0,0 +1,121 @@ + + + Newspeak + newspeak + *.ns2 + text/x-newspeak + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nginx_configuration_file.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nginx_configuration_file.xml new file mode 100644 index 000000000..a80d04993 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nginx_configuration_file.xml @@ -0,0 +1,101 @@ + + + Nginx configuration file + nginx + nginx.conf + text/x-nginx-conf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nim.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nim.xml new file mode 100644 index 000000000..bfdd6156a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nim.xml @@ -0,0 +1,211 @@ + + + Nim + nim + nimrod + *.nim + *.nimrod + text/x-nim + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nix.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nix.xml new file mode 100644 index 000000000..dd54d36fd --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nix.xml @@ -0,0 +1,258 @@ + + + Nix + nixos + nix + *.nix + text/x-nix + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nsis.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nsis.xml new file mode 100644 index 000000000..6c3a7be96 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nsis.xml @@ -0,0 +1,59 @@ + + + NSIS + nsis + nsi + nsh + *.nsi + *.nsh + text/x-nsis + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nu.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nu.xml new file mode 100644 index 000000000..82d4b3759 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nu.xml @@ -0,0 +1,126 @@ + + + Nu + nu + *.nu + application/x-shellscript + text/plain + text/x-shellscript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objective-c.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objective-c.xml new file mode 100644 index 000000000..0dc932857 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objective-c.xml @@ -0,0 +1,510 @@ + + + Objective-C + objective-c + objectivec + obj-c + objc + *.m + *.h + text/x-objective-c + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objectpascal.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objectpascal.xml new file mode 100644 index 000000000..0b7213121 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objectpascal.xml @@ -0,0 +1,142 @@ + + + ObjectPascal + objectpascal + *.pas + *.pp + *.inc + *.dpr + *.dpk + *.lpr + *.lpk + text/x-pascal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ocaml.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ocaml.xml new file mode 100644 index 000000000..1770d1d15 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ocaml.xml @@ -0,0 +1,153 @@ + + + OCaml + ocaml + *.ml + *.mli + *.mll + *.mly + text/x-ocaml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/octave.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/octave.xml new file mode 100644 index 000000000..0515d2898 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/octave.xml @@ -0,0 +1,101 @@ + + + Octave + octave + *.m + text/octave + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/odin.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/odin.xml new file mode 100644 index 000000000..8a529497d --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/odin.xml @@ -0,0 +1,127 @@ + + + Odin + odin + *.odin + text/odin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/onesenterprise.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/onesenterprise.xml new file mode 100644 index 000000000..530bad70e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/onesenterprise.xml @@ -0,0 +1,92 @@ + + + OnesEnterprise + ones + onesenterprise + 1S + 1S:Enterprise + *.EPF + *.epf + *.ERF + *.erf + application/octet-stream + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openedge_abl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openedge_abl.xml new file mode 100644 index 000000000..04a80f3ce --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openedge_abl.xml @@ -0,0 +1,101 @@ + + + OpenEdge ABL + openedge + abl + progress + openedgeabl + *.p + *.cls + *.w + *.i + text/x-openedge + application/x-openedge + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openscad.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openscad.xml new file mode 100644 index 000000000..84d0fe135 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openscad.xml @@ -0,0 +1,96 @@ + + + OpenSCAD + openscad + *.scad + text/x-scad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/org_mode.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/org_mode.xml new file mode 100644 index 000000000..259e54ef5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/org_mode.xml @@ -0,0 +1,329 @@ + + + Org Mode + org + orgmode + *.org + text/org + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 4 + + + + + + + + + + + + 2 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pacmanconf.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pacmanconf.xml new file mode 100644 index 000000000..caf723675 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pacmanconf.xml @@ -0,0 +1,37 @@ + + + PacmanConf + pacmanconf + pacman.conf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/perl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/perl.xml new file mode 100644 index 000000000..8ac02ab40 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/perl.xml @@ -0,0 +1,400 @@ + + + Perl + perl + pl + *.pl + *.pm + *.t + text/x-perl + application/x-perl + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/php.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/php.xml new file mode 100644 index 000000000..774bb79be --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/php.xml @@ -0,0 +1,258 @@ + + + PHP + php + php3 + php4 + php5 + *.php + *.php[345] + *.inc + text/x-php + true + true + true + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pig.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pig.xml new file mode 100644 index 000000000..5acd77399 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pig.xml @@ -0,0 +1,105 @@ + + + Pig + pig + *.pig + text/x-pig + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pkgconfig.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pkgconfig.xml new file mode 100644 index 000000000..875dcba6a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pkgconfig.xml @@ -0,0 +1,73 @@ + + + PkgConfig + pkgconfig + *.pc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pl_pgsql.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pl_pgsql.xml new file mode 100644 index 000000000..e3e813ad5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pl_pgsql.xml @@ -0,0 +1,119 @@ + + + PL/pgSQL + plpgsql + text/x-plpgsql + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plaintext.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plaintext.xml new file mode 100644 index 000000000..d5e3243ee --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plaintext.xml @@ -0,0 +1,21 @@ + + + plaintext + text + plain + no-highlight + *.txt + text/plain + -1 + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plutus_core.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plutus_core.xml new file mode 100644 index 000000000..4ff5a9703 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plutus_core.xml @@ -0,0 +1,105 @@ + + + Plutus Core + plutus-core + plc + *.plc + text/x-plutus-core + application/x-plutus-core + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pony.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pony.xml new file mode 100644 index 000000000..4efa9db50 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pony.xml @@ -0,0 +1,135 @@ + + + Pony + pony + *.pony + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postgresql_sql_dialect.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postgresql_sql_dialect.xml new file mode 100644 index 000000000..e901c1855 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postgresql_sql_dialect.xml @@ -0,0 +1,155 @@ + + + PostgreSQL SQL dialect + postgresql + postgres + text/x-postgresql + true + true + + + + + + + + + + + + + + + + + + + 6 + 12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 12 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postscript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postscript.xml new file mode 100644 index 000000000..15a3422d0 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postscript.xml @@ -0,0 +1,89 @@ + + + PostScript + postscript + postscr + *.ps + *.eps + application/postscript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/povray.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/povray.xml new file mode 100644 index 000000000..f37dab908 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/povray.xml @@ -0,0 +1,58 @@ + + + POVRay + pov + *.pov + *.inc + text/x-povray + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powerquery.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powerquery.xml new file mode 100644 index 000000000..0ff1e3557 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powerquery.xml @@ -0,0 +1,51 @@ + + + PowerQuery + powerquery + pq + *.pq + text/x-powerquery + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powershell.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powershell.xml new file mode 100644 index 000000000..b63a15081 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powershell.xml @@ -0,0 +1,230 @@ + + + PowerShell + powershell + posh + ps1 + psm1 + psd1 + pwsh + *.ps1 + *.psm1 + *.psd1 + text/x-powershell + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prolog.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prolog.xml new file mode 100644 index 000000000..391bae36b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prolog.xml @@ -0,0 +1,115 @@ + + + Prolog + prolog + *.ecl + *.prolog + *.pro + *.pl + text/x-prolog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promela.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promela.xml new file mode 100644 index 000000000..84558c3b6 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promela.xml @@ -0,0 +1,119 @@ + + + + Promela + promela + *.pml + *.prom + *.prm + *.promela + *.pr + *.pm + text/x-promela + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promql.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promql.xml new file mode 100644 index 000000000..e95e333d5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promql.xml @@ -0,0 +1,123 @@ + + + PromQL + promql + *.promql + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/properties.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/properties.xml new file mode 100644 index 000000000..d5ae0a283 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/properties.xml @@ -0,0 +1,45 @@ + + + properties + java-properties + *.properties + text/x-java-properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/protocol_buffer.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/protocol_buffer.xml new file mode 100644 index 000000000..f8f0c8d79 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/protocol_buffer.xml @@ -0,0 +1,124 @@ + + + Protocol Buffer + protobuf + proto + *.proto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prql.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prql.xml new file mode 100644 index 000000000..21f21c65c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prql.xml @@ -0,0 +1,161 @@ + + + PRQL + prql + *.prql + application/prql + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/psl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/psl.xml new file mode 100644 index 000000000..ab375dae4 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/psl.xml @@ -0,0 +1,213 @@ + + + PSL + psl + *.psl + *.BATCH + *.TRIG + *.PROC + text/x-psl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/puppet.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/puppet.xml new file mode 100644 index 000000000..fbb587cf5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/puppet.xml @@ -0,0 +1,100 @@ + + + Puppet + puppet + *.pp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python.xml new file mode 100644 index 000000000..eaa9c3076 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python.xml @@ -0,0 +1,595 @@ + + + Python + python + py + sage + python3 + py3 + starlark + *.py + *.pyi + *.pyw + *.jy + *.sage + *.sc + SConstruct + SConscript + *.bzl + BUCK + BUILD + BUILD.bazel + WORKSPACE + WORKSPACE.bzlmod + WORKSPACE.bazel + MODULE.bazel + REPO.bazel + *.star + *.tac + text/x-python + application/x-python + text/x-python3 + application/x-python3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python_2.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python_2.xml new file mode 100644 index 000000000..3297a2260 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python_2.xml @@ -0,0 +1,356 @@ + + + Python 2 + python2 + py2 + text/x-python2 + application/x-python2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qbasic.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qbasic.xml new file mode 100644 index 000000000..193fe1807 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qbasic.xml @@ -0,0 +1,173 @@ + + + QBasic + qbasic + basic + *.BAS + *.bas + text/basic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qml.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qml.xml new file mode 100644 index 000000000..43eb3eb3d --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qml.xml @@ -0,0 +1,113 @@ + + + QML + qml + qbs + *.qml + *.qbs + application/x-qml + application/x-qt.qbs+qml + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/r.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/r.xml new file mode 100644 index 000000000..c1fba4e5e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/r.xml @@ -0,0 +1,128 @@ + + + R + splus + s + r + *.S + *.R + *.r + .Rhistory + .Rprofile + .Renviron + text/S-plus + text/S + text/x-r-source + text/x-r + text/x-R + text/x-r-history + text/x-r-profile + 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/racket.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/racket.xml new file mode 100644 index 000000000..6cdd30312 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/racket.xml @@ -0,0 +1,260 @@ + + + Racket + racket + rkt + *.rkt + *.rktd + *.rktl + text/x-racket + application/x-racket + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ragel.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ragel.xml new file mode 100644 index 000000000..69638d2ec --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ragel.xml @@ -0,0 +1,149 @@ + + + Ragel + ragel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/react.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/react.xml new file mode 100644 index 000000000..a4109b094 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/react.xml @@ -0,0 +1,236 @@ + + + react + jsx + react + *.jsx + *.react + text/jsx + text/typescript-jsx + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reasonml.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reasonml.xml new file mode 100644 index 000000000..8b7bcc506 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reasonml.xml @@ -0,0 +1,147 @@ + + + ReasonML + reason + reasonml + *.re + *.rei + text/x-reasonml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reg.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reg.xml new file mode 100644 index 000000000..501d38033 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reg.xml @@ -0,0 +1,68 @@ + + + reg + registry + *.reg + text/x-windows-registry + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rego.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rego.xml new file mode 100644 index 000000000..517b7133b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rego.xml @@ -0,0 +1,94 @@ + + + Rego + rego + *.rego + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rexx.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rexx.xml new file mode 100644 index 000000000..e682500cb --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rexx.xml @@ -0,0 +1,127 @@ + + + Rexx + rexx + arexx + *.rexx + *.rex + *.rx + *.arexx + text/x-rexx + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rgbasm.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rgbasm.xml new file mode 100644 index 000000000..6814ca1c7 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rgbasm.xml @@ -0,0 +1,239 @@ + + + RGBDS Assembly + rgbasm + *.asm + 0.5 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ring.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ring.xml new file mode 100644 index 000000000..44454ea8f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ring.xml @@ -0,0 +1,74 @@ + + + Ring + ring + *.ring + *.rh + *.rform + text/x-ring + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpgle.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpgle.xml new file mode 100644 index 000000000..c74cd2c39 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpgle.xml @@ -0,0 +1,176 @@ + + + RPGLE + SQLRPGLE + RPG IV + *.RPGLE + *.rpgle + *.SQLRPGLE + *.sqlrpgle + text/x-rpgle + text/x-sqlrpgle + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpm_spec.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpm_spec.xml new file mode 100644 index 000000000..8362772a6 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpm_spec.xml @@ -0,0 +1,58 @@ + + + + RPMSpec + spec + *.spec + text/x-rpm-spec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ruby.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ruby.xml new file mode 100644 index 000000000..15e96ba81 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ruby.xml @@ -0,0 +1,728 @@ + + + Ruby + rb + ruby + duby + *.rb + *.rbw + Rakefile + *.rake + *.gemspec + *.rbx + *.duby + Gemfile + *.gemfile + Vagrantfile + Appraisals + .pryrc + *.json.jbuilder + text/x-ruby + application/x-ruby + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rust.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rust.xml new file mode 100644 index 000000000..083b96ffe --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rust.xml @@ -0,0 +1,375 @@ + + + Rust + rust + rs + *.rs + *.rs.in + text/rust + text/x-rust + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sas.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sas.xml new file mode 100644 index 000000000..af1107bf9 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sas.xml @@ -0,0 +1,191 @@ + + + SAS + sas + *.SAS + *.sas + text/x-sas + text/sas + application/x-sas + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sass.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sass.xml new file mode 100644 index 000000000..f80159491 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sass.xml @@ -0,0 +1,362 @@ + + + Sass + sass + *.sass + text/x-sass + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scala.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scala.xml new file mode 100644 index 000000000..2f8ddd49e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scala.xml @@ -0,0 +1,274 @@ + + + Scala + scala + *.scala + text/x-scala + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scdoc.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scdoc.xml new file mode 100644 index 000000000..1b3a876ef --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scdoc.xml @@ -0,0 +1,115 @@ + + + scdoc + scdoc + *.scd + *.scdoc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scheme.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scheme.xml new file mode 100644 index 000000000..0198bd722 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scheme.xml @@ -0,0 +1,106 @@ + + + Scheme + scheme + scm + *.scm + *.ss + text/x-scheme + application/x-scheme + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scilab.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scilab.xml new file mode 100644 index 000000000..9e109495f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scilab.xml @@ -0,0 +1,98 @@ + + + Scilab + scilab + *.sci + *.sce + *.tst + text/scilab + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scss.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scss.xml new file mode 100644 index 000000000..ee060fc2b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scss.xml @@ -0,0 +1,373 @@ + + + SCSS + scss + *.scss + text/x-scss + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sed.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sed.xml new file mode 100644 index 000000000..fd77d083f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sed.xml @@ -0,0 +1,92 @@ + + + + Sed + sed + gsed + ssed + *.sed + *.[gs]sed + text/x-sed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sieve.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sieve.xml new file mode 100644 index 000000000..fc605638a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sieve.xml @@ -0,0 +1,61 @@ + + + Sieve + sieve + *.siv + *.sieve + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smali.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smali.xml new file mode 100644 index 000000000..e468766d1 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smali.xml @@ -0,0 +1,73 @@ + + + + Smali + smali + *.smali + text/smali + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smalltalk.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smalltalk.xml new file mode 100644 index 000000000..00271118f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smalltalk.xml @@ -0,0 +1,294 @@ + + + Smalltalk + smalltalk + squeak + st + *.st + text/x-smalltalk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smarty.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smarty.xml new file mode 100644 index 000000000..dd7752c58 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smarty.xml @@ -0,0 +1,79 @@ + + + Smarty + smarty + *.tpl + application/x-smarty + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snbt.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snbt.xml new file mode 100644 index 000000000..fdb12d0fe --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snbt.xml @@ -0,0 +1,58 @@ + + + + SNBT + snbt + *.snbt + text/snbt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snobol.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snobol.xml new file mode 100644 index 000000000..f53dbcba1 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snobol.xml @@ -0,0 +1,95 @@ + + + Snobol + snobol + *.snobol + text/x-snobol + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/solidity.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/solidity.xml new file mode 100644 index 000000000..24c4ccbae --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/solidity.xml @@ -0,0 +1,291 @@ + + + Solidity + sol + solidity + *.sol + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sourcepawn.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sourcepawn.xml new file mode 100644 index 000000000..caca401e3 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sourcepawn.xml @@ -0,0 +1,59 @@ + + + SourcePawn + sp + *.sp + *.inc + text/x-sourcepawn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/spade.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/spade.xml new file mode 100644 index 000000000..4dfe3292b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/spade.xml @@ -0,0 +1,292 @@ + + + Spade + spade + *.spade + text/spade + text/x-spade + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sparql.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sparql.xml new file mode 100644 index 000000000..7dc65af71 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sparql.xml @@ -0,0 +1,160 @@ + + + SPARQL + sparql + *.rq + *.sparql + application/sparql-query + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sql.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sql.xml new file mode 100644 index 000000000..b542b65fe --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sql.xml @@ -0,0 +1,90 @@ + + + SQL + sql + *.sql + text/x-sql + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/squidconf.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/squidconf.xml new file mode 100644 index 000000000..cbd8dbce3 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/squidconf.xml @@ -0,0 +1,63 @@ + + + SquidConf + squidconf + squid.conf + squid + squid.conf + text/x-squidconf + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/standard_ml.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/standard_ml.xml new file mode 100644 index 000000000..39cf4f2af --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/standard_ml.xml @@ -0,0 +1,548 @@ + + + Standard ML + sml + *.sml + *.sig + *.fun + text/x-standardml + application/x-standardml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stas.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stas.xml new file mode 100644 index 000000000..56b4f9238 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stas.xml @@ -0,0 +1,85 @@ + + + stas + *.stas + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stylus.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stylus.xml new file mode 100644 index 000000000..c2d88073a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stylus.xml @@ -0,0 +1,132 @@ + + + Stylus + stylus + *.styl + text/x-styl + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/swift.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/swift.xml new file mode 100644 index 000000000..416bf90c5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/swift.xml @@ -0,0 +1,207 @@ + + + Swift + swift + *.swift + text/x-swift + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemd.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemd.xml new file mode 100644 index 000000000..e31bfc29f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemd.xml @@ -0,0 +1,63 @@ + + + SYSTEMD + systemd + *.automount + *.device + *.dnssd + *.link + *.mount + *.netdev + *.network + *.path + *.scope + *.service + *.slice + *.socket + *.swap + *.target + *.timer + text/plain + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemverilog.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemverilog.xml new file mode 100644 index 000000000..fac3da235 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemverilog.xml @@ -0,0 +1,181 @@ + + + systemverilog + systemverilog + sv + *.sv + *.svh + text/x-systemverilog + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tablegen.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tablegen.xml new file mode 100644 index 000000000..a020ce804 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tablegen.xml @@ -0,0 +1,69 @@ + + + TableGen + tablegen + *.td + text/x-tablegen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tal.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tal.xml new file mode 100644 index 000000000..a071d4c1a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tal.xml @@ -0,0 +1,43 @@ + + + + Tal + tal + uxntal + *.tal + text/x-uxntal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tasm.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tasm.xml new file mode 100644 index 000000000..1347f5396 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tasm.xml @@ -0,0 +1,135 @@ + + + TASM + tasm + *.asm + *.ASM + *.tasm + text/x-tasm + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcl.xml new file mode 100644 index 000000000..7ed69bc26 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcl.xml @@ -0,0 +1,272 @@ + + + Tcl + tcl + *.tcl + *.rvt + text/x-tcl + text/x-script.tcl + application/x-tcl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcsh.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcsh.xml new file mode 100644 index 000000000..9895643c4 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcsh.xml @@ -0,0 +1,121 @@ + + + Tcsh + tcsh + csh + *.tcsh + *.csh + application/x-csh + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/termcap.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/termcap.xml new file mode 100644 index 000000000..e863bbd05 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/termcap.xml @@ -0,0 +1,75 @@ + + + Termcap + termcap + termcap + termcap.src + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terminfo.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terminfo.xml new file mode 100644 index 000000000..9e8f56ec4 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terminfo.xml @@ -0,0 +1,84 @@ + + + Terminfo + terminfo + terminfo + terminfo.src + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terraform.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terraform.xml new file mode 100644 index 000000000..f8df6be2a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terraform.xml @@ -0,0 +1,149 @@ + + + Terraform + terraform + tf + hcl + *.tf + *.hcl + application/x-tf + application/x-terraform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tex.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tex.xml new file mode 100644 index 000000000..809bb9a29 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tex.xml @@ -0,0 +1,113 @@ + + + TeX + tex + latex + *.tex + *.aux + *.toc + text/x-tex + text/x-latex + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/thrift.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/thrift.xml new file mode 100644 index 000000000..f14257d4f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/thrift.xml @@ -0,0 +1,154 @@ + + + Thrift + thrift + *.thrift + application/x-thrift + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/toml.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/toml.xml new file mode 100644 index 000000000..87bd19df4 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/toml.xml @@ -0,0 +1,45 @@ + + + TOML + toml + *.toml + Pipfile + poetry.lock + uv.lock + text/x-toml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tradingview.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tradingview.xml new file mode 100644 index 000000000..3671f61ed --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tradingview.xml @@ -0,0 +1,81 @@ + + + TradingView + tradingview + tv + *.tv + text/x-tradingview + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/transact-sql.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/transact-sql.xml new file mode 100644 index 000000000..b0490aa7c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/transact-sql.xml @@ -0,0 +1,137 @@ + + + Transact-SQL + tsql + t-sql + text/x-tsql + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turing.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turing.xml new file mode 100644 index 000000000..4eab69b73 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turing.xml @@ -0,0 +1,82 @@ + + + Turing + turing + *.turing + *.tu + text/x-turing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turtle.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turtle.xml new file mode 100644 index 000000000..7c572f9ca --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turtle.xml @@ -0,0 +1,170 @@ + + + Turtle + turtle + *.ttl + text/turtle + application/x-turtle + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/twig.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/twig.xml new file mode 100644 index 000000000..de95c5f91 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/twig.xml @@ -0,0 +1,155 @@ + + + Twig + twig + *.twig + application/x-twig + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/txtpb.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/txtpb.xml new file mode 100644 index 000000000..9cb48c031 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/txtpb.xml @@ -0,0 +1,162 @@ + + + Protocol Buffer Text Format + txtpb + *.txtpb + *.textproto + *.textpb + *.pbtxt + application/x-protobuf-text + false + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typescript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typescript.xml new file mode 100644 index 000000000..b39c9649c --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typescript.xml @@ -0,0 +1,302 @@ + + + TypeScript + ts + tsx + typescript + *.ts + *.tsx + *.mts + *.cts + text/x-typescript + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscript.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscript.xml new file mode 100644 index 000000000..bc416d472 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscript.xml @@ -0,0 +1,178 @@ + + + TypoScript + typoscript + *.ts + text/x-typoscript + true + 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscriptcssdata.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscriptcssdata.xml new file mode 100644 index 000000000..62c42c153 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscriptcssdata.xml @@ -0,0 +1,52 @@ + + + TypoScriptCssData + typoscriptcssdata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscripthtmldata.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscripthtmldata.xml new file mode 100644 index 000000000..1b0af3a4e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscripthtmldata.xml @@ -0,0 +1,52 @@ + + + TypoScriptHtmlData + typoscripthtmldata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typst.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typst.xml new file mode 100644 index 000000000..330dc40fa --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typst.xml @@ -0,0 +1,108 @@ + + + + Typst + typst + *.typ + text/x-typst + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ucode.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ucode.xml new file mode 100644 index 000000000..054fa8913 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ucode.xml @@ -0,0 +1,147 @@ + + + ucode + *.uc + application/x.ucode + text/x.ucode + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v.xml new file mode 100644 index 000000000..e1af3d1cd --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v.xml @@ -0,0 +1,355 @@ + + + V + v + vlang + *.v + *.vv + v.mod + text/x-v + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v_shell.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v_shell.xml new file mode 100644 index 000000000..34ce61065 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v_shell.xml @@ -0,0 +1,365 @@ + + + V shell + vsh + vshell + *.vsh + text/x-vsh + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vala.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vala.xml new file mode 100644 index 000000000..17c1acf48 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vala.xml @@ -0,0 +1,72 @@ + + + + Vala + vala + vapi + *.vala + *.vapi + text/x-vala + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vb_net.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vb_net.xml new file mode 100644 index 000000000..9f85afd0e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vb_net.xml @@ -0,0 +1,162 @@ + + + VB.net + vb.net + vbnet + *.vb + *.bas + text/x-vbnet + text/x-vba + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/verilog.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/verilog.xml new file mode 100644 index 000000000..cd4b9ff09 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/verilog.xml @@ -0,0 +1,158 @@ + + + verilog + verilog + v + *.v + text/x-verilog + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhdl.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhdl.xml new file mode 100644 index 000000000..aa4204486 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhdl.xml @@ -0,0 +1,171 @@ + + + VHDL + vhdl + *.vhdl + *.vhd + text/x-vhdl + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhs.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhs.xml new file mode 100644 index 000000000..ee84d1292 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhs.xml @@ -0,0 +1,48 @@ + + + VHS + vhs + tape + cassette + *.tape + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/viml.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/viml.xml new file mode 100644 index 000000000..43e6bfa74 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/viml.xml @@ -0,0 +1,85 @@ + + + VimL + vim + *.vim + .vimrc + .exrc + .gvimrc + _vimrc + _exrc + _gvimrc + vimrc + gvimrc + text/x-vim + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vue.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vue.xml new file mode 100644 index 000000000..e2f75e1dc --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vue.xml @@ -0,0 +1,295 @@ + + + vue + vue + vuejs + *.vue + text/x-vue + application/x-vue + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wat.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wat.xml new file mode 100644 index 000000000..4a7f7e465 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wat.xml @@ -0,0 +1,149 @@ + + + WebAssembly Text Format + wast + wat + *.wat + *.wast + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wdte.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wdte.xml new file mode 100644 index 000000000..c663ee2fe --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wdte.xml @@ -0,0 +1,43 @@ + + + WDTE + *.wdte + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webgpu_shading_language.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webgpu_shading_language.xml new file mode 100644 index 000000000..ea2b6e1ef --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webgpu_shading_language.xml @@ -0,0 +1,142 @@ + + + WebGPU Shading Language + wgsl + *.wgsl + text/wgsl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webvtt.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webvtt.xml new file mode 100644 index 000000000..08a7efc3a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webvtt.xml @@ -0,0 +1,283 @@ + + + WebVTT + vtt + *.vtt + text/vtt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/whiley.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/whiley.xml new file mode 100644 index 000000000..1762c966e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/whiley.xml @@ -0,0 +1,57 @@ + + + Whiley + whiley + *.whiley + text/x-whiley + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xml.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xml.xml new file mode 100644 index 000000000..99d5d5302 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xml.xml @@ -0,0 +1,96 @@ + + + XML + xml + *.xml + *.xsl + *.rss + *.xslt + *.xsd + *.wsdl + *.wsf + *.svg + *.qrc + *.csproj + *.vcxproj + *.fsproj + text/xml + application/xml + image/svg+xml + application/rss+xml + application/atom+xml + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xorg.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xorg.xml new file mode 100644 index 000000000..53bf43286 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xorg.xml @@ -0,0 +1,35 @@ + + + Xorg + xorg.conf + xorg.conf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yaml.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yaml.xml new file mode 100644 index 000000000..070755416 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yaml.xml @@ -0,0 +1,122 @@ + + + YAML + yaml + *.yaml + *.yml + text/x-yaml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yang.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yang.xml new file mode 100644 index 000000000..f3da7ceb3 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yang.xml @@ -0,0 +1,99 @@ + + + YANG + yang + *.yang + application/yang + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/z80_assembly.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/z80_assembly.xml new file mode 100644 index 000000000..5bb77a9ad --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/z80_assembly.xml @@ -0,0 +1,74 @@ + + + Z80 Assembly + z80 + *.z80 + *.asm + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zed.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zed.xml new file mode 100644 index 000000000..929f49532 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zed.xml @@ -0,0 +1,51 @@ + + + Zed + zed + *.zed + text/zed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zig.xml b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zig.xml new file mode 100644 index 000000000..5617f9141 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zig.xml @@ -0,0 +1,187 @@ + + + Zig + zig + *.zig + *.zon + text/zig + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/erb.go b/vendor/github.com/alecthomas/chroma/v2/lexers/erb.go new file mode 100644 index 000000000..2d141ed9e --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/erb.go @@ -0,0 +1,29 @@ +package lexers + +import ( + "strings" + + . "github.com/alecthomas/chroma/v2" // nolint +) + +// ERB lexer is Ruby embedded in HTML. +var ERB = Register(DelegatingLexer(HTML, MustNewXMLLexer( + embedded, + "embedded/erb.xml", +).SetConfig( + &Config{ + Name: "ERB", + Aliases: []string{"erb", "html+erb", "html+ruby", "rhtml"}, + Filenames: []string{"*.erb", "*.html.erb", "*.xml.erb", "*.rhtml"}, + MimeTypes: []string{"application/x-ruby-templating"}, + DotAll: true, + }, +).SetAnalyser(func(text string) float32 { + if strings.Contains(text, "<%=") && strings.Contains(text, "%>") { + return 0.4 + } + if strings.Contains(text, "<%") { + return 0.1 + } + return 0.0 +}))) diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/gemtext.go b/vendor/github.com/alecthomas/chroma/v2/lexers/gemtext.go new file mode 100644 index 000000000..2fed8d62f --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/gemtext.go @@ -0,0 +1,37 @@ +package lexers + +import ( + . "github.com/alecthomas/chroma/v2" // nolint +) + +// Gemtext lexer. +var Gemtext = Register(MustNewLexer( + &Config{ + Name: "Gemtext", + Aliases: []string{"gemtext", "gmi", "gmni", "gemini"}, + Filenames: []string{"*.gmi", "*.gmni", "*.gemini"}, + MimeTypes: []string{"text/gemini"}, + }, + gemtextRules, +)) + +func gemtextRules() Rules { + return Rules{ + "root": { + {`^(#[^#].+\r?\n)`, ByGroups(GenericHeading), nil}, + {`^(#{2,3}.+\r?\n)`, ByGroups(GenericSubheading), nil}, + {`^(\* )(.+\r?\n)`, ByGroups(Keyword, Text), nil}, + {`^(>)(.+\r?\n)`, ByGroups(Keyword, GenericEmph), nil}, + {"^(```\\r?\\n)([\\w\\W]*?)(^```)(.+\\r?\\n)?", ByGroups(String, Text, String, Comment), nil}, + { + "^(```)(\\w+)(\\r?\\n)([\\w\\W]*?)(^```)(.+\\r?\\n)?", + UsingByGroup(2, 4, String, String, String, Text, String, Comment), + nil, + }, + {"^(```)(.+\\r?\\n)([\\w\\W]*?)(^```)(.+\\r?\\n)?", ByGroups(String, String, Text, String, Comment), nil}, + {`^(=>)(\s*)([^\s]+)(\s*)$`, ByGroups(Keyword, Text, NameAttribute, Text), nil}, + {`^(=>)(\s*)([^\s]+)(\s+)(.+)$`, ByGroups(Keyword, Text, NameAttribute, Text, NameTag), nil}, + {`(.|(?:\r?\n))`, ByGroups(Text), nil}, + }, + } +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/genshi.go b/vendor/github.com/alecthomas/chroma/v2/lexers/genshi.go new file mode 100644 index 000000000..7f396f4b8 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/genshi.go @@ -0,0 +1,118 @@ +package lexers + +import ( + . "github.com/alecthomas/chroma/v2" // nolint +) + +// Genshi Text lexer. +var GenshiText = Register(MustNewLexer( + &Config{ + Name: "Genshi Text", + Aliases: []string{"genshitext"}, + Filenames: []string{}, + MimeTypes: []string{"application/x-genshi-text", "text/x-genshi"}, + }, + genshiTextRules, +)) + +func genshiTextRules() Rules { + return Rules{ + "root": { + {`[^#$\s]+`, Other, nil}, + {`^(\s*)(##.*)$`, ByGroups(Text, Comment), nil}, + {`^(\s*)(#)`, ByGroups(Text, CommentPreproc), Push("directive")}, + Include("variable"), + {`[#$\s]`, Other, nil}, + }, + "directive": { + {`\n`, Text, Pop(1)}, + {`(?:def|for|if)\s+.*`, Using("Python"), Pop(1)}, + {`(choose|when|with)([^\S\n]+)(.*)`, ByGroups(Keyword, Text, Using("Python")), Pop(1)}, + {`(choose|otherwise)\b`, Keyword, Pop(1)}, + {`(end\w*)([^\S\n]*)(.*)`, ByGroups(Keyword, Text, Comment), Pop(1)}, + }, + "variable": { + {`(?)`, ByGroups(CommentPreproc, Using("Python"), CommentPreproc), nil}, + {`<\s*(script|style)\s*.*?>.*?<\s*/\1\s*>`, Other, nil}, + {`<\s*py:[a-zA-Z0-9]+`, NameTag, Push("pytag")}, + {`<\s*[a-zA-Z0-9:.]+`, NameTag, Push("tag")}, + Include("variable"), + {`[<$]`, Other, nil}, + }, + "pytag": { + {`\s+`, Text, nil}, + {`[\w:-]+\s*=`, NameAttribute, Push("pyattr")}, + {`/?\s*>`, NameTag, Pop(1)}, + }, + "pyattr": { + {`(")(.*?)(")`, ByGroups(LiteralString, Using("Python"), LiteralString), Pop(1)}, + {`(')(.*?)(')`, ByGroups(LiteralString, Using("Python"), LiteralString), Pop(1)}, + {`[^\s>]+`, LiteralString, Pop(1)}, + }, + "tag": { + {`\s+`, Text, nil}, + {`py:[\w-]+\s*=`, NameAttribute, Push("pyattr")}, + {`[\w:-]+\s*=`, NameAttribute, Push("attr")}, + {`/?\s*>`, NameTag, Pop(1)}, + }, + "attr": { + {`"`, LiteralString, Push("attr-dstring")}, + {`'`, LiteralString, Push("attr-sstring")}, + {`[^\s>]*`, LiteralString, Pop(1)}, + }, + "attr-dstring": { + {`"`, LiteralString, Pop(1)}, + Include("strings"), + {`'`, LiteralString, nil}, + }, + "attr-sstring": { + {`'`, LiteralString, Pop(1)}, + Include("strings"), + {`'`, LiteralString, nil}, + }, + "strings": { + {`[^"'$]+`, LiteralString, nil}, + Include("variable"), + }, + "variable": { + {`(?>=|<<|>>|<=|>=|&\^=|&\^|\+=|-=|\*=|/=|%=|&=|\|=|&&|\|\||<-|\+\+|--|==|!=|:=|\.\.\.|[+\-*/%&])`, Operator, nil}, + {`([a-zA-Z_]\w*)(\s*)(\()`, ByGroups(NameFunction, UsingSelf("root"), Punctuation), nil}, + {`[|^<>=!()\[\]{}.,;:~]`, Punctuation, nil}, + {`[^\W\d]\w*`, NameOther, nil}, + }, + } +} + +var GoHTMLTemplate = Register(DelegatingLexer(HTML, MustNewXMLLexer( + embedded, + "embedded/go_template.xml", +).SetConfig( + &Config{ + Name: "Go HTML Template", + Aliases: []string{"go-html-template"}, + }, +))) + +var GoTextTemplate = Register(MustNewXMLLexer( + embedded, + "embedded/go_template.xml", +).SetConfig( + &Config{ + Name: "Go Text Template", + Aliases: []string{"go-text-template"}, + }, +)) diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/haxe.go b/vendor/github.com/alecthomas/chroma/v2/lexers/haxe.go new file mode 100644 index 000000000..9a72de865 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/haxe.go @@ -0,0 +1,647 @@ +package lexers + +import ( + . "github.com/alecthomas/chroma/v2" // nolint +) + +// Haxe lexer. +var Haxe = Register(MustNewLexer( + &Config{ + Name: "Haxe", + Aliases: []string{"hx", "haxe", "hxsl"}, + Filenames: []string{"*.hx", "*.hxsl"}, + MimeTypes: []string{"text/haxe", "text/x-haxe", "text/x-hx"}, + DotAll: true, + }, + haxeRules, +)) + +func haxeRules() Rules { + return Rules{ + "root": { + Include("spaces"), + Include("meta"), + {`(?:package)\b`, KeywordNamespace, Push("semicolon", "package")}, + {`(?:import)\b`, KeywordNamespace, Push("semicolon", "import")}, + {`(?:using)\b`, KeywordNamespace, Push("semicolon", "using")}, + {`(?:extern|private)\b`, KeywordDeclaration, nil}, + {`(?:abstract)\b`, KeywordDeclaration, Push("abstract")}, + {`(?:class|interface)\b`, KeywordDeclaration, Push("class")}, + {`(?:enum)\b`, KeywordDeclaration, Push("enum")}, + {`(?:typedef)\b`, KeywordDeclaration, Push("typedef")}, + {`(?=.)`, Text, Push("expr-statement")}, + }, + "spaces": { + {`\s+`, Text, nil}, + {`//[^\n\r]*`, CommentSingle, nil}, + {`/\*.*?\*/`, CommentMultiline, nil}, + {`(#)(if|elseif|else|end|error)\b`, CommentPreproc, MutatorFunc(haxePreProcMutator)}, + }, + "string-single-interpol": { + {`\$\{`, LiteralStringInterpol, Push("string-interpol-close", "expr")}, + {`\$\$`, LiteralStringEscape, nil}, + {`\$(?=(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+))`, LiteralStringInterpol, Push("ident")}, + Include("string-single"), + }, + "string-single": { + {`'`, LiteralStringSingle, Pop(1)}, + {`\\.`, LiteralStringEscape, nil}, + {`.`, LiteralStringSingle, nil}, + }, + "string-double": { + {`"`, LiteralStringDouble, Pop(1)}, + {`\\.`, LiteralStringEscape, nil}, + {`.`, LiteralStringDouble, nil}, + }, + "string-interpol-close": { + {`\$(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, LiteralStringInterpol, nil}, + {`\}`, LiteralStringInterpol, Pop(1)}, + }, + "package": { + Include("spaces"), + {`(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, NameNamespace, nil}, + {`\.`, Punctuation, Push("import-ident")}, + Default(Pop(1)), + }, + "import": { + Include("spaces"), + {`(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, NameNamespace, nil}, + {`\*`, Keyword, nil}, + {`\.`, Punctuation, Push("import-ident")}, + {`in`, KeywordNamespace, Push("ident")}, + Default(Pop(1)), + }, + "import-ident": { + Include("spaces"), + {`\*`, Keyword, Pop(1)}, + {`(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, NameNamespace, Pop(1)}, + }, + "using": { + Include("spaces"), + {`(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, NameNamespace, nil}, + {`\.`, Punctuation, Push("import-ident")}, + Default(Pop(1)), + }, + "preproc-error": { + {`\s+`, CommentPreproc, nil}, + {`'`, LiteralStringSingle, Push("#pop", "string-single")}, + {`"`, LiteralStringDouble, Push("#pop", "string-double")}, + Default(Pop(1)), + }, + "preproc-expr": { + {`\s+`, CommentPreproc, nil}, + {`\!`, CommentPreproc, nil}, + {`\(`, CommentPreproc, Push("#pop", "preproc-parenthesis")}, + {`(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, CommentPreproc, Pop(1)}, + {`\.[0-9]+`, LiteralNumberFloat, nil}, + {`[0-9]+[eE][+\-]?[0-9]+`, LiteralNumberFloat, nil}, + {`[0-9]+\.[0-9]*[eE][+\-]?[0-9]+`, LiteralNumberFloat, nil}, + {`[0-9]+\.[0-9]+`, LiteralNumberFloat, nil}, + {`[0-9]+\.(?!(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)|\.\.)`, LiteralNumberFloat, nil}, + {`0x[0-9a-fA-F]+`, LiteralNumberHex, nil}, + {`[0-9]+`, LiteralNumberInteger, nil}, + {`'`, LiteralStringSingle, Push("#pop", "string-single")}, + {`"`, LiteralStringDouble, Push("#pop", "string-double")}, + }, + "preproc-parenthesis": { + {`\s+`, CommentPreproc, nil}, + {`\)`, CommentPreproc, Pop(1)}, + Default(Push("preproc-expr-in-parenthesis")), + }, + "preproc-expr-chain": { + {`\s+`, CommentPreproc, nil}, + {`(?:%=|&=|\|=|\^=|\+=|\-=|\*=|/=|<<=|>\s*>\s*=|>\s*>\s*>\s*=|==|!=|<=|>\s*=|&&|\|\||<<|>>>|>\s*>|\.\.\.|<|>|%|&|\||\^|\+|\*|/|\-|=>|=)`, CommentPreproc, Push("#pop", "preproc-expr-in-parenthesis")}, + Default(Pop(1)), + }, + "preproc-expr-in-parenthesis": { + {`\s+`, CommentPreproc, nil}, + {`\!`, CommentPreproc, nil}, + {`\(`, CommentPreproc, Push("#pop", "preproc-expr-chain", "preproc-parenthesis")}, + {`(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, CommentPreproc, Push("#pop", "preproc-expr-chain")}, + {`\.[0-9]+`, LiteralNumberFloat, Push("#pop", "preproc-expr-chain")}, + {`[0-9]+[eE][+\-]?[0-9]+`, LiteralNumberFloat, Push("#pop", "preproc-expr-chain")}, + {`[0-9]+\.[0-9]*[eE][+\-]?[0-9]+`, LiteralNumberFloat, Push("#pop", "preproc-expr-chain")}, + {`[0-9]+\.[0-9]+`, LiteralNumberFloat, Push("#pop", "preproc-expr-chain")}, + {`[0-9]+\.(?!(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)|\.\.)`, LiteralNumberFloat, Push("#pop", "preproc-expr-chain")}, + {`0x[0-9a-fA-F]+`, LiteralNumberHex, Push("#pop", "preproc-expr-chain")}, + {`[0-9]+`, LiteralNumberInteger, Push("#pop", "preproc-expr-chain")}, + {`'`, LiteralStringSingle, Push("#pop", "preproc-expr-chain", "string-single")}, + {`"`, LiteralStringDouble, Push("#pop", "preproc-expr-chain", "string-double")}, + }, + "abstract": { + Include("spaces"), + Default(Pop(1), Push("abstract-body"), Push("abstract-relation"), Push("abstract-opaque"), Push("type-param-constraint"), Push("type-name")), + }, + "abstract-body": { + Include("spaces"), + {`\{`, Punctuation, Push("#pop", "class-body")}, + }, + "abstract-opaque": { + Include("spaces"), + {`\(`, Punctuation, Push("#pop", "parenthesis-close", "type")}, + Default(Pop(1)), + }, + "abstract-relation": { + Include("spaces"), + {`(?:to|from)`, KeywordDeclaration, Push("type")}, + {`,`, Punctuation, nil}, + Default(Pop(1)), + }, + "meta": { + Include("spaces"), + {`@`, NameDecorator, Push("meta-body", "meta-ident", "meta-colon")}, + }, + "meta-colon": { + Include("spaces"), + {`:`, NameDecorator, Pop(1)}, + Default(Pop(1)), + }, + "meta-ident": { + Include("spaces"), + {`(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, NameDecorator, Pop(1)}, + }, + "meta-body": { + Include("spaces"), + {`\(`, NameDecorator, Push("#pop", "meta-call")}, + Default(Pop(1)), + }, + "meta-call": { + Include("spaces"), + {`\)`, NameDecorator, Pop(1)}, + Default(Pop(1), Push("meta-call-sep"), Push("expr")), + }, + "meta-call-sep": { + Include("spaces"), + {`\)`, NameDecorator, Pop(1)}, + {`,`, Punctuation, Push("#pop", "meta-call")}, + }, + "typedef": { + Include("spaces"), + Default(Pop(1), Push("typedef-body"), Push("type-param-constraint"), Push("type-name")), + }, + "typedef-body": { + Include("spaces"), + {`=`, Operator, Push("#pop", "optional-semicolon", "type")}, + }, + "enum": { + Include("spaces"), + Default(Pop(1), Push("enum-body"), Push("bracket-open"), Push("type-param-constraint"), Push("type-name")), + }, + "enum-body": { + Include("spaces"), + Include("meta"), + {`\}`, Punctuation, Pop(1)}, + {`(?!(?:function|class|static|var|if|else|while|do|for|break|return|continue|extends|implements|import|switch|case|default|public|private|try|untyped|catch|new|this|throw|extern|enum|in|interface|cast|override|dynamic|typedef|package|inline|using|null|true|false|abstract)\b)(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, Name, Push("enum-member", "type-param-constraint")}, + }, + "enum-member": { + Include("spaces"), + {`\(`, Punctuation, Push("#pop", "semicolon", "flag", "function-param")}, + Default(Pop(1), Push("semicolon"), Push("flag")), + }, + "class": { + Include("spaces"), + Default(Pop(1), Push("class-body"), Push("bracket-open"), Push("extends"), Push("type-param-constraint"), Push("type-name")), + }, + "extends": { + Include("spaces"), + {`(?:extends|implements)\b`, KeywordDeclaration, Push("type")}, + {`,`, Punctuation, nil}, + Default(Pop(1)), + }, + "bracket-open": { + Include("spaces"), + {`\{`, Punctuation, Pop(1)}, + }, + "bracket-close": { + Include("spaces"), + {`\}`, Punctuation, Pop(1)}, + }, + "class-body": { + Include("spaces"), + Include("meta"), + {`\}`, Punctuation, Pop(1)}, + {`(?:static|public|private|override|dynamic|inline|macro)\b`, KeywordDeclaration, nil}, + Default(Push("class-member")), + }, + "class-member": { + Include("spaces"), + {`(var)\b`, KeywordDeclaration, Push("#pop", "optional-semicolon", "var")}, + {`(function)\b`, KeywordDeclaration, Push("#pop", "optional-semicolon", "class-method")}, + }, + "function-local": { + Include("spaces"), + {`(?!(?:function|class|static|var|if|else|while|do|for|break|return|continue|extends|implements|import|switch|case|default|public|private|try|untyped|catch|new|this|throw|extern|enum|in|interface|cast|override|dynamic|typedef|package|inline|using|null|true|false|abstract)\b)(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, NameFunction, Push("#pop", "optional-expr", "flag", "function-param", "parenthesis-open", "type-param-constraint")}, + Default(Pop(1), Push("optional-expr"), Push("flag"), Push("function-param"), Push("parenthesis-open"), Push("type-param-constraint")), + }, + "optional-expr": { + Include("spaces"), + Include("expr"), + Default(Pop(1)), + }, + "class-method": { + Include("spaces"), + {`(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, NameFunction, Push("#pop", "optional-expr", "flag", "function-param", "parenthesis-open", "type-param-constraint")}, + }, + "function-param": { + Include("spaces"), + {`\)`, Punctuation, Pop(1)}, + {`\?`, Punctuation, nil}, + {`(?!(?:function|class|static|var|if|else|while|do|for|break|return|continue|extends|implements|import|switch|case|default|public|private|try|untyped|catch|new|this|throw|extern|enum|in|interface|cast|override|dynamic|typedef|package|inline|using|null|true|false|abstract)\b)(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, Name, Push("#pop", "function-param-sep", "assign", "flag")}, + }, + "function-param-sep": { + Include("spaces"), + {`\)`, Punctuation, Pop(1)}, + {`,`, Punctuation, Push("#pop", "function-param")}, + }, + "prop-get-set": { + Include("spaces"), + {`\(`, Punctuation, Push("#pop", "parenthesis-close", "prop-get-set-opt", "comma", "prop-get-set-opt")}, + Default(Pop(1)), + }, + "prop-get-set-opt": { + Include("spaces"), + {`(?:default|null|never|dynamic|get|set)\b`, Keyword, Pop(1)}, + {`(?!(?:function|class|static|var|if|else|while|do|for|break|return|continue|extends|implements|import|switch|case|default|public|private|try|untyped|catch|new|this|throw|extern|enum|in|interface|cast|override|dynamic|typedef|package|inline|using|null|true|false|abstract)\b)(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, Text, Pop(1)}, + }, + "expr-statement": { + Include("spaces"), + Default(Pop(1), Push("optional-semicolon"), Push("expr")), + }, + "expr": { + Include("spaces"), + {`@`, NameDecorator, Push("#pop", "optional-expr", "meta-body", "meta-ident", "meta-colon")}, + {`(?:\+\+|\-\-|~(?!/)|!|\-)`, Operator, nil}, + {`\(`, Punctuation, Push("#pop", "expr-chain", "parenthesis")}, + {`(?:static|public|private|override|dynamic|inline)\b`, KeywordDeclaration, nil}, + {`(?:function)\b`, KeywordDeclaration, Push("#pop", "expr-chain", "function-local")}, + {`\{`, Punctuation, Push("#pop", "expr-chain", "bracket")}, + {`(?:true|false|null)\b`, KeywordConstant, Push("#pop", "expr-chain")}, + {`(?:this)\b`, Keyword, Push("#pop", "expr-chain")}, + {`(?:cast)\b`, Keyword, Push("#pop", "expr-chain", "cast")}, + {`(?:try)\b`, Keyword, Push("#pop", "catch", "expr")}, + {`(?:var)\b`, KeywordDeclaration, Push("#pop", "var")}, + {`(?:new)\b`, Keyword, Push("#pop", "expr-chain", "new")}, + {`(?:switch)\b`, Keyword, Push("#pop", "switch")}, + {`(?:if)\b`, Keyword, Push("#pop", "if")}, + {`(?:do)\b`, Keyword, Push("#pop", "do")}, + {`(?:while)\b`, Keyword, Push("#pop", "while")}, + {`(?:for)\b`, Keyword, Push("#pop", "for")}, + {`(?:untyped|throw)\b`, Keyword, nil}, + {`(?:return)\b`, Keyword, Push("#pop", "optional-expr")}, + {`(?:macro)\b`, Keyword, Push("#pop", "macro")}, + {`(?:continue|break)\b`, Keyword, Pop(1)}, + {`(?:\$\s*[a-z]\b|\$(?!(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)))`, Name, Push("#pop", "dollar")}, + {`(?!(?:function|class|static|var|if|else|while|do|for|break|return|continue|extends|implements|import|switch|case|default|public|private|try|untyped|catch|new|this|throw|extern|enum|in|interface|cast|override|dynamic|typedef|package|inline|using|null|true|false|abstract)\b)(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, Name, Push("#pop", "expr-chain")}, + {`\.[0-9]+`, LiteralNumberFloat, Push("#pop", "expr-chain")}, + {`[0-9]+[eE][+\-]?[0-9]+`, LiteralNumberFloat, Push("#pop", "expr-chain")}, + {`[0-9]+\.[0-9]*[eE][+\-]?[0-9]+`, LiteralNumberFloat, Push("#pop", "expr-chain")}, + {`[0-9]+\.[0-9]+`, LiteralNumberFloat, Push("#pop", "expr-chain")}, + {`[0-9]+\.(?!(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)|\.\.)`, LiteralNumberFloat, Push("#pop", "expr-chain")}, + {`0x[0-9a-fA-F]+`, LiteralNumberHex, Push("#pop", "expr-chain")}, + {`[0-9]+`, LiteralNumberInteger, Push("#pop", "expr-chain")}, + {`'`, LiteralStringSingle, Push("#pop", "expr-chain", "string-single-interpol")}, + {`"`, LiteralStringDouble, Push("#pop", "expr-chain", "string-double")}, + {`~/(\\\\|\\/|[^/\n])*/[gimsu]*`, LiteralStringRegex, Push("#pop", "expr-chain")}, + {`\[`, Punctuation, Push("#pop", "expr-chain", "array-decl")}, + }, + "expr-chain": { + Include("spaces"), + {`(?:\+\+|\-\-)`, Operator, nil}, + {`(?:%=|&=|\|=|\^=|\+=|\-=|\*=|/=|<<=|>\s*>\s*=|>\s*>\s*>\s*=|==|!=|<=|>\s*=|&&|\|\||<<|>>>|>\s*>|\.\.\.|<|>|%|&|\||\^|\+|\*|/|\-|=>|=)`, Operator, Push("#pop", "expr")}, + {`(?:in)\b`, Keyword, Push("#pop", "expr")}, + {`\?`, Operator, Push("#pop", "expr", "ternary", "expr")}, + {`(\.)((?!(?:function|class|static|var|if|else|while|do|for|break|return|continue|extends|implements|import|switch|case|default|public|private|try|untyped|catch|new|this|throw|extern|enum|in|interface|cast|override|dynamic|typedef|package|inline|using|null|true|false|abstract)\b)(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+))`, ByGroups(Punctuation, Name), nil}, + {`\[`, Punctuation, Push("array-access")}, + {`\(`, Punctuation, Push("call")}, + Default(Pop(1)), + }, + "macro": { + Include("spaces"), + Include("meta"), + {`:`, Punctuation, Push("#pop", "type")}, + {`(?:extern|private)\b`, KeywordDeclaration, nil}, + {`(?:abstract)\b`, KeywordDeclaration, Push("#pop", "optional-semicolon", "abstract")}, + {`(?:class|interface)\b`, KeywordDeclaration, Push("#pop", "optional-semicolon", "macro-class")}, + {`(?:enum)\b`, KeywordDeclaration, Push("#pop", "optional-semicolon", "enum")}, + {`(?:typedef)\b`, KeywordDeclaration, Push("#pop", "optional-semicolon", "typedef")}, + Default(Pop(1), Push("expr")), + }, + "macro-class": { + {`\{`, Punctuation, Push("#pop", "class-body")}, + Include("class"), + }, + "cast": { + Include("spaces"), + {`\(`, Punctuation, Push("#pop", "parenthesis-close", "cast-type", "expr")}, + Default(Pop(1), Push("expr")), + }, + "cast-type": { + Include("spaces"), + {`,`, Punctuation, Push("#pop", "type")}, + Default(Pop(1)), + }, + "catch": { + Include("spaces"), + {`(?:catch)\b`, Keyword, Push("expr", "function-param", "parenthesis-open")}, + Default(Pop(1)), + }, + "do": { + Include("spaces"), + Default(Pop(1), Push("do-while"), Push("expr")), + }, + "do-while": { + Include("spaces"), + {`(?:while)\b`, Keyword, Push("#pop", "parenthesis", "parenthesis-open")}, + }, + "while": { + Include("spaces"), + {`\(`, Punctuation, Push("#pop", "expr", "parenthesis")}, + }, + "for": { + Include("spaces"), + {`\(`, Punctuation, Push("#pop", "expr", "parenthesis")}, + }, + "if": { + Include("spaces"), + {`\(`, Punctuation, Push("#pop", "else", "optional-semicolon", "expr", "parenthesis")}, + }, + "else": { + Include("spaces"), + {`(?:else)\b`, Keyword, Push("#pop", "expr")}, + Default(Pop(1)), + }, + "switch": { + Include("spaces"), + Default(Pop(1), Push("switch-body"), Push("bracket-open"), Push("expr")), + }, + "switch-body": { + Include("spaces"), + {`(?:case|default)\b`, Keyword, Push("case-block", "case")}, + {`\}`, Punctuation, Pop(1)}, + }, + "case": { + Include("spaces"), + {`:`, Punctuation, Pop(1)}, + Default(Pop(1), Push("case-sep"), Push("case-guard"), Push("expr")), + }, + "case-sep": { + Include("spaces"), + {`:`, Punctuation, Pop(1)}, + {`,`, Punctuation, Push("#pop", "case")}, + }, + "case-guard": { + Include("spaces"), + {`(?:if)\b`, Keyword, Push("#pop", "parenthesis", "parenthesis-open")}, + Default(Pop(1)), + }, + "case-block": { + Include("spaces"), + {`(?!(?:case|default)\b|\})`, Keyword, Push("expr-statement")}, + Default(Pop(1)), + }, + "new": { + Include("spaces"), + Default(Pop(1), Push("call"), Push("parenthesis-open"), Push("type")), + }, + "array-decl": { + Include("spaces"), + {`\]`, Punctuation, Pop(1)}, + Default(Pop(1), Push("array-decl-sep"), Push("expr")), + }, + "array-decl-sep": { + Include("spaces"), + {`\]`, Punctuation, Pop(1)}, + {`,`, Punctuation, Push("#pop", "array-decl")}, + }, + "array-access": { + Include("spaces"), + Default(Pop(1), Push("array-access-close"), Push("expr")), + }, + "array-access-close": { + Include("spaces"), + {`\]`, Punctuation, Pop(1)}, + }, + "comma": { + Include("spaces"), + {`,`, Punctuation, Pop(1)}, + }, + "colon": { + Include("spaces"), + {`:`, Punctuation, Pop(1)}, + }, + "semicolon": { + Include("spaces"), + {`;`, Punctuation, Pop(1)}, + }, + "optional-semicolon": { + Include("spaces"), + {`;`, Punctuation, Pop(1)}, + Default(Pop(1)), + }, + "ident": { + Include("spaces"), + {`(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, Name, Pop(1)}, + }, + "dollar": { + Include("spaces"), + {`\{`, Punctuation, Push("#pop", "expr-chain", "bracket-close", "expr")}, + Default(Pop(1), Push("expr-chain")), + }, + "type-name": { + Include("spaces"), + {`_*[A-Z]\w*`, Name, Pop(1)}, + }, + "type-full-name": { + Include("spaces"), + {`\.`, Punctuation, Push("ident")}, + Default(Pop(1)), + }, + "type": { + Include("spaces"), + {`\?`, Punctuation, nil}, + {`(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, Name, Push("#pop", "type-check", "type-full-name")}, + {`\{`, Punctuation, Push("#pop", "type-check", "type-struct")}, + {`\(`, Punctuation, Push("#pop", "type-check", "type-parenthesis")}, + }, + "type-parenthesis": { + Include("spaces"), + Default(Pop(1), Push("parenthesis-close"), Push("type")), + }, + "type-check": { + Include("spaces"), + {`->`, Punctuation, Push("#pop", "type")}, + {`<(?!=)`, Punctuation, Push("type-param")}, + Default(Pop(1)), + }, + "type-struct": { + Include("spaces"), + {`\}`, Punctuation, Pop(1)}, + {`\?`, Punctuation, nil}, + {`>`, Punctuation, Push("comma", "type")}, + {`(?!(?:function|class|static|var|if|else|while|do|for|break|return|continue|extends|implements|import|switch|case|default|public|private|try|untyped|catch|new|this|throw|extern|enum|in|interface|cast|override|dynamic|typedef|package|inline|using|null|true|false|abstract)\b)(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, Name, Push("#pop", "type-struct-sep", "type", "colon")}, + Include("class-body"), + }, + "type-struct-sep": { + Include("spaces"), + {`\}`, Punctuation, Pop(1)}, + {`,`, Punctuation, Push("#pop", "type-struct")}, + }, + "type-param-type": { + {`\.[0-9]+`, LiteralNumberFloat, Pop(1)}, + {`[0-9]+[eE][+\-]?[0-9]+`, LiteralNumberFloat, Pop(1)}, + {`[0-9]+\.[0-9]*[eE][+\-]?[0-9]+`, LiteralNumberFloat, Pop(1)}, + {`[0-9]+\.[0-9]+`, LiteralNumberFloat, Pop(1)}, + {`[0-9]+\.(?!(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)|\.\.)`, LiteralNumberFloat, Pop(1)}, + {`0x[0-9a-fA-F]+`, LiteralNumberHex, Pop(1)}, + {`[0-9]+`, LiteralNumberInteger, Pop(1)}, + {`'`, LiteralStringSingle, Push("#pop", "string-single")}, + {`"`, LiteralStringDouble, Push("#pop", "string-double")}, + {`~/(\\\\|\\/|[^/\n])*/[gim]*`, LiteralStringRegex, Pop(1)}, + {`\[`, Operator, Push("#pop", "array-decl")}, + Include("type"), + }, + "type-param": { + Include("spaces"), + Default(Pop(1), Push("type-param-sep"), Push("type-param-type")), + }, + "type-param-sep": { + Include("spaces"), + {`>`, Punctuation, Pop(1)}, + {`,`, Punctuation, Push("#pop", "type-param")}, + }, + "type-param-constraint": { + Include("spaces"), + {`<(?!=)`, Punctuation, Push("#pop", "type-param-constraint-sep", "type-param-constraint-flag", "type-name")}, + Default(Pop(1)), + }, + "type-param-constraint-sep": { + Include("spaces"), + {`>`, Punctuation, Pop(1)}, + {`,`, Punctuation, Push("#pop", "type-param-constraint-sep", "type-param-constraint-flag", "type-name")}, + }, + "type-param-constraint-flag": { + Include("spaces"), + {`:`, Punctuation, Push("#pop", "type-param-constraint-flag-type")}, + Default(Pop(1)), + }, + "type-param-constraint-flag-type": { + Include("spaces"), + {`\(`, Punctuation, Push("#pop", "type-param-constraint-flag-type-sep", "type")}, + Default(Pop(1), Push("type")), + }, + "type-param-constraint-flag-type-sep": { + Include("spaces"), + {`\)`, Punctuation, Pop(1)}, + {`,`, Punctuation, Push("type")}, + }, + "parenthesis": { + Include("spaces"), + Default(Pop(1), Push("parenthesis-close"), Push("flag"), Push("expr")), + }, + "parenthesis-open": { + Include("spaces"), + {`\(`, Punctuation, Pop(1)}, + }, + "parenthesis-close": { + Include("spaces"), + {`\)`, Punctuation, Pop(1)}, + }, + "var": { + Include("spaces"), + {`(?!(?:function|class|static|var|if|else|while|do|for|break|return|continue|extends|implements|import|switch|case|default|public|private|try|untyped|catch|new|this|throw|extern|enum|in|interface|cast|override|dynamic|typedef|package|inline|using|null|true|false|abstract)\b)(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, Text, Push("#pop", "var-sep", "assign", "flag", "prop-get-set")}, + }, + "var-sep": { + Include("spaces"), + {`,`, Punctuation, Push("#pop", "var")}, + Default(Pop(1)), + }, + "assign": { + Include("spaces"), + {`=`, Operator, Push("#pop", "expr")}, + Default(Pop(1)), + }, + "flag": { + Include("spaces"), + {`:`, Punctuation, Push("#pop", "type")}, + Default(Pop(1)), + }, + "ternary": { + Include("spaces"), + {`:`, Operator, Pop(1)}, + }, + "call": { + Include("spaces"), + {`\)`, Punctuation, Pop(1)}, + Default(Pop(1), Push("call-sep"), Push("expr")), + }, + "call-sep": { + Include("spaces"), + {`\)`, Punctuation, Pop(1)}, + {`,`, Punctuation, Push("#pop", "call")}, + }, + "bracket": { + Include("spaces"), + {`(?!(?:\$\s*[a-z]\b|\$(?!(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+))))(?!(?:function|class|static|var|if|else|while|do|for|break|return|continue|extends|implements|import|switch|case|default|public|private|try|untyped|catch|new|this|throw|extern|enum|in|interface|cast|override|dynamic|typedef|package|inline|using|null|true|false|abstract)\b)(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, Name, Push("#pop", "bracket-check")}, + {`'`, LiteralStringSingle, Push("#pop", "bracket-check", "string-single")}, + {`"`, LiteralStringDouble, Push("#pop", "bracket-check", "string-double")}, + Default(Pop(1), Push("block")), + }, + "bracket-check": { + Include("spaces"), + {`:`, Punctuation, Push("#pop", "object-sep", "expr")}, + Default(Pop(1), Push("block"), Push("optional-semicolon"), Push("expr-chain")), + }, + "block": { + Include("spaces"), + {`\}`, Punctuation, Pop(1)}, + Default(Push("expr-statement")), + }, + "object": { + Include("spaces"), + {`\}`, Punctuation, Pop(1)}, + Default(Pop(1), Push("object-sep"), Push("expr"), Push("colon"), Push("ident-or-string")), + }, + "ident-or-string": { + Include("spaces"), + {`(?!(?:function|class|static|var|if|else|while|do|for|break|return|continue|extends|implements|import|switch|case|default|public|private|try|untyped|catch|new|this|throw|extern|enum|in|interface|cast|override|dynamic|typedef|package|inline|using|null|true|false|abstract)\b)(?:_*[a-z]\w*|_+[0-9]\w*|_*[A-Z]\w*|_+|\$\w+)`, Name, Pop(1)}, + {`'`, LiteralStringSingle, Push("#pop", "string-single")}, + {`"`, LiteralStringDouble, Push("#pop", "string-double")}, + }, + "object-sep": { + Include("spaces"), + {`\}`, Punctuation, Pop(1)}, + {`,`, Punctuation, Push("#pop", "object")}, + }, + } +} + +func haxePreProcMutator(state *LexerState) error { + stack, ok := state.Get("haxe-pre-proc").([][]string) + if !ok { + stack = [][]string{} + } + + proc := state.Groups[2] + switch proc { + case "if": + stack = append(stack, state.Stack) + case "else", "elseif": + if len(stack) > 0 { + state.Stack = stack[len(stack)-1] + } + case "end": + if len(stack) > 0 { + stack = stack[:len(stack)-1] + } + } + + if proc == "if" || proc == "elseif" { + state.Stack = append(state.Stack, "preproc-expr") + } + + if proc == "error" { + state.Stack = append(state.Stack, "preproc-error") + } + state.Set("haxe-pre-proc", stack) + return nil +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/html.go b/vendor/github.com/alecthomas/chroma/v2/lexers/html.go new file mode 100644 index 000000000..c858042bf --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/html.go @@ -0,0 +1,8 @@ +package lexers + +import ( + "github.com/alecthomas/chroma/v2" +) + +// HTML lexer. +var HTML = chroma.MustNewXMLLexer(embedded, "embedded/html.xml") diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/http.go b/vendor/github.com/alecthomas/chroma/v2/lexers/http.go new file mode 100644 index 000000000..fee74d3b6 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/http.go @@ -0,0 +1,131 @@ +package lexers + +import ( + "strings" + + . "github.com/alecthomas/chroma/v2" // nolint +) + +// HTTP lexer. +var HTTP = Register(httpBodyContentTypeLexer(MustNewLexer( + &Config{ + Name: "HTTP", + Aliases: []string{"http"}, + Filenames: []string{}, + MimeTypes: []string{}, + NotMultiline: true, + DotAll: true, + }, + httpRules, +))) + +func httpRules() Rules { + return Rules{ + "root": { + {`(GET|POST|PUT|DELETE|HEAD|OPTIONS|TRACE|PATCH|CONNECT)( +)([^ ]+)( +)(HTTP)(/)([123](?:\.[01])?)(\r?\n|\Z)`, ByGroups(NameFunction, Text, NameNamespace, Text, KeywordReserved, Operator, LiteralNumber, Text), Push("headers")}, + {`(HTTP)(/)([123](?:\.[01])?)( +)(\d{3})( *)([^\r\n]*)(\r?\n|\Z)`, ByGroups(KeywordReserved, Operator, LiteralNumber, Text, LiteralNumber, Text, NameException, Text), Push("headers")}, + }, + "headers": { + {`([^\s:]+)( *)(:)( *)([^\r\n]+)(\r?\n|\Z)`, EmitterFunc(httpHeaderBlock), nil}, + {`([\t ]+)([^\r\n]+)(\r?\n|\Z)`, EmitterFunc(httpContinuousHeaderBlock), nil}, + {`\r?\n`, Text, Push("content")}, + }, + "content": { + {`.+`, EmitterFunc(httpContentBlock), nil}, + }, + } +} + +func httpContentBlock(groups []string, state *LexerState) Iterator { + tokens := []Token{ + {Generic, groups[0]}, + } + return Literator(tokens...) +} + +func httpHeaderBlock(groups []string, state *LexerState) Iterator { + tokens := []Token{ + {Name, groups[1]}, + {Text, groups[2]}, + {Operator, groups[3]}, + {Text, groups[4]}, + {Literal, groups[5]}, + {Text, groups[6]}, + } + return Literator(tokens...) +} + +func httpContinuousHeaderBlock(groups []string, state *LexerState) Iterator { + tokens := []Token{ + {Text, groups[1]}, + {Literal, groups[2]}, + {Text, groups[3]}, + } + return Literator(tokens...) +} + +func httpBodyContentTypeLexer(lexer Lexer) Lexer { return &httpBodyContentTyper{lexer} } + +type httpBodyContentTyper struct{ Lexer } + +func (d *httpBodyContentTyper) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { // nolint: gocognit + var contentType string + var isContentType bool + var subIterator Iterator + + it, err := d.Lexer.Tokenise(options, text) + if err != nil { + return nil, err + } + + return func() Token { + token := it() + + if token == EOF { + if subIterator != nil { + return subIterator() + } + return EOF + } + + switch { + case token.Type == Name && strings.ToLower(token.Value) == "content-type": + { + isContentType = true + } + case token.Type == Literal && isContentType: + { + isContentType = false + contentType = strings.TrimSpace(token.Value) + pos := strings.Index(contentType, ";") + if pos > 0 { + contentType = strings.TrimSpace(contentType[:pos]) + } + } + case token.Type == Generic && contentType != "": + { + lexer := MatchMimeType(contentType) + + // application/calendar+xml can be treated as application/xml + // if there's not a better match. + if lexer == nil && strings.Contains(contentType, "+") { + slashPos := strings.Index(contentType, "/") + plusPos := strings.LastIndex(contentType, "+") + contentType = contentType[:slashPos+1] + contentType[plusPos+1:] + lexer = MatchMimeType(contentType) + } + + if lexer == nil { + token.Type = Text + } else { + subIterator, err = lexer.Tokenise(nil, token.Value) + if err != nil { + panic(err) + } + return subIterator() + } + } + } + return token + }, nil +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/lexers.go b/vendor/github.com/alecthomas/chroma/v2/lexers/lexers.go new file mode 100644 index 000000000..bef42edec --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/lexers.go @@ -0,0 +1,85 @@ +package lexers + +import ( + "embed" + "io/fs" + + "github.com/alecthomas/chroma/v2" +) + +//go:embed embedded +var embedded embed.FS + +// GlobalLexerRegistry is the global LexerRegistry of Lexers. +var GlobalLexerRegistry = func() *chroma.LexerRegistry { + reg := chroma.NewLexerRegistry() + // index(reg) + paths, err := fs.Glob(embedded, "embedded/*.xml") + if err != nil { + panic(err) + } + for _, path := range paths { + reg.Register(chroma.MustNewXMLLexer(embedded, path)) + } + return reg +}() + +// Names of all lexers, optionally including aliases. +func Names(withAliases bool) []string { + return GlobalLexerRegistry.Names(withAliases) +} + +// Aliases of all the lexers, and skip those lexers who do not have any aliases, +// or show their name instead +func Aliases(skipWithoutAliases bool) []string { + return GlobalLexerRegistry.Aliases(skipWithoutAliases) +} + +// Get a Lexer by name, alias or file extension. +// +// Note that this if there isn't an exact match on name or alias, this will +// call Match(), so it is not efficient. +func Get(name string) chroma.Lexer { + return GlobalLexerRegistry.Get(name) +} + +// MatchMimeType attempts to find a lexer for the given MIME type. +func MatchMimeType(mimeType string) chroma.Lexer { + return GlobalLexerRegistry.MatchMimeType(mimeType) +} + +// Match returns the first lexer matching filename. +// +// Note that this iterates over all file patterns in all lexers, so it's not +// particularly efficient. +func Match(filename string) chroma.Lexer { + return GlobalLexerRegistry.Match(filename) +} + +// Register a Lexer with the global registry. +func Register(lexer chroma.Lexer) chroma.Lexer { + return GlobalLexerRegistry.Register(lexer) +} + +// Analyse text content and return the "best" lexer.. +func Analyse(text string) chroma.Lexer { + return GlobalLexerRegistry.Analyse(text) +} + +// PlaintextRules is used for the fallback lexer as well as the explicit +// plaintext lexer. +func PlaintextRules() chroma.Rules { + return chroma.Rules{ + "root": []chroma.Rule{ + {`.+`, chroma.Text, nil}, + {`\n`, chroma.Text, nil}, + }, + } +} + +// Fallback lexer if no other is found. +var Fallback chroma.Lexer = chroma.MustNewLexer(&chroma.Config{ + Name: "fallback", + Filenames: []string{"*"}, + Priority: -1, +}, PlaintextRules) diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/markdown.go b/vendor/github.com/alecthomas/chroma/v2/lexers/markdown.go new file mode 100644 index 000000000..eed2cb7e5 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/markdown.go @@ -0,0 +1,107 @@ +package lexers + +import ( + "strings" + + . "github.com/alecthomas/chroma/v2" // nolint +) + +// Markdown lexer with YAML frontmatter and HTML comment support. +var Markdown = Register(&markdownLexer{Lexer: MustNewLexer( + &Config{ + Name: "markdown", + Aliases: []string{"md", "mkd"}, + Filenames: []string{"*.md", "*.mkd", "*.markdown"}, + MimeTypes: []string{"text/x-markdown"}, + }, + markdownRules, +)}) + +// markdownLexer wraps the base Markdown lexer to highlight top-of-file YAML frontmatter. +type markdownLexer struct { + Lexer +} + +// Lexes Markdown, highlighting a leading YAML frontmatter block before delegating to Markdown rules. +func (m *markdownLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { + frontmatter, rest, ok := splitFrontmatter(text) + if !ok { + return m.Lexer.Tokenise(options, text) + } + + yamlLexer := Get("YAML") + if yamlLexer == nil { + return m.Lexer.Tokenise(options, text) + } + + yamlTokens, err := yamlLexer.Tokenise(options, frontmatter) + if err != nil { + return nil, err + } + markdownTokens, err := m.Lexer.Tokenise(options, rest) + if err != nil { + return nil, err + } + return Concaterator(yamlTokens, markdownTokens), nil +} + +// Extracts a leading YAML frontmatter block if the document starts with one. +func splitFrontmatter(text string) (frontmatter string, rest string, ok bool) { + if !strings.HasPrefix(text, "---\n") && !strings.HasPrefix(text, "---\r\n") { + return "", text, false + } + + lineEnd := strings.IndexByte(text, '\n') + if lineEnd < 0 { + return "", text, false + } + if strings.TrimSuffix(text[:lineEnd], "\r") != "---" { + return "", text, false + } + + for pos := lineEnd + 1; pos < len(text); { + next := strings.IndexByte(text[pos:], '\n') + if next < 0 { + break + } + lineEnd = pos + next + line := strings.TrimSuffix(text[pos:lineEnd], "\r") + if line == "---" { + return text[:lineEnd+1], text[lineEnd+1:], true + } + pos = lineEnd + 1 + } + return "", text, false +} + +func markdownRules() Rules { + return Rules{ + "root": { + {``, CommentMultiline, nil}, + {`^(#[^#].+\n)`, ByGroups(GenericHeading), nil}, + {`^(#{2,6}.+\n)`, ByGroups(GenericSubheading), nil}, + {`^(\s*)([*-] )(\[[ xX]\])( .+\n)`, ByGroups(Text, Keyword, Keyword, UsingSelf("inline")), nil}, + {`^(\s*)([*-])(\s)(.+\n)`, ByGroups(Text, Keyword, Text, UsingSelf("inline")), nil}, + {`^(\s*)([0-9]+\.)( .+\n)`, ByGroups(Text, Keyword, UsingSelf("inline")), nil}, + {`^(\s*>\s)(.+\n)`, ByGroups(Keyword, GenericEmph), nil}, + {"^(```\\n)([\\w\\W]*?)(^```$)", ByGroups(String, Text, String), nil}, + { + "^(```)(\\w+)(\\n)([\\w\\W]*?)(^```$)", + UsingByGroup(2, 4, String, String, String, Text, String), + nil, + }, + Include("inline"), + }, + "inline": { + {``, CommentMultiline, nil}, + {`\\.`, Text, nil}, + {`(\s)(\*|_)((?:(?!\2).)*)(\2)((?=\W|\n))`, ByGroups(Text, GenericEmph, GenericEmph, GenericEmph, Text), nil}, + {`(\s)((\*\*|__).*?)\3((?=\W|\n))`, ByGroups(Text, GenericStrong, GenericStrong, Text), nil}, + {`(\s)(~~[^~]+~~)((?=\W|\n))`, ByGroups(Text, GenericDeleted, Text), nil}, + {"`[^`]+`", LiteralStringBacktick, nil}, + {`[@#][\w/:]+`, NameEntity, nil}, + {`(!?\[)([^]]+)(\])(\()([^)]+)(\))`, ByGroups(Text, NameTag, Text, Text, NameAttribute, Text), nil}, + {`.|\n`, Text, nil}, + }, + } +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/markless.go b/vendor/github.com/alecthomas/chroma/v2/lexers/markless.go new file mode 100644 index 000000000..437590e9b --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/markless.go @@ -0,0 +1,168 @@ +package lexers + +import ( + . "github.com/alecthomas/chroma/v2" // nolint +) + +// Markless lexer. +var Markless = Register(MustNewLexer( + &Config{ + Name: "Markless", + Aliases: []string{"mess"}, + Filenames: []string{"*.mess", "*.markless"}, + MimeTypes: []string{"text/x-markless"}, + }, + marklessRules, +)) + +func marklessRules() Rules { + return Rules{ + "root": { + Include("block"), + }, + // Block directives + "block": { + Include("header"), + Include("ordered-list"), + Include("unordered-list"), + Include("code-block"), + Include("blockquote"), + Include("blockquote-header"), + Include("align"), + Include("comment"), + Include("instruction"), + Include("embed"), + Include("footnote"), + Include("horizontal-rule"), + Include("paragraph"), + }, + "header": { + {`(# )(.*)$`, ByGroups(Keyword, GenericHeading), Push("inline")}, + {`(##+)(.*)$`, ByGroups(Keyword, GenericSubheading), Push("inline")}, + }, + "ordered-list": { + {`([0-9]+\.)`, Keyword, nil}, + }, + "unordered-list": { + {`(- )`, Keyword, nil}, + }, + "code-block": { + {`(::+)( *)(\w*)([^\n]*)(\n)([\w\W]*?)(^\1$)`, UsingByGroup(3, 6, Keyword, TextWhitespace, NameFunction, String, TextWhitespace, Text, Keyword), nil}, + }, + "blockquote": { + {`(\| )(.*)$`, ByGroups(Keyword, GenericInserted), nil}, + }, + "blockquote-header": { + {`(~ )([^|\n]+)(\| )(.*?\n)`, ByGroups(Keyword, NameEntity, Keyword, GenericInserted), Push("inline-blockquote")}, + {`(~ )(.*)$`, ByGroups(Keyword, NameEntity), nil}, + }, + "inline-blockquote": { + {`^( +)(\| )(.*$)`, ByGroups(TextWhitespace, Keyword, GenericInserted), nil}, + Default(Pop(1)), + }, + "align": { + {`(\|\|)|(\|<)|(\|>)|(><)`, Keyword, nil}, + }, + "comment": { + {`(;[; ]).*?$`, CommentSingle, nil}, + }, + "instruction": { + {`(! )([^ ]+)(.+?)$`, ByGroups(Keyword, NameFunction, NameVariable), nil}, + }, + "embed": { + {`(\[ )([^ ]+)( )([^,\]\n]+)`, ByGroups(Keyword, NameFunction, TextWhitespace, String), Push("embed-options")}, + }, + "embed-options": { + {`\\.`, Text, nil}, + {`,`, Punctuation, nil}, + {`\]?$`, Keyword, Pop(1)}, + // Generic key or key/value pair + {`( *)([^, \]\n]+)([^,\]\n]+)?`, ByGroups(TextWhitespace, NameFunction, String), nil}, + {`.`, Text, nil}, + }, + "footnote": { + {`(\[)([0-9]+)(\])`, ByGroups(Keyword, NameVariable, Keyword), Push("inline")}, + }, + "horizontal-rule": { + {`(==+)$`, LiteralOther, nil}, + }, + "paragraph": { + {` *`, TextWhitespace, Push("inline")}, + }, + // Inline directives + "inline": { + Include("escapes"), + Include("dashes"), + Include("newline"), + Include("italic"), + Include("underline"), + Include("bold"), + Include("strikethrough"), + Include("code"), + Include("compound"), + Include("footnote-reference"), + Include("subtext"), + Include("subtext"), + Include("url"), + {`.`, Text, nil}, + {`\n`, TextWhitespace, Pop(1)}, + }, + "escapes": { + {`\\.`, Text, nil}, + }, + "dashes": { + {`-{2,3}`, TextPunctuation, nil}, + }, + "newline": { + {`-/-`, TextWhitespace, nil}, + }, + "italic": { + {`(//)(.*?)(\1)`, ByGroups(Keyword, GenericEmph, Keyword), nil}, + }, + "underline": { + {`(__)(.*?)(\1)`, ByGroups(Keyword, GenericUnderline, Keyword), nil}, + }, + "bold": { + {`(\*\*)(.*?)(\1)`, ByGroups(Keyword, GenericStrong, Keyword), nil}, + }, + "strikethrough": { + {`(<-)(.*?)(->)`, ByGroups(Keyword, GenericDeleted, Keyword), nil}, + }, + "code": { + {"(``+)(.*?)(\\1)", ByGroups(Keyword, LiteralStringBacktick, Keyword), nil}, + }, + "compound": { + {`(''+)(.*?)(''\()`, ByGroups(Keyword, UsingSelf("inline"), Keyword), Push("compound-options")}, + }, + "compound-options": { + {`\\.`, Text, nil}, + {`,`, Punctuation, nil}, + {`\)`, Keyword, Pop(1)}, + // Hex Color + {` *#[0-9A-Fa-f]{3,6} *`, LiteralNumberHex, nil}, + // Named Color + {` *(indian-red|light-coral|salmon|dark-salmon|light-salmon|crimson|red|firebrick|dark-red|pink|light-pink|hot-pink|deep-pink|medium-violet-red|pale-violet-red|coral|tomato|orange-red|dark-orange|orange|gold|yellow|light-yellow|lemon-chiffon|light-goldenrod-yellow|papayawhip|moccasin|peachpuff|pale-goldenrod|khaki|dark-khaki|lavender|thistle|plum|violet|orchid|fuchsia|magenta|medium-orchid|medium-purple|rebecca-purple|blue-violet|dark-violet|dark-orchid|dark-magenta|purple|indigo|slate-blue|dark-slate-blue|medium-slate-blue|green-yellow|chartreuse|lawn-green|lime|lime-green|pale-green|light-green|medium-spring-green|spring-green|medium-sea-green|sea-green|forest-green|green|dark-green|yellow-green|olive-drab|olive|dark-olive-green|medium-aquamarine|dark-sea-green|light-sea-green|dark-cyan|teal|aqua|cyan|light-cyan|pale-turquoise|aquamarine|turquoise|medium-turquoise|dark-turquoise|cadet-blue|steel-blue|light-steel-blue|powder-blue|light-blue|sky-blue|light-sky-blue|deep-sky-blue|dodger-blue|cornflower-blue|royal-blue|blue|medium-blue|dark-blue|navy|midnight-blue|cornsilk|blanched-almond|bisque|navajo-white|wheat|burlywood|tan|rosy-brown|sandy-brown|goldenrod|dark-goldenrod|peru|chocolate|saddle-brown|sienna|brown|maroon|white|snow|honeydew|mintcream|azure|alice-blue|ghost-white|white-smoke|seashell|beige|oldlace|floral-white|ivory|antique-white|linen|lavenderblush|mistyrose|gainsboro|light-gray|silver|dark-gray|gray|dim-gray|light-slate-gray|slate-gray|dark-slate-gray) *`, LiteralOther, nil}, + // Named size + {` *(microscopic|tiny|small|normal|big|large|huge|gigantic) *`, NameTag, nil}, + // Options + {` *(bold|italic|underline|strikethrough|subtext|supertext|spoiler) *`, NameBuiltin, nil}, + // URL. Note the missing ) and , in the match. + {` *\w[-\w+.]*://[\w$\-_.+!*'(&/:;=?@z%#\\]+ *`, String, nil}, + // Generic key or key/value pair + {`( *)([^, )]+)( [^,)]+)?`, ByGroups(TextWhitespace, NameFunction, String), nil}, + {`.`, Text, nil}, + }, + "footnote-reference": { + {`(\[)([0-9]+)(\])`, ByGroups(Keyword, NameVariable, Keyword), nil}, + }, + "subtext": { + {`(v\()(.*?)(\))`, ByGroups(Keyword, UsingSelf("inline"), Keyword), nil}, + }, + "supertext": { + {`(\^\()(.*?)(\))`, ByGroups(Keyword, UsingSelf("inline"), Keyword), nil}, + }, + "url": { + {`\w[-\w+.]*://[\w\$\-_.+!*'()&,/:;=?@z%#\\]+`, String, nil}, + }, + } +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/mysql.go b/vendor/github.com/alecthomas/chroma/v2/lexers/mysql.go new file mode 100644 index 000000000..32e94c2f2 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/mysql.go @@ -0,0 +1,33 @@ +package lexers + +import ( + "regexp" +) + +var ( + mysqlAnalyserNameBetweenBacktickRe = regexp.MustCompile("`[a-zA-Z_]\\w*`") + mysqlAnalyserNameBetweenBracketRe = regexp.MustCompile(`\[[a-zA-Z_]\w*\]`) +) + +func init() { // nolint: gochecknoinits + Get("mysql"). + SetAnalyser(func(text string) float32 { + nameBetweenBacktickCount := len(mysqlAnalyserNameBetweenBacktickRe.FindAllString(text, -1)) + nameBetweenBracketCount := len(mysqlAnalyserNameBetweenBracketRe.FindAllString(text, -1)) + + var result float32 + + // Same logic as above in the TSQL analysis. + dialectNameCount := nameBetweenBacktickCount + nameBetweenBracketCount + if dialectNameCount >= 1 && nameBetweenBacktickCount >= (2*nameBetweenBracketCount) { + // Found at least twice as many `name` as [name]. + result += 0.5 + } else if nameBetweenBacktickCount > nameBetweenBracketCount { + result += 0.2 + } else if nameBetweenBacktickCount > 0 { + result += 0.1 + } + + return result + }) +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/php.go b/vendor/github.com/alecthomas/chroma/v2/lexers/php.go new file mode 100644 index 000000000..ff82f6eaf --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/php.go @@ -0,0 +1,37 @@ +package lexers + +import ( + "strings" + + . "github.com/alecthomas/chroma/v2" // nolint +) + +// phtml lexer is PHP in HTML. +var _ = Register(DelegatingLexer(HTML, MustNewLexer( + &Config{ + Name: "PHTML", + Aliases: []string{"phtml"}, + Filenames: []string{"*.phtml", "*.php", "*.php[345]", "*.inc"}, + MimeTypes: []string{"application/x-php", "application/x-httpd-php", "application/x-httpd-php3", "application/x-httpd-php4", "application/x-httpd-php5", "text/x-php"}, + DotAll: true, + CaseInsensitive: true, + EnsureNL: true, + Priority: 2, + }, + func() Rules { + return Get("PHP").(*RegexLexer).MustRules(). + Rename("root", "php"). + Merge(Rules{ + "root": { + {`<\?(php)?`, CommentPreproc, Push("php")}, + {`[^<]+`, Other, nil}, + {`<`, Other, nil}, + }, + }) + }, +).SetAnalyser(func(text string) float32 { + if strings.Contains(text, ">|>|»|\)|\]|\})` + colonPairPattern = `(?:)(?\w[\w'-]*)(?` + colonPairOpeningBrackets + `)` + colonPairLookahead = `(?=(:['\w-]+` + + colonPairOpeningBrackets + `.+?` + colonPairClosingBrackets + `)?` + namePattern = `(?:(?!` + colonPairPattern + `)(?:::|[\w':-]))+` + variablePattern = `[$@%&]+[.^:?=!~]?` + namePattern + globalVariablePattern = `[$@%&]+\*` + namePattern + ) + + keywords := []string{ + `BEGIN`, `CATCH`, `CHECK`, `CLOSE`, `CONTROL`, `DOC`, `END`, `ENTER`, `FIRST`, `INIT`, + `KEEP`, `LAST`, `LEAVE`, `NEXT`, `POST`, `PRE`, `QUIT`, `UNDO`, `anon`, `augment`, `but`, + `class`, `constant`, `default`, `does`, `else`, `elsif`, `enum`, `for`, `gather`, `given`, + `grammar`, `has`, `if`, `import`, `is`, `of`, `let`, `loop`, `made`, `make`, `method`, + `module`, `multi`, `my`, `need`, `orwith`, `our`, `proceed`, `proto`, `repeat`, `require`, + `where`, `return`, `return-rw`, `returns`, `->`, `-->`, `role`, `state`, `sub`, `no`, + `submethod`, `subset`, `succeed`, `supersede`, `try`, `unit`, `unless`, `until`, + `use`, `when`, `while`, `with`, `without`, `export`, `native`, `repr`, `required`, `rw`, + `symbol`, `default`, `cached`, `DEPRECATED`, `dynamic`, `hidden-from-backtrace`, `nodal`, + `pure`, `raw`, `start`, `react`, `supply`, `whenever`, `also`, `rule`, `token`, `regex`, + `dynamic-scope`, `built`, `temp`, + } + + keywordsPattern := Words(`(?)`, `(>=)`, `minmax`, `notandthen`, `S`, + } + + wordOperatorsPattern := Words(`(?<=^|\b|\s)`, `(?=$|\b|\s)`, wordOperators...) + + operators := []string{ + `++`, `--`, `-`, `**`, `!`, `+`, `~`, `?`, `+^`, `~^`, `?^`, `^`, `*`, `/`, `%`, `%%`, `+&`, + `+<`, `+>`, `~&`, `~<`, `~>`, `?&`, `+|`, `+^`, `~|`, `~^`, `?`, `?|`, `?^`, `&`, `^`, + `<=>`, `^…^`, `^…`, `…^`, `…`, `...`, `...^`, `^...`, `^...^`, `..`, `..^`, `^..`, `^..^`, + `::=`, `:=`, `!=`, `==`, `<=`, `<`, `>=`, `>`, `~~`, `===`, `&&`, `||`, `|`, `^^`, `//`, + `??`, `!!`, `^fff^`, `^ff^`, `<==`, `==>`, `<<==`, `==>>`, `=>`, `=`, `<<`, `«`, `>>`, `»`, + `,`, `>>.`, `».`, `.&`, `.=`, `.^`, `.?`, `.+`, `.*`, `.`, `∘`, `∩`, `⊍`, `∪`, `⊎`, `∖`, + `⊖`, `≠`, `≤`, `≥`, `=:=`, `=~=`, `≅`, `∈`, `∉`, `≡`, `≢`, `∋`, `∌`, `⊂`, `⊄`, `⊆`, `⊈`, + `⊃`, `⊅`, `⊇`, `⊉`, `:`, `!!!`, `???`, `¯`, `×`, `÷`, `−`, `⁺`, `⁻`, + } + + operatorsPattern := Words(``, ``, operators...) + + builtinTypes := []string{ + `False`, `True`, `Order`, `More`, `Less`, `Same`, `Any`, `Array`, `Associative`, `AST`, + `atomicint`, `Attribute`, `Backtrace`, `Backtrace::Frame`, `Bag`, `Baggy`, `BagHash`, + `Blob`, `Block`, `Bool`, `Buf`, `Callable`, `CallFrame`, `Cancellation`, `Capture`, + `CArray`, `Channel`, `Code`, `compiler`, `Complex`, `ComplexStr`, `CompUnit`, + `CompUnit::PrecompilationRepository`, `CompUnit::Repository`, `Empty`, + `CompUnit::Repository::FileSystem`, `CompUnit::Repository::Installation`, `Cool`, + `CurrentThreadScheduler`, `CX::Warn`, `CX::Take`, `CX::Succeed`, `CX::Return`, `CX::Redo`, + `CX::Proceed`, `CX::Next`, `CX::Last`, `CX::Emit`, `CX::Done`, `Cursor`, `Date`, `Dateish`, + `DateTime`, `Distribution`, `Distribution::Hash`, `Distribution::Locally`, + `Distribution::Path`, `Distribution::Resource`, `Distro`, `Duration`, `Encoding`, + `Encoding::GlobalLexerRegistry`, `Endian`, `Enumeration`, `Exception`, `Failure`, `FatRat`, `Grammar`, + `Hash`, `HyperWhatever`, `Instant`, `Int`, `int`, `int16`, `int32`, `int64`, `int8`, `str`, + `IntStr`, `IO`, `IO::ArgFiles`, `IO::CatHandle`, `IO::Handle`, `IO::Notification`, + `IO::Notification::Change`, `IO::Path`, `IO::Path::Cygwin`, `IO::Path::Parts`, + `IO::Path::QNX`, `IO::Path::Unix`, `IO::Path::Win32`, `IO::Pipe`, `IO::Socket`, + `IO::Socket::Async`, `IO::Socket::Async::ListenSocket`, `IO::Socket::INET`, `IO::Spec`, + `IO::Spec::Cygwin`, `IO::Spec::QNX`, `IO::Spec::Unix`, `IO::Spec::Win32`, `IO::Special`, + `Iterable`, `Iterator`, `Junction`, `Kernel`, `Label`, `List`, `Lock`, `Lock::Async`, + `Lock::ConditionVariable`, `long`, `longlong`, `Macro`, `Map`, `Match`, + `Metamodel::AttributeContainer`, `Metamodel::C3MRO`, `Metamodel::ClassHOW`, + `Metamodel::ConcreteRoleHOW`, `Metamodel::CurriedRoleHOW`, `Metamodel::DefiniteHOW`, + `Metamodel::Documenting`, `Metamodel::EnumHOW`, `Metamodel::Finalization`, + `Metamodel::MethodContainer`, `Metamodel::Mixins`, `Metamodel::MROBasedMethodDispatch`, + `Metamodel::MultipleInheritance`, `Metamodel::Naming`, `Metamodel::Primitives`, + `Metamodel::PrivateMethodContainer`, `Metamodel::RoleContainer`, `Metamodel::RolePunning`, + `Metamodel::Stashing`, `Metamodel::Trusting`, `Metamodel::Versioning`, `Method`, `Mix`, + `MixHash`, `Mixy`, `Mu`, `NFC`, `NFD`, `NFKC`, `NFKD`, `Nil`, `Num`, `num32`, `num64`, + `Numeric`, `NumStr`, `ObjAt`, `Order`, `Pair`, `Parameter`, `Perl`, `Pod::Block`, + `Pod::Block::Code`, `Pod::Block::Comment`, `Pod::Block::Declarator`, `Pod::Block::Named`, + `Pod::Block::Para`, `Pod::Block::Table`, `Pod::Heading`, `Pod::Item`, `Pointer`, + `Positional`, `PositionalBindFailover`, `Proc`, `Proc::Async`, `Promise`, `Proxy`, + `PseudoStash`, `QuantHash`, `RaceSeq`, `Raku`, `Range`, `Rat`, `Rational`, `RatStr`, + `Real`, `Regex`, `Routine`, `Routine::WrapHandle`, `Scalar`, `Scheduler`, `Semaphore`, + `Seq`, `Sequence`, `Set`, `SetHash`, `Setty`, `Signature`, `size_t`, `Slip`, `Stash`, + `Str`, `StrDistance`, `Stringy`, `Sub`, `Submethod`, `Supplier`, `Supplier::Preserving`, + `Supply`, `Systemic`, `Tap`, `Telemetry`, `Telemetry::Instrument::Thread`, + `Telemetry::Instrument::ThreadPool`, `Telemetry::Instrument::Usage`, `Telemetry::Period`, + `Telemetry::Sampler`, `Thread`, `Test`, `ThreadPoolScheduler`, `UInt`, `uint16`, `uint32`, + `uint64`, `uint8`, `Uni`, `utf8`, `ValueObjAt`, `Variable`, `Version`, `VM`, `Whatever`, + `WhateverCode`, `WrapHandle`, `NativeCall`, + // Pragmas + `precompilation`, `experimental`, `worries`, `MONKEY-TYPING`, `MONKEY-SEE-NO-EVAL`, + `MONKEY-GUTS`, `fatal`, `lib`, `isms`, `newline`, `nqp`, `soft`, + `strict`, `trace`, `variables`, + } + + builtinTypesPattern := Words(`(? 0 { + if tokenClass == rakuPod { + match, err := podRegex.FindRunesMatchStartingAt(text, searchPos+nChars) + if err == nil { + closingChars = match.Runes() + nextClosePos = match.RuneIndex + } else { + nextClosePos = -1 + } + } else { + nextClosePos = indexAt(text, closingChars, searchPos+nChars) + } + + nextOpenPos := indexAt(text, openingChars, searchPos+nChars) + + switch { + case nextClosePos == -1: + nextClosePos = len(text) + nestingLevel = 0 + case nextOpenPos != -1 && nextOpenPos < nextClosePos: + nestingLevel++ + nChars = len(openingChars) + searchPos = nextOpenPos + default: // next_close_pos < next_open_pos + nestingLevel-- + nChars = len(closingChars) + searchPos = nextClosePos + } + } + + endPos = nextClosePos + } + + if endPos < 0 { + // if we didn't find a closer, just highlight the + // rest of the text in this class + endPos = len(text) + } + + adverbre := regexp.MustCompile(`:to\b|:heredoc\b`) + var heredocTerminator []rune + var endHeredocPos int + if adverbre.MatchString(string(adverbs)) { + if endPos != len(text) { + heredocTerminator = text[state.Pos:endPos] + nChars = len(heredocTerminator) + } else { + endPos = state.Pos + 1 + heredocTerminator = []rune{} + nChars = 0 + } + + if nChars > 0 { + endHeredocPos = indexAt(text[endPos:], heredocTerminator, 0) + if endHeredocPos > -1 { + endPos += endHeredocPos + } else { + endPos = len(text) + } + } + } + + textBetweenBrackets := string(text[state.Pos:endPos]) + switch tokenClass { + case rakuPod, rakuPodDeclaration, rakuNameAttribute: + state.NamedGroups[`value`] = textBetweenBrackets + state.NamedGroups[`closing_delimiters`] = string(closingChars) + case rakuQuote: + if len(heredocTerminator) > 0 { + // Length of heredoc terminator + closing chars + `;` + heredocFristPunctuationLen := nChars + len(openingChars) + 1 + + state.NamedGroups[`opening_delimiters`] = string(openingChars) + + string(text[state.Pos:state.Pos+heredocFristPunctuationLen]) + + state.NamedGroups[`value`] = + string(text[state.Pos+heredocFristPunctuationLen : endPos]) + + if endHeredocPos > -1 { + state.NamedGroups[`closing_delimiters`] = string(heredocTerminator) + } + } else { + state.NamedGroups[`value`] = textBetweenBrackets + if nChars > 0 { + state.NamedGroups[`closing_delimiters`] = string(closingChars) + } + } + default: + state.Groups = []string{state.Groups[0] + string(text[state.Pos:endPos+nChars])} + } + + state.Pos = endPos + nChars + + return nil + } + } + + // Raku rules + // Empty capture groups are placeholders and will be replaced by mutators + // DO NOT REMOVE THEM! + return Rules{ + "root": { + // Placeholder, will be overwritten by mutators, DO NOT REMOVE! + {`\A\z`, nil, nil}, + Include("common"), + {`{`, Punctuation, Push(`root`)}, + {`\(`, Punctuation, Push(`root`)}, + {`[)}]`, Punctuation, Pop(1)}, + {`;`, Punctuation, nil}, + {`\[|\]`, Operator, nil}, + {`.+?`, Text, nil}, + }, + "common": { + {`^#![^\n]*$`, CommentHashbang, nil}, + Include("pod"), + // Multi-line, Embedded comment + { + "#`(?(?" + bracketsPattern + `)\k*)`, + CommentMultiline, + findBrackets(rakuMultilineComment), + }, + {`#[^\n]*$`, CommentSingle, nil}, + // /regex/ + { + `(?<=(?:^|\(|=|:|~~|\[|{|,|=>)\s*)(/)(?!\]|\))((?:\\\\|\\/|.)*?)((?>)(\S+?)(<<)`, ByGroups(Operator, UsingSelf("root"), Operator), nil}, + {`(»)(\S+?)(«)`, ByGroups(Operator, UsingSelf("root"), Operator), nil}, + // Hyperoperator | «*« + {`(<<)(\S+?)(<<)`, ByGroups(Operator, UsingSelf("root"), Operator), nil}, + {`(«)(\S+?)(«)`, ByGroups(Operator, UsingSelf("root"), Operator), nil}, + // Hyperoperator | »*» + {`(>>)(\S+?)(>>)`, ByGroups(Operator, UsingSelf("root"), Operator), nil}, + {`(»)(\S+?)(»)`, ByGroups(Operator, UsingSelf("root"), Operator), nil}, + // <> + {`(?>)[^\n])+?[},;] *\n)(?!(?:(?!>>).)+?>>\S+?>>)`, Punctuation, Push("<<")}, + // «quoted words» + {`(? operators | something < onething > something + { + `(?<=[$@%&]?\w[\w':-]* +)(<=?)( *[^ ]+? *)(>=?)(?= *[$@%&]?\w[\w':-]*)`, + ByGroups(Operator, UsingSelf("root"), Operator), + nil, + }, + // + { + `(?])+?)(>)(?!\s*(?:\d+|\.(?:Int|Numeric)|[$@%]\*?\w[\w':-]*[^(]|\s+\[))`, + ByGroups(Punctuation, String, Punctuation), + nil, + }, + {`C?X::['\w:-]+`, NameException, nil}, + Include("metaoperator"), + // Pair | key => value + { + `(\w[\w'-]*)(\s*)(=>)`, + ByGroups(String, Text, Operator), + nil, + }, + Include("colon-pair"), + // Token + { + `(?<=(?:^|\s)(?:regex|token|rule)(\s+))` + namePattern + colonPairLookahead + `\s*[({])`, + NameFunction, + Push("token", "name-adverb"), + }, + // Substitution + {`(?<=^|\b|\s)(?(?:qq|q|Q))(?(?::?(?:heredoc|to|qq|ww|q|w|s|a|h|f|c|b|to|v|x))*)(?\s*)(?(?[^0-9a-zA-Z:\s])\k*)`, + EmitterFunc(quote), + findBrackets(rakuQuote), + }, + // Function + { + `\b` + namePattern + colonPairLookahead + `\()`, + NameFunction, + Push("name-adverb"), + }, + // Method + { + `(?(?[^\w:\s])\k*)`, + ByGroupNames( + map[string]Emitter{ + `opening_delimiters`: Punctuation, + `delimiter`: nil, + }, + ), + findBrackets(rakuMatchRegex), + }, + }, + "substitution": { + Include("colon-pair-attribute"), + // Substitution | s{regex} = value + { + `(?(?` + bracketsPattern + `)\k*)`, + ByGroupNames(map[string]Emitter{ + `opening_delimiters`: Punctuation, + `delimiter`: nil, + }), + findBrackets(rakuMatchRegex), + }, + // Substitution | s/regex/string/ + { + `(?[^\w:\s])`, + Punctuation, + findBrackets(rakuSubstitutionRegex), + }, + }, + "number": { + {`0_?[0-7]+(_[0-7]+)*`, LiteralNumberOct, nil}, + {`0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*`, LiteralNumberHex, nil}, + {`0b[01]+(_[01]+)*`, LiteralNumberBin, nil}, + { + `(?i)(\d*(_\d*)*\.\d+(_\d*)*|\d+(_\d*)*\.\d+(_\d*)*)(e[+-]?\d+)?`, + LiteralNumberFloat, + nil, + }, + {`(?i)\d+(_\d*)*e[+-]?\d+(_\d*)*`, LiteralNumberFloat, nil}, + {`(?<=\d+)i`, NameConstant, nil}, + {`\d+(_\d+)*`, LiteralNumberInteger, nil}, + }, + "name-adverb": { + Include("colon-pair-attribute-keyvalue"), + Default(Pop(1)), + }, + "colon-pair": { + // :key(value) + {colonPairPattern, colonPair(String), findBrackets(rakuNameAttribute)}, + // :123abc + { + `(:)(\d+)(\w[\w'-]*)`, + ByGroups(Punctuation, UsingSelf("number"), String), + nil, + }, + // :key + {`(:)(!?)(\w[\w'-]*)`, ByGroups(Punctuation, Operator, String), nil}, + {`\s+`, Text, nil}, + }, + "colon-pair-attribute": { + // :key(value) + {colonPairPattern, colonPair(NameAttribute), findBrackets(rakuNameAttribute)}, + // :123abc + { + `(:)(\d+)(\w[\w'-]*)`, + ByGroups(Punctuation, UsingSelf("number"), NameAttribute), + nil, + }, + // :key + {`(:)(!?)(\w[\w'-]*)`, ByGroups(Punctuation, Operator, NameAttribute), nil}, + {`\s+`, Text, nil}, + }, + "colon-pair-attribute-keyvalue": { + // :key(value) + {colonPairPattern, colonPair(NameAttribute), findBrackets(rakuNameAttribute)}, + }, + "escape-qq": { + { + `(? + { + `(?`), + tokenType: Punctuation, + stateName: `root`, + pushState: true, + }), + }, + // {code} + Include(`closure`), + // Properties + {`(:)(\w+)`, ByGroups(Punctuation, NameAttribute), nil}, + // Operator + {`\|\||\||&&|&|\.\.|\*\*|%%|%|:|!|<<|«|>>|»|\+|\*\*|\*|\?|=|~|<~~>`, Operator, nil}, + // Anchors + {`\^\^|\^|\$\$|\$`, NameEntity, nil}, + {`\.`, NameEntity, nil}, + {`#[^\n]*\n`, CommentSingle, nil}, + // Lookaround + { + `(?`), + tokenType: Punctuation, + stateName: `regex`, + pushState: true, + }), + }, + { + `(?)`, + ByGroups(Punctuation, Operator, OperatorWord, Punctuation), + nil, + }, + // <$variable> + { + `(?)`, + ByGroups(Punctuation, Operator, NameVariable, Punctuation), + nil, + }, + // Capture markers + {`(?`, Operator, nil}, + { + `(? + {`(?`, Punctuation, Pop(1)}, + // + { + `\(`, + Punctuation, + replaceRule(ruleReplacingConfig{ + delimiter: []rune(`)>`), + tokenType: Punctuation, + stateName: `root`, + popState: true, + pushState: true, + }), + }, + // + { + `\s+`, + StringRegex, + replaceRule(ruleReplacingConfig{ + delimiter: []rune(`>`), + tokenType: Punctuation, + stateName: `regex`, + popState: true, + pushState: true, + }), + }, + // + { + `:`, + Punctuation, + replaceRule(ruleReplacingConfig{ + delimiter: []rune(`>`), + tokenType: Punctuation, + stateName: `root`, + popState: true, + pushState: true, + }), + }, + }, + "regex-variable": { + Include(`regex-starting-operators`), + // + {`(&)?(\w[\w':-]*)(>)`, ByGroups(Operator, NameFunction, Punctuation), Pop(1)}, + // `, Punctuation, Pop(1)}, + Include("regex-class-builtin"), + Include("variable"), + Include(`regex-starting-operators`), + Include("colon-pair-attribute"), + {`(?] + { + `\b([RZX]+)\b(\[)([^\s\]]+?)(\])`, + ByGroups(OperatorWord, Punctuation, UsingSelf("root"), Punctuation), + nil, + }, + // Z=> + {`\b([RZX]+)\b([^\s\]]+)`, ByGroups(OperatorWord, UsingSelf("operator")), nil}, + }, + "operator": { + // Word Operator + {wordOperatorsPattern, OperatorWord, nil}, + // Operator + {operatorsPattern, Operator, nil}, + }, + "pod": { + // Single-line pod declaration + {`(#[|=])\s`, Keyword, Push("pod-single")}, + // Multi-line pod declaration + { + "(?#[|=])(?(?" + bracketsPattern + `)\k*)(?)(?)`, + ByGroupNames( + map[string]Emitter{ + `keyword`: Keyword, + `opening_delimiters`: Punctuation, + `delimiter`: nil, + `value`: UsingSelf("pod-declaration"), + `closing_delimiters`: Punctuation, + }), + findBrackets(rakuPodDeclaration), + }, + Include("pod-blocks"), + }, + "pod-blocks": { + // =begin code + { + `(?<=^ *)(? *)(?=begin)(? +)(?code)(?[^\n]*)(?.*?)(?^\k)(?=end)(? +)\k`, + EmitterFunc(podCode), + nil, + }, + // =begin + { + `(?<=^ *)(? *)(?=begin)(? +)(?!code)(?\w[\w'-]*)(?[^\n]*)(?)(?)`, + ByGroupNames( + map[string]Emitter{ + `ws`: Comment, + `keyword`: Keyword, + `ws2`: StringDoc, + `name`: Keyword, + `config`: EmitterFunc(podConfig), + `value`: UsingSelf("pod-begin"), + `closing_delimiters`: Keyword, + }), + findBrackets(rakuPod), + }, + // =for ... + { + `(?<=^ *)(? *)(?=(?:for|defn))(? +)(?\w[\w'-]*)(?[^\n]*\n)`, + ByGroups(Comment, Keyword, StringDoc, Keyword, EmitterFunc(podConfig)), + Push("pod-paragraph"), + }, + // =config + { + `(?<=^ *)(? *)(?=config)(? +)(?\w[\w'-]*)(?[^\n]*\n)`, + ByGroups(Comment, Keyword, StringDoc, Keyword, EmitterFunc(podConfig)), + nil, + }, + // =alias + { + `(?<=^ *)(? *)(?=alias)(? +)(?\w[\w'-]*)(?[^\n]*\n)`, + ByGroups(Comment, Keyword, StringDoc, Keyword, StringDoc), + nil, + }, + // =encoding + { + `(?<=^ *)(? *)(?=encoding)(? +)(?[^\n]+)`, + ByGroups(Comment, Keyword, StringDoc, Name), + nil, + }, + // =para ... + { + `(?<=^ *)(? *)(?=(?:para|table|pod))(?(? *)(?=head\d+)(? *)(?#?)`, + ByGroups(Comment, Keyword, GenericHeading, Keyword), + Push("pod-heading"), + }, + // =item ... + { + `(?<=^ *)(? *)(?=(?:item\d*|comment|data|[A-Z]+))(? *)(?#?)`, + ByGroups(Comment, Keyword, StringDoc, Keyword), + Push("pod-paragraph"), + }, + { + `(?<=^ *)(? *)(?=finish)(?[^\n]*)`, + ByGroups(Comment, Keyword, EmitterFunc(podConfig)), + Push("pod-finish"), + }, + // ={custom} ... + { + `(?<=^ *)(? *)(?=\w[\w'-]*)(? *)(?#?)`, + ByGroups(Comment, Name, StringDoc, Keyword), + Push("pod-paragraph"), + }, + // = podconfig + { + `(?<=^ *)(? *=)(? *)(?(?::\w[\w'-]*(?:` + colonPairOpeningBrackets + `.+?` + + colonPairClosingBrackets + `) *)*\n)`, + ByGroups(Keyword, StringDoc, EmitterFunc(podConfig)), + nil, + }, + }, + "pod-begin": { + Include("pod-blocks"), + Include("pre-pod-formatter"), + {`.+?`, StringDoc, nil}, + }, + "pod-declaration": { + Include("pre-pod-formatter"), + {`.+?`, StringDoc, nil}, + }, + "pod-paragraph": { + {`\n *\n|\n(?=^ *=)`, StringDoc, Pop(1)}, + Include("pre-pod-formatter"), + {`.+?`, StringDoc, nil}, + }, + "pod-single": { + {`\n`, StringDoc, Pop(1)}, + Include("pre-pod-formatter"), + {`.+?`, StringDoc, nil}, + }, + "pod-heading": { + {`\n *\n|\n(?=^ *=)`, GenericHeading, Pop(1)}, + Include("pre-pod-formatter"), + {`.+?`, GenericHeading, nil}, + }, + "pod-finish": { + {`\z`, nil, Pop(1)}, + Include("pre-pod-formatter"), + {`.+?`, StringDoc, nil}, + }, + "pre-pod-formatter": { + // C, B, ... + { + `(?[CBIUDTKRPAELZVMSXN])(?<+|«)`, + ByGroups(Keyword, Punctuation), + findBrackets(rakuPodFormatter), + }, + }, + "pod-formatter": { + // Placeholder rule, will be replaced by mutators. DO NOT REMOVE! + {`>`, Punctuation, Pop(1)}, + Include("pre-pod-formatter"), + // Placeholder rule, will be replaced by mutators. DO NOT REMOVE! + {`.+?`, StringOther, nil}, + }, + "variable": { + {variablePattern, NameVariable, Push("name-adverb")}, + {globalVariablePattern, NameVariableGlobal, Push("name-adverb")}, + {`[$@]<[^>]+>`, NameVariable, nil}, + {`\$[/!¢]`, NameVariable, nil}, + {`[$@%]`, NameVariable, nil}, + }, + "single-quote": { + {`(?>(?!\s*(?:\d+|\.(?:Int|Numeric)|[$@%]\*?[\w':-]+|\s+\[))`, Punctuation, Pop(1)}, + Include("ww"), + }, + "«": { + {`»(?!\s*(?:\d+|\.(?:Int|Numeric)|[$@%]\*?[\w':-]+|\s+\[))`, Punctuation, Pop(1)}, + Include("ww"), + }, + "ww": { + Include("single-quote"), + Include("qq"), + }, + "qq": { + Include("qq-variable"), + Include("closure"), + Include(`escape-char`), + Include("escape-hexadecimal"), + Include("escape-c-name"), + Include("escape-qq"), + {`.+?`, StringDouble, nil}, + }, + "qq-variable": { + { + `(?\.)(?` + namePattern + `)` + colonPairLookahead + `\()`, + ByGroupNames(map[string]Emitter{ + `operator`: Operator, + `method_name`: NameFunction, + }), + Push(`name-adverb`), + }, + // Function/Signature + { + `\(`, Punctuation, replaceRule( + ruleReplacingConfig{ + delimiter: []rune(`)`), + tokenType: Punctuation, + stateName: `root`, + pushState: true, + }), + }, + Default(Pop(1)), + }, + "Q": { + Include("escape-qq"), + {`.+?`, String, nil}, + }, + "Q-closure": { + Include("escape-qq"), + Include("closure"), + {`.+?`, String, nil}, + }, + "Q-variable": { + Include("escape-qq"), + Include("qq-variable"), + {`.+?`, String, nil}, + }, + "closure": { + {`(? -1 { + idx = utf8.RuneCountInString(text[:idx]) + + // Search again if the substr is escaped with backslash + if (idx > 1 && strFromPos[idx-1] == '\\' && strFromPos[idx-2] != '\\') || + (idx == 1 && strFromPos[idx-1] == '\\') { + idx = indexAt(str[pos:], substr, idx+1) + + idx = utf8.RuneCountInString(text[:idx]) + + if idx < 0 { + return idx + } + } + idx += pos + } + + return idx +} + +type rulePosition int + +const ( + topRule rulePosition = 0 - iota + bottomRule +) + +type ruleMakingConfig struct { + delimiter []rune + pattern string + tokenType Emitter + mutator Mutator + numberOfDelimiterChars int +} + +type ruleReplacingConfig struct { + delimiter []rune + pattern string + tokenType Emitter + numberOfDelimiterChars int + mutator Mutator + appendMutator Mutator + rulePosition rulePosition + stateName string + pop bool + popState bool + pushState bool +} + +// Pops rule from state-stack and replaces the rule with the previous rule +func popRule(rule ruleReplacingConfig) MutatorFunc { + return func(state *LexerState) error { + stackName := genStackName(rule.stateName, rule.rulePosition) + + stack, ok := state.Get(stackName).([]ruleReplacingConfig) + + if ok && len(stack) > 0 { + // Pop from stack + stack = stack[:len(stack)-1] + lastRule := stack[len(stack)-1] + lastRule.pushState = false + lastRule.popState = false + lastRule.pop = true + state.Set(stackName, stack) + + // Call replaceRule to use the last rule + err := replaceRule(lastRule)(state) + if err != nil { + panic(err) + } + } + + return nil + } +} + +// Replaces a state's rule based on the rule config and position +func replaceRule(rule ruleReplacingConfig) MutatorFunc { + return func(state *LexerState) error { + stateName := rule.stateName + stackName := genStackName(rule.stateName, rule.rulePosition) + + stack, ok := state.Get(stackName).([]ruleReplacingConfig) + if !ok { + stack = []ruleReplacingConfig{} + } + + // If state-stack is empty fill it with the placeholder rule + if len(stack) == 0 { + stack = []ruleReplacingConfig{ + { + // Placeholder, will be overwritten by mutators, DO NOT REMOVE! + pattern: `\A\z`, + tokenType: nil, + mutator: nil, + stateName: stateName, + rulePosition: rule.rulePosition, + }, + } + state.Set(stackName, stack) + } + + var mutator Mutator + mutators := []Mutator{} + + switch { + case rule.rulePosition == topRule && rule.mutator == nil: + // Default mutator for top rule + mutators = []Mutator{Pop(1), popRule(rule)} + case rule.rulePosition == topRule && rule.mutator != nil: + // Default mutator for top rule, when rule.mutator is set + mutators = []Mutator{rule.mutator, popRule(rule)} + case rule.mutator != nil: + mutators = []Mutator{rule.mutator} + } + + if rule.appendMutator != nil { + mutators = append(mutators, rule.appendMutator) + } + + if len(mutators) > 0 { + mutator = Mutators(mutators...) + } else { + mutator = nil + } + + ruleConfig := ruleMakingConfig{ + pattern: rule.pattern, + delimiter: rule.delimiter, + numberOfDelimiterChars: rule.numberOfDelimiterChars, + tokenType: rule.tokenType, + mutator: mutator, + } + + cRule := makeRule(ruleConfig) + + switch rule.rulePosition { + case topRule: + state.Rules[stateName][0] = cRule + case bottomRule: + state.Rules[stateName][len(state.Rules[stateName])-1] = cRule + } + + // Pop state name from stack if asked. State should be popped first before Pushing + if rule.popState { + err := Pop(1).Mutate(state) + if err != nil { + panic(err) + } + } + + // Push state name to stack if asked + if rule.pushState { + err := Push(stateName).Mutate(state) + if err != nil { + panic(err) + } + } + + if !rule.pop { + state.Set(stackName, append(stack, rule)) + } + + return nil + } +} + +// Generates rule replacing stack using state name and rule position +func genStackName(stateName string, rulePosition rulePosition) (stackName string) { + switch rulePosition { + case topRule: + stackName = stateName + `-top-stack` + case bottomRule: + stackName = stateName + `-bottom-stack` + } + return +} + +// Makes a compiled rule and returns it +func makeRule(config ruleMakingConfig) *CompiledRule { + var rePattern string + + if len(config.delimiter) > 0 { + delimiter := string(config.delimiter) + + if config.numberOfDelimiterChars > 1 { + delimiter = strings.Repeat(delimiter, config.numberOfDelimiterChars) + } + + rePattern = `(? 1 { + lang = langMatch[1] + } + + // Tokenise code based on lang property + sublexer := Get(lang) + if sublexer != nil { + iterator, err := sublexer.Tokenise(nil, state.NamedGroups[`value`]) + + if err != nil { + panic(err) + } else { + iterators = append(iterators, iterator) + } + } else { + iterators = append(iterators, Literator(tokens[4])) + } + + // Append the rest of the tokens + iterators = append(iterators, Literator(tokens[5:]...)) + + return Concaterator(iterators...) +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/rst.go b/vendor/github.com/alecthomas/chroma/v2/lexers/rst.go new file mode 100644 index 000000000..66ec03cdf --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/rst.go @@ -0,0 +1,89 @@ +package lexers + +import ( + "strings" + + . "github.com/alecthomas/chroma/v2" // nolint +) + +// Restructuredtext lexer. +var Restructuredtext = Register(MustNewLexer( + &Config{ + Name: "reStructuredText", + Aliases: []string{"rst", "rest", "restructuredtext"}, + Filenames: []string{"*.rst", "*.rest"}, + MimeTypes: []string{"text/x-rst", "text/prs.fallenstein.rst"}, + }, + restructuredtextRules, +)) + +func restructuredtextRules() Rules { + return Rules{ + "root": { + {"^(=+|-+|`+|:+|\\.+|\\'+|\"+|~+|\\^+|_+|\\*+|\\++|#+)([ \\t]*\\n)(.+)(\\n)(\\1)(\\n)", ByGroups(GenericHeading, Text, GenericHeading, Text, GenericHeading, Text), nil}, + {"^(\\S.*)(\\n)(={3,}|-{3,}|`{3,}|:{3,}|\\.{3,}|\\'{3,}|\"{3,}|~{3,}|\\^{3,}|_{3,}|\\*{3,}|\\+{3,}|#{3,})(\\n)", ByGroups(GenericHeading, Text, GenericHeading, Text), nil}, + {`^(\s*)([-*+])( .+\n(?:\1 .+\n)*)`, ByGroups(Text, LiteralNumber, UsingSelf("inline")), nil}, + {`^(\s*)([0-9#ivxlcmIVXLCM]+\.)( .+\n(?:\1 .+\n)*)`, ByGroups(Text, LiteralNumber, UsingSelf("inline")), nil}, + {`^(\s*)(\(?[0-9#ivxlcmIVXLCM]+\))( .+\n(?:\1 .+\n)*)`, ByGroups(Text, LiteralNumber, UsingSelf("inline")), nil}, + {`^(\s*)([A-Z]+\.)( .+\n(?:\1 .+\n)+)`, ByGroups(Text, LiteralNumber, UsingSelf("inline")), nil}, + {`^(\s*)(\(?[A-Za-z]+\))( .+\n(?:\1 .+\n)+)`, ByGroups(Text, LiteralNumber, UsingSelf("inline")), nil}, + {`^(\s*)(\|)( .+\n(?:\| .+\n)*)`, ByGroups(Text, Operator, UsingSelf("inline")), nil}, + {`^( *\.\.)(\s*)((?:source)?code(?:-block)?)(::)([ \t]*)([^\n]+)(\n[ \t]*\n)([ \t]+)(.*)(\n)((?:(?:\8.*|)\n)+)`, EmitterFunc(rstCodeBlock), nil}, + {`^( *\.\.)(\s*)([\w:-]+?)(::)(?:([ \t]*)(.*))`, ByGroups(Punctuation, Text, OperatorWord, Punctuation, Text, UsingSelf("inline")), nil}, + {`^( *\.\.)(\s*)(_(?:[^:\\]|\\.)+:)(.*?)$`, ByGroups(Punctuation, Text, NameTag, UsingSelf("inline")), nil}, + {`^( *\.\.)(\s*)(\[.+\])(.*?)$`, ByGroups(Punctuation, Text, NameTag, UsingSelf("inline")), nil}, + {`^( *\.\.)(\s*)(\|.+\|)(\s*)([\w:-]+?)(::)(?:([ \t]*)(.*))`, ByGroups(Punctuation, Text, NameTag, Text, OperatorWord, Punctuation, Text, UsingSelf("inline")), nil}, + {`^ *\.\..*(\n( +.*\n|\n)+)?`, CommentPreproc, nil}, + {`^( *)(:[a-zA-Z-]+:)(\s*)$`, ByGroups(Text, NameClass, Text), nil}, + {`^( *)(:.*?:)([ \t]+)(.*?)$`, ByGroups(Text, NameClass, Text, NameFunction), nil}, + {`^(\S.*(?)(`__?)", ByGroups(LiteralString, LiteralStringInterpol, LiteralString), nil}, + {"`.+?`__?", LiteralString, nil}, + {"(`.+?`)(:[a-zA-Z0-9:-]+?:)?", ByGroups(NameVariable, NameAttribute), nil}, + {"(:[a-zA-Z0-9:-]+?:)(`.+?`)", ByGroups(NameAttribute, NameVariable), nil}, + {`\*\*.+?\*\*`, GenericStrong, nil}, + {`\*.+?\*`, GenericEmph, nil}, + {`\[.*?\]_`, LiteralString, nil}, + {`<.+?>`, NameTag, nil}, + {"[^\\\\\\n\\[*`:]+", Text, nil}, + {`.`, Text, nil}, + }, + "literal": { + {"[^`]+", LiteralString, nil}, + {"``((?=$)|(?=[-/:.,; \\n\\x00\\\u2010\\\u2011\\\u2012\\\u2013\\\u2014\\\u00a0\\'\\\"\\)\\]\\}\\>\\\u2019\\\u201d\\\u00bb\\!\\?]))", LiteralString, Pop(1)}, + {"`", LiteralString, nil}, + }, + } +} + +func rstCodeBlock(groups []string, state *LexerState) Iterator { + iterators := []Iterator{} + tokens := []Token{ + {Punctuation, groups[1]}, + {Text, groups[2]}, + {OperatorWord, groups[3]}, + {Punctuation, groups[4]}, + {Text, groups[5]}, + {Keyword, groups[6]}, + {Text, groups[7]}, + } + code := strings.Join(groups[8:], "") + lexer := Get(groups[6]) + if lexer == nil { + tokens = append(tokens, Token{String, code}) + iterators = append(iterators, Literator(tokens...)) + } else { + sub, err := lexer.Tokenise(nil, code) + if err != nil { + panic(err) + } + iterators = append(iterators, Literator(tokens...), sub) + } + return Concaterator(iterators...) +} diff --git a/vendor/github.com/alecthomas/chroma/v2/lexers/svelte.go b/vendor/github.com/alecthomas/chroma/v2/lexers/svelte.go new file mode 100644 index 000000000..39211c4fc --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/lexers/svelte.go @@ -0,0 +1,70 @@ +package lexers + +import ( + . "github.com/alecthomas/chroma/v2" // nolint +) + +// Svelte lexer. +var Svelte = Register(DelegatingLexer(HTML, MustNewLexer( + &Config{ + Name: "Svelte", + Aliases: []string{"svelte"}, + Filenames: []string{"*.svelte"}, + MimeTypes: []string{"application/x-svelte"}, + DotAll: true, + }, + svelteRules, +))) + +func svelteRules() Rules { + return Rules{ + "root": { + // Let HTML handle the comments, including comments containing script and style tags + {``, Other, Pop(1)}, + {`.+?`, Other, nil}, + }, + "templates": { + {`}`, Punctuation, Pop(1)}, + // Let TypeScript handle strings and the curly braces inside them + {`(?]*>`, Using("TypoScriptHTMLData"), nil}, + {`&[^;\n]*;`, LiteralString, nil}, + {`(_CSS_DEFAULT_STYLE)(\s*)(\()(?s)(.*(?=\n\)))`, ByGroups(NameClass, Text, LiteralStringSymbol, Using("TypoScriptCSSData")), nil}, + }, + "literal": { + {`0x[0-9A-Fa-f]+t?`, LiteralNumberHex, nil}, + {`[0-9]+`, LiteralNumberInteger, nil}, + {`(###\w+###)`, NameConstant, nil}, + }, + "label": { + {`(EXT|FILE|LLL):[^}\n"]*`, LiteralString, nil}, + {`(?![^\w\-])([\w\-]+(?:/[\w\-]+)+/?)(\S*\n)`, ByGroups(LiteralString, LiteralString), nil}, + }, + "punctuation": { + {`[,.]`, Punctuation, nil}, + }, + "operator": { + {`[<>,:=.*%+|]`, Operator, nil}, + }, + "structure": { + {`[{}()\[\]\\]`, LiteralStringSymbol, nil}, + }, + "constant": { + {`(\{)(\$)((?:[\w\-]+\.)*)([\w\-]+)(\})`, ByGroups(LiteralStringSymbol, Operator, NameConstant, NameConstant, LiteralStringSymbol), nil}, + {`(\{)([\w\-]+)(\s*:\s*)([\w\-]+)(\})`, ByGroups(LiteralStringSymbol, NameConstant, Operator, NameConstant, LiteralStringSymbol), nil}, + {`(#[a-fA-F0-9]{6}\b|#[a-fA-F0-9]{3}\b)`, LiteralStringChar, nil}, + }, + "comment": { + {`(? 0 { + // Exhaust the iterator stack, if any. + for len(l.iteratorStack) > 0 { + n := len(l.iteratorStack) - 1 + t := l.iteratorStack[n]() + if t.Type == Ignore { + continue + } + if t == EOF { + l.iteratorStack = l.iteratorStack[:n] + continue + } + return t + } + + l.State = l.Stack[len(l.Stack)-1] + selectedRule, ok := l.Rules[l.State] + if !ok { + panic("unknown state " + l.State) + } + var start time.Time + if l.Lexer.trace { + start = time.Now() + } + ruleIndex, rule, groups, namedGroups := matchRules(l.Text, l.Pos, selectedRule) + if l.Lexer.trace { + var length int + if groups != nil { + length = len(groups[0]) + } else { + length = -1 + } + _ = trace.Encode(Trace{ //nolint + Lexer: l.Lexer.config.Name, + State: l.State, + Rule: ruleIndex, + Pattern: rule.Pattern, + Pos: l.Pos, + Length: length, + Elapsed: float64(time.Since(start)) / float64(time.Millisecond), + }) + // fmt.Fprintf(os.Stderr, "%s: pos=%d, text=%q, elapsed=%s\n", l.State, l.Pos, string(l.Text[l.Pos:]), time.Since(start)) + } + // No match. + if groups == nil { + // From Pygments :\ + // + // If the RegexLexer encounters a newline that is flagged as an error token, the stack is + // emptied and the lexer continues scanning in the 'root' state. This can help producing + // error-tolerant highlighting for erroneous input, e.g. when a single-line string is not + // closed. + if l.Text[l.Pos] == '\n' && l.State != l.options.State { + l.Stack = []string{l.options.State} + continue + } + l.Pos++ + return Token{Error, string(l.Text[l.Pos-1 : l.Pos])} + } + l.Rule = ruleIndex + l.Groups = groups + l.NamedGroups = namedGroups + l.Pos += utf8.RuneCountInString(groups[0]) + if rule.Mutator != nil { + if err := rule.Mutator.Mutate(l); err != nil { + panic(err) + } + } + if rule.Type != nil { + l.iteratorStack = append(l.iteratorStack, rule.Type.Emit(l.Groups, l)) + } + } + // Exhaust the IteratorStack, if any. + // Duplicate code, but eh. + for len(l.iteratorStack) > 0 { + n := len(l.iteratorStack) - 1 + t := l.iteratorStack[n]() + if t.Type == Ignore { + continue + } + if t == EOF { + l.iteratorStack = l.iteratorStack[:n] + continue + } + return t + } + + // If we get to here and we still have text, return it as an error. + if l.Pos != len(l.Text) && len(l.Stack) == 0 { + value := string(l.Text[l.Pos:]) + l.Pos = len(l.Text) + return Token{Type: Error, Value: value} + } + return EOF +} + +// RegexLexer is the default lexer implementation used in Chroma. +type RegexLexer struct { + registry *LexerRegistry // The LexerRegistry this Lexer is associated with, if any. + config *Config + analyser func(text string) float32 + trace bool + + mu sync.Mutex + compiled bool + rawRules Rules + rules map[string][]*CompiledRule + fetchRulesFunc func() (Rules, error) + compileOnce sync.Once + compileError error +} + +func (r *RegexLexer) String() string { + return r.config.Name +} + +// Rules in the Lexer. +func (r *RegexLexer) Rules() (Rules, error) { + if err := r.needRules(); err != nil { + return nil, err + } + return r.rawRules, nil +} + +// SetRegistry the lexer will use to lookup other lexers if necessary. +func (r *RegexLexer) SetRegistry(registry *LexerRegistry) Lexer { + r.registry = registry + return r +} + +// SetAnalyser sets the analyser function used to perform content inspection. +func (r *RegexLexer) SetAnalyser(analyser func(text string) float32) Lexer { + r.analyser = analyser + return r +} + +// AnalyseText scores how likely a fragment of text is to match this lexer, between 0.0 and 1.0. +func (r *RegexLexer) AnalyseText(text string) float32 { + if r.analyser != nil { + return r.analyser(text) + } + return 0 +} + +// SetConfig replaces the Config for this Lexer. +func (r *RegexLexer) SetConfig(config *Config) *RegexLexer { + r.config = config + return r +} + +// Config returns the Config for this Lexer. +func (r *RegexLexer) Config() *Config { + return r.config +} + +// Regex compilation is deferred until the lexer is used. This is to avoid significant init() time costs. +func (r *RegexLexer) maybeCompile() (err error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.compiled { + return nil + } + for state, rules := range r.rules { + for i, rule := range rules { + if rule.Regexp == nil { + pattern := "(?:" + rule.Pattern + ")" + if rule.flags != "" { + pattern = "(?" + rule.flags + ")" + pattern + } + pattern = `\G` + pattern + rule.Regexp, err = regexp2.Compile(pattern) + if err != nil { + return fmt.Errorf("failed to compile rule %s.%d: %s", state, i, err) + } + rule.Regexp.MatchTimeout = time.Millisecond * 250 + } + } + } +restart: + seen := map[LexerMutator]bool{} + for state := range r.rules { + for i := range len(r.rules[state]) { + rule := r.rules[state][i] + if compile, ok := rule.Mutator.(LexerMutator); ok { + if seen[compile] { + return fmt.Errorf("saw mutator %T twice; this should not happen", compile) + } + seen[compile] = true + if err := compile.MutateLexer(r.rules, state, i); err != nil { + return err + } + // Process the rules again in case the mutator added/removed rules. + // + // This sounds bad, but shouldn't be significant in practice. + goto restart + } + } + } + // Validate emitters + for state := range r.rules { + for i := range len(r.rules[state]) { + rule := r.rules[state][i] + if validate, ok := rule.Type.(ValidatingEmitter); ok { + if err := validate.ValidateEmitter(rule); err != nil { + return fmt.Errorf("%s: %s: %s: %w", r.config.Name, state, rule.Pattern, err) + } + } + } + } + r.compiled = true + return nil +} + +func (r *RegexLexer) fetchRules() error { + rules, err := r.fetchRulesFunc() + if err != nil { + return fmt.Errorf("%s: failed to compile rules: %w", r.config.Name, err) + } + if _, ok := rules["root"]; !ok { + return fmt.Errorf("no \"root\" state") + } + compiledRules := map[string][]*CompiledRule{} + for state, rules := range rules { + compiledRules[state] = nil + for _, rule := range rules { + flags := "" + if !r.config.NotMultiline { + flags += "m" + } + if r.config.CaseInsensitive { + flags += "i" + } + if r.config.DotAll { + flags += "s" + } + compiledRules[state] = append(compiledRules[state], &CompiledRule{Rule: rule, flags: flags}) + } + } + + r.rawRules = rules + r.rules = compiledRules + return nil +} + +func (r *RegexLexer) needRules() error { + var err error + if r.fetchRulesFunc != nil { + r.compileOnce.Do(func() { + r.compileError = r.fetchRules() + }) + if r.compileError != nil { + return r.compileError + } + } + if err := r.maybeCompile(); err != nil { + return err + } + return err +} + +// Tokenise text using lexer, returning an iterator. +func (r *RegexLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { + err := r.needRules() + if err != nil { + return nil, err + } + if options == nil { + options = defaultOptions + } + if options.EnsureLF { + text = ensureLF(text) + } + newlineAdded := false + if !options.Nested && r.config.EnsureNL && !strings.HasSuffix(text, "\n") { + text += "\n" + newlineAdded = true + } + state := &LexerState{ + Registry: r.registry, + newlineAdded: newlineAdded, + options: options, + Lexer: r, + Text: []rune(text), + Stack: []string{options.State}, + Rules: r.rules, + MutatorContext: map[any]any{}, + } + return state.Iterator, nil +} + +// MustRules is like Rules() but will panic on error. +func (r *RegexLexer) MustRules() Rules { + rules, err := r.Rules() + if err != nil { + panic(err) + } + return rules +} + +func matchRules(text []rune, pos int, rules []*CompiledRule) (int, *CompiledRule, []string, map[string]string) { + for i, rule := range rules { + match, err := rule.Regexp.FindRunesMatchStartingAt(text, pos) + if match != nil && err == nil && match.RuneIndex == pos { + groups := []string{} + namedGroups := make(map[string]string) + for _, g := range match.Groups() { + namedGroups[g.Name] = g.String() + groups = append(groups, g.String()) + } + return i, rule, groups, namedGroups + } + } + return 0, &CompiledRule{}, nil, nil +} + +// replace \r and \r\n with \n +// same as strings.ReplaceAll but more efficient +func ensureLF(text string) string { + buf := make([]byte, len(text)) + var j int + for i := range len(text) { + c := text[i] + if c == '\r' { + if i < len(text)-1 && text[i+1] == '\n' { + continue + } + c = '\n' + } + buf[j] = c + j++ + } + return string(buf[:j]) +} diff --git a/vendor/github.com/alecthomas/chroma/v2/registry.go b/vendor/github.com/alecthomas/chroma/v2/registry.go new file mode 100644 index 000000000..a309af9db --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/registry.go @@ -0,0 +1,228 @@ +package chroma + +import ( + "path/filepath" + "sort" + "strings" +) + +var ( + ignoredSuffixes = [...]string{ + // Editor backups + "~", ".bak", ".old", ".orig", + // Debian and derivatives apt/dpkg/ucf backups + ".dpkg-dist", ".dpkg-old", ".ucf-dist", ".ucf-new", ".ucf-old", + // Red Hat and derivatives rpm backups + ".rpmnew", ".rpmorig", ".rpmsave", + // Build system input/template files + ".in", + } +) + +// LexerRegistry is a registry of Lexers. +type LexerRegistry struct { + Lexers Lexers + byName map[string]Lexer + byAlias map[string]Lexer +} + +// NewLexerRegistry creates a new LexerRegistry of Lexers. +func NewLexerRegistry() *LexerRegistry { + return &LexerRegistry{ + byName: map[string]Lexer{}, + byAlias: map[string]Lexer{}, + } +} + +// Names of all lexers, optionally including aliases. +func (l *LexerRegistry) Names(withAliases bool) []string { + out := []string{} + for _, lexer := range l.Lexers { + config := lexer.Config() + out = append(out, config.Name) + if withAliases { + out = append(out, config.Aliases...) + } + } + sort.Strings(out) + return out +} + +// Aliases of all the lexers, and skip those lexers who do not have any aliases, +// or show their name instead +func (l *LexerRegistry) Aliases(skipWithoutAliases bool) []string { + out := []string{} + for _, lexer := range l.Lexers { + config := lexer.Config() + if len(config.Aliases) == 0 { + if skipWithoutAliases { + continue + } + out = append(out, config.Name) + } + out = append(out, config.Aliases...) + } + sort.Strings(out) + return out +} + +// Get a Lexer by name, alias or file extension. +func (l *LexerRegistry) Get(name string) Lexer { + if lexer := l.byName[name]; lexer != nil { + return lexer + } + if lexer := l.byAlias[name]; lexer != nil { + return lexer + } + if lexer := l.byName[strings.ToLower(name)]; lexer != nil { + return lexer + } + if lexer := l.byAlias[strings.ToLower(name)]; lexer != nil { + return lexer + } + + candidates := PrioritisedLexers{} + // Try file extension. + if lexer := l.Match("filename." + name); lexer != nil { + candidates = append(candidates, lexer) + } + // Try exact filename. + if lexer := l.Match(name); lexer != nil { + candidates = append(candidates, lexer) + } + if len(candidates) == 0 { + return nil + } + sort.Sort(candidates) + return candidates[0] +} + +// MatchMimeType attempts to find a lexer for the given MIME type. +func (l *LexerRegistry) MatchMimeType(mimeType string) Lexer { + matched := PrioritisedLexers{} + for _, l := range l.Lexers { + for _, lmt := range l.Config().MimeTypes { + if mimeType == lmt { + matched = append(matched, l) + } + } + } + if len(matched) != 0 { + sort.Sort(matched) + return matched[0] + } + return nil +} + +// Match returns the first lexer matching filename. +// +// Note that this iterates over all file patterns in all lexers, so is not fast. +func (l *LexerRegistry) Match(filename string) Lexer { + filename = filepath.Base(filename) + matched := PrioritisedLexers{} + // First, try primary filename matches. + for _, lexer := range l.Lexers { + config := lexer.Config() + for _, glob := range config.Filenames { + ok, err := filepath.Match(glob, filename) + if err != nil { // nolint + panic(err) + } else if ok { + matched = append(matched, lexer) + } else { + for _, suf := range &ignoredSuffixes { + ok, err := filepath.Match(glob+suf, filename) + if err != nil { + panic(err) + } else if ok { + matched = append(matched, lexer) + break + } + } + } + } + } + if len(matched) > 0 { + sort.Sort(matched) + return matched[0] + } + matched = nil + // Next, try filename aliases. + for _, lexer := range l.Lexers { + config := lexer.Config() + for _, glob := range config.AliasFilenames { + ok, err := filepath.Match(glob, filename) + if err != nil { // nolint + panic(err) + } else if ok { + matched = append(matched, lexer) + } else { + for _, suf := range &ignoredSuffixes { + ok, err := filepath.Match(glob+suf, filename) + if err != nil { + panic(err) + } else if ok { + matched = append(matched, lexer) + break + } + } + } + } + } + if len(matched) > 0 { + sort.Sort(matched) + return matched[0] + } + return nil +} + +// Analyse text content and return the "best" lexer.. +func (l *LexerRegistry) Analyse(text string) Lexer { + var picked Lexer + highest := float32(0.0) + for _, lexer := range l.Lexers { + if analyser, ok := lexer.(Analyser); ok { + weight := analyser.AnalyseText(text) + if weight > highest { + picked = lexer + highest = weight + } + } + } + return picked +} + +// Register a Lexer with the LexerRegistry. If the lexer is already registered +// it will be replaced. +func (l *LexerRegistry) Register(lexer Lexer) Lexer { + lexer.SetRegistry(l) + config := lexer.Config() + + l.byName[config.Name] = lexer + l.byName[strings.ToLower(config.Name)] = lexer + + for _, alias := range config.Aliases { + l.byAlias[alias] = lexer + l.byAlias[strings.ToLower(alias)] = lexer + } + + l.Lexers = add(l.Lexers, lexer) + + return lexer +} + +// add adds a lexer to a slice of lexers if it doesn't already exist, or if found will replace it. +func add(lexers Lexers, lexer Lexer) Lexers { + for i, val := range lexers { + if val == nil { + continue + } + + if val.Config().Name == lexer.Config().Name { + lexers[i] = lexer + return lexers + } + } + + return append(lexers, lexer) +} diff --git a/vendor/github.com/alecthomas/chroma/v2/remap.go b/vendor/github.com/alecthomas/chroma/v2/remap.go new file mode 100644 index 000000000..bcf5e66d1 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/remap.go @@ -0,0 +1,94 @@ +package chroma + +type remappingLexer struct { + lexer Lexer + mapper func(Token) []Token +} + +// RemappingLexer remaps a token to a set of, potentially empty, tokens. +func RemappingLexer(lexer Lexer, mapper func(Token) []Token) Lexer { + return &remappingLexer{lexer, mapper} +} + +func (r *remappingLexer) AnalyseText(text string) float32 { + return r.lexer.AnalyseText(text) +} + +func (r *remappingLexer) SetAnalyser(analyser func(text string) float32) Lexer { + r.lexer.SetAnalyser(analyser) + return r +} + +func (r *remappingLexer) SetRegistry(registry *LexerRegistry) Lexer { + r.lexer.SetRegistry(registry) + return r +} + +func (r *remappingLexer) Config() *Config { + return r.lexer.Config() +} + +func (r *remappingLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { + it, err := r.lexer.Tokenise(options, text) + if err != nil { + return nil, err + } + var buffer []Token + return func() Token { + for { + if len(buffer) > 0 { + t := buffer[0] + buffer = buffer[1:] + return t + } + t := it() + if t == EOF { + return t + } + buffer = r.mapper(t) + } + }, nil +} + +// TypeMapping defines type maps for the TypeRemappingLexer. +type TypeMapping []struct { + From, To TokenType + Words []string +} + +// TypeRemappingLexer remaps types of tokens coming from a parent Lexer. +// +// eg. Map "defvaralias" tokens of type NameVariable to NameFunction: +// +// mapping := TypeMapping{ +// {NameVariable, NameFunction, []string{"defvaralias"}, +// } +// lexer = TypeRemappingLexer(lexer, mapping) +func TypeRemappingLexer(lexer Lexer, mapping TypeMapping) Lexer { + // Lookup table for fast remapping. + lut := map[TokenType]map[string]TokenType{} + for _, rt := range mapping { + km, ok := lut[rt.From] + if !ok { + km = map[string]TokenType{} + lut[rt.From] = km + } + if len(rt.Words) == 0 { + km[""] = rt.To + } else { + for _, k := range rt.Words { + km[k] = rt.To + } + } + } + return RemappingLexer(lexer, func(t Token) []Token { + if k, ok := lut[t.Type]; ok { + if tt, ok := k[t.Value]; ok { + t.Type = tt + } else if tt, ok := k[""]; ok { + t.Type = tt + } + } + return []Token{t} + }) +} diff --git a/vendor/github.com/alecthomas/chroma/v2/renovate.json5 b/vendor/github.com/alecthomas/chroma/v2/renovate.json5 new file mode 100644 index 000000000..9ade48124 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/renovate.json5 @@ -0,0 +1,24 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":semanticCommits", + ":semanticCommitTypeAll(chore)", + ":semanticCommitScope(deps)", + "group:allNonMajor", + "schedule:earlyMondays", // Run once a week. + 'helpers:pinGitHubActionDigests', + ], + "packageRules": [ + { + "matchPackageNames": ["golangci-lint"], + "matchManagers": ["hermit"], + "enabled": false + }, + { + "matchPackageNames": ["github.com/gorilla/csrf"], + "matchManagers": ["gomod"], + "enabled": false + } + ] +} diff --git a/vendor/github.com/alecthomas/chroma/v2/serialise.go b/vendor/github.com/alecthomas/chroma/v2/serialise.go new file mode 100644 index 000000000..dacf69c5d --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/serialise.go @@ -0,0 +1,483 @@ +package chroma + +import ( + "compress/gzip" + "encoding/xml" + "errors" + "fmt" + "io" + "io/fs" + "math" + "path/filepath" + "reflect" + "regexp" + "strings" + + "github.com/dlclark/regexp2/v2" +) + +// Serialisation of Chroma rules to XML. The format is: +// +// +// +// +// [<$EMITTER ...>] +// [<$MUTATOR ...>] +// +// +// +// +// eg. Include("String") would become: +// +// +// +// +// +// [null, null, {"kind": "include", "state": "String"}] +// +// eg. Rule{`\d+`, Text, nil} would become: +// +// +// +// +// +// eg. Rule{`"`, String, Push("String")} +// +// +// +// +// +// +// eg. Rule{`(\w+)(\n)`, ByGroups(Keyword, Whitespace), nil}, +// +// +// +// +// +var ( + // ErrNotSerialisable is returned if a lexer contains Rules that cannot be serialised. + ErrNotSerialisable = fmt.Errorf("not serialisable") + emitterTemplates = func() map[string]SerialisableEmitter { + out := map[string]SerialisableEmitter{} + for _, emitter := range []SerialisableEmitter{ + &byGroupsEmitter{}, + &usingSelfEmitter{}, + TokenType(0), + &usingEmitter{}, + &usingByGroup{}, + } { + out[emitter.EmitterKind()] = emitter + } + return out + }() + mutatorTemplates = func() map[string]SerialisableMutator { + out := map[string]SerialisableMutator{} + for _, mutator := range []SerialisableMutator{ + &includeMutator{}, + &combinedMutator{}, + &multiMutator{}, + &pushMutator{}, + &popMutator{}, + } { + out[mutator.MutatorKind()] = mutator + } + return out + }() +) + +// fastUnmarshalConfig unmarshals only the Config from a serialised lexer. +func fastUnmarshalConfig(from fs.FS, path string) (*Config, error) { + r, err := from.Open(path) + if err != nil { + return nil, err + } + defer r.Close() + dec := xml.NewDecoder(r) + for { + token, err := dec.Token() + if err != nil { + if errors.Is(err, io.EOF) { + return nil, fmt.Errorf("could not find element") + } + return nil, err + } + switch se := token.(type) { + case xml.StartElement: + if se.Name.Local != "config" { + break + } + + var config Config + err = dec.DecodeElement(&config, &se) + if err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + return &config, nil + } + } +} + +// MustNewXMLLexer constructs a new RegexLexer from an XML file or panics. +func MustNewXMLLexer(from fs.FS, path string) *RegexLexer { + lex, err := NewXMLLexer(from, path) + if err != nil { + panic(err) + } + return lex +} + +// NewXMLLexer creates a new RegexLexer from a serialised RegexLexer. +func NewXMLLexer(from fs.FS, path string) (*RegexLexer, error) { + config, err := fastUnmarshalConfig(from, path) + if err != nil { + return nil, err + } + + for _, glob := range append(config.Filenames, config.AliasFilenames...) { + _, err := filepath.Match(glob, "") + if err != nil { + return nil, fmt.Errorf("%s: %q is not a valid glob: %w", config.Name, glob, err) + } + } + + var analyserFn func(string) float32 + + if config.Analyse != nil { + type regexAnalyse struct { + re *regexp2.Regexp + score float32 + } + + regexAnalysers := make([]regexAnalyse, 0, len(config.Analyse.Regexes)) + + regexFlags := regexp2.None + if config.CaseInsensitive { + regexFlags = regexp2.IgnoreCase + } + for _, ra := range config.Analyse.Regexes { + re, err := regexp2.Compile(ra.Pattern, regexFlags) + if err != nil { + return nil, fmt.Errorf("%s: %q is not a valid analyser regex: %w", config.Name, ra.Pattern, err) + } + + regexAnalysers = append(regexAnalysers, regexAnalyse{re, ra.Score}) + } + + analyserFn = func(text string) float32 { + var score float32 + + for _, ra := range regexAnalysers { + ok, err := ra.re.MatchString(text) + if err != nil { + return 0 + } + + if ok && config.Analyse.First { + return float32(math.Min(float64(ra.score), 1.0)) + } + + if ok { + score += ra.score + } + } + + return float32(math.Min(float64(score), 1.0)) + } + } + + return &RegexLexer{ + config: config, + analyser: analyserFn, + fetchRulesFunc: func() (Rules, error) { + var lexer struct { + Config + Rules Rules `xml:"rules"` + } + // Try to open .xml fallback to .xml.gz + fr, err := from.Open(path) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + path += ".gz" + fr, err = from.Open(path) + if err != nil { + return nil, err + } + } else { + return nil, err + } + } + defer fr.Close() + var r io.Reader = fr + if strings.HasSuffix(path, ".gz") { + r, err = gzip.NewReader(r) + if err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + } + err = xml.NewDecoder(r).Decode(&lexer) + if err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + return lexer.Rules, nil + }, + }, nil +} + +// Marshal a RegexLexer to XML. +func Marshal(l *RegexLexer) ([]byte, error) { + type lexer struct { + Config Config `xml:"config"` + Rules Rules `xml:"rules"` + } + + rules, err := l.Rules() + if err != nil { + return nil, err + } + root := &lexer{ + Config: *l.Config(), + Rules: rules, + } + data, err := xml.MarshalIndent(root, "", " ") + if err != nil { + return nil, err + } + re := regexp.MustCompile(`>`) + data = re.ReplaceAll(data, []byte(`/>`)) + return data, nil +} + +// Unmarshal a RegexLexer from XML. +func Unmarshal(data []byte) (*RegexLexer, error) { + type lexer struct { + Config Config `xml:"config"` + Rules Rules `xml:"rules"` + } + root := &lexer{} + err := xml.Unmarshal(data, root) + if err != nil { + return nil, fmt.Errorf("invalid Lexer XML: %w", err) + } + lex, err := NewLexer(&root.Config, func() Rules { return root.Rules }) + if err != nil { + return nil, err + } + return lex, nil +} + +func marshalMutator(e *xml.Encoder, mutator Mutator) error { + if mutator == nil { + return nil + } + smutator, ok := mutator.(SerialisableMutator) + if !ok { + return fmt.Errorf("unsupported mutator: %w", ErrNotSerialisable) + } + return e.EncodeElement(mutator, xml.StartElement{Name: xml.Name{Local: smutator.MutatorKind()}}) +} + +func unmarshalMutator(d *xml.Decoder, start xml.StartElement) (Mutator, error) { + kind := start.Name.Local + mutator, ok := mutatorTemplates[kind] + if !ok { + return nil, fmt.Errorf("unknown mutator %q: %w", kind, ErrNotSerialisable) + } + value, target := newFromTemplate(mutator) + if err := d.DecodeElement(target, &start); err != nil { + return nil, err + } + return value().(SerialisableMutator), nil +} + +func marshalEmitter(e *xml.Encoder, emitter Emitter) error { + if emitter == nil { + return nil + } + semitter, ok := emitter.(SerialisableEmitter) + if !ok { + return fmt.Errorf("unsupported emitter %T: %w", emitter, ErrNotSerialisable) + } + return e.EncodeElement(emitter, xml.StartElement{ + Name: xml.Name{Local: semitter.EmitterKind()}, + }) +} + +func unmarshalEmitter(d *xml.Decoder, start xml.StartElement) (Emitter, error) { + kind := start.Name.Local + mutator, ok := emitterTemplates[kind] + if !ok { + return nil, fmt.Errorf("unknown emitter %q: %w", kind, ErrNotSerialisable) + } + value, target := newFromTemplate(mutator) + if err := d.DecodeElement(target, &start); err != nil { + return nil, err + } + return value().(SerialisableEmitter), nil +} + +func (r Rule) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { + start := xml.StartElement{ + Name: xml.Name{Local: "rule"}, + } + if r.Pattern != "" { + start.Attr = append(start.Attr, xml.Attr{ + Name: xml.Name{Local: "pattern"}, + Value: r.Pattern, + }) + } + if err := e.EncodeToken(start); err != nil { + return err + } + if err := marshalEmitter(e, r.Type); err != nil { + return err + } + if err := marshalMutator(e, r.Mutator); err != nil { + return err + } + return e.EncodeToken(xml.EndElement{Name: start.Name}) +} + +func (r *Rule) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for _, attr := range start.Attr { + if attr.Name.Local == "pattern" { + r.Pattern = attr.Value + break + } + } + for { + token, err := d.Token() + if err != nil { + return err + } + switch token := token.(type) { + case xml.StartElement: + mutator, err := unmarshalMutator(d, token) + if err != nil && !errors.Is(err, ErrNotSerialisable) { + return err + } else if err == nil { + if r.Mutator != nil { + return fmt.Errorf("duplicate mutator") + } + r.Mutator = mutator + continue + } + emitter, err := unmarshalEmitter(d, token) + if err != nil && !errors.Is(err, ErrNotSerialisable) { // nolint: gocritic + return err + } else if err == nil { + if r.Type != nil { + return fmt.Errorf("duplicate emitter") + } + r.Type = emitter + continue + } else { + return err + } + + case xml.EndElement: + return nil + } + } +} + +type xmlRuleState struct { + Name string `xml:"name,attr"` + Rules []Rule `xml:"rule"` +} + +type xmlRules struct { + States []xmlRuleState `xml:"state"` +} + +func (r Rules) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { + xr := xmlRules{} + for state, rules := range r { + xr.States = append(xr.States, xmlRuleState{ + Name: state, + Rules: rules, + }) + } + return e.EncodeElement(xr, xml.StartElement{Name: xml.Name{Local: "rules"}}) +} + +func (r *Rules) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + xr := xmlRules{} + if err := d.DecodeElement(&xr, &start); err != nil { + return err + } + if *r == nil { + *r = Rules{} + } + for _, state := range xr.States { + (*r)[state.Name] = state.Rules + } + return nil +} + +type xmlTokenType struct { + Type string `xml:"type,attr"` +} + +func (t *TokenType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + el := xmlTokenType{} + if err := d.DecodeElement(&el, &start); err != nil { + return err + } + tt, err := TokenTypeString(el.Type) + if err != nil { + return err + } + *t = tt + return nil +} + +func (t TokenType) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: t.String()}) + if err := e.EncodeToken(start); err != nil { + return err + } + return e.EncodeToken(xml.EndElement{Name: start.Name}) +} + +// This hijinks is a bit unfortunate but without it we can't deserialise into TokenType. +func newFromTemplate(template any) (value func() any, target any) { + t := reflect.TypeOf(template) + if t.Kind() == reflect.Pointer { + v := reflect.New(t.Elem()) + return v.Interface, v.Interface() + } + v := reflect.New(t) + return func() any { return v.Elem().Interface() }, v.Interface() +} + +func (b *Emitters) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for { + token, err := d.Token() + if err != nil { + return err + } + switch token := token.(type) { + case xml.StartElement: + emitter, err := unmarshalEmitter(d, token) + if err != nil { + return err + } + *b = append(*b, emitter) + + case xml.EndElement: + return nil + } + } +} + +func (b Emitters) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if err := e.EncodeToken(start); err != nil { + return err + } + for _, m := range b { + if err := marshalEmitter(e, m); err != nil { + return err + } + } + return e.EncodeToken(xml.EndElement{Name: start.Name}) +} diff --git a/vendor/github.com/alecthomas/chroma/v2/style.go b/vendor/github.com/alecthomas/chroma/v2/style.go new file mode 100644 index 000000000..36989ad35 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/style.go @@ -0,0 +1,530 @@ +package chroma + +import ( + "encoding/xml" + "fmt" + "io" + "maps" + "slices" + "strings" +) + +// Trilean value for StyleEntry value inheritance. +type Trilean uint8 + +// Trilean states. +const ( + Pass Trilean = iota + Yes + No +) + +// Mode indicates whether a style is intended for a light or dark background. +type Mode uint8 + +// Mode values. +const ( + Light Mode = iota + Dark +) + +func (m Mode) String() string { + switch m { + case Dark: + return "dark" + default: + return "light" + } +} + +func (t Trilean) String() string { + switch t { + case Yes: + return "Yes" + case No: + return "No" + default: + return "Pass" + } +} + +// Prefix returns s with "no" as a prefix if Trilean is no. +func (t Trilean) Prefix(s string) string { + switch t { + case Yes: + return s + case No: + return "no" + s + default: + return "" + } +} + +// A StyleEntry in the Style map. +type StyleEntry struct { + // Hex colours. + Colour Colour + Background Colour + Border Colour + + Bold Trilean + Italic Trilean + Underline Trilean + NoInherit bool +} + +func (s StyleEntry) MarshalText() ([]byte, error) { + return []byte(s.String()), nil +} + +func (s StyleEntry) String() string { + out := []string{} + if s.Bold != Pass { + out = append(out, s.Bold.Prefix("bold")) + } + if s.Italic != Pass { + out = append(out, s.Italic.Prefix("italic")) + } + if s.Underline != Pass { + out = append(out, s.Underline.Prefix("underline")) + } + if s.NoInherit { + out = append(out, "noinherit") + } + if s.Colour.IsSet() { + out = append(out, s.Colour.String()) + } + if s.Background.IsSet() { + out = append(out, "bg:"+s.Background.String()) + } + if s.Border.IsSet() { + out = append(out, "border:"+s.Border.String()) + } + return strings.Join(out, " ") +} + +// Sub subtracts e from s where elements match. +func (s StyleEntry) Sub(e StyleEntry) StyleEntry { + out := StyleEntry{} + if e.Colour != s.Colour { + out.Colour = s.Colour + } + if e.Background != s.Background { + out.Background = s.Background + } + if e.Bold != s.Bold { + out.Bold = s.Bold + } + if e.Italic != s.Italic { + out.Italic = s.Italic + } + if e.Underline != s.Underline { + out.Underline = s.Underline + } + if e.Border != s.Border { + out.Border = s.Border + } + return out +} + +// Inherit styles from ancestors. +// +// Ancestors should be provided from oldest to newest. +func (s StyleEntry) Inherit(ancestors ...StyleEntry) StyleEntry { + out := s + for _, ancestor := range slices.Backward(ancestors) { + if out.NoInherit { + return out + } + if !out.Colour.IsSet() { + out.Colour = ancestor.Colour + } + if !out.Background.IsSet() { + out.Background = ancestor.Background + } + if !out.Border.IsSet() { + out.Border = ancestor.Border + } + if out.Bold == Pass { + out.Bold = ancestor.Bold + } + if out.Italic == Pass { + out.Italic = ancestor.Italic + } + if out.Underline == Pass { + out.Underline = ancestor.Underline + } + } + return out +} + +func (s StyleEntry) IsZero() bool { + return s.Colour == 0 && s.Background == 0 && s.Border == 0 && s.Bold == Pass && s.Italic == Pass && + s.Underline == Pass && !s.NoInherit +} + +// A StyleBuilder is a mutable structure for building styles. +// +// Once built, a Style is immutable. +type StyleBuilder struct { + entries map[TokenType]string + name string + counterpart string + parent *Style +} + +func NewStyleBuilder(name string) *StyleBuilder { + return &StyleBuilder{name: name, entries: map[TokenType]string{}} +} + +// Counterpart sets the lowercase name of the opposite-mode style. +func (s *StyleBuilder) Counterpart(name string) *StyleBuilder { + s.counterpart = strings.ToLower(name) + return s +} + +func (s *StyleBuilder) AddAll(entries StyleEntries) *StyleBuilder { + maps.Copy(s.entries, entries) + return s +} + +func (s *StyleBuilder) Get(ttype TokenType) StyleEntry { + // This is less than ideal, but it's the price for not having to check errors on each Add(). + entry, _ := ParseStyleEntry(s.entries[ttype]) + if s.parent != nil { + entry = entry.Inherit(s.parent.Get(ttype)) + } + return entry +} + +// Add an entry to the Style map. +// +// See http://pygments.org/docs/styles/#style-rules for details. +func (s *StyleBuilder) Add(ttype TokenType, entry string) *StyleBuilder { // nolint: gocyclo + s.entries[ttype] = entry + return s +} + +func (s *StyleBuilder) AddEntry(ttype TokenType, entry StyleEntry) *StyleBuilder { + s.entries[ttype] = entry.String() + return s +} + +// Transform passes each style entry currently defined in the builder to the supplied +// function and saves the returned value. This can be used to adjust a style's colours; +// see Colour's ClampBrightness function, for example. +func (s *StyleBuilder) Transform(transform func(StyleEntry) StyleEntry) *StyleBuilder { + types := make(map[TokenType]struct{}) + for tt := range s.entries { + types[tt] = struct{}{} + } + if s.parent != nil { + for _, tt := range s.parent.Types() { + types[tt] = struct{}{} + } + } + for tt := range types { + s.AddEntry(tt, transform(s.Get(tt))) + } + return s +} + +func (s *StyleBuilder) Build() (*Style, error) { + counterpart := s.counterpart + if counterpart == "" && s.parent != nil { + counterpart = s.parent.Counterpart + } + style := &Style{ + Name: s.name, + Counterpart: counterpart, + entries: map[TokenType]StyleEntry{}, + parent: s.parent, + } + for ttype, descriptor := range s.entries { + entry, err := ParseStyleEntry(descriptor) + if err != nil { + return nil, fmt.Errorf("invalid entry for %s: %s", ttype, err) + } + style.entries[ttype] = entry + } + return style, nil +} + +// StyleEntries mapping TokenType to colour definition. +type StyleEntries map[TokenType]string + +// NewXMLStyle parses an XML style definition. +func NewXMLStyle(r io.Reader) (*Style, error) { + dec := xml.NewDecoder(r) + style := &Style{} + return style, dec.Decode(style) +} + +// MustNewXMLStyle is like NewXMLStyle but panics on error. +func MustNewXMLStyle(r io.Reader) *Style { + style, err := NewXMLStyle(r) + if err != nil { + panic(err) + } + return style +} + +// NewStyle creates a new style definition. +func NewStyle(name string, entries StyleEntries) (*Style, error) { + return NewStyleBuilder(name).AddAll(entries).Build() +} + +// MustNewStyle creates a new style or panics. +func MustNewStyle(name string, entries StyleEntries) *Style { + style, err := NewStyle(name, entries) + if err != nil { + panic(err) + } + return style +} + +// A Style definition. +// +// See http://pygments.org/docs/styles/ for details. Semantics are intended to be identical. +type Style struct { + Name string + // Counterpart is the lowercase name of the style intended as this style's + // opposite-mode pair (eg. "github-dark" for "github"). Resolved via + // styles.GetForMode. May be empty. + Counterpart string + entries map[TokenType]StyleEntry + parent *Style +} + +// Mode returns Light or Dark based on the brightness of the Background entry's +// background colour. Styles with an unset Background default to Light. +func (s *Style) Mode() Mode { + bg := s.get(Background).Background + if bg.IsSet() && bg.Brightness() < 0.5 { + return Dark + } + return Light +} + +func (s *Style) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if s.parent != nil { + return fmt.Errorf("cannot marshal style with parent") + } + start.Name = xml.Name{Local: "style"} + start.Attr = []xml.Attr{{Name: xml.Name{Local: "name"}, Value: s.Name}} + if s.Counterpart != "" { + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "counterpart"}, Value: s.Counterpart}) + } + if err := e.EncodeToken(start); err != nil { + return err + } + sorted := make([]TokenType, 0, len(s.entries)) + for ttype := range s.entries { + sorted = append(sorted, ttype) + } + slices.Sort(sorted) + for _, ttype := range sorted { + entry := s.entries[ttype] + el := xml.StartElement{Name: xml.Name{Local: "entry"}} + el.Attr = []xml.Attr{ + {Name: xml.Name{Local: "type"}, Value: ttype.String()}, + {Name: xml.Name{Local: "style"}, Value: entry.String()}, + } + if err := e.EncodeToken(el); err != nil { + return err + } + if err := e.EncodeToken(xml.EndElement{Name: el.Name}); err != nil { + return err + } + } + return e.EncodeToken(xml.EndElement{Name: start.Name}) +} + +func (s *Style) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for _, attr := range start.Attr { + switch attr.Name.Local { + case "name": + s.Name = attr.Value + case "counterpart": + s.Counterpart = strings.ToLower(attr.Value) + default: + return fmt.Errorf("unexpected attribute %s", attr.Name.Local) + } + } + if s.Name == "" { + return fmt.Errorf("missing style name attribute") + } + s.entries = map[TokenType]StyleEntry{} + for { + tok, err := d.Token() + if err != nil { + return err + } + switch el := tok.(type) { + case xml.StartElement: + if el.Name.Local != "entry" { + return fmt.Errorf("unexpected element %s", el.Name.Local) + } + var ttype TokenType + var entry StyleEntry + for _, attr := range el.Attr { + switch attr.Name.Local { + case "type": + ttype, err = TokenTypeString(attr.Value) + if err != nil { + return err + } + + case "style": + entry, err = ParseStyleEntry(attr.Value) + if err != nil { + return err + } + + default: + return fmt.Errorf("unexpected attribute %s", attr.Name.Local) + } + } + s.entries[ttype] = entry + + case xml.EndElement: + if el.Name.Local == start.Name.Local { + return nil + } + } + } +} + +// Types that are styled. +func (s *Style) Types() []TokenType { + dedupe := map[TokenType]bool{} + for tt := range s.entries { + dedupe[tt] = true + } + if s.parent != nil { + for _, tt := range s.parent.Types() { + dedupe[tt] = true + } + } + out := make([]TokenType, 0, len(dedupe)) + for tt := range dedupe { + out = append(out, tt) + } + return out +} + +// Builder creates a mutable builder from this Style. +// +// The builder can then be safely modified. This is a cheap operation. +func (s *Style) Builder() *StyleBuilder { + return &StyleBuilder{ + name: s.Name, + entries: map[TokenType]string{}, + parent: s, + } +} + +// Has checks if an exact style entry match exists for a token type. +// +// This is distinct from Get() which will merge parent tokens. +func (s *Style) Has(ttype TokenType) bool { + return !s.get(ttype).IsZero() || s.synthesisable(ttype) +} + +// Get a style entry. Will try sub-category or category if an exact match is not found, and +// finally return the Background. +func (s *Style) Get(ttype TokenType) StyleEntry { + return s.get(ttype).Inherit( + s.get(Background), + s.get(Text), + s.get(ttype.Category()), + s.get(ttype.SubCategory())) +} + +func (s *Style) get(ttype TokenType) StyleEntry { + out := s.entries[ttype] + if out.IsZero() && s.parent != nil { + return s.parent.get(ttype) + } + if out.IsZero() && s.synthesisable(ttype) { + out = s.synthesise(ttype) + } + return out +} + +func (s *Style) synthesise(ttype TokenType) StyleEntry { + bg := s.get(Background) + text := StyleEntry{Colour: bg.Colour} + text.Colour = text.Colour.BrightenOrDarken(0.5) + + switch ttype { + // If we don't have a line highlight colour, make one that is 10% brighter/darker than the background. + case LineHighlight: + return StyleEntry{Background: bg.Background.BrightenOrDarken(0.1)} + + // If we don't have line numbers, use the text colour but 20% brighter/darker + case LineNumbers, LineNumbersTable: + return text + + default: + return StyleEntry{} + } +} + +func (s *Style) synthesisable(ttype TokenType) bool { + return ttype == LineHighlight || ttype == LineNumbers || ttype == LineNumbersTable +} + +// MustParseStyleEntry parses a Pygments style entry or panics. +func MustParseStyleEntry(entry string) StyleEntry { + out, err := ParseStyleEntry(entry) + if err != nil { + panic(err) + } + return out +} + +// ParseStyleEntry parses a Pygments style entry. +func ParseStyleEntry(entry string) (StyleEntry, error) { // nolint: gocyclo + out := StyleEntry{} + for part := range strings.FieldsSeq(entry) { + switch { + case part == "italic": + out.Italic = Yes + case part == "noitalic": + out.Italic = No + case part == "bold": + out.Bold = Yes + case part == "nobold": + out.Bold = No + case part == "underline": + out.Underline = Yes + case part == "nounderline": + out.Underline = No + case part == "inherit": + out.NoInherit = false + case part == "noinherit": + out.NoInherit = true + case part == "bg:": + out.Background = 0 + case strings.HasPrefix(part, "bg:#"): + out.Background = ParseColour(part[3:]) + if !out.Background.IsSet() { + return StyleEntry{}, fmt.Errorf("invalid background colour %q", part) + } + case strings.HasPrefix(part, "border:#"): + out.Border = ParseColour(part[7:]) + if !out.Border.IsSet() { + return StyleEntry{}, fmt.Errorf("invalid border colour %q", part) + } + case strings.HasPrefix(part, "#"): + out.Colour = ParseColour(part) + if !out.Colour.IsSet() { + return StyleEntry{}, fmt.Errorf("invalid colour %q", part) + } + default: + return StyleEntry{}, fmt.Errorf("unknown style element %q", part) + } + } + return out, nil +} diff --git a/vendor/github.com/alecthomas/chroma/v2/table.py b/vendor/github.com/alecthomas/chroma/v2/table.py new file mode 100644 index 000000000..ea4b7556a --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/table.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +import re +from collections import defaultdict +from subprocess import check_output + +README_FILE = "README.md" + +lines = check_output(["chroma", "--list"]).decode("utf-8").splitlines() +lines = [line.strip() for line in lines if line.startswith(" ") and not line.startswith(" ")] +lines = sorted(lines, key=lambda l: l.lower()) + +table = defaultdict(list) + +for line in lines: + table[line[0].upper()].append(line) + +rows = [] +for key, value in table.items(): + rows.append("{} | {}".format(key, ", ".join(value))) +tbody = "\n".join(rows) + +with open(README_FILE, "r") as f: + content = f.read() + +with open(README_FILE, "w") as f: + marker = re.compile(r"(?P:----: \\| --------\n).*?(?P\n\n)", re.DOTALL) + replacement = r"\g%s\g" % tbody + updated_content = marker.sub(replacement, content) + f.write(updated_content) + +print(tbody) diff --git a/vendor/github.com/alecthomas/chroma/v2/test.jsonl b/vendor/github.com/alecthomas/chroma/v2/test.jsonl new file mode 100644 index 000000000..a6a039880 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/test.jsonl @@ -0,0 +1,2 @@ +{"name": "Alice"} +{"name": "Bob"} diff --git a/vendor/github.com/alecthomas/chroma/v2/tokentype_enumer.go b/vendor/github.com/alecthomas/chroma/v2/tokentype_enumer.go new file mode 100644 index 000000000..e40386aa8 --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/tokentype_enumer.go @@ -0,0 +1,588 @@ +// Code generated by "enumer -text -type TokenType"; DO NOT EDIT. + +package chroma + +import ( + "fmt" + "strings" +) + +const _TokenTypeName = "IgnoreNoneOtherErrorCodeLineLineLinkLineTableTDLineTableLineHighlightLineNumbersTableLineNumbersLinePreWrapperBackgroundEOFTypeKeywordKeywordConstantKeywordDeclarationKeywordNamespaceKeywordPseudoKeywordReservedKeywordTypeNameNameAttributeNameClassNameConstantNameDecoratorNameEntityNameExceptionNameKeywordNameLabelNameNamespaceNameOperatorNameOtherNamePseudoNamePropertyNameTagNameBuiltinNameBuiltinPseudoNameVariableNameVariableAnonymousNameVariableClassNameVariableGlobalNameVariableInstanceNameVariableMagicNameFunctionNameFunctionMagicLiteralLiteralDateLiteralOtherLiteralStringLiteralStringAffixLiteralStringAtomLiteralStringBacktickLiteralStringBooleanLiteralStringCharLiteralStringDelimiterLiteralStringDocLiteralStringDoubleLiteralStringEscapeLiteralStringHeredocLiteralStringInterpolLiteralStringNameLiteralStringOtherLiteralStringRegexLiteralStringSingleLiteralStringSymbolLiteralNumberLiteralNumberBinLiteralNumberFloatLiteralNumberHexLiteralNumberIntegerLiteralNumberIntegerLongLiteralNumberOctLiteralNumberByteOperatorOperatorWordOperatorReservedPunctuationCommentCommentHashbangCommentMultilineCommentSingleCommentSpecialCommentPreprocCommentPreprocFileGenericGenericDeletedGenericEmphGenericErrorGenericHeadingGenericInsertedGenericOutputGenericPromptGenericStrongGenericSubheadingGenericTracebackGenericUnderlineTextTextWhitespaceTextSymbolTextPunctuation" +const _TokenTypeLowerName = "ignorenoneothererrorcodelinelinelinklinetabletdlinetablelinehighlightlinenumberstablelinenumberslineprewrapperbackgroundeoftypekeywordkeywordconstantkeyworddeclarationkeywordnamespacekeywordpseudokeywordreservedkeywordtypenamenameattributenameclassnameconstantnamedecoratornameentitynameexceptionnamekeywordnamelabelnamenamespacenameoperatornameothernamepseudonamepropertynametagnamebuiltinnamebuiltinpseudonamevariablenamevariableanonymousnamevariableclassnamevariableglobalnamevariableinstancenamevariablemagicnamefunctionnamefunctionmagicliteralliteraldateliteralotherliteralstringliteralstringaffixliteralstringatomliteralstringbacktickliteralstringbooleanliteralstringcharliteralstringdelimiterliteralstringdocliteralstringdoubleliteralstringescapeliteralstringheredocliteralstringinterpolliteralstringnameliteralstringotherliteralstringregexliteralstringsingleliteralstringsymbolliteralnumberliteralnumberbinliteralnumberfloatliteralnumberhexliteralnumberintegerliteralnumberintegerlongliteralnumberoctliteralnumberbyteoperatoroperatorwordoperatorreservedpunctuationcommentcommenthashbangcommentmultilinecommentsinglecommentspecialcommentpreproccommentpreprocfilegenericgenericdeletedgenericemphgenericerrorgenericheadinggenericinsertedgenericoutputgenericpromptgenericstronggenericsubheadinggenerictracebackgenericunderlinetexttextwhitespacetextsymboltextpunctuation" + +var _TokenTypeMap = map[TokenType]string{ + -14: _TokenTypeName[0:6], + -13: _TokenTypeName[6:10], + -12: _TokenTypeName[10:15], + -11: _TokenTypeName[15:20], + -10: _TokenTypeName[20:28], + -9: _TokenTypeName[28:36], + -8: _TokenTypeName[36:47], + -7: _TokenTypeName[47:56], + -6: _TokenTypeName[56:69], + -5: _TokenTypeName[69:85], + -4: _TokenTypeName[85:96], + -3: _TokenTypeName[96:100], + -2: _TokenTypeName[100:110], + -1: _TokenTypeName[110:120], + 0: _TokenTypeName[120:127], + 1000: _TokenTypeName[127:134], + 1001: _TokenTypeName[134:149], + 1002: _TokenTypeName[149:167], + 1003: _TokenTypeName[167:183], + 1004: _TokenTypeName[183:196], + 1005: _TokenTypeName[196:211], + 1006: _TokenTypeName[211:222], + 2000: _TokenTypeName[222:226], + 2001: _TokenTypeName[226:239], + 2002: _TokenTypeName[239:248], + 2003: _TokenTypeName[248:260], + 2004: _TokenTypeName[260:273], + 2005: _TokenTypeName[273:283], + 2006: _TokenTypeName[283:296], + 2007: _TokenTypeName[296:307], + 2008: _TokenTypeName[307:316], + 2009: _TokenTypeName[316:329], + 2010: _TokenTypeName[329:341], + 2011: _TokenTypeName[341:350], + 2012: _TokenTypeName[350:360], + 2013: _TokenTypeName[360:372], + 2014: _TokenTypeName[372:379], + 2100: _TokenTypeName[379:390], + 2101: _TokenTypeName[390:407], + 2200: _TokenTypeName[407:419], + 2201: _TokenTypeName[419:440], + 2202: _TokenTypeName[440:457], + 2203: _TokenTypeName[457:475], + 2204: _TokenTypeName[475:495], + 2205: _TokenTypeName[495:512], + 2300: _TokenTypeName[512:524], + 2301: _TokenTypeName[524:541], + 3000: _TokenTypeName[541:548], + 3001: _TokenTypeName[548:559], + 3002: _TokenTypeName[559:571], + 3100: _TokenTypeName[571:584], + 3101: _TokenTypeName[584:602], + 3102: _TokenTypeName[602:619], + 3103: _TokenTypeName[619:640], + 3104: _TokenTypeName[640:660], + 3105: _TokenTypeName[660:677], + 3106: _TokenTypeName[677:699], + 3107: _TokenTypeName[699:715], + 3108: _TokenTypeName[715:734], + 3109: _TokenTypeName[734:753], + 3110: _TokenTypeName[753:773], + 3111: _TokenTypeName[773:794], + 3112: _TokenTypeName[794:811], + 3113: _TokenTypeName[811:829], + 3114: _TokenTypeName[829:847], + 3115: _TokenTypeName[847:866], + 3116: _TokenTypeName[866:885], + 3200: _TokenTypeName[885:898], + 3201: _TokenTypeName[898:914], + 3202: _TokenTypeName[914:932], + 3203: _TokenTypeName[932:948], + 3204: _TokenTypeName[948:968], + 3205: _TokenTypeName[968:992], + 3206: _TokenTypeName[992:1008], + 3207: _TokenTypeName[1008:1025], + 4000: _TokenTypeName[1025:1033], + 4001: _TokenTypeName[1033:1045], + 4002: _TokenTypeName[1045:1061], + 5000: _TokenTypeName[1061:1072], + 6000: _TokenTypeName[1072:1079], + 6001: _TokenTypeName[1079:1094], + 6002: _TokenTypeName[1094:1110], + 6003: _TokenTypeName[1110:1123], + 6004: _TokenTypeName[1123:1137], + 6100: _TokenTypeName[1137:1151], + 6101: _TokenTypeName[1151:1169], + 7000: _TokenTypeName[1169:1176], + 7001: _TokenTypeName[1176:1190], + 7002: _TokenTypeName[1190:1201], + 7003: _TokenTypeName[1201:1213], + 7004: _TokenTypeName[1213:1227], + 7005: _TokenTypeName[1227:1242], + 7006: _TokenTypeName[1242:1255], + 7007: _TokenTypeName[1255:1268], + 7008: _TokenTypeName[1268:1281], + 7009: _TokenTypeName[1281:1298], + 7010: _TokenTypeName[1298:1314], + 7011: _TokenTypeName[1314:1330], + 8000: _TokenTypeName[1330:1334], + 8001: _TokenTypeName[1334:1348], + 8002: _TokenTypeName[1348:1358], + 8003: _TokenTypeName[1358:1373], +} + +func (i TokenType) String() string { + if str, ok := _TokenTypeMap[i]; ok { + return str + } + return fmt.Sprintf("TokenType(%d)", i) +} + +// An "invalid array index" compiler error signifies that the constant values have changed. +// Re-run the stringer command to generate them again. +func _TokenTypeNoOp() { + var x [1]struct{} + _ = x[Ignore-(-14)] + _ = x[None-(-13)] + _ = x[Other-(-12)] + _ = x[Error-(-11)] + _ = x[CodeLine-(-10)] + _ = x[LineLink-(-9)] + _ = x[LineTableTD-(-8)] + _ = x[LineTable-(-7)] + _ = x[LineHighlight-(-6)] + _ = x[LineNumbersTable-(-5)] + _ = x[LineNumbers-(-4)] + _ = x[Line-(-3)] + _ = x[PreWrapper-(-2)] + _ = x[Background-(-1)] + _ = x[EOFType-(0)] + _ = x[Keyword-(1000)] + _ = x[KeywordConstant-(1001)] + _ = x[KeywordDeclaration-(1002)] + _ = x[KeywordNamespace-(1003)] + _ = x[KeywordPseudo-(1004)] + _ = x[KeywordReserved-(1005)] + _ = x[KeywordType-(1006)] + _ = x[Name-(2000)] + _ = x[NameAttribute-(2001)] + _ = x[NameClass-(2002)] + _ = x[NameConstant-(2003)] + _ = x[NameDecorator-(2004)] + _ = x[NameEntity-(2005)] + _ = x[NameException-(2006)] + _ = x[NameKeyword-(2007)] + _ = x[NameLabel-(2008)] + _ = x[NameNamespace-(2009)] + _ = x[NameOperator-(2010)] + _ = x[NameOther-(2011)] + _ = x[NamePseudo-(2012)] + _ = x[NameProperty-(2013)] + _ = x[NameTag-(2014)] + _ = x[NameBuiltin-(2100)] + _ = x[NameBuiltinPseudo-(2101)] + _ = x[NameVariable-(2200)] + _ = x[NameVariableAnonymous-(2201)] + _ = x[NameVariableClass-(2202)] + _ = x[NameVariableGlobal-(2203)] + _ = x[NameVariableInstance-(2204)] + _ = x[NameVariableMagic-(2205)] + _ = x[NameFunction-(2300)] + _ = x[NameFunctionMagic-(2301)] + _ = x[Literal-(3000)] + _ = x[LiteralDate-(3001)] + _ = x[LiteralOther-(3002)] + _ = x[LiteralString-(3100)] + _ = x[LiteralStringAffix-(3101)] + _ = x[LiteralStringAtom-(3102)] + _ = x[LiteralStringBacktick-(3103)] + _ = x[LiteralStringBoolean-(3104)] + _ = x[LiteralStringChar-(3105)] + _ = x[LiteralStringDelimiter-(3106)] + _ = x[LiteralStringDoc-(3107)] + _ = x[LiteralStringDouble-(3108)] + _ = x[LiteralStringEscape-(3109)] + _ = x[LiteralStringHeredoc-(3110)] + _ = x[LiteralStringInterpol-(3111)] + _ = x[LiteralStringName-(3112)] + _ = x[LiteralStringOther-(3113)] + _ = x[LiteralStringRegex-(3114)] + _ = x[LiteralStringSingle-(3115)] + _ = x[LiteralStringSymbol-(3116)] + _ = x[LiteralNumber-(3200)] + _ = x[LiteralNumberBin-(3201)] + _ = x[LiteralNumberFloat-(3202)] + _ = x[LiteralNumberHex-(3203)] + _ = x[LiteralNumberInteger-(3204)] + _ = x[LiteralNumberIntegerLong-(3205)] + _ = x[LiteralNumberOct-(3206)] + _ = x[LiteralNumberByte-(3207)] + _ = x[Operator-(4000)] + _ = x[OperatorWord-(4001)] + _ = x[OperatorReserved-(4002)] + _ = x[Punctuation-(5000)] + _ = x[Comment-(6000)] + _ = x[CommentHashbang-(6001)] + _ = x[CommentMultiline-(6002)] + _ = x[CommentSingle-(6003)] + _ = x[CommentSpecial-(6004)] + _ = x[CommentPreproc-(6100)] + _ = x[CommentPreprocFile-(6101)] + _ = x[Generic-(7000)] + _ = x[GenericDeleted-(7001)] + _ = x[GenericEmph-(7002)] + _ = x[GenericError-(7003)] + _ = x[GenericHeading-(7004)] + _ = x[GenericInserted-(7005)] + _ = x[GenericOutput-(7006)] + _ = x[GenericPrompt-(7007)] + _ = x[GenericStrong-(7008)] + _ = x[GenericSubheading-(7009)] + _ = x[GenericTraceback-(7010)] + _ = x[GenericUnderline-(7011)] + _ = x[Text-(8000)] + _ = x[TextWhitespace-(8001)] + _ = x[TextSymbol-(8002)] + _ = x[TextPunctuation-(8003)] +} + +var _TokenTypeValues = []TokenType{Ignore, None, Other, Error, CodeLine, LineLink, LineTableTD, LineTable, LineHighlight, LineNumbersTable, LineNumbers, Line, PreWrapper, Background, EOFType, Keyword, KeywordConstant, KeywordDeclaration, KeywordNamespace, KeywordPseudo, KeywordReserved, KeywordType, Name, NameAttribute, NameClass, NameConstant, NameDecorator, NameEntity, NameException, NameKeyword, NameLabel, NameNamespace, NameOperator, NameOther, NamePseudo, NameProperty, NameTag, NameBuiltin, NameBuiltinPseudo, NameVariable, NameVariableAnonymous, NameVariableClass, NameVariableGlobal, NameVariableInstance, NameVariableMagic, NameFunction, NameFunctionMagic, Literal, LiteralDate, LiteralOther, LiteralString, LiteralStringAffix, LiteralStringAtom, LiteralStringBacktick, LiteralStringBoolean, LiteralStringChar, LiteralStringDelimiter, LiteralStringDoc, LiteralStringDouble, LiteralStringEscape, LiteralStringHeredoc, LiteralStringInterpol, LiteralStringName, LiteralStringOther, LiteralStringRegex, LiteralStringSingle, LiteralStringSymbol, LiteralNumber, LiteralNumberBin, LiteralNumberFloat, LiteralNumberHex, LiteralNumberInteger, LiteralNumberIntegerLong, LiteralNumberOct, LiteralNumberByte, Operator, OperatorWord, OperatorReserved, Punctuation, Comment, CommentHashbang, CommentMultiline, CommentSingle, CommentSpecial, CommentPreproc, CommentPreprocFile, Generic, GenericDeleted, GenericEmph, GenericError, GenericHeading, GenericInserted, GenericOutput, GenericPrompt, GenericStrong, GenericSubheading, GenericTraceback, GenericUnderline, Text, TextWhitespace, TextSymbol, TextPunctuation} + +var _TokenTypeNameToValueMap = map[string]TokenType{ + _TokenTypeName[0:6]: Ignore, + _TokenTypeLowerName[0:6]: Ignore, + _TokenTypeName[6:10]: None, + _TokenTypeLowerName[6:10]: None, + _TokenTypeName[10:15]: Other, + _TokenTypeLowerName[10:15]: Other, + _TokenTypeName[15:20]: Error, + _TokenTypeLowerName[15:20]: Error, + _TokenTypeName[20:28]: CodeLine, + _TokenTypeLowerName[20:28]: CodeLine, + _TokenTypeName[28:36]: LineLink, + _TokenTypeLowerName[28:36]: LineLink, + _TokenTypeName[36:47]: LineTableTD, + _TokenTypeLowerName[36:47]: LineTableTD, + _TokenTypeName[47:56]: LineTable, + _TokenTypeLowerName[47:56]: LineTable, + _TokenTypeName[56:69]: LineHighlight, + _TokenTypeLowerName[56:69]: LineHighlight, + _TokenTypeName[69:85]: LineNumbersTable, + _TokenTypeLowerName[69:85]: LineNumbersTable, + _TokenTypeName[85:96]: LineNumbers, + _TokenTypeLowerName[85:96]: LineNumbers, + _TokenTypeName[96:100]: Line, + _TokenTypeLowerName[96:100]: Line, + _TokenTypeName[100:110]: PreWrapper, + _TokenTypeLowerName[100:110]: PreWrapper, + _TokenTypeName[110:120]: Background, + _TokenTypeLowerName[110:120]: Background, + _TokenTypeName[120:127]: EOFType, + _TokenTypeLowerName[120:127]: EOFType, + _TokenTypeName[127:134]: Keyword, + _TokenTypeLowerName[127:134]: Keyword, + _TokenTypeName[134:149]: KeywordConstant, + _TokenTypeLowerName[134:149]: KeywordConstant, + _TokenTypeName[149:167]: KeywordDeclaration, + _TokenTypeLowerName[149:167]: KeywordDeclaration, + _TokenTypeName[167:183]: KeywordNamespace, + _TokenTypeLowerName[167:183]: KeywordNamespace, + _TokenTypeName[183:196]: KeywordPseudo, + _TokenTypeLowerName[183:196]: KeywordPseudo, + _TokenTypeName[196:211]: KeywordReserved, + _TokenTypeLowerName[196:211]: KeywordReserved, + _TokenTypeName[211:222]: KeywordType, + _TokenTypeLowerName[211:222]: KeywordType, + _TokenTypeName[222:226]: Name, + _TokenTypeLowerName[222:226]: Name, + _TokenTypeName[226:239]: NameAttribute, + _TokenTypeLowerName[226:239]: NameAttribute, + _TokenTypeName[239:248]: NameClass, + _TokenTypeLowerName[239:248]: NameClass, + _TokenTypeName[248:260]: NameConstant, + _TokenTypeLowerName[248:260]: NameConstant, + _TokenTypeName[260:273]: NameDecorator, + _TokenTypeLowerName[260:273]: NameDecorator, + _TokenTypeName[273:283]: NameEntity, + _TokenTypeLowerName[273:283]: NameEntity, + _TokenTypeName[283:296]: NameException, + _TokenTypeLowerName[283:296]: NameException, + _TokenTypeName[296:307]: NameKeyword, + _TokenTypeLowerName[296:307]: NameKeyword, + _TokenTypeName[307:316]: NameLabel, + _TokenTypeLowerName[307:316]: NameLabel, + _TokenTypeName[316:329]: NameNamespace, + _TokenTypeLowerName[316:329]: NameNamespace, + _TokenTypeName[329:341]: NameOperator, + _TokenTypeLowerName[329:341]: NameOperator, + _TokenTypeName[341:350]: NameOther, + _TokenTypeLowerName[341:350]: NameOther, + _TokenTypeName[350:360]: NamePseudo, + _TokenTypeLowerName[350:360]: NamePseudo, + _TokenTypeName[360:372]: NameProperty, + _TokenTypeLowerName[360:372]: NameProperty, + _TokenTypeName[372:379]: NameTag, + _TokenTypeLowerName[372:379]: NameTag, + _TokenTypeName[379:390]: NameBuiltin, + _TokenTypeLowerName[379:390]: NameBuiltin, + _TokenTypeName[390:407]: NameBuiltinPseudo, + _TokenTypeLowerName[390:407]: NameBuiltinPseudo, + _TokenTypeName[407:419]: NameVariable, + _TokenTypeLowerName[407:419]: NameVariable, + _TokenTypeName[419:440]: NameVariableAnonymous, + _TokenTypeLowerName[419:440]: NameVariableAnonymous, + _TokenTypeName[440:457]: NameVariableClass, + _TokenTypeLowerName[440:457]: NameVariableClass, + _TokenTypeName[457:475]: NameVariableGlobal, + _TokenTypeLowerName[457:475]: NameVariableGlobal, + _TokenTypeName[475:495]: NameVariableInstance, + _TokenTypeLowerName[475:495]: NameVariableInstance, + _TokenTypeName[495:512]: NameVariableMagic, + _TokenTypeLowerName[495:512]: NameVariableMagic, + _TokenTypeName[512:524]: NameFunction, + _TokenTypeLowerName[512:524]: NameFunction, + _TokenTypeName[524:541]: NameFunctionMagic, + _TokenTypeLowerName[524:541]: NameFunctionMagic, + _TokenTypeName[541:548]: Literal, + _TokenTypeLowerName[541:548]: Literal, + _TokenTypeName[548:559]: LiteralDate, + _TokenTypeLowerName[548:559]: LiteralDate, + _TokenTypeName[559:571]: LiteralOther, + _TokenTypeLowerName[559:571]: LiteralOther, + _TokenTypeName[571:584]: LiteralString, + _TokenTypeLowerName[571:584]: LiteralString, + _TokenTypeName[584:602]: LiteralStringAffix, + _TokenTypeLowerName[584:602]: LiteralStringAffix, + _TokenTypeName[602:619]: LiteralStringAtom, + _TokenTypeLowerName[602:619]: LiteralStringAtom, + _TokenTypeName[619:640]: LiteralStringBacktick, + _TokenTypeLowerName[619:640]: LiteralStringBacktick, + _TokenTypeName[640:660]: LiteralStringBoolean, + _TokenTypeLowerName[640:660]: LiteralStringBoolean, + _TokenTypeName[660:677]: LiteralStringChar, + _TokenTypeLowerName[660:677]: LiteralStringChar, + _TokenTypeName[677:699]: LiteralStringDelimiter, + _TokenTypeLowerName[677:699]: LiteralStringDelimiter, + _TokenTypeName[699:715]: LiteralStringDoc, + _TokenTypeLowerName[699:715]: LiteralStringDoc, + _TokenTypeName[715:734]: LiteralStringDouble, + _TokenTypeLowerName[715:734]: LiteralStringDouble, + _TokenTypeName[734:753]: LiteralStringEscape, + _TokenTypeLowerName[734:753]: LiteralStringEscape, + _TokenTypeName[753:773]: LiteralStringHeredoc, + _TokenTypeLowerName[753:773]: LiteralStringHeredoc, + _TokenTypeName[773:794]: LiteralStringInterpol, + _TokenTypeLowerName[773:794]: LiteralStringInterpol, + _TokenTypeName[794:811]: LiteralStringName, + _TokenTypeLowerName[794:811]: LiteralStringName, + _TokenTypeName[811:829]: LiteralStringOther, + _TokenTypeLowerName[811:829]: LiteralStringOther, + _TokenTypeName[829:847]: LiteralStringRegex, + _TokenTypeLowerName[829:847]: LiteralStringRegex, + _TokenTypeName[847:866]: LiteralStringSingle, + _TokenTypeLowerName[847:866]: LiteralStringSingle, + _TokenTypeName[866:885]: LiteralStringSymbol, + _TokenTypeLowerName[866:885]: LiteralStringSymbol, + _TokenTypeName[885:898]: LiteralNumber, + _TokenTypeLowerName[885:898]: LiteralNumber, + _TokenTypeName[898:914]: LiteralNumberBin, + _TokenTypeLowerName[898:914]: LiteralNumberBin, + _TokenTypeName[914:932]: LiteralNumberFloat, + _TokenTypeLowerName[914:932]: LiteralNumberFloat, + _TokenTypeName[932:948]: LiteralNumberHex, + _TokenTypeLowerName[932:948]: LiteralNumberHex, + _TokenTypeName[948:968]: LiteralNumberInteger, + _TokenTypeLowerName[948:968]: LiteralNumberInteger, + _TokenTypeName[968:992]: LiteralNumberIntegerLong, + _TokenTypeLowerName[968:992]: LiteralNumberIntegerLong, + _TokenTypeName[992:1008]: LiteralNumberOct, + _TokenTypeLowerName[992:1008]: LiteralNumberOct, + _TokenTypeName[1008:1025]: LiteralNumberByte, + _TokenTypeLowerName[1008:1025]: LiteralNumberByte, + _TokenTypeName[1025:1033]: Operator, + _TokenTypeLowerName[1025:1033]: Operator, + _TokenTypeName[1033:1045]: OperatorWord, + _TokenTypeLowerName[1033:1045]: OperatorWord, + _TokenTypeName[1045:1061]: OperatorReserved, + _TokenTypeLowerName[1045:1061]: OperatorReserved, + _TokenTypeName[1061:1072]: Punctuation, + _TokenTypeLowerName[1061:1072]: Punctuation, + _TokenTypeName[1072:1079]: Comment, + _TokenTypeLowerName[1072:1079]: Comment, + _TokenTypeName[1079:1094]: CommentHashbang, + _TokenTypeLowerName[1079:1094]: CommentHashbang, + _TokenTypeName[1094:1110]: CommentMultiline, + _TokenTypeLowerName[1094:1110]: CommentMultiline, + _TokenTypeName[1110:1123]: CommentSingle, + _TokenTypeLowerName[1110:1123]: CommentSingle, + _TokenTypeName[1123:1137]: CommentSpecial, + _TokenTypeLowerName[1123:1137]: CommentSpecial, + _TokenTypeName[1137:1151]: CommentPreproc, + _TokenTypeLowerName[1137:1151]: CommentPreproc, + _TokenTypeName[1151:1169]: CommentPreprocFile, + _TokenTypeLowerName[1151:1169]: CommentPreprocFile, + _TokenTypeName[1169:1176]: Generic, + _TokenTypeLowerName[1169:1176]: Generic, + _TokenTypeName[1176:1190]: GenericDeleted, + _TokenTypeLowerName[1176:1190]: GenericDeleted, + _TokenTypeName[1190:1201]: GenericEmph, + _TokenTypeLowerName[1190:1201]: GenericEmph, + _TokenTypeName[1201:1213]: GenericError, + _TokenTypeLowerName[1201:1213]: GenericError, + _TokenTypeName[1213:1227]: GenericHeading, + _TokenTypeLowerName[1213:1227]: GenericHeading, + _TokenTypeName[1227:1242]: GenericInserted, + _TokenTypeLowerName[1227:1242]: GenericInserted, + _TokenTypeName[1242:1255]: GenericOutput, + _TokenTypeLowerName[1242:1255]: GenericOutput, + _TokenTypeName[1255:1268]: GenericPrompt, + _TokenTypeLowerName[1255:1268]: GenericPrompt, + _TokenTypeName[1268:1281]: GenericStrong, + _TokenTypeLowerName[1268:1281]: GenericStrong, + _TokenTypeName[1281:1298]: GenericSubheading, + _TokenTypeLowerName[1281:1298]: GenericSubheading, + _TokenTypeName[1298:1314]: GenericTraceback, + _TokenTypeLowerName[1298:1314]: GenericTraceback, + _TokenTypeName[1314:1330]: GenericUnderline, + _TokenTypeLowerName[1314:1330]: GenericUnderline, + _TokenTypeName[1330:1334]: Text, + _TokenTypeLowerName[1330:1334]: Text, + _TokenTypeName[1334:1348]: TextWhitespace, + _TokenTypeLowerName[1334:1348]: TextWhitespace, + _TokenTypeName[1348:1358]: TextSymbol, + _TokenTypeLowerName[1348:1358]: TextSymbol, + _TokenTypeName[1358:1373]: TextPunctuation, + _TokenTypeLowerName[1358:1373]: TextPunctuation, +} + +var _TokenTypeNames = []string{ + _TokenTypeName[0:6], + _TokenTypeName[6:10], + _TokenTypeName[10:15], + _TokenTypeName[15:20], + _TokenTypeName[20:28], + _TokenTypeName[28:36], + _TokenTypeName[36:47], + _TokenTypeName[47:56], + _TokenTypeName[56:69], + _TokenTypeName[69:85], + _TokenTypeName[85:96], + _TokenTypeName[96:100], + _TokenTypeName[100:110], + _TokenTypeName[110:120], + _TokenTypeName[120:127], + _TokenTypeName[127:134], + _TokenTypeName[134:149], + _TokenTypeName[149:167], + _TokenTypeName[167:183], + _TokenTypeName[183:196], + _TokenTypeName[196:211], + _TokenTypeName[211:222], + _TokenTypeName[222:226], + _TokenTypeName[226:239], + _TokenTypeName[239:248], + _TokenTypeName[248:260], + _TokenTypeName[260:273], + _TokenTypeName[273:283], + _TokenTypeName[283:296], + _TokenTypeName[296:307], + _TokenTypeName[307:316], + _TokenTypeName[316:329], + _TokenTypeName[329:341], + _TokenTypeName[341:350], + _TokenTypeName[350:360], + _TokenTypeName[360:372], + _TokenTypeName[372:379], + _TokenTypeName[379:390], + _TokenTypeName[390:407], + _TokenTypeName[407:419], + _TokenTypeName[419:440], + _TokenTypeName[440:457], + _TokenTypeName[457:475], + _TokenTypeName[475:495], + _TokenTypeName[495:512], + _TokenTypeName[512:524], + _TokenTypeName[524:541], + _TokenTypeName[541:548], + _TokenTypeName[548:559], + _TokenTypeName[559:571], + _TokenTypeName[571:584], + _TokenTypeName[584:602], + _TokenTypeName[602:619], + _TokenTypeName[619:640], + _TokenTypeName[640:660], + _TokenTypeName[660:677], + _TokenTypeName[677:699], + _TokenTypeName[699:715], + _TokenTypeName[715:734], + _TokenTypeName[734:753], + _TokenTypeName[753:773], + _TokenTypeName[773:794], + _TokenTypeName[794:811], + _TokenTypeName[811:829], + _TokenTypeName[829:847], + _TokenTypeName[847:866], + _TokenTypeName[866:885], + _TokenTypeName[885:898], + _TokenTypeName[898:914], + _TokenTypeName[914:932], + _TokenTypeName[932:948], + _TokenTypeName[948:968], + _TokenTypeName[968:992], + _TokenTypeName[992:1008], + _TokenTypeName[1008:1025], + _TokenTypeName[1025:1033], + _TokenTypeName[1033:1045], + _TokenTypeName[1045:1061], + _TokenTypeName[1061:1072], + _TokenTypeName[1072:1079], + _TokenTypeName[1079:1094], + _TokenTypeName[1094:1110], + _TokenTypeName[1110:1123], + _TokenTypeName[1123:1137], + _TokenTypeName[1137:1151], + _TokenTypeName[1151:1169], + _TokenTypeName[1169:1176], + _TokenTypeName[1176:1190], + _TokenTypeName[1190:1201], + _TokenTypeName[1201:1213], + _TokenTypeName[1213:1227], + _TokenTypeName[1227:1242], + _TokenTypeName[1242:1255], + _TokenTypeName[1255:1268], + _TokenTypeName[1268:1281], + _TokenTypeName[1281:1298], + _TokenTypeName[1298:1314], + _TokenTypeName[1314:1330], + _TokenTypeName[1330:1334], + _TokenTypeName[1334:1348], + _TokenTypeName[1348:1358], + _TokenTypeName[1358:1373], +} + +// TokenTypeString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func TokenTypeString(s string) (TokenType, error) { + if val, ok := _TokenTypeNameToValueMap[s]; ok { + return val, nil + } + + if val, ok := _TokenTypeNameToValueMap[strings.ToLower(s)]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to TokenType values", s) +} + +// TokenTypeValues returns all values of the enum +func TokenTypeValues() []TokenType { + return _TokenTypeValues +} + +// TokenTypeStrings returns a slice of all String values of the enum +func TokenTypeStrings() []string { + strs := make([]string, len(_TokenTypeNames)) + copy(strs, _TokenTypeNames) + return strs +} + +// IsATokenType returns "true" if the value is listed in the enum definition. "false" otherwise +func (i TokenType) IsATokenType() bool { + _, ok := _TokenTypeMap[i] + return ok +} + +// MarshalText implements the encoding.TextMarshaler interface for TokenType +func (i TokenType) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for TokenType +func (i *TokenType) UnmarshalText(text []byte) error { + var err error + *i, err = TokenTypeString(string(text)) + return err +} diff --git a/vendor/github.com/alecthomas/chroma/v2/types.go b/vendor/github.com/alecthomas/chroma/v2/types.go new file mode 100644 index 000000000..96c0603be --- /dev/null +++ b/vendor/github.com/alecthomas/chroma/v2/types.go @@ -0,0 +1,357 @@ +package chroma + +//go:generate enumer -text -type TokenType + +// TokenType is the type of token to highlight. +// +// It is also an Emitter, emitting a single token of itself +type TokenType int + +// Set of TokenTypes. +// +// Categories of types are grouped in ranges of 1000, while sub-categories are in ranges of 100. For +// example, the literal category is in the range 3000-3999. The sub-category for literal strings is +// in the range 3100-3199. + +// Meta token types. +const ( + // Default background style. + Background TokenType = -1 - iota + // PreWrapper style. + PreWrapper + // Line style. + Line + // Line numbers in output. + LineNumbers + // Line numbers in output when in table. + LineNumbersTable + // Line higlight style. + LineHighlight + // Line numbers table wrapper style. + LineTable + // Line numbers table TD wrapper style. + LineTableTD + // Line number links. + LineLink + // Code line wrapper style. + CodeLine + // Input that could not be tokenised. + Error + // Other is used by the Delegate lexer to indicate which tokens should be handled by the delegate. + Other + // No highlighting. + None + // Don't emit this token to the output. + Ignore + // Used as an EOF marker / nil token + EOFType TokenType = 0 +) + +// Keywords. +const ( + Keyword TokenType = 1000 + iota + KeywordConstant + KeywordDeclaration + KeywordNamespace + KeywordPseudo + KeywordReserved + KeywordType +) + +// Names. +const ( + Name TokenType = 2000 + iota + NameAttribute + NameClass + NameConstant + NameDecorator + NameEntity + NameException + NameKeyword + NameLabel + NameNamespace + NameOperator + NameOther + NamePseudo + NameProperty + NameTag +) + +// Builtin names. +const ( + NameBuiltin TokenType = 2100 + iota + NameBuiltinPseudo +) + +// Variable names. +const ( + NameVariable TokenType = 2200 + iota + NameVariableAnonymous + NameVariableClass + NameVariableGlobal + NameVariableInstance + NameVariableMagic +) + +// Function names. +const ( + NameFunction TokenType = 2300 + iota + NameFunctionMagic +) + +// Literals. +const ( + Literal TokenType = 3000 + iota + LiteralDate + LiteralOther +) + +// Strings. +const ( + LiteralString TokenType = 3100 + iota + LiteralStringAffix + LiteralStringAtom + LiteralStringBacktick + LiteralStringBoolean + LiteralStringChar + LiteralStringDelimiter + LiteralStringDoc + LiteralStringDouble + LiteralStringEscape + LiteralStringHeredoc + LiteralStringInterpol + LiteralStringName + LiteralStringOther + LiteralStringRegex + LiteralStringSingle + LiteralStringSymbol +) + +// Literals. +const ( + LiteralNumber TokenType = 3200 + iota + LiteralNumberBin + LiteralNumberFloat + LiteralNumberHex + LiteralNumberInteger + LiteralNumberIntegerLong + LiteralNumberOct + LiteralNumberByte +) + +// Operators. +const ( + Operator TokenType = 4000 + iota + OperatorWord + OperatorReserved +) + +// Punctuation. +const ( + Punctuation TokenType = 5000 + iota +) + +// Comments. +const ( + Comment TokenType = 6000 + iota + CommentHashbang + CommentMultiline + CommentSingle + CommentSpecial +) + +// Preprocessor "comments". +const ( + CommentPreproc TokenType = 6100 + iota + CommentPreprocFile +) + +// Generic tokens. +const ( + Generic TokenType = 7000 + iota + GenericDeleted + GenericEmph + GenericError + GenericHeading + GenericInserted + GenericOutput + GenericPrompt + GenericStrong + GenericSubheading + GenericTraceback + GenericUnderline +) + +// Text. +const ( + Text TokenType = 8000 + iota + TextWhitespace + TextSymbol + TextPunctuation +) + +// Aliases. +const ( + Whitespace = TextWhitespace + + Date = LiteralDate + + String = LiteralString + StringAffix = LiteralStringAffix + StringBacktick = LiteralStringBacktick + StringChar = LiteralStringChar + StringDelimiter = LiteralStringDelimiter + StringDoc = LiteralStringDoc + StringDouble = LiteralStringDouble + StringEscape = LiteralStringEscape + StringHeredoc = LiteralStringHeredoc + StringInterpol = LiteralStringInterpol + StringOther = LiteralStringOther + StringRegex = LiteralStringRegex + StringSingle = LiteralStringSingle + StringSymbol = LiteralStringSymbol + + Number = LiteralNumber + NumberBin = LiteralNumberBin + NumberFloat = LiteralNumberFloat + NumberHex = LiteralNumberHex + NumberInteger = LiteralNumberInteger + NumberIntegerLong = LiteralNumberIntegerLong + NumberOct = LiteralNumberOct +) + +var ( + StandardTypes = map[TokenType]string{ + Background: "bg", + PreWrapper: "chroma", + Line: "line", + LineNumbers: "ln", + LineNumbersTable: "lnt", + LineHighlight: "hl", + LineTable: "lntable", + LineTableTD: "lntd", + LineLink: "lnlinks", + CodeLine: "cl", + Text: "", + Whitespace: "w", + Error: "err", + Other: "x", + // I have no idea what this is used for... + // Escape: "esc", + + Keyword: "k", + KeywordConstant: "kc", + KeywordDeclaration: "kd", + KeywordNamespace: "kn", + KeywordPseudo: "kp", + KeywordReserved: "kr", + KeywordType: "kt", + + Name: "n", + NameAttribute: "na", + NameBuiltin: "nb", + NameBuiltinPseudo: "bp", + NameClass: "nc", + NameConstant: "no", + NameDecorator: "nd", + NameEntity: "ni", + NameException: "ne", + NameFunction: "nf", + NameFunctionMagic: "fm", + NameProperty: "py", + NameLabel: "nl", + NameNamespace: "nn", + NameOther: "nx", + NameTag: "nt", + NameVariable: "nv", + NameVariableClass: "vc", + NameVariableGlobal: "vg", + NameVariableInstance: "vi", + NameVariableMagic: "vm", + + Literal: "l", + LiteralDate: "ld", + + String: "s", + StringAffix: "sa", + StringBacktick: "sb", + StringChar: "sc", + StringDelimiter: "dl", + StringDoc: "sd", + StringDouble: "s2", + StringEscape: "se", + StringHeredoc: "sh", + StringInterpol: "si", + StringOther: "sx", + StringRegex: "sr", + StringSingle: "s1", + StringSymbol: "ss", + + Number: "m", + NumberBin: "mb", + NumberFloat: "mf", + NumberHex: "mh", + NumberInteger: "mi", + NumberIntegerLong: "il", + NumberOct: "mo", + + Operator: "o", + OperatorWord: "ow", + OperatorReserved: "or", + + Punctuation: "p", + + Comment: "c", + CommentHashbang: "ch", + CommentMultiline: "cm", + CommentPreproc: "cp", + CommentPreprocFile: "cpf", + CommentSingle: "c1", + CommentSpecial: "cs", + + Generic: "g", + GenericDeleted: "gd", + GenericEmph: "ge", + GenericError: "gr", + GenericHeading: "gh", + GenericInserted: "gi", + GenericOutput: "go", + GenericPrompt: "gp", + GenericStrong: "gs", + GenericSubheading: "gu", + GenericTraceback: "gt", + GenericUnderline: "gl", + } +) + +func (t TokenType) Parent() TokenType { + if t%100 != 0 { + return t / 100 * 100 + } + if t%1000 != 0 { + return t / 1000 * 1000 + } + return 0 +} + +func (t TokenType) Category() TokenType { + return t / 1000 * 1000 +} + +func (t TokenType) SubCategory() TokenType { + return t / 100 * 100 +} + +func (t TokenType) InCategory(other TokenType) bool { + return t/1000 == other/1000 +} + +func (t TokenType) InSubCategory(other TokenType) bool { + return t/100 == other/100 +} + +func (t TokenType) Emit(groups []string, _ *LexerState) Iterator { + return Literator(Token{Type: t, Value: groups[0]}) +} + +func (t TokenType) EmitterKind() string { return "token" } diff --git a/vendor/github.com/dlclark/regexp2/v2/.gitignore b/vendor/github.com/dlclark/regexp2/v2/.gitignore new file mode 100644 index 000000000..e41389dff --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/.gitignore @@ -0,0 +1,29 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.out + +.DS_Store +*.txt +benchmarks/ diff --git a/vendor/github.com/dlclark/regexp2/v2/ATTRIB b/vendor/github.com/dlclark/regexp2/v2/ATTRIB new file mode 100644 index 000000000..cdf4560b9 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/ATTRIB @@ -0,0 +1,133 @@ +============ +These pieces of code were ported from dotnet/corefx: + +syntax/charclass.go (from RegexCharClass.cs): ported to use the built-in Go unicode classes. Canonicalize is + a direct port, but most of the other code required large changes because the C# implementation + used a string to represent the CharSet data structure and I cleaned that up in my implementation. + +syntax/code.go (from RegexCode.cs): ported literally with various cleanups and layout to make it more Go-ish. + +syntax/escape.go (from RegexParser.cs): ported Escape method and added some optimizations. Unescape is inspired by + the C# implementation but couldn't be directly ported because of the lack of do-while syntax in Go. + +syntax/parser.go (from RegexpParser.cs and RegexOptions.cs): ported parser struct and associated methods as + literally as possible. Several language differences required changes. E.g. lack pre/post-fix increments as + expressions, lack of do-while loops, lack of overloads, etc. + +syntax/prefix.go (from RegexFCD.cs and RegexBoyerMoore.cs): ported as literally as possible and added support + for unicode chars that are longer than the 16-bit char in C# for the 32-bit rune in Go. + +syntax/replacerdata.go (from RegexReplacement.cs): conceptually ported and re-organized to handle differences + in charclass implementation, and fix odd code layout between RegexParser.cs, Regex.cs, and RegexReplacement.cs. + +syntax/tree.go (from RegexTree.cs and RegexNode.cs): ported literally as possible. + +syntax/writer.go (from RegexWriter.cs): ported literally with minor changes to make it more Go-ish. + +match.go (from RegexMatch.cs): ported, simplified, and changed to handle Go's lack of inheritence. + +regexp.go (from Regex.cs and RegexOptions.cs): conceptually serves the same "starting point", but is simplified + and changed to handle differences in C# strings and Go strings/runes. + +replace.go (from RegexReplacement.cs): ported closely and then cleaned up to combine the MatchEvaluator and + simple string replace implementations. + +runner.go (from RegexRunner.cs): ported literally as possible. + +regexp_test.go (from CaptureTests.cs and GroupNamesAndNumbers.cs): conceptually ported, but the code was + manually structured like Go tests. + +replace_test.go (from RegexReplaceStringTest0.cs): conceptually ported + +rtl_test.go (from RightToLeft.cs): conceptually ported +--- +dotnet/corefx was released under this license: + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +============ +These pieces of code are copied from the Go framework: + +- The overall directory structure of regexp2 was inspired by the Go runtime regexp package. +- The optimization in the escape method of syntax/escape.go is from the Go runtime QuoteMeta() func in regexp/regexp.go +- The method signatures in regexp.go are designed to match the Go framework regexp methods closely +- func regexp2.MustCompile and func quote are almost identifical to the regexp package versions +- BenchmarkMatch* and TestProgramTooLong* funcs in regexp_performance_test.go were copied from the framework + regexp/exec_test.go +--- +The Go framework was released under this license: + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +============ +Some test data were gathered from the Mono project. + +regexp_mono_test.go: ported from https://github.com/mono/mono/blob/master/mcs/class/System/Test/System.Text.RegularExpressions/PerlTrials.cs +--- +Mono tests released under this license: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/github.com/dlclark/regexp2/v2/LICENSE b/vendor/github.com/dlclark/regexp2/v2/LICENSE new file mode 100644 index 000000000..fe83dfdc9 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Doug Clark + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/dlclark/regexp2/v2/README.md b/vendor/github.com/dlclark/regexp2/v2/README.md new file mode 100644 index 000000000..ead72f69e --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/README.md @@ -0,0 +1,267 @@ +# regexp2 - full featured regular expressions for Go +Regexp2 is a feature-rich RegExp engine for Go. It doesn't have constant time guarantees like the built-in `regexp` package, but it allows backtracking and is compatible with Perl5 and .NET. You'll likely be better off with the RE2 engine from the `regexp` package and should only use this if you need to write very complex patterns or require compatibility with .NET. + +## Basis of the engine +The engine is ported from the .NET framework's System.Text.RegularExpressions.Regex engine. That engine was open sourced in 2015 under the MIT license. There are some fundamental differences between .NET strings and Go strings that required a bit of borrowing from the Go framework regex engine as well. I cleaned up a couple of the dirtier bits during the port (regexcharclass.cs was terrible), but the parse tree, code emmitted, and therefore patterns matched should be identical. + +## New Code Generation +For extra performance use `regexp2` with [`regexp2cg`](https://github.com/dlclark/regexp2cg). It is a code generation utility for `regexp2` and you can likely improve your regexp runtime performance by 3-10x in hot code paths. As always you should benchmark your specifics to confirm the results. Give it a try! + +## Installing +This is a go-gettable library, so install is easy: + + go get github.com/dlclark/regexp2/v2@latest + +## Changes in v2 +Version 2 includes changes that may affect compatibility with existing v1 users: + +* The module path is now `github.com/dlclark/regexp2/v2`, so imports need to use the `/v2` suffix. +* The minimum supported Go version is now Go 1.26. +* Changes to support https://github.com/dlclark/regexp2cg are merged in to support generated regex engines. +* `Regexp.Split` is now available for splitting strings with regexp matches. +* The new `compat` sub-package provides a [`regexp` compatibility adapter](#regexp-compatibility-adapter) with the same `Find*` and `Match*` method signatures as `regexp.Regexp`, plus a `compat.Matcher` interface that is implemented by both `*regexp.Regexp` and the adapter. +* The parser, optimizer, and runner internals have changed significantly to support generated regexes and additional matching optimizations. +* `Compile` and `MustCompile` now use variadic compile options for regex behavior and memory/performance tuning. See [Compile options](#compile-options) for more details. +* Moved `regexp2.Debug` and `regexp2.Compile` to new `regexp2.OptionDebug()` and `regexp2.OptionIsCodeGen()` compile options. +* Some types and constants in the `syntax` package have been exported or changed to support code generation. +* Conceptually changed the goal of the `regexp2.ECMAScript` option to be closer to the ECMAScript standard rather than C#'s ECMAScript behavior. +* Renamed the fields `Capture.Index` and `Capture.Length` to `Capture.RuneIndex` and `Capture.RuneLength` to be more clear that we're dealing with rune offsets. +* Added `Capture.ByteRange()` to return the byte offset index and length of the captured text. This requires some additional processing to be done behind the scenes the first time it's called for a given capture to convert the native rune offsets to byte offsets. + +## Usage +Usage is similar to the Go `regexp` package. Just like in `regexp`, you start by converting a regex into a state machine via the `Compile` or `MustCompile` methods. They ultimately do the same thing, but `MustCompile` will panic if the regex is invalid. You can then use the provided `Regexp` struct to find matches repeatedly. A `Regexp` struct is safe to use across goroutines. + +```go +re := regexp2.MustCompile(`Your pattern`) +if isMatch, _ := re.MatchString(`Something to match`); isMatch { + //do something +} +``` + +The only error that the `*Match*` methods *should* return is a Timeout if you set the `re.MatchTimeout` field. Any other error is a bug in the `regexp2` package. If you need more details about capture groups in a match then use the `FindStringMatch` method, like so: + +```go +if m, _ := re.FindStringMatch(`Something to match`); m != nil { + // the whole match is always group 0 + fmt.Printf("Group 0: %v\n", m.String()) + + // you can get all the groups too + gps := m.Groups() + + // a group can be captured multiple times, so each cap is separately addressable + fmt.Printf("Group 1, first capture", gps[1].Captures[0].String()) + fmt.Printf("Group 1, second capture", gps[1].Captures[1].String()) +} +``` + +Group 0 is embedded in the Match. Group 0 is an automatically-assigned group that encompasses the whole pattern. This means that `m.String()` is the same as `m.Group.String()` and `m.Groups()[0].String()` + +The __last__ capture is embedded in each group, so `g.String()` will return the same thing as `g.Capture.String()` and `g.Captures[len(g.Captures)-1].String()`. + +If you want to find multiple matches from a single input string you should use the `FindNextMatch` method. For example, to implement a function similar to `regexp.FindAllString`: + +```go +func regexp2FindAllString(re *regexp2.Regexp, s string) []string { + var matches []string + m, _ := re.FindStringMatch(s) + for m != nil { + matches = append(matches, m.String()) + m, _ = re.FindNextMatch(m) + } + return matches +} +``` + +`FindNextMatch` is optmized so that it re-uses the underlying string/rune slice. + +The internals of `regexp2` always operate on `[]rune` so `RuneIndex` and `RuneLength` data in a `Match` always reference a position in `rune`s rather than `byte`s (even if the input was given as a string). `ByteRange()` provides UTF-8 byte offsets, matching the original string input for string APIs. It's advisable to use the provided `String()` methods when you do not need explicit offsets. `ByteRange()` lazily caches byte offsets on the shared match text, so the first call on captures from the same match is not safe to run concurrently with other `ByteRange()` calls on that match. + +## `regexp` compatibility adapter + +The `github.com/dlclark/regexp2/v2/compat` package provides an adapter for callers that want the same `Find*` and `Match*` method signatures as the standard library's `regexp.Regexp`, while still using the `regexp2` engine. + +```go +import ( + "github.com/dlclark/regexp2/v2" + "github.com/dlclark/regexp2/v2/compat" +) + +re := compat.MustCompile(`Your pattern`, regexp2.RE2) +if re.MatchString(`Something to match`) { + // do something +} + +matches := re.FindAllString(`abc axbc`, -1) +_ = matches +``` + +You can also wrap an existing compiled regexp: + +```go +base := regexp2.MustCompile(`Your pattern`) +re := compat.Wrap(base) +``` + +The adapter includes the standard-library matching surface: `Match`, `MatchString`, `MatchReader`, and all `Find(All)?(String)?(Submatch)?(Index)?` methods. Index-returning methods use UTF-8 byte offsets like `regexp`, not regexp2's rune offsets. + +The package also defines `compat.Matcher`, a common interface implemented by both `*regexp.Regexp` and `*compat.Regexp`. Use it when code should accept either the standard library engine or a regexp2-backed adapter: + +```go +func findWords(re compat.Matcher, input string) []string { + return re.FindAllString(input, -1) +} +``` + +Because those standard-library method signatures do not return errors, the adapter panics if the wrapped regexp2 matcher returns an error such as a match timeout. Use the main `regexp2` APIs directly when you need to handle timeouts as errors. + +## Compile options + +`Compile` and `MustCompile` take variadic compile options. Most users can omit them and get default regex behavior plus bounded shared pools for rune buffers and replacement output buffers, plus per-regexp caches for parsed replacement patterns and ASCII character class bitmaps. + +Regex option constants can be passed directly, individually or as a bitmask: + +```go +re := regexp2.MustCompile(`Your pattern`, regexp2.IgnoreCase, regexp2.Singleline) +re = regexp2.MustCompile(`Your pattern`, regexp2.IgnoreCase|regexp2.Singleline) +``` + +Performance tuning options override the default cache settings: + +```go +re := regexp2.MustCompile(`Your pattern`, + regexp2.IgnoreCase, + regexp2.OptionMaxCachedRuneBufferLength(64*1024), + regexp2.OptionMaxCachedReplacerDataEntries(8), +) +``` + +Compile-only options configure behavior that is not settable from the pattern: + +```go +re := regexp2.MustCompile(`(?This) (is)`, regexp2.OptionMaintainCaptureOrder()) +``` + +The defaults are intentionally bounded: + +| Option | Default | Used by | Working-set growth | Tradeoffs | +| --- | ---: | --- | --- | --- | +| `OptionMaintainCaptureOrder()` | false | Parser capture-slot assignment for mixed named and unnamed captures. | None at match time. This changes compile-time capture numbering only. | Keeps named and unnamed captures in pattern order instead of appending named captures after unnamed captures. This can change numeric backreference meaning, so it is caller-controlled rather than an inline regex option. | +| `OptionDebug()` | false | Compile dumps and runner tracing. | Debug output volume only. | Useful for diagnostics, but it can produce noisy output and slower traced matching. | +| `OptionIsCodeGen()` | false | Compile-time find-optimization analysis for [`regexp2cg`](https://github.com/dlclark/regexp2cg). | Per compiled regexp, during `Compile` or `MustCompile`. | Enables more expensive analysis intended for generated engines. Do not use it for normal interpreter execution; the interpreter defaults intentionally avoid this extra compile-time cost. | +| `OptionMaxCachedRuneBufferLength(n)` | 256K runes | String APIs that run through pooled runners, such as `MatchString` and replacement-pattern `Replace`, when converting input strings to the engine's internal `[]rune` representation. | Process-wide shared `sync.Pool` retention by size class. This does not grow per compiled regexp or per input string; the practical working set follows recent and concurrent use across all regexps and can be dropped by GC. | Raising this lets calls use larger pooled rune buffers and can reduce allocations for repeated matches against large strings. Lowering it prevents larger buffers from being borrowed or returned, so large inputs allocate directly. | +| `OptionMaxCachedReplaceBufferLength(n)` | 256 KB | Replacement-pattern `Replace` calls that build output through a shared byte buffer. | Process-wide shared `sync.Pool` retention by size class after replacement-pattern `Replace` runs. It does not grow from evaluator-based `ReplaceFunc` output and is shared across compiled regexps. | Raising this lets larger replacement outputs use pooled buffers and can reduce allocations. Lowering it prevents larger output buffers from being retained, so large replacements allocate directly. | +| `OptionMaxCachedReplacerDataEntries(n)` | `16` | `Replace` with replacement pattern strings, after the replacement pattern is parsed into reusable replacement data. | Per compiled regexp. The cache grows as distinct cacheable replacement strings are used with `Replace`, up to this entry count. | Raising this helps when a single compiled regexp is used with many recurring replacement patterns. It increases per-regexp cache memory and lock-protected cache bookkeeping. Setting it to `0` disables this cache. | +| `OptionMaxCachedReplacerDataBytes(n)` | 4 KB | The parsed replacement-pattern cache. Replacement strings longer than this are parsed for the call but not retained. | Per compiled regexp, combined with `OptionMaxCachedReplacerDataEntries`. Only replacement strings whose source text is at or below this size can add parsed data to the cache. | Raising this helps if large replacement patterns are reused. It can retain more memory per cached replacement. Lowering it avoids keeping unusual large replacement patterns around. | +| `OptionDisableCharClassASCIIBitmap()` | false | Compile-time preparation of character classes and first-character prefix sets. By default, character classes with ASCII membership get a small bitmap used by `CharIn`. | Per compiled regexp, during `Compile` or `MustCompile`. Each eligible character class can hold one small bitmap; this does not scale with match concurrency or input size. | Leaving this false speeds up ASCII-heavy character class checks at the cost of a small amount of per-char-class memory and compile-time work. Setting to true can reduce memory for large numbers of compiled char classes in regexps, but ASCII character class matching may be slower. | + +For pooled buffer cache options, set `n` to `0` to disable pooling, or `-1` to allow all built-in size classes. The rune buffer classes are 1K, 4K, 16K, 64K, and 256K runes. The replacement byte buffer classes are 4 KB, 16 KB, 64 KB, 256 KB, and 1 MB. By default the 1 MB pool is unused. For replacement data byte-size cache options, `-1` means unbounded. For entry-count cache options, set `n` to `0` to disable the cache. + +## Compare `regexp` and `regexp2` +| Category | regexp | regexp2 | +| --- | --- | --- | +| Catastrophic backtracking possible | no, constant execution time guarantees | yes, if your pattern is at risk you can use the `re.MatchTimeout` field | +| Python-style capture groups `(?Pre)` | yes | no (yes in RE2 compat mode) | +| .NET-style capture groups `(?re)` or `(?'name're)` | yes | yes | +| comments `(?#comment)` | no | yes | +| branch numbering reset `(?\|a\|b)` | no | no | +| possessive match `(?>re)` | no | yes | +| positive lookahead `(?=re)` | no | yes | +| negative lookahead `(?!re)` | no | yes | +| positive lookbehind `(?<=re)` | no | yes | +| negative lookbehind `(?re)`) +* add support for python-style named backreferences (e.g. `(?P=name)`) +* change singleline behavior for `$` to only match end of string (like RE2) (see [#24](https://github.com/dlclark/regexp2/issues/24)) +* change the character classes `\d` `\s` and `\w` to match the same characters as RE2. NOTE: if you also use the `ECMAScript` option then this will change the `\s` character class to match ECMAScript instead of RE2. ECMAScript allows more whitespace characters in `\s` than RE2 (but still fewer than the the default behavior). +* allow character escape sequences to have defaults. For example, by default `\_` isn't a known character escape and will fail to compile, but in RE2 mode it will match the literal character `_` + +```go +re := regexp2.MustCompile(`Your RE2-compatible pattern`, regexp2.RE2) +if isMatch, _ := re.MatchString(`Something to match`); isMatch { + //do something +} +``` + +This feature is a work in progress and I'm open to ideas for more things to put here (maybe more relaxed character escaping rules?). + +## Catastrophic Backtracking and Timeouts + +`regexp2` supports features that can lead to catastrophic backtracking. +`Regexp.MatchTimeout` can be set to to limit the impact of such behavior; the +match will fail with an error after approximately MatchTimeout. No timeout +checks are done by default. + +Timeout checking is not free. The current timeout checking implementation starts +a background worker that updates a clock value approximately once every 100 +milliseconds. The matching code compares this value against the precomputed +deadline for the match. The performance impact is as follows. + +1. A match with a timeout runs almost as fast as a match without a timeout. +2. If any live matches have a timeout, there will be a background CPU load + (`~0.15%` currently on a modern machine). This load will remain constant + regardless of the number of matches done including matches done in parallel. +3. If no live matches are using a timeout, the background load will remain + until the longest deadline (match timeout + the time when the match started) + is reached. E.g., if you set a timeout of one minute the load will persist + for approximately a minute even if the match finishes quickly. + +See [PR #58](https://github.com/dlclark/regexp2/pull/58) for more details and +alternatives considered. + +## Goroutine leak error +If you're using a library during unit tests (e.g. https://github.com/uber-go/goleak) that validates all goroutines are exited then you'll likely get an error if you or any of your dependencies use regex's with a MatchTimeout. +To remedy the problem you'll need to tell the unit test to wait until the backgroup timeout goroutine is exited. + +```go +func TestSomething(t *testing.T) { + defer goleak.VerifyNone(t) + defer regexp2.StopTimeoutClock() + + // ... test +} + +//or + +func TestMain(m *testing.M) { + // setup + // ... + + // run + m.Run() + + //tear down + regexp2.StopTimeoutClock() + goleak.VerifyNone(t) +} +``` + +This will add ~100ms runtime to each test (or TestMain). If that's too much time you can set the clock cycle rate of the timeout goroutine in an init function in a test file. `regexp2.SetTimeoutCheckPeriod` isn't threadsafe so it must be setup before starting any regex's with Timeouts. + +```go +func init() { + //speed up testing by making the timeout clock 1ms + regexp2.SetTimeoutCheckPeriod(time.Millisecond) +} +``` + +## ECMAScript compatibility mode +In this mode the engine attempts to match the [regex engine](https://tc39.es/ecma262/multipage/text-processing.html#sec-regexp-regular-expression-objects) described in the ECMAScript specification as closely as reasonably possible within regexp2's API and implementation. + +This flag should not be treated as compatibility with C#'s `RegexOptions.ECMAScript`. regexp2's ECMAScript behavior prioritizes ECMAScript specification behavior over matching the C# regex engine's interpretation of that option. + +Additionally a Unicode mode is provided which allows parsing of `\u{CodePoint}` syntax only when both `ECMAScript` and `Unicode` are provided. + +## Potential bugs +I've run a battery of tests against regexp2 from various sources and found the debug output matches the .NET engine, but .NET and Go handle strings very differently. I've attempted to handle these differences, but most of my testing deals with basic ASCII with a little bit of multi-byte Unicode. There's a chance that there are bugs in the string handling related to character sets with supplementary Unicode chars. Right-to-Left support is coded, but not well tested either. + +## Find a bug? +I'm open to new issues and pull requests with tests if you find something odd! diff --git a/vendor/github.com/dlclark/regexp2/v2/Taskfile.yml b/vendor/github.com/dlclark/regexp2/v2/Taskfile.yml new file mode 100644 index 000000000..fffb3f270 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/Taskfile.yml @@ -0,0 +1,64 @@ +version: '3' + +tasks: + benchcmp: + desc: Compare current benchmarks against an optional commit, defaulting to HEAD + vars: + BENCH_COUNT: '{{default "5" .COUNT}}' + REF: + sh: | + if [ -n "{{.COMMIT}}" ]; then + printf '%s\n' "{{.COMMIT}}" + elif [ -n "{{.CLI_ARGS}}" ]; then + set -- {{.CLI_ARGS}} + printf '%s\n' "$1" + else + printf '%s\n' HEAD + fi + COMMIT_HASH: + sh: git rev-parse --verify "{{.REF}}^{commit}" + cmds: + - | + set -eu + + mkdir -p benchmarks + + base_file="benchmarks/{{.COMMIT_HASH}}-count{{.BENCH_COUNT}}.txt" + new_file="benchmarks/new.txt" + base_tmp="" + new_tmp="" + worktree_dir="$(mktemp -d /tmp/regexp2-benchcmp.XXXXXX)" + + cleanup() { + if [ -n "$base_tmp" ]; then + rm -f "$base_tmp" + fi + if [ -n "$new_tmp" ]; then + rm -f "$new_tmp" + fi + git worktree remove --force "$worktree_dir" >/dev/null 2>&1 || true + } + trap cleanup EXIT + + if [ -f "$base_file" ] && grep -q '^Benchmark' "$base_file"; then + printf 'Using cached baseline benchmarks from %s...\n' "$base_file" + else + if [ -f "$base_file" ]; then + printf 'Ignoring cached baseline without benchmark results at %s...\n' "$base_file" + fi + printf 'Running baseline benchmarks for %s (%s samples)...\n' "{{.COMMIT_HASH}}" "{{.BENCH_COUNT}}" + git worktree add --detach "$worktree_dir" "{{.COMMIT_HASH}}" + base_tmp="$(mktemp "${base_file}.tmp.XXXXXX")" + (cd "$worktree_dir" && env GOWORK=off go test -run '^$' -bench . -benchmem -count "{{.BENCH_COUNT}}" ./...) > "$base_tmp" + mv "$base_tmp" "$base_file" + base_tmp="" + fi + + printf 'Running current benchmarks (%s samples)...\n' "{{.BENCH_COUNT}}" + new_tmp="$(mktemp "${new_file}.tmp.XXXXXX")" + env GOWORK=off go test -run '^$' -bench . -benchmem -count "{{.BENCH_COUNT}}" ./... > "$new_tmp" + mv "$new_tmp" "$new_file" + new_tmp="" + + printf 'Comparing benchmark results with benchstat...\n' + benchstat "$base_file" "$new_file" diff --git a/vendor/github.com/dlclark/regexp2/v2/bufferpool.go b/vendor/github.com/dlclark/regexp2/v2/bufferpool.go new file mode 100644 index 000000000..0580d6a17 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/bufferpool.go @@ -0,0 +1,76 @@ +package regexp2 + +import ( + "bytes" + "slices" + "sync" +) + +type pooledSliceBuffers[T any] struct { + sizes []int + pools []sync.Pool +} + +func newPooledSliceBuffers[T any](sizes ...int) *pooledSliceBuffers[T] { + sizes = slices.Clone(sizes) + slices.Sort(sizes) + return &pooledSliceBuffers[T]{ + sizes: sizes, + pools: make([]sync.Pool, len(sizes)), + } +} + +func (p *pooledSliceBuffers[T]) poolIndex(neededSize, maxSize int) int { + if maxSize == 0 { + return -1 + } + for i, classSize := range p.sizes { + if neededSize <= classSize { + if maxSize > 0 && classSize > maxSize { + return -1 + } + return i + } + } + return -1 +} + +func (p *pooledSliceBuffers[T]) get(neededSize, maxSize int) ([]T, *[]T) { + idx := p.poolIndex(neededSize, maxSize) + if idx < 0 { + return make([]T, neededSize), nil + } + if v := p.pools[idx].Get(); v != nil { + bufp := v.(*[]T) + if cap(*bufp) >= neededSize { + return (*bufp)[:neededSize], bufp + } + } + buf := make([]T, neededSize, p.sizes[idx]) + return buf, &buf +} + +func (p *pooledSliceBuffers[T]) put(bufp *[]T) { + idx := p.poolIndex(cap(*bufp), -1) + if idx < 0 || cap(*bufp) != p.sizes[idx] { + return + } + *bufp = (*bufp)[:0] + p.pools[idx].Put(bufp) +} + +// our specific pooled buffers +var ( + pooledRuneBuffers = newPooledSliceBuffers[rune](1<<10, 4<<10, 16<<10, 64<<10, 256<<10) + pooledByteBuffers = newPooledSliceBuffers[byte](4<<10, 16<<10, 64<<10, 256<<10, 1<<20) +) + +func getPooledReplaceBuffer(neededBytes, maxSize int) (*bytes.Buffer, *[]byte) { + buf, pooled := pooledByteBuffers.get(neededBytes, maxSize) + return bytes.NewBuffer(buf[:0]), pooled +} + +func putPooledReplaceBuffer(buf *bytes.Buffer, pooled *[]byte) { + *pooled = buf.Bytes() + pooledByteBuffers.put(pooled) +} diff --git a/vendor/github.com/dlclark/regexp2/v2/fastclock.go b/vendor/github.com/dlclark/regexp2/v2/fastclock.go new file mode 100644 index 000000000..d256e63c7 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/fastclock.go @@ -0,0 +1,141 @@ +package regexp2 + +import ( + "sync" + "sync/atomic" + "time" +) + +// fasttime holds a time value (ticks since clock initialization) +type fasttime int64 + +// fastclock provides a fast clock implementation. +// +// A background goroutine periodically stores the current time +// into an atomic variable. +// +// A deadline can be quickly checked for expiration by comparing +// its value to the clock stored in the atomic variable. +// +// The goroutine automatically stops once clockEnd is reached. +// (clockEnd covers the largest deadline seen so far + some +// extra time). This ensures that if regexp2 with timeouts +// stops being used we will stop background work. +type fastclock struct { + // instances of atomicTime must be at the start of the struct (or at least 64-bit aligned) + // otherwise 32-bit architectures will panic + + current atomicTime // Current time (approximate) + clockEnd atomicTime // When clock updater is supposed to stop (>= any existing deadline) + + // current and clockEnd can be read via atomic loads. + // Reads and writes of other fields require mu to be held. + mu sync.Mutex + start time.Time // Time corresponding to fasttime(0) + running bool // Is a clock updater running? +} + +var fast fastclock + +// reached returns true if current time is at or past t. +func (t fasttime) reached() bool { + return fast.current.read() >= t +} + +// makeDeadline returns a time that is approximately time.Now().Add(d) +func makeDeadline(d time.Duration) fasttime { + // Increase the deadline since the clock we are reading may be + // just about to tick forwards. + end := fast.current.read() + durationToTicks(d+clockPeriod) + + // Start or extend clock if necessary. + if end > fast.clockEnd.read() { + // If time.Since(last use) > timeout, there's a chance that + // fast.current will no longer be updated, which can lead to + // incorrect 'end' calculations that can trigger a false timeout + fast.mu.Lock() + if !fast.running && !fast.start.IsZero() { + // update fast.current + fast.current.write(durationToTicks(time.Since(fast.start))) + // recalculate our end value + end = fast.current.read() + durationToTicks(d+clockPeriod) + } + fast.mu.Unlock() + extendClock(end) + } + + return end +} + +// extendClock ensures that clock is live and will run until at least end. +func extendClock(end fasttime) { + fast.mu.Lock() + defer fast.mu.Unlock() + + if fast.start.IsZero() { + fast.start = time.Now() + } + + // Extend the running time to cover end as well as a bit of slop. + if shutdown := end + durationToTicks(time.Second); shutdown > fast.clockEnd.read() { + fast.clockEnd.write(shutdown) + } + + // Start clock if necessary + if !fast.running { + fast.running = true + go runClock() + } +} + +// stop the timeout clock in the background +// should only used for unit tests to abandon the background goroutine +func stopClock() { + fast.mu.Lock() + if fast.running { + fast.clockEnd.write(fasttime(0)) + } + fast.mu.Unlock() + + // pause until not running + // get and release the lock + isRunning := true + for isRunning { + time.Sleep(clockPeriod / 2) + fast.mu.Lock() + isRunning = fast.running + fast.mu.Unlock() + } +} + +func durationToTicks(d time.Duration) fasttime { + // Downscale nanoseconds to approximately a millisecond so that we can avoid + // overflow even if the caller passes in math.MaxInt64. + return fasttime(d) >> 20 +} + +const DefaultClockPeriod = 100 * time.Millisecond + +// clockPeriod is the approximate interval between updates of approximateClock. +var clockPeriod = DefaultClockPeriod + +func runClock() { + fast.mu.Lock() + defer fast.mu.Unlock() + + for fast.current.read() <= fast.clockEnd.read() { + // Unlock while sleeping. + fast.mu.Unlock() + time.Sleep(clockPeriod) + fast.mu.Lock() + + newTime := durationToTicks(time.Since(fast.start)) + fast.current.write(newTime) + } + fast.running = false +} + +type atomicTime struct{ v int64 } // Should change to atomic.Int64 when we can use go 1.19 + +func (t *atomicTime) read() fasttime { return fasttime(atomic.LoadInt64(&t.v)) } +func (t *atomicTime) write(v fasttime) { atomic.StoreInt64(&t.v, int64(v)) } diff --git a/vendor/github.com/dlclark/regexp2/v2/helpers/indexof.go b/vendor/github.com/dlclark/regexp2/v2/helpers/indexof.go new file mode 100644 index 000000000..8d24a8c08 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/helpers/indexof.go @@ -0,0 +1,394 @@ +package helpers + +import ( + "bytes" + "slices" + "strings" + "unicode" + "unsafe" + + "github.com/dlclark/regexp2/v2/syntax" +) + +func IndexOfAny(in []rune, find []rune) int { + // special case + if len(find) == 0 { + return -1 + } + // naive version + for i, c := range in { + if slices.Contains(find, c) { + return i + } + } + return -1 +} + +func IndexOfAny1(in []rune, find rune) int { + //TODO: bytes optimization? + return slices.Index(in, find) +} + +func IndexOfAny2(in []rune, find1, find2 rune) int { + for i, c := range in { + if c == find1 || c == find2 { + return i + } + } + + return -1 +} + +func IndexOfAny3(in []rune, find1, find2, find3 rune) int { + for i, c := range in { + if c == find1 || c == find2 || c == find3 { + return i + } + } + + return -1 +} + +func IndexOfAnyInRange(in []rune, first, last rune) int { + for i, c := range in { + if c >= first && c <= last { + return i + } + } + return -1 +} + +func IndexOfAnyExcept(in []rune, bad []rune) int { + for i, c := range in { + found := false + for _, b := range bad { + if b == c { + found = true + break + } + } + if !found { + return i + } + } + return -1 +} + +func IndexOfAnyExcept1(in []rune, bad rune) int { + for i, c := range in { + if c != bad { + return i + } + } + return -1 +} + +func IndexOfAnyExcept2(in []rune, bad1, bad2 rune) int { + for i, c := range in { + if c != bad1 && c != bad2 { + return i + } + } + + return -1 +} + +func IndexOfAnyExcept3(in []rune, bad1, bad2, bad3 rune) int { + for i, c := range in { + if c != bad1 && c != bad2 && c != bad3 { + return i + } + } + + return -1 +} + +func IndexOfAnyExceptInRange(in []rune, first, last rune) int { + for i, c := range in { + if c > last { + return i + } + if c < first { + return i + } + } + return -1 +} + +func IndexFunc(in []rune, f func(ch rune) bool) int { + for i := range in { + if f(in[i]) { + return i + } + } + return -1 +} + +func IndexOfAnyExceptInSet(in []rune, set syntax.CharSet) int { + //TODO: this + panic("not implemented") +} + +func LastIndexOf(in []rune, find []rune) int { + end := len(in) - len(find) + first := find[0] + lastOffset := len(find) - 1 + last := find[lastOffset] + for i := end; i >= 0; i-- { + //TODO: check 2 chars needed? + // match start and end...check the middle + if in[i] == first && in[i+lastOffset] == last { + // found our first char + // check if the rest are equal + if bytesEqual(in[i:i+len(find)], find) { + return i + } + } + } + + //not found + return -1 +} + +func LastIndexOfAnyExcept1(in []rune, not rune) int { + for i := len(in) - 1; i >= 0; i-- { + if in[i] != not { + return i + } + } + return -1 +} + +func LastIndexOfAny1(in []rune, find rune) int { + for i := len(in) - 1; i >= 0; i-- { + if in[i] == find { + // found our char + return i + } + } + + //not found + return -1 +} + +func LastIndexOfAnyInRange(in []rune, first, last rune) int { + for i := len(in) - 1; i >= 0; i-- { + if in[i] >= first && in[i] <= last { + return i + } + } + return -1 +} + +//TODO: LastIndexOf methods +//IndexOfAnyInRange +//LastIndexOfAnyInRange +//LastIndexOfAnyExceptInRange + +// find should always be sent in lower-case +func IndexOfIgnoreCase(in []rune, find []rune) int { + // search the in slice for the "find" slice, ignoring case in the comparisons + end := len(in) - len(find) + first := find[0] + for i := 0; i <= end; i++ { + if in[i] != first && unicode.ToLower(in[i]) != first { + continue + } + match := true + for j := 1; j < len(find); j++ { + inChar := in[i+j] + if inChar != find[j] && unicode.ToLower(inChar) != find[j] { + match = false + break + } + } + if match { + return i + } + } + return -1 +} + +func IndexOfIgnoreCaseAscii(in []rune, find []rune) int { + // search the in slice for the "find" slice, ignoring case in the comparisons + // we can assume the find chars are ascii and do simple masks on them + if len(find) == 0 { + return 0 + } + end := len(in) - len(find) + first := foldASCII(rune(find[0])) + for i := 0; i <= end; i++ { + if foldASCII(in[i]) != first { + continue + } + match := true + for j := 1; j < len(find); j++ { + if foldASCII(in[i+j]) != foldASCII(find[j]) { + match = false + break + } + } + if match { + return i + } + } + return -1 +} + +func IndexStringIgnoreCaseASCII(s, prefix string) int { + if len(prefix) == 0 { + return 0 + } + + for start, end := 0, len(s)-len(prefix); start <= end; { + offset := indexASCIIByteIgnoreCase(s[start:], prefix[0]) + if offset < 0 || start+offset > end { + return -1 + } + + i := start + offset + if EqualStringIgnoreCaseASCII(s[i:i+len(prefix)], prefix) { + return i + } + start = i + 1 + } + return -1 +} + +func EqualStringIgnoreCaseASCII(s, prefix string) bool { + if len(s) < len(prefix) { + return false + } + for i := 0; i < len(prefix); i++ { + if foldASCII(rune(s[i])) != foldASCII(rune(prefix[i])) { + return false + } + } + return true +} + +func indexASCIIByteIgnoreCase(s string, ch byte) int { + ch = byte(foldASCII(rune(ch))) + lower := strings.IndexByte(s, ch) + if ch < 'a' || ch > 'z' { + return lower + } + upper := strings.IndexByte(s, ch-('a'-'A')) + if lower < 0 { + return upper + } + if upper >= 0 && upper < lower { + return upper + } + return lower +} + +func foldASCII(c rune) rune { + if 'A' <= c && c <= 'Z' { + return c + ('a' - 'A') + } + return c +} + +func IndexOf(in []rune, find []rune) int { + /* + Since we auto-gen the find code this shouldn't happen + if len(find) == 0 { + //special case + return -1 + }*/ + end := len(in) - len(find) + first := find[0] + //TODO: benchmark checking last char too or first two chars + for i := 0; i <= end; i++ { + // match start...check the rest + if in[i] == first { + // found our first char + // check if the rest are equal + if bytesEqual(in[i:i+len(find)], find) { + return i + } + /*if slices.Equal(in[i:i+len(find)], find) { + return i + }*/ + } + } + + //not found + return -1 +} + +func StartsWith(in []rune, find []rune) bool { + // if text is less than our "begin" then can't find it + if len(in) < len(find) { + return false + } + + return bytesEqual(in[:len(find)], find) + + /*for i := 0; i < len(find); i++ { + if in[i] != find[i] { + return false + } + } + + return true*/ +} + +//StartsWithIgnoreCaseAscii would be faster + +// find should always be sent in lower-case +func StartsWithIgnoreCase(in []rune, find []rune) bool { + // if text is less than our "begin" then can't find it + if len(in) < len(find) { + return false + } + + for i := 0; i < len(find); i++ { + if in[i] == find[i] { + // if we match the char exactly then we're good + continue + } + // if the to-lower still doesn't match then it's not a match + if unicode.ToLower(in[i]) != find[i] { + return false + } + } + + return true +} + +// internal function, assumes the bounds are already set right on the slices for equality +// casts the rune slices to bytes to use framework fast []byte comparison +func bytesEqual(a, b []rune) bool { + bytesA := unsafe.Slice((*byte)(unsafe.Pointer(&a[0])), len(a)*4) + bytesB := unsafe.Slice((*byte)(unsafe.Pointer(&b[0])), len(b)*4) + return bytes.Equal(bytesA, bytesB) +} + +func Equals(in []rune, start int, length int, find []rune) bool { + if len(find) == 0 { + return true + } + return bytesEqual(in[start:start+length], find) +} + +func EqualsIgnoreCase(in []rune, start int, length int, find []rune) bool { + //fast path if case matches + if Equals(in, start, length, find) { + return true + } + + // search the in slice for the "find" slice, ignoring case in the comparisons + // we can't assume casing or ascii-ness for either letter, have to toLower them both + for j := 0; j < len(find); j++ { + inChar := in[start+j] + findChar := find[j] + if inChar != findChar && unicode.ToLower(inChar) != unicode.ToLower(findChar) { + return false + } + } + + // we've checked all chars and found matches every time + return true +} diff --git a/vendor/github.com/dlclark/regexp2/v2/helpers/math.go b/vendor/github.com/dlclark/regexp2/v2/helpers/math.go new file mode 100644 index 000000000..7f58991e3 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/helpers/math.go @@ -0,0 +1,15 @@ +package helpers + +func Min(a, b int) int { + if a < b { + return a + } + return b +} + +func Max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/vendor/github.com/dlclark/regexp2/v2/helpers/runes.go b/vendor/github.com/dlclark/regexp2/v2/helpers/runes.go new file mode 100644 index 000000000..d878da80b --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/helpers/runes.go @@ -0,0 +1,58 @@ +package helpers + +import "unicode" + +func IsBetween(val rune, first, last rune) bool { + if val > last { + return false + } + if val >= first { + return true + } + return false +} + +// According to UTS#18 Unicode Regular Expressions (http://www.unicode.org/reports/tr18/) +// RL 1.4 Simple Word Boundaries The class of includes all Alphabetic +// values from the Unicode character database, from UnicodeData.txt [UData], plus the U+200C +// ZERO WIDTH NON-JOINER and U+200D ZERO WIDTH JOINER. +func IsWordChar(r rune) bool { + // matches charclass.go + + //TODO: add optimization here for ascii + + //"L", "Mn", "Nd", "Pc" + return unicode.In(r, + unicode.Categories["L"], unicode.Categories["Mn"], + unicode.Categories["Nd"], unicode.Categories["Pc"]) || r == '\u200D' || r == '\u200C' + //return 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' || '0' <= r && r <= '9' || r == '_' +} + +func IsInMask32(ch rune, mask uint32) bool { + //BDFHJLNPRTVX = 10101010 10101010 10101010 00000000 + //B = 00000000 00000000 00000000 01000010 + //char=B-B = 00000000 00000000 00000000 00000000 + //BDFH.. << 0 = 10101010 10101010 10101010 00000000 + //char-32 = 11111111 11111111 11111111 11100000 + //& = 10101010 10101010 10101010 00000000 + // first bit is 1 then negative, so it matches + + //charMinusLowUInt32 := int32(ch - low) + return int32((mask< unicode.MaxASCII { + // a bug got us here. that's bad. + panic(fmt.Errorf("non-ascii value found in ascii search values: %s", vals)) + } + idx := c / 64 + shift := c % 64 + sv.set[idx] |= 1 << shift + } + + return sv +} + +// return the first index of our original vals values within the slice given +func (s AsciiSearchValues) IndexOfAny(chars []rune) int { + for i := 0; i < len(chars); i++ { + c := chars[i] + if c > unicode.MaxASCII { + continue + } + idx := c / 64 + shift := c % 64 + if s.set[idx]&(1< unicode.MaxASCII { + return i + } + idx := c / 64 + shift := c % 64 + if s.set[idx]&(1< len(val) { + min = len(val) + } + if !slices.Contains(firstLetters, val[0]) { + firstLetters = append(firstLetters, val[0]) + if ignoreCase && val[0] != unicode.ToUpper(val[0]) { + //if we're ignoring case and this letter is impacted by case, add it to our set + firstLetters = append(firstLetters, unicode.ToUpper(val[0])) + } + } + } + + return StringSearchValues{ + vals: vals, + ignoreCase: ignoreCase, + shortestVal: min, + firstChars: newRuneSearchValues(firstLetters), + } +} + +func (s StringSearchValues) StartsWith(chars []rune) int { + panic("not implemented") +} + +func (s StringSearchValues) StartsWithIgnoreCase(chars []rune) int { + panic("not implemented") +} + +func (s StringSearchValues) IndexOfAny(in []rune) int { + // go through our input once + end := len(in) - s.shortestVal + for i := 0; i <= end; i++ { + // check if the char are in our starting chars + j := s.firstChars.IndexOfAny(in[i:]) + // first chars not found at all + if j < 0 { + return -1 + } + j += i + // found a first char, do our full search through each item + for _, val := range s.vals { + if len(in)-j >= len(val) && Equals(in, j, len(val), val) { + return j + } + if s.ignoreCase && len(in)-j >= len(val) && EqualsIgnoreCase(in, j, len(val), val) { + return j + } + } + //skip ahead + i = j + } + + return -1 +} diff --git a/vendor/github.com/dlclark/regexp2/v2/match.go b/vendor/github.com/dlclark/regexp2/v2/match.go new file mode 100644 index 000000000..4003ff0f5 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/match.go @@ -0,0 +1,455 @@ +package regexp2 + +import ( + "bytes" + "fmt" + "unicode/utf8" +) + +// Match is a single regex result match that contains groups and repeated captures +// +// -Groups +// -Capture +type Match struct { + Group //embeded group 0 + + regex *Regexp + otherGroups []Group + + // input to the match + textpos int + textstart int + + capcount int + sparseCaps map[int]int + + // output from the match + matches [][]int + matchcount []int + + // whether we've done any balancing with this match. If we + // have done balancing, we'll need to do extra work in Tidy(). + balancing bool +} + +// Group is an explicit or implit (group 0) matched group within the pattern +type Group struct { + Capture // the last capture of this group is embeded for ease of use + + Name string // group name + Captures []Capture // captures of this group +} + +// Capture is a single capture of text within the larger original string +type Capture struct { + // the original string + text *matchText + // RuneIndex is the position in the underlying rune slice where the first character of + // captured substring was found. Even if you pass in a string this will be in Runes. + RuneIndex int + // RuneLength is the number of runes in the captured substring. + RuneLength int +} + +type matchText struct { + runes []rune + input string + hasStringInput bool + byteOffsets []int + byteOffsetsReady bool +} + +// String returns the captured text as a String +func (c *Capture) String() string { + return string(c.text.runes[c.RuneIndex : c.RuneIndex+c.RuneLength]) +} + +// Runes returns the captured text as a rune slice +func (c *Capture) Runes() []rune { + return c.text.runes[c.RuneIndex : c.RuneIndex+c.RuneLength] +} + +// ByteRange returns the UTF-8 byte index and byte length of the captured +// substring. The first call lazily caches byte offsets on shared match text, +// so it is not safe to call concurrently with ByteRange on another capture +// from the same match until the cache has been initialized. +func (c *Capture) ByteRange() (index, length int) { + if c.text == nil { + return c.RuneIndex, c.RuneLength + } + return c.text.byteRange(c.RuneIndex, c.RuneLength) +} + +func newMatchText(r []rune) *matchText { + return &matchText{runes: r} +} + +func newStringMatchText(input string, r []rune) *matchText { + return &matchText{runes: r, input: input, hasStringInput: true} +} + +func (t *matchText) byteRange(runeIndex, runeLength int) (int, int) { + if !t.byteOffsetsReady { + t.byteOffsets = t.buildByteOffsets() + t.byteOffsetsReady = true + } + if t.byteOffsets == nil { + return runeIndex, runeLength + } + byteIndex := t.byteOffsets[runeIndex] + return byteIndex, t.byteOffsets[runeIndex+runeLength] - byteIndex +} + +func (t *matchText) buildByteOffsets() []int { + if t.hasStringInput { + return stringByteOffsets(t.input) + } + return runeByteOffsets(t.runes) +} + +func stringByteOffsets(s string) []int { + var byteOffsets []int + runeIndex := 0 + for strIdx, ch := range s { + if byteOffsets != nil { + byteOffsets[runeIndex] = strIdx + } + runeLen := utf8.RuneLen(ch) + if ch == utf8.RuneError { + _, runeLen = utf8.DecodeRuneInString(s[strIdx:]) + } + if byteOffsets == nil && (strIdx != runeIndex || runeLen != 1) { + byteOffsets = make([]int, len(s)+1) + for i := 0; i < runeIndex; i++ { + byteOffsets[i] = i + } + byteOffsets[runeIndex] = strIdx + } + runeIndex++ + } + if byteOffsets != nil { + byteOffsets[runeIndex] = len(s) + return byteOffsets[:runeIndex+1] + } + return nil +} + +func runeByteOffsets(runes []rune) []int { + var byteOffsets []int + bytePos := 0 + for i, ch := range runes { + if byteOffsets != nil { + byteOffsets[i] = bytePos + } + runeLen := utf8.RuneLen(ch) + if runeLen < 0 { + runeLen = utf8.RuneLen(utf8.RuneError) + } + if byteOffsets == nil && runeLen != 1 { + byteOffsets = make([]int, len(runes)+1) + for j := 0; j < i; j++ { + byteOffsets[j] = j + } + byteOffsets[i] = bytePos + } + bytePos += runeLen + } + if byteOffsets != nil { + byteOffsets[len(runes)] = bytePos + } + return byteOffsets +} + +func newMatch(regex *Regexp, capcount int, text *matchText, startpos int) *Match { + m := Match{ + regex: regex, + matchcount: make([]int, capcount), + matches: make([][]int, capcount), + textstart: startpos, + balancing: false, + } + if (regex.options & ECMAScript) == 0 { + m.Name = "0" + } + m.text = text + m.matches[0] = make([]int, 2) + return &m +} + +func newMatchSparse(regex *Regexp, caps map[int]int, capcount int, text *matchText, startpos int) *Match { + m := newMatch(regex, capcount, text, startpos) + m.sparseCaps = caps + return m +} + +func (m *Match) reset(text *matchText, textstart int) { + m.text = text + m.textstart = textstart + for i := 0; i < len(m.matchcount); i++ { + m.matchcount[i] = 0 + } + m.balancing = false +} + +func (m *Match) tidy(textpos int) { + + interval := m.matches[0] + setCaptureFields(&m.Capture, interval[0], interval[1]) + m.textpos = textpos + m.capcount = m.matchcount[0] + //copy our root capture to the list + m.Captures = []Capture{m.Capture} + + if m.balancing { + // The idea here is that we want to compact all of our unbalanced captures. To do that we + // use j basically as a count of how many unbalanced captures we have at any given time + // (really j is an index, but j/2 is the count). First we skip past all of the real captures + // until we find a balance captures. Then we check each subsequent entry. If it's a balance + // capture (it's negative), we decrement j. If it's a real capture, we increment j and copy + // it down to the last free position. + for cap := 0; cap < len(m.matchcount); cap++ { + limit := m.matchcount[cap] * 2 + matcharray := m.matches[cap] + + var i, j int + + for i = 0; i < limit; i++ { + if matcharray[i] < 0 { + break + } + } + + for j = i; i < limit; i++ { + if matcharray[i] < 0 { + // skip negative values + j-- + } else { + // but if we find something positive (an actual capture), copy it back to the last + // unbalanced position. + if i != j { + matcharray[j] = matcharray[i] + } + j++ + } + } + + m.matchcount[cap] = j / 2 + } + + m.balancing = false + } +} + +// isMatched tells if a group was matched by capnum +func (m *Match) isMatched(cap int) bool { + return cap < len(m.matchcount) && m.matchcount[cap] > 0 && m.matches[cap][m.matchcount[cap]*2-1] != (-3+1) +} + +// matchIndex returns the index of the last specified matched group by capnum +func (m *Match) matchIndex(cap int) int { + i := m.matches[cap][m.matchcount[cap]*2-2] + if i >= 0 { + return i + } + + return m.matches[cap][-3-i] +} + +// matchLength returns the length of the last specified matched group by capnum +func (m *Match) matchLength(cap int) int { + i := m.matches[cap][m.matchcount[cap]*2-1] + if i >= 0 { + return i + } + + return m.matches[cap][-3-i] +} + +// Nonpublic builder: add a capture to the group specified by "c" +func (m *Match) addMatch(c, start, l int) { + + if m.matches[c] == nil { + m.matches[c] = make([]int, 2) + } + + capcount := m.matchcount[c] + + if capcount*2+2 > len(m.matches[c]) { + oldmatches := m.matches[c] + newmatches := make([]int, capcount*8) + copy(newmatches, oldmatches[:capcount*2]) + m.matches[c] = newmatches + } + + m.matches[c][capcount*2] = start + m.matches[c][capcount*2+1] = l + m.matchcount[c] = capcount + 1 + //log.Printf("addMatch: c=%v, i=%v, l=%v ... matches: %v", c, start, l, m.matches) +} + +// Nonpublic builder: Add a capture to balance the specified group. This is used by the +// +// balanced match construct. (?...) +// +// If there were no such thing as backtracking, this would be as simple as calling RemoveMatch(c). +// However, since we have backtracking, we need to keep track of everything. +func (m *Match) balanceMatch(c int) { + m.balancing = true + + // we'll look at the last capture first + capcount := m.matchcount[c] + target := capcount*2 - 2 + + // first see if it is negative, and therefore is a reference to the next available + // capture group for balancing. If it is, we'll reset target to point to that capture. + if m.matches[c][target] < 0 { + target = -3 - m.matches[c][target] + } + + // move back to the previous capture + target -= 2 + + // if the previous capture is a reference, just copy that reference to the end. Otherwise, point to it. + if target >= 0 && m.matches[c][target] < 0 { + m.addMatch(c, m.matches[c][target], m.matches[c][target+1]) + } else { + m.addMatch(c, -3-target, -4-target /* == -3 - (target + 1) */) + } +} + +// Nonpublic builder: removes a group match by capnum +func (m *Match) removeMatch(c int) { + m.matchcount[c]-- +} + +// GroupCount returns the number of groups this match has matched +func (m *Match) GroupCount() int { + return len(m.matchcount) +} + +// GroupByName returns a group based on the name of the group, or nil if the group name does not exist +func (m *Match) GroupByName(name string) *Group { + num := m.regex.GroupNumberFromName(name) + if num < 0 { + return nil + } + return m.GroupByNumber(num) +} + +// GroupByNumber returns a group based on the number of the group, or nil if the group number does not exist +func (m *Match) GroupByNumber(num int) *Group { + // check our sparse map + if m.sparseCaps != nil { + if newNum, ok := m.sparseCaps[num]; ok { + num = newNum + } + } + if num >= len(m.matchcount) || num < 0 { + return nil + } + + if num == 0 { + return &m.Group + } + + m.populateOtherGroups() + + return &m.otherGroups[num-1] +} + +// Groups returns all the capture groups, starting with group 0 (the full match) +func (m *Match) Groups() []Group { + m.populateOtherGroups() + g := make([]Group, len(m.otherGroups)+1) + g[0] = m.Group + copy(g[1:], m.otherGroups) + return g +} + +func (m *Match) populateOtherGroups() { + // Construct all the Group objects first time called + if m.otherGroups == nil { + m.otherGroups = make([]Group, len(m.matchcount)-1) + for i := 0; i < len(m.otherGroups); i++ { + m.otherGroups[i] = newGroup(m.regex.GroupNameFromNumber(i+1), m.text, m.matches[i+1], m.matchcount[i+1]) + } + } +} + +func (m *Match) groupValueAppendToBuf(groupnum int, buf *bytes.Buffer) { + c := m.matchcount[groupnum] + if c == 0 { + return + } + + matches := m.matches[groupnum] + + index := matches[(c-1)*2] + last := index + matches[(c*2)-1] + + for ; index < last; index++ { + buf.WriteRune(m.text.runes[index]) + } +} + +func newGroup(name string, text *matchText, caps []int, capcount int) Group { + g := Group{} + g.text = text + if capcount > 0 { + setCaptureFields(&g.Capture, caps[(capcount-1)*2], caps[(capcount*2)-1]) + } + g.Name = name + g.Captures = make([]Capture, capcount) + for i := 0; i < capcount; i++ { + g.Captures[i] = newCapture(text, caps[i*2], caps[i*2+1]) + } + //log.Printf("newGroup! capcount %v, %+v", capcount, g) + + return g +} + +func newCapture(text *matchText, runeIndex, runeLength int) Capture { + c := Capture{text: text} + setCaptureFields(&c, runeIndex, runeLength) + return c +} + +func setCaptureFields(c *Capture, runeIndex, runeLength int) { + c.RuneIndex = runeIndex + c.RuneLength = runeLength +} + +func (m *Match) dump() string { + buf := &bytes.Buffer{} + buf.WriteRune('\n') + if len(m.sparseCaps) > 0 { + for k, v := range m.sparseCaps { + fmt.Fprintf(buf, "Slot %v -> %v\n", k, v) + } + } + + for i, g := range m.Groups() { + fmt.Fprintf(buf, "Group %v (%v), %v caps:\n", i, g.Name, len(g.Captures)) + + for _, c := range g.Captures { + fmt.Fprintf(buf, " (%v, %v) %v\n", c.RuneIndex, c.RuneLength, c.String()) + } + } + /* + for i := 0; i < len(m.matchcount); i++ { + fmt.Fprintf(buf, "\nGroup %v (%v):\n", i, m.regex.GroupNameFromNumber(i)) + + for j := 0; j < m.matchcount[i]; j++ { + text := "" + + if m.matches[i][j*2] >= 0 { + start := m.matches[i][j*2] + text = m.text.runes[start : start+m.matches[i][j*2+1]] + } + + fmt.Fprintf(buf, " (%v, %v) %v\n", m.matches[i][j*2], m.matches[i][j*2+1], text) + } + } + */ + return buf.String() +} diff --git a/vendor/github.com/dlclark/regexp2/v2/options.go b/vendor/github.com/dlclark/regexp2/v2/options.go new file mode 100644 index 000000000..c0e49fff5 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/options.go @@ -0,0 +1,157 @@ +package regexp2 + +var ( + // DefaultUnmarshalOptions used when unmarshaling a regex from text + DefaultUnmarshalOptions = None + // DefaultOptimizationOptions controls the default memory/performance trade-offs used by Compile. + DefaultOptimizationOptions = OptimizationOptions{ + MaxCachedRuneBufferLength: 256 << 10, + MaxCachedReplaceBufferLength: 256 << 10, + MaxCachedReplacerDataEntries: 16, + MaxCachedReplacerDataBytes: 4 << 10, + DisableCharClassASCIIBitmap: false, + } +) + +// RegexOptions impact the runtime and parsing behavior +// for each specific regex. They are setable in code as well +// as in the regex pattern itself. +type RegexOptions int32 + +func (o RegexOptions) applyCompileOption(c *compileConfig) { + c.regexOptions |= o +} + +const ( + None RegexOptions = 0x0 + IgnoreCase RegexOptions = 0x0001 // "i" + Multiline RegexOptions = 0x0002 // "m" + ExplicitCapture RegexOptions = 0x0004 // "n" + Singleline RegexOptions = 0x0010 // "s" + IgnorePatternWhitespace RegexOptions = 0x0020 // "x" + RightToLeft RegexOptions = 0x0040 // "r" + // ECMAScript attempts to follow ECMAScript regex behavior rather than C# RegexOptions.ECMAScript compatibility. + ECMAScript RegexOptions = 0x0100 // "e" + RE2 RegexOptions = 0x0200 // RE2 (regexp package) compatibility mode + Unicode RegexOptions = 0x0400 // "u" +) + +// OptimizationOptions controls optional runtime caches and compile-time fast paths. +// +// For replacement data cache size fields, 0 disables persistent retention and +// -1 means unbounded. For pooled buffer cache size fields, 0 disables pooling +// and -1 allows all built-in size classes. +// Defaults are intentionally bounded so Compile is safe for mixed-cardinality inputs. +type OptimizationOptions struct { + // MaxCachedRuneBufferLength limits retained string-to-rune buffers in the shared size-classed pool. + MaxCachedRuneBufferLength int + // MaxCachedReplaceBufferLength limits retained replacement output buffers in the shared size-classed pool. + MaxCachedReplaceBufferLength int + // MaxCachedReplacerDataEntries limits the number of parsed replacement patterns cached per Regexp. + MaxCachedReplacerDataEntries int + // MaxCachedReplacerDataBytes skips caching replacement patterns longer than this many bytes. + MaxCachedReplacerDataBytes int + // DisableCharClassASCIIBitmap disables compile-time ASCII bitmap construction for character classes. + DisableCharClassASCIIBitmap bool +} + +// CompileOption configures Compile and MustCompile. +type CompileOption interface { + applyCompileOption(*compileConfig) +} + +type compileConfig struct { + regexOptions RegexOptions + optimizations OptimizationOptions + codeGen bool + debug bool + maintainCaptureOrder bool +} + +type compileOptionFunc func(*compileConfig) + +func (f compileOptionFunc) applyCompileOption(c *compileConfig) { + f(c) +} + +func (o OptimizationOptions) cacheReplacerData(replacement string) bool { + if o.MaxCachedReplacerDataEntries == 0 { + return false + } + return keepCacheBytes(o.MaxCachedReplacerDataBytes, len(replacement)) +} + +func keepCacheBytes(maxBytes, actualBytes int) bool { + if maxBytes < 0 { + return true + } + return maxBytes > 0 && actualBytes <= maxBytes +} + +func newCompileConfig(options []CompileOption) compileConfig { + c := compileConfig{ + optimizations: DefaultOptimizationOptions, + } + for _, option := range options { + if option != nil { + option.applyCompileOption(&c) + } + } + return c +} + +// OptionMaxCachedRuneBufferLength limits retained string-to-rune buffers in the shared size-classed pool. +func OptionMaxCachedRuneBufferLength(n int) CompileOption { + return compileOptionFunc(func(c *compileConfig) { + c.optimizations.MaxCachedRuneBufferLength = n + }) +} + +// OptionMaxCachedReplaceBufferLength limits retained replacement output buffers in the shared size-classed pool. +func OptionMaxCachedReplaceBufferLength(n int) CompileOption { + return compileOptionFunc(func(c *compileConfig) { + c.optimizations.MaxCachedReplaceBufferLength = n + }) +} + +// OptionMaxCachedReplacerDataEntries limits parsed replacement patterns cached per Regexp. +func OptionMaxCachedReplacerDataEntries(n int) CompileOption { + return compileOptionFunc(func(c *compileConfig) { + c.optimizations.MaxCachedReplacerDataEntries = n + }) +} + +// OptionMaxCachedReplacerDataBytes skips caching replacement patterns longer than n bytes. +func OptionMaxCachedReplacerDataBytes(n int) CompileOption { + return compileOptionFunc(func(c *compileConfig) { + c.optimizations.MaxCachedReplacerDataBytes = n + }) +} + +// OptionDisableCharClassASCIIBitmap disables compile-time ASCII bitmaps for character classes. +func OptionDisableCharClassASCIIBitmap() CompileOption { + return compileOptionFunc(func(c *compileConfig) { + c.optimizations.DisableCharClassASCIIBitmap = true + }) +} + +// OptionIsCodeGen enables more expensive compile-time analysis intended for regexp2cg generated engines. +func OptionIsCodeGen() CompileOption { + return compileOptionFunc(func(c *compileConfig) { + c.codeGen = true + }) +} + +// OptionDebug enables debug output and runner tracing for the compiled regexp. +func OptionDebug() CompileOption { + return compileOptionFunc(func(c *compileConfig) { + c.debug = true + }) +} + +// OptionMaintainCaptureOrder assigns named and unnamed capture slots in pattern order. +func OptionMaintainCaptureOrder() CompileOption { + return compileOptionFunc(func(c *compileConfig) { + c.maintainCaptureOrder = true + }) +} diff --git a/vendor/github.com/dlclark/regexp2/v2/regexp.go b/vendor/github.com/dlclark/regexp2/v2/regexp.go new file mode 100644 index 000000000..0a5d140a3 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/regexp.go @@ -0,0 +1,717 @@ +/* +Package regexp2 is a regexp package that has an interface similar to Go's framework regexp engine but uses a +more feature full regex engine behind the scenes. + +It doesn't have constant time guarantees, but it allows backtracking and is compatible with Perl5 and .NET. +You'll likely be better off with the RE2 engine from the regexp package and should only use this if you +need to write very complex patterns or require compatibility with .NET. +*/ +package regexp2 + +import ( + "container/list" + "errors" + "log" + "math" + "sort" + "strconv" + "sync" + "time" + "unicode/utf8" + + "github.com/dlclark/regexp2/v2/syntax" +) + +var ( + // DefaultMatchTimeout used when running regexp matches -- "forever" + DefaultMatchTimeout = time.Duration(math.MaxInt64) +) + +// Regexp is the representation of a compiled regular expression. +// A Regexp is safe for concurrent use by multiple goroutines. +type Regexp struct { + // A match will time out if it takes (approximately) more than + // MatchTimeout. This is a safety check in case the match + // encounters catastrophic backtracking. The default value + // (DefaultMatchTimeout) causes all time out checking to be + // suppressed. + MatchTimeout time.Duration + + // read-only after Compile + pattern string // as passed to Compile + options RegexOptions // options + debug bool + + caps map[int]int // capnum->index + capnames map[string]int //capture group name -> index + capslist []string //sorted list of capture group names + capsize int // size of the capture array + + code *syntax.Code // compiled program + + optimizations OptimizationOptions + + // cache of machines for running regexp + runnerPool *sync.Pool + + replaceCache *replacerDataCache + + // hook points to override runner functions + findFirstChar func(r *Runner) bool + execute func(r *Runner) error + stringPrefixFilter StringPrefixFilter +} + +// Compile parses a regular expression and returns, if successful, +// a Regexp object that can be used to match against text. +func Compile(expr string, options ...CompileOption) (*Regexp, error) { + c := newCompileConfig(options) + return compile(expr, c) +} + +func compile(expr string, c compileConfig) (*Regexp, error) { + // parse it + parseOptions := syntax.ParseOptions{ + RegexOptions: syntax.RegexOptions(c.regexOptions), + MaintainCaptureOrder: c.maintainCaptureOrder, + CodeGen: c.codeGen, + } + tree, err := syntax.Parse(expr, parseOptions) + if err != nil { + return nil, err + } + + if c.debug { + log.Print(tree.Dump()) + } + + // translate it to code + code, err := syntax.Write(tree) + if err != nil { + return nil, err + } + if c.debug { + log.Print(code.Dump()) + } + if !c.optimizations.DisableCharClassASCIIBitmap { + code.PrepareCharSetASCIIBitmaps() + } + + // return it + re := &Regexp{ + pattern: expr, + options: c.regexOptions, + debug: c.debug, + caps: code.Caps, + capnames: tree.Capnames, + capslist: tree.Caplist, + capsize: code.Capsize, + code: code, + MatchTimeout: DefaultMatchTimeout, + optimizations: c.optimizations, + } + re.stringPrefixFilter = newStringPrefixFilter(code) + re.initCaches() + return re, nil +} + +// MustCompile is like Compile but panics if the expression cannot be parsed. +// It simplifies safe initialization of global variables holding compiled regular +// expressions. +func MustCompile(str string, options ...CompileOption) *Regexp { + c := newCompileConfig(options) + + // lookup if we have a pre-built state machine for this pattern and options + regexp := getEngineRegexp(str, c) + if regexp != nil { + return regexp + } + + regexp, err := compile(str, c) + if err != nil { + panic(`regexp2: Compile(` + quote(str) + `): ` + err.Error()) + } + return regexp +} + +// Escape adds backslashes to any special characters in the input string +func Escape(input string) string { + return syntax.Escape(input) +} + +// Unescape removes any backslashes from previously-escaped special characters in the input string +func Unescape(input string) (string, error) { + return syntax.Unescape(input) +} + +// SetTimeoutPeriod is a debug function that sets the frequency of the timeout goroutine's sleep cycle. +// Defaults to 100ms. The only benefit of setting this lower is that the 1 background goroutine that manages +// timeouts may exit slightly sooner after all the timeouts have expired. See Github issue #63 +func SetTimeoutCheckPeriod(d time.Duration) { + clockPeriod = d +} + +// StopTimeoutClock should only be used in unit tests to prevent the timeout clock goroutine +// from appearing like a leaking goroutine +func StopTimeoutClock() { + stopClock() +} + +// String returns the source text used to compile the regular expression. +func (re *Regexp) String() string { + return re.pattern +} + +func quote(s string) string { + if strconv.CanBackquote(s) { + return "`" + s + "`" + } + return strconv.Quote(s) +} + +func (re *Regexp) RightToLeft() bool { + return re.options&RightToLeft != 0 +} + +func (re *Regexp) Debug() bool { + return re.debug +} + +// Replace searches the input string and replaces each match found with the replacement text. +// Count will limit the number of matches attempted and startAt will allow +// us to skip past possible matches at the start of the input (left or right depending on RightToLeft option). +// Set startAt and count to -1 to go through the whole string +func (re *Regexp) Replace(input, replacement string, startAt, count int) (string, error) { + data, err := re.getReplacerData(replacement) + if err != nil { + return "", err + } + + return replace(re, data, nil, input, startAt, count) +} + +func (re *Regexp) getReplacerData(replacement string) (*syntax.ReplacerData, error) { + shouldCache := re.replaceCache != nil && re.optimizations.cacheReplacerData(replacement) + if shouldCache { + if data, ok := re.replaceCache.get(replacement); ok { + return data, nil + } + } + + data, err := syntax.NewReplacerData(replacement, re.caps, re.capsize, re.capnames, syntax.RegexOptions(re.options)) + if err != nil { + return nil, err + } + if shouldCache { + re.replaceCache.add(replacement, data) + } + return data, nil +} + +// ReplaceFunc searches the input string and replaces each match found using the string from the evaluator +// Count will limit the number of matches attempted and startAt will allow +// us to skip past possible matches at the start of the input (left or right depending on RightToLeft option). +// Set startAt and count to -1 to go through the whole string. +func (re *Regexp) ReplaceFunc(input string, evaluator MatchEvaluator, startAt, count int) (string, error) { + return replace(re, nil, evaluator, input, startAt, count) +} + +// FindStringMatch searches the input string for a Regexp match +func (re *Regexp) FindStringMatch(s string) (*Match, error) { + startAt, ok, err := re.findStringMatchStart(s, -1) + if err != nil { + return nil, err + } + if !ok { + return nil, nil + } + + r, runeStart := re.getRunesAndStart(s, startAt) + if runeStart < 0 { + runeStart = 0 + } + return re.run(false, runeStart, r, newStringMatchText(s, r)) +} + +// FindRunesMatch searches the input rune slice for a Regexp match +func (re *Regexp) FindRunesMatch(r []rune) (*Match, error) { + return re.run(false, -1, r, newMatchText(r)) +} + +// FindStringMatchStartingAt searches the input string for a Regexp match starting at the startAt index +func (re *Regexp) FindStringMatchStartingAt(s string, startAt int) (*Match, error) { + startAt, ok, err := re.findStringMatchStart(s, startAt) + if err != nil { + return nil, err + } + if !ok { + return nil, nil + } + + r, startAt := re.getRunesAndStart(s, startAt) + if startAt == -1 { + // we didn't find our start index in the string -- that's a problem + return nil, errors.New("startAt must align to the start of a valid rune in the input string") + } + + return re.run(false, startAt, r, newStringMatchText(s, r)) +} + +// FindRunesMatchStartingAt searches the input rune slice for a Regexp match starting at the startAt index +func (re *Regexp) FindRunesMatchStartingAt(r []rune, startAt int) (*Match, error) { + return re.run(false, startAt, r, newMatchText(r)) +} + +// FindAllStringIndex returns a slice of byte index pairs identifying all +// successive matches in s. +func (re *Regexp) FindAllStringIndex(s string, n int) ([][]int, error) { + if n == 0 { + return nil, nil + } + + startAt, ok, err := re.findStringMatchStart(s, -1) + if err != nil { + return nil, err + } + if !ok { + return nil, nil + } + + runner := re.getRunner() + var input []rune + var pooledInput *[]rune + runeStart := 0 + if startAt == 0 { + input, pooledInput = runner.decodeString(s) + } else { + input, runeStart, pooledInput = runner.decodeStringWithStart(s, startAt) + } + defer func() { + re.putRunner(runner) + if pooledInput != nil { + *pooledInput = input + pooledRuneBuffers.put(pooledInput) + } + }() + + if runeStart < 0 { + runeStart = 0 + } + + byteOffsets := newStringByteMapper(s) + return re.findAllRunesIndex(runner, input, runeStart, n, func(runeIndex, runeLength int) (int, int) { + if byteOffsets == nil { + return runeIndex, runeIndex + runeLength + } + return byteOffsets.byteIndex(runeIndex), byteOffsets.byteIndex(runeIndex + runeLength) + }) +} + +// FindAllRunesIndex returns a slice of rune index pairs identifying all +// successive matches in r. +func (re *Regexp) FindAllRunesIndex(r []rune, n int) ([][]int, error) { + if n == 0 { + return nil, nil + } + + runner := re.getRunner() + defer re.putRunner(runner) + + startAt := 0 + if re.RightToLeft() { + startAt = len(r) + } + return re.findAllRunesIndex(runner, r, startAt, n, func(runeIndex, runeLength int) (int, int) { + return runeIndex, runeIndex + runeLength + }) +} + +func (re *Regexp) findAllRunesIndex(runner *Runner, input []rune, startAt, n int, makeIndex func(runeIndex, runeLength int) (int, int)) ([][]int, error) { + var out [][]int + var flat []int + if n > 0 { + out = make([][]int, 0, n) + flat = make([]int, 0, n*2) + } + + prevEnd := -1 + for n != 0 { + m, err := runner.scan(input, nil, startAt, true, re.MatchTimeout) + if err != nil { + return nil, err + } + if m == nil { + break + } + + if m.RuneLength != 0 || m.RuneIndex != prevEnd { + start, end := makeIndex(m.RuneIndex, m.RuneLength) + flat = append(flat, start, end) + out = append(out, flat[len(flat)-2:len(flat):len(flat)]) + prevEnd = m.RuneIndex + m.RuneLength + if n > 0 { + n-- + } + } + + startAt = m.textpos + if m.RuneLength == 0 { + if re.RightToLeft() { + if m.textpos == 0 { + break + } + if startAt == m.textstart { + startAt-- + } + } else { + if m.textpos == len(input) { + break + } + if startAt == m.textstart { + startAt++ + } + } + } + } + return out, nil +} + +type stringByteMapper struct { + runeIndexes []int + deltas []int +} + +func newStringByteMapper(s string) *stringByteMapper { + var mapper *stringByteMapper + runeIndex := 0 + delta := 0 + for strIdx, ch := range s { + runeLen := utf8.RuneLen(ch) + if ch == utf8.RuneError { + _, runeLen = utf8.DecodeRuneInString(s[strIdx:]) + } + if runeLen != 1 { + if mapper == nil { + mapper = &stringByteMapper{} + } + delta += runeLen - 1 + mapper.runeIndexes = append(mapper.runeIndexes, runeIndex+1) + mapper.deltas = append(mapper.deltas, delta) + } + runeIndex++ + } + return mapper +} + +func (m *stringByteMapper) byteIndex(runeIndex int) int { + i := sort.Search(len(m.runeIndexes), func(i int) bool { + return m.runeIndexes[i] > runeIndex + }) - 1 + if i < 0 { + return runeIndex + } + return runeIndex + m.deltas[i] +} + +// FindNextMatch returns the next match in the same input string as the match parameter. +// Will return nil if there is no next match or if given a nil match. +func (re *Regexp) FindNextMatch(m *Match) (*Match, error) { + if m == nil { + return nil, nil + } + + // If previous match was empty, advance by one before matching to prevent + // infinite loop + startAt := m.textpos + if m.RuneLength == 0 { + if re.RightToLeft() { + if m.textpos == 0 { + return nil, nil + } + if startAt == m.textstart { + startAt-- + } + } else { + if m.textpos == len(m.text.runes) { + return nil, nil + } + + if startAt == m.textstart { + startAt++ + } + + } + } + return re.run(false, startAt, m.text.runes, m.text) +} + +// MatchString return true if the string matches the regex +// error will be set if a timeout occurs +func (re *Regexp) MatchString(s string) (bool, error) { + if re.stringPrefixFilter != nil && !re.RightToLeft() { + candidateByteIndex, ok := re.stringPrefixFilter(s, 0) + if !ok { + return false, nil + } + + return re.matchStringAt(s, candidateByteIndex) + } + return re.matchString(s) +} + +func (re *Regexp) matchString(s string) (bool, error) { + return re.matchStringAt(s, -1) +} + +func (re *Regexp) matchStringAt(s string, startAt int) (bool, error) { + runner := re.getRunner() + var input []rune + var pooledInput *[]rune + runeStart := 0 + if startAt <= 0 { + input, pooledInput = runner.decodeString(s) + if re.RightToLeft() { + runeStart = len(input) + } + } else { + input, runeStart, pooledInput = runner.decodeStringWithStart(s, startAt) + if runeStart < 0 { + runeStart = 0 + } + } + defer func() { + re.putRunner(runner) + if pooledInput != nil { + *pooledInput = input + pooledRuneBuffers.put(pooledInput) + } + }() + + m, err := runner.scan(input, nil, runeStart, true, re.MatchTimeout) + if err != nil { + return false, err + } + return m != nil, nil +} + +func (re *Regexp) getRunesAndStart(s string, startAt int) ([]rune, int) { + if startAt < 0 { + if re.RightToLeft() { + r := getRunes(s) + return r, len(r) + } + return getRunes(s), 0 + } + ret := make([]rune, len(s)) + i := 0 + runeIdx := -1 + for strIdx, r := range s { + if strIdx == startAt { + runeIdx = i + } + ret[i] = r + i++ + } + if startAt == len(s) { + runeIdx = i + } + return ret[:i], runeIdx +} + +func getRunes(s string) []rune { + return []rune(s) +} + +// MatchRunes return true if the runes matches the regex +// error will be set if a timeout occurs +func (re *Regexp) MatchRunes(r []rune) (bool, error) { + m, err := re.run(true, -1, r, nil) + if err != nil { + return false, err + } + return m != nil, nil +} + +// GetGroupNames Returns the set of strings used to name capturing groups in the expression. +func (re *Regexp) GetGroupNames() []string { + var result []string + + if re.capslist == nil { + result = make([]string, re.capsize) + + for i := 0; i < len(result); i++ { + result[i] = strconv.Itoa(i) + } + } else { + result = make([]string, len(re.capslist)) + copy(result, re.capslist) + } + + return result +} + +// GetGroupNumbers returns the integer group numbers corresponding to a group name. +func (re *Regexp) GetGroupNumbers() []int { + var result []int + + if re.caps == nil { + result = make([]int, re.capsize) + + for i := 0; i < len(result); i++ { + result[i] = i + } + } else { + result = make([]int, len(re.caps)) + + for k, v := range re.caps { + result[v] = k + } + } + + return result +} + +// GroupNameFromNumber retrieves a group name that corresponds to a group number. +// It will return "" for an unknown group number. Unnamed groups automatically +// receive a name that is the decimal string equivalent of its number, except in +// ECMAScript mode where unnamed groups have no name. +func (re *Regexp) GroupNameFromNumber(i int) string { + if re.capslist == nil { + if i >= 0 && i < re.capsize { + return strconv.Itoa(i) + } + + return "" + } + + if re.caps != nil { + var ok bool + if i, ok = re.caps[i]; !ok { + return "" + } + } + + if i >= 0 && i < len(re.capslist) { + return re.capslist[i] + } + + return "" +} + +// GroupNumberFromName returns a group number that corresponds to a group name. +// Returns -1 if the name is not a recognized group name. Numbered groups +// automatically get a group name that is the decimal string equivalent of its +// number, except in ECMAScript mode where unnamed groups have no name. +func (re *Regexp) GroupNumberFromName(name string) int { + // look up name if we have a hashtable of names + if re.capnames != nil { + if k, ok := re.capnames[name]; ok { + return k + } + + return -1 + } + + // convert to an int if it looks like a number + result := 0 + for i := 0; i < len(name); i++ { + ch := name[i] + + if ch > '9' || ch < '0' { + return -1 + } + + result *= 10 + result += int(ch - '0') + } + + // return int if it's in range + if result >= 0 && result < re.capsize { + return result + } + + return -1 +} + +// MarshalText implements [encoding.TextMarshaler]. The output +// matches that of calling the [Regexp.String] method. +func (re *Regexp) MarshalText() ([]byte, error) { + return []byte(re.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler] by calling +// [Compile] on the encoded value. +func (re *Regexp) UnmarshalText(text []byte) error { + newRE, err := Compile(string(text), DefaultUnmarshalOptions) + if err != nil { + return err + } + *re = *newRE + return nil +} + +func (re *Regexp) initCaches() { + re.runnerPool = &sync.Pool{ + New: func() any { + return &Runner{ + re: re, + code: re.code, + } + }, + } + if re.optimizations.MaxCachedReplacerDataEntries > 0 { + re.replaceCache = newReplacerDataCache(re.optimizations.MaxCachedReplacerDataEntries) + } +} + +type replacerDataCache struct { + mu sync.Mutex + maxSize int + ll *list.List + cache map[string]*list.Element +} + +type replacerDataCacheEntry struct { + key string + data *syntax.ReplacerData +} + +func newReplacerDataCache(maxSize int) *replacerDataCache { + return &replacerDataCache{ + maxSize: maxSize, + ll: list.New(), + cache: make(map[string]*list.Element), + } +} + +func (c *replacerDataCache) get(key string) (*syntax.ReplacerData, bool) { + c.mu.Lock() + defer c.mu.Unlock() + + if ele, ok := c.cache[key]; ok { + c.ll.MoveToFront(ele) + return ele.Value.(*replacerDataCacheEntry).data, true + } + return nil, false +} + +func (c *replacerDataCache) add(key string, data *syntax.ReplacerData) { + c.mu.Lock() + defer c.mu.Unlock() + + if ele, ok := c.cache[key]; ok { + ele.Value.(*replacerDataCacheEntry).data = data + c.ll.MoveToFront(ele) + return + } + + ele := c.ll.PushFront(&replacerDataCacheEntry{key: key, data: data}) + c.cache[key] = ele + if c.maxSize > 0 && c.ll.Len() > c.maxSize { + oldest := c.ll.Back() + if oldest != nil { + c.ll.Remove(oldest) + delete(c.cache, oldest.Value.(*replacerDataCacheEntry).key) + } + } +} diff --git a/vendor/github.com/dlclark/regexp2/v2/regexp_codegen.go b/vendor/github.com/dlclark/regexp2/v2/regexp_codegen.go new file mode 100644 index 000000000..41c1aef35 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/regexp_codegen.go @@ -0,0 +1,70 @@ +package regexp2 + +import ( + "sync" +) + +type RuntimeEngineData struct { + Caps map[int]int // capnum->index + CapNames map[string]int // cap group name -> index + CapsList []string // sorted list of capture group names + CapSize int // size of the capture array + FindFirstChar func(*Runner) bool // generated candidate search + Execute func(*Runner) error + StringPrefixFilter StringPrefixFilter // optional pre-decode candidate search for string input +} + +type cacheKey struct { + pattern string + opt RegexOptions + maintainCaptureOrder bool +} + +func RegisterEngine(pattern string, engine RuntimeEngineData, options ...CompileOption) { + c := newCompileConfig(options) + enginesMu.Lock() + engines[cacheKeyFromConfig(pattern, c)] = engine + enginesMu.Unlock() +} + +func newEngineRegexp(pattern string, c compileConfig, engine RuntimeEngineData) *Regexp { + re := &Regexp{ + pattern: pattern, + options: c.regexOptions, + debug: c.debug, + caps: engine.Caps, + capnames: engine.CapNames, + capslist: engine.CapsList, + capsize: engine.CapSize, + MatchTimeout: DefaultMatchTimeout, + optimizations: c.optimizations, + findFirstChar: engine.FindFirstChar, + execute: engine.Execute, + stringPrefixFilter: engine.StringPrefixFilter, + } + re.initCaches() + return re +} + +func getEngineRegexp(pattern string, c compileConfig) *Regexp { + enginesMu.RLock() + engine, ok := engines[cacheKeyFromConfig(pattern, c)] + enginesMu.RUnlock() + if !ok { + return nil + } + return newEngineRegexp(pattern, c, engine) +} + +func cacheKeyFromConfig(pattern string, c compileConfig) cacheKey { + return cacheKey{ + pattern: pattern, + opt: c.regexOptions, + maintainCaptureOrder: c.maintainCaptureOrder, + } +} + +var ( + enginesMu sync.RWMutex + engines = map[cacheKey]RuntimeEngineData{} +) diff --git a/vendor/github.com/dlclark/regexp2/v2/replace.go b/vendor/github.com/dlclark/regexp2/v2/replace.go new file mode 100644 index 000000000..240b40f4a --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/replace.go @@ -0,0 +1,357 @@ +package regexp2 + +import ( + "bytes" + "errors" + + "github.com/dlclark/regexp2/v2/syntax" +) + +const ( + replaceSpecials = 4 + replaceLeftPortion = -1 + replaceRightPortion = -2 + replaceLastGroup = -3 + replaceWholeString = -4 +) + +// MatchEvaluator is a function that takes a match and returns a replacement string to be used +type MatchEvaluator func(Match) string + +// Three very similar algorithms appear below: replace (pattern), +// replace (evaluator), and split. + +func writeRunes(buf *bytes.Buffer, text []rune, start, end int) { + for i := start; i < end; i++ { + buf.WriteRune(text[i]) + } +} + +func compactBalancedMatches(m *Match) { + for cap := 0; cap < len(m.matchcount); cap++ { + limit := m.matchcount[cap] * 2 + matcharray := m.matches[cap] + + var i, j int + for i = 0; i < limit; i++ { + if matcharray[i] < 0 { + break + } + } + + for j = i; i < limit; i++ { + if matcharray[i] < 0 { + j-- + } else { + if i != j { + matcharray[j] = matcharray[i] + } + j++ + } + } + + m.matchcount[cap] = j / 2 + } + m.balancing = false +} + +// Replace Replaces all occurrences of the regex in the string with the +// replacement pattern. +// +// Note that the special case of no matches is handled on its own: +// with no matches, the input string is returned unchanged. +// The right-to-left case is split out because StringBuilder +// doesn't handle right-to-left string building directly very well. +func replace(regex *Regexp, data *syntax.ReplacerData, evaluator MatchEvaluator, input string, startAt, count int) (string, error) { + if count < -1 { + return "", errors.New("count too small") + } + if count == 0 { + return "", nil + } + + if evaluator == nil { + if !regex.RightToLeft() { + return replaceRunnerLTR(regex, data, input, startAt, count) + } + return replaceRunnerRTL(regex, data, input, startAt, count) + } + + m, err := regex.FindStringMatchStartingAt(input, startAt) + + if err != nil { + return "", err + } + if m == nil { + return input, nil + } + + buf := &bytes.Buffer{} + text := m.text.runes + + if !regex.RightToLeft() { + prevat := 0 + for m != nil { + if m.RuneIndex != prevat { + buf.WriteString(string(text[prevat:m.RuneIndex])) + } + prevat = m.RuneIndex + m.RuneLength + buf.WriteString(evaluator(*m)) + + count-- + if count == 0 { + break + } + m, err = regex.FindNextMatch(m) + if err != nil { + return "", err + } + } + + if prevat < len(text) { + buf.WriteString(string(text[prevat:])) + } + } else { + prevat := len(text) + var al []string + + for m != nil { + if m.RuneIndex+m.RuneLength != prevat { + al = append(al, string(text[m.RuneIndex+m.RuneLength:prevat])) + } + prevat = m.RuneIndex + al = append(al, evaluator(*m)) + + count-- + if count == 0 { + break + } + m, err = regex.FindNextMatch(m) + if err != nil { + return "", err + } + } + + if prevat > 0 { + buf.WriteString(string(text[:prevat])) + } + + for i := len(al) - 1; i >= 0; i-- { + buf.WriteString(al[i]) + } + } + + return buf.String(), nil +} + +func replaceRunnerLTR(regex *Regexp, data *syntax.ReplacerData, input string, startAt, count int) (string, error) { + if startAt > len(input) { + return "", errors.New("startAt must be less than the length of the input string") + } + + runner := regex.getRunner() + text, runeStart, pooledText := runner.decodeStringWithStart(input, startAt) + textInfo := newStringMatchText(input, text) + defer func() { + regex.putRunner(runner) + if pooledText != nil { + pooledRuneBuffers.put(pooledText) + } + }() + if startAt >= 0 && runeStart < 0 { + return "", errors.New("startAt must align to the start of a valid rune in the input string") + } + if runeStart < 0 { + runeStart = 0 + } + + m, err := runner.scan(text, textInfo, runeStart, true, regex.MatchTimeout) + if err != nil { + return "", err + } + if m == nil { + return input, nil + } + + buf, pooledBuf := getPooledReplaceBuffer(len(input), regex.optimizations.MaxCachedReplaceBufferLength) + if pooledBuf != nil { + defer putPooledReplaceBuffer(buf, pooledBuf) + } + + prevat := 0 + for m != nil { + if m.balancing { + compactBalancedMatches(m) + } + + if m.RuneIndex != prevat { + writeRunes(buf, text, prevat, m.RuneIndex) + } + prevat = m.RuneIndex + m.RuneLength + replacementImpl(data, buf, m) + + count-- + if count == 0 { + break + } + + scanStart := m.textpos + if m.RuneLength == 0 { + if scanStart >= len(text) { + break + } + scanStart++ + } + + m, err = runner.scan(text, textInfo, scanStart, true, regex.MatchTimeout) + if err != nil { + return "", err + } + } + + if prevat < len(text) { + writeRunes(buf, text, prevat, len(text)) + } + return buf.String(), nil +} + +func replaceRunnerRTL(regex *Regexp, data *syntax.ReplacerData, input string, startAt, count int) (string, error) { + if startAt > len(input) { + return "", errors.New("startAt must be less than the length of the input string") + } + + runner := regex.getRunner() + text, runeStart, pooledText := runner.decodeStringWithStart(input, startAt) + textInfo := newStringMatchText(input, text) + defer func() { + regex.putRunner(runner) + if pooledText != nil { + pooledRuneBuffers.put(pooledText) + } + }() + if startAt >= 0 && runeStart < 0 { + return "", errors.New("startAt must align to the start of a valid rune in the input string") + } + if runeStart < 0 { + runeStart = len(text) + } + + m, err := runner.scan(text, textInfo, runeStart, true, regex.MatchTimeout) + if err != nil { + return "", err + } + if m == nil { + return input, nil + } + + buf, pooledBuf := getPooledReplaceBuffer(len(input), regex.optimizations.MaxCachedReplaceBufferLength) + if pooledBuf != nil { + defer putPooledReplaceBuffer(buf, pooledBuf) + } + + prevat := len(text) + var al []string + + for m != nil { + if m.balancing { + compactBalancedMatches(m) + } + + if m.RuneIndex+m.RuneLength != prevat { + al = append(al, string(text[m.RuneIndex+m.RuneLength:prevat])) + } + prevat = m.RuneIndex + replacementImplRTL(data, &al, m) + + count-- + if count == 0 { + break + } + + scanStart := m.textpos + if m.RuneLength == 0 { + if scanStart <= 0 { + break + } + scanStart-- + } + + m, err = runner.scan(text, textInfo, scanStart, true, regex.MatchTimeout) + if err != nil { + return "", err + } + } + + if prevat > 0 { + writeRunes(buf, text, 0, prevat) + } + for i := len(al) - 1; i >= 0; i-- { + buf.WriteString(al[i]) + } + return buf.String(), nil +} + +// Given a Match, emits into the StringBuilder the evaluated +// substitution pattern. +func replacementImpl(data *syntax.ReplacerData, buf *bytes.Buffer, m *Match) { + for _, r := range data.Rules { + + if r >= 0 { // string lookup + buf.WriteString(data.Strings[r]) + } else if r < -replaceSpecials { // group lookup + m.groupValueAppendToBuf(-replaceSpecials-1-r, buf) + } else { + switch -replaceSpecials - 1 - r { // special insertion patterns + case replaceLeftPortion: + for i := 0; i < m.RuneIndex; i++ { + buf.WriteRune(m.text.runes[i]) + } + case replaceRightPortion: + for i := m.RuneIndex + m.RuneLength; i < len(m.text.runes); i++ { + buf.WriteRune(m.text.runes[i]) + } + case replaceLastGroup: + m.groupValueAppendToBuf(m.GroupCount()-1, buf) + case replaceWholeString: + for i := 0; i < len(m.text.runes); i++ { + buf.WriteRune(m.text.runes[i]) + } + } + } + } +} + +func replacementImplRTL(data *syntax.ReplacerData, al *[]string, m *Match) { + l := *al + buf := &bytes.Buffer{} + + for _, r := range data.Rules { + buf.Reset() + if r >= 0 { // string lookup + l = append(l, data.Strings[r]) + } else if r < -replaceSpecials { // group lookup + m.groupValueAppendToBuf(-replaceSpecials-1-r, buf) + l = append(l, buf.String()) + } else { + switch -replaceSpecials - 1 - r { // special insertion patterns + case replaceLeftPortion: + for i := 0; i < m.RuneIndex; i++ { + buf.WriteRune(m.text.runes[i]) + } + case replaceRightPortion: + for i := m.RuneIndex + m.RuneLength; i < len(m.text.runes); i++ { + buf.WriteRune(m.text.runes[i]) + } + case replaceLastGroup: + m.groupValueAppendToBuf(m.GroupCount()-1, buf) + case replaceWholeString: + for i := 0; i < len(m.text.runes); i++ { + buf.WriteRune(m.text.runes[i]) + } + } + l = append(l, buf.String()) + } + } + + *al = l +} diff --git a/vendor/github.com/dlclark/regexp2/v2/runner.go b/vendor/github.com/dlclark/regexp2/v2/runner.go new file mode 100644 index 000000000..9b37a79d8 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/runner.go @@ -0,0 +1,2198 @@ +package regexp2 + +import ( + "bytes" + "fmt" + "math" + "slices" + "strconv" + "strings" + "time" + "unicode" + + "github.com/dlclark/regexp2/v2/helpers" + "github.com/dlclark/regexp2/v2/syntax" +) + +type Runner struct { + re *Regexp + code *syntax.Code + debug bool + + Runtextstart int // starting point for search + + Runtext []rune // text to search + Runtextpos int // current position in text + Runtextend int + + // The backtracking stack. Opcodes use this to store data regarding + // what they have matched and where to backtrack to. Each "frame" on + // the stack takes the form of [CodePosition Data1 Data2...], where + // CodePosition is the position of the current opcode and + // the data values are all optional. The CodePosition can be negative, and + // these values (also called "back2") are used by the BranchMark family of opcodes + // to indicate whether they are backtracking after a successful or failed + // match. + // When we backtrack, we pop the CodePosition off the stack, set the current + // instruction pointer to that code position, and mark the opcode + // with a backtracking flag ("Back"). Each opcode then knows how to + // handle its own data. + runtrack []int + Runtrackpos int + + // This stack is used to track text positions across different opcodes. + // For example, in /(a*b)+/, the parentheses result in a SetMark/CaptureMark + // pair. SetMark records the text position before we match a*b. Then + // CaptureMark uses that position to figure out where the capture starts. + // Opcodes which push onto this stack are always paired with other opcodes + // which will pop the value from it later. A successful match should mean + // that this stack is empty. + runstack []int + Runstackpos int + + // The crawl stack is used to keep track of captures. Every time a group + // has a capture, we push its group number onto the runcrawl stack. In + // the case of a balanced match, we push BOTH groups onto the stack. + runcrawl []int + runcrawlpos int + + runtrackcount int // count of states that may do backtracking + + runmatch *Match // result object + + ignoreTimeout bool + timeout time.Duration // timeout in milliseconds (needed for actual) + deadline fasttime + + operator syntax.InstOp + codepos int + rightToLeft bool + caseInsensitive bool +} + +// run searches for matches and can continue from the previous match. +// +// quick is usually false, but can be true to not return matches, just put it in caches. +// textstart is -1 to start at the "beginning" (depending on Right-To-Left), otherwise an index in input. +// textInfo is nil for quick scans that do not need returned capture text metadata. +func (re *Regexp) run(quick bool, textstart int, input []rune, textInfo *matchText) (*Match, error) { + + // get a cached runner + runner := re.getRunner() + defer re.putRunner(runner) + + if textstart < 0 { + if re.RightToLeft() { + textstart = len(input) + } else { + textstart = 0 + } + } + + return runner.scan(input, textInfo, textstart, quick, re.MatchTimeout) +} + +// Scans the string to find the first match. Uses the Match object +// both to feed text in and as a place to store matches that come out. +// +// All the action is in the Go() method. Our +// responsibility is to load up the class members before +// calling Go. +// +// The optimizer can compute a set of candidate starting characters, +// and we could use a separate method Skip() that will quickly scan past +// any characters that we know can't match. +// +// The input slice is passed separately from matchText so quick scans can avoid +// allocating match metadata. When textInfo is nil, successful matches are only +// used as a boolean result and capture text is intentionally unavailable. If +// we collapsed down to just textInfo it would "escape" and hit the GC for fast +// scans without captures. +func (r *Runner) scan(rt []rune, textInfo *matchText, textstart int, quick bool, timeout time.Duration) (*Match, error) { + r.timeout = timeout + r.ignoreTimeout = (time.Duration(math.MaxInt64) == timeout) + r.debug = r.re.Debug() + r.Runtextstart = textstart + r.Runtext = rt + r.Runtextend = len(rt) + + stoppos := r.Runtextend + bump := 1 + + if r.re.RightToLeft() { + bump = -1 + stoppos = 0 + } + + r.Runtextpos = textstart + //initted := false + + // setup our scanner functions + findFirstChar := r.re.findFirstChar + execute := r.re.execute + if findFirstChar == nil { + findFirstChar = findFirstCharDefault + } + if execute == nil { + execute = executeDefault + } + + minRequiredLength := 0 + if r.code != nil && r.code.FindOptimizations != nil { + minRequiredLength = r.code.FindOptimizations.MinRequiredLength + } + + r.initMatch(textInfo) + + r.startTimeoutWatch() + for { + if minRequiredLength > 0 { + if r.code.RightToLeft { + if r.Runtextpos < minRequiredLength { + r.tidyMatch(true) + return nil, nil + } + } else if r.Runtextend-r.Runtextpos < minRequiredLength { + r.tidyMatch(true) + return nil, nil + } + } + + if r.debug { + //fmt.Printf("\nSearch content: %v\n", string(r.runtext)) + fmt.Printf("\nSearch range: from 0 to %v\n", r.Runtextend) + fmt.Printf("Firstchar search starting at %v stopping at %v\n", r.Runtextpos, stoppos) + } + + if findFirstChar(r) { + if !r.ignoreTimeout { + if err := r.CheckTimeout(); err != nil { + return nil, err + } + } + + if r.debug { + fmt.Printf("Executing engine starting at %v\n\n", r.Runtextpos) + } + + if err := execute(r); err != nil { + return nil, err + } + + if r.runmatch.matchcount[0] > 0 { + // We'll return a match even if it touches a previous empty match + return r.tidyMatch(quick), nil + } + + // reset state for another go + r.Runtrackpos = len(r.runtrack) + r.Runstackpos = len(r.runstack) + r.runcrawlpos = len(r.runcrawl) + } + + // failure! + + if r.Runtextpos == stoppos { + r.tidyMatch(true) + return nil, nil + } + + // Recognize leading []* and various anchors, and bump on failure accordingly + + // r.bump by one and start again + + r.Runtextpos += bump + } + // We never get here +} + +func executeDefault(r *Runner) error { + + r.goTo(0) + + for { + + if r.debug { + r.dumpState() + } + + if !r.ignoreTimeout { + if err := r.CheckTimeout(); err != nil { + return err + } + } + + switch r.operator { + case syntax.Stop: + return nil + + case syntax.Nothing: + //noop + + case syntax.Goto: + r.goTo(r.operand(0)) + continue + + case syntax.Testref: + if !r.runmatch.isMatched(r.operand(0)) { + break + } + r.advance(1) + continue + + case syntax.Lazybranch: + r.trackPush1(r.textPos()) + r.advance(1) + continue + + case syntax.Lazybranch | syntax.Back: + r.trackPop() + r.textto(r.trackPeek()) + r.goTo(r.operand(0)) + continue + + case syntax.Setmark: + r.stackPush(r.textPos()) + r.trackPush() + r.advance(0) + continue + + case syntax.Nullmark: + r.stackPush(-1) + r.trackPush() + r.advance(0) + continue + + case syntax.Setmark | syntax.Back, syntax.Nullmark | syntax.Back: + r.stackPop() + + case syntax.Getmark: + r.stackPop() + r.trackPush1(r.stackPeek()) + r.textto(r.stackPeek()) + r.advance(0) + continue + + case syntax.Getmark | syntax.Back: + r.trackPop() + r.stackPush(r.trackPeek()) + + case syntax.Capturemark: + if r.operand(1) != -1 && !r.runmatch.isMatched(r.operand(1)) { + break + } + r.stackPop() + if r.operand(1) != -1 { + r.transferCapture(r.operand(0), r.operand(1), r.stackPeek(), r.textPos()) + } else { + r.Capture(r.operand(0), r.stackPeek(), r.textPos()) + } + r.trackPush1(r.stackPeek()) + + r.advance(2) + + continue + + case syntax.Capturemark | syntax.Back: + r.trackPop() + r.stackPush(r.trackPeek()) + r.uncapture() + if r.operand(0) != -1 && r.operand(1) != -1 { + r.uncapture() + } + + case syntax.Branchmark: + r.stackPop() + + matched := r.textPos() - r.stackPeek() + + if matched != 0 { // Nonempty match -> loop now + r.trackPush2(r.stackPeek(), r.textPos()) // Save old mark, textpos + r.stackPush(r.textPos()) // Make new mark + r.goTo(r.operand(0)) // Loop + } else { // Empty match -> straight now + r.trackPushNeg1(r.stackPeek()) // Save old mark + r.advance(1) // Straight + } + continue + + case syntax.Branchmark | syntax.Back: + r.trackPopN(2) + r.stackPop() + r.textto(r.trackPeekN(1)) // Recall position + r.trackPushNeg1(r.trackPeek()) // Save old mark + r.advance(1) // Straight + continue + + case syntax.Branchmark | syntax.Back2: + r.trackPop() + r.stackPush(r.trackPeek()) // Recall old mark + // Backtrack + + case syntax.Lazybranchmark: + { + // We hit this the first time through a lazy loop and after each + // successful match of the inner expression. It simply continues + // on and doesn't loop. + r.stackPop() + + oldMarkPos := r.stackPeek() + + if r.textPos() != oldMarkPos { // Nonempty match -> try to loop again by going to 'back' state + if oldMarkPos != -1 { + r.trackPush2(oldMarkPos, r.textPos()) // Save old mark, textpos + } else { + r.trackPush2(r.textPos(), r.textPos()) + } + } else { + // The inner expression found an empty match, so we'll go directly to 'back2' if we + // backtrack. Don't touch the grouping stack here; instead, record the old mark and + // a flag indicating that backtracking doesn't need to pop a grouping stack frame. + r.trackPushNeg2(oldMarkPos, 0) + } + r.advance(1) + continue + } + + case syntax.Lazybranchmark | syntax.Back: + + // After the first time, Lazybranchmark | syntax.Back occurs + // with each iteration of the loop, and therefore with every attempted + // match of the inner expression. We'll try to match the inner expression, + // then go back to Lazybranchmark if successful. If the inner expression + // fails, we go to Lazybranchmark | syntax.Back2 + + r.trackPopN(2) + pos := r.trackPeekN(1) + r.trackPushNeg2(r.trackPeek(), 1) // Save old mark, note that we pushed a new mark + r.stackPush(pos) // Make new mark + r.textto(pos) // Recall position + r.goTo(r.operand(0)) // Loop + continue + + case syntax.Lazybranchmark | syntax.Back2: + // The lazy loop has failed. We'll do a true backtrack and + // start over before the lazy loop. + r.trackPopN(2) + oldMark := r.trackPeek() + needsPop := r.trackPeekN(1) + if needsPop != 0 { + r.stackPop() + } + r.stackPush(oldMark) // Recall old mark + + case syntax.Setcount: + r.stackPush2(r.textPos(), r.operand(0)) + r.trackPush() + r.advance(1) + continue + + case syntax.Nullcount: + r.stackPush2(-1, r.operand(0)) + r.trackPush() + r.advance(1) + continue + + case syntax.Setcount | syntax.Back: + r.stackPopN(2) + + case syntax.Nullcount | syntax.Back: + r.stackPopN(2) + + case syntax.Branchcount: + // r.stackPush: + // 0: Mark + // 1: Count + + r.stackPopN(2) + mark := r.stackPeek() + count := r.stackPeekN(1) + matched := r.textPos() - mark + + if count >= r.operand(1) || (matched == 0 && count >= 0) { // Max loops or empty match -> straight now + r.trackPushNeg2(mark, count) // Save old mark, count + r.advance(2) // Straight + } else { // Nonempty match -> count+loop now + r.trackPush1(mark) // remember mark + r.stackPush2(r.textPos(), count+1) // Make new mark, incr count + r.goTo(r.operand(0)) // Loop + } + continue + + case syntax.Branchcount | syntax.Back: + // r.trackPush: + // 0: Previous mark + // r.stackPush: + // 0: Mark (= current pos, discarded) + // 1: Count + r.trackPop() + r.stackPopN(2) + if r.stackPeekN(1) > 0 { // Positive -> can go straight + r.textto(r.stackPeek()) // Zap to mark + r.trackPushNeg2(r.trackPeek(), r.stackPeekN(1)-1) // Save old mark, old count + r.advance(2) // Straight + continue + } + r.stackPush2(r.trackPeek(), r.stackPeekN(1)-1) // recall old mark, old count + + case syntax.Branchcount | syntax.Back2: + // r.trackPush: + // 0: Previous mark + // 1: Previous count + r.trackPopN(2) + r.stackPush2(r.trackPeek(), r.trackPeekN(1)) // Recall old mark, old count + + case syntax.Lazybranchcount: + // r.stackPush: + // 0: Mark + // 1: Count + + r.stackPopN(2) + mark := r.stackPeek() + count := r.stackPeekN(1) + + if count < 0 { // Negative count -> loop now + r.trackPushNeg1(mark) // Save old mark + r.stackPush2(r.textPos(), count+1) // Make new mark, incr count + r.goTo(r.operand(0)) // Loop + } else { // Nonneg count -> straight now + r.trackPush3(mark, count, r.textPos()) // Save mark, count, position + r.advance(2) // Straight + } + continue + + case syntax.Lazybranchcount | syntax.Back: + // r.trackPush: + // 0: Mark + // 1: Count + // 2: r.textPos + + r.trackPopN(3) + mark := r.trackPeek() + textpos := r.trackPeekN(2) + + if r.trackPeekN(1) < r.operand(1) && textpos != mark { // Under limit and not empty match -> loop + r.textto(textpos) // Recall position + r.stackPush2(textpos, r.trackPeekN(1)+1) // Make new mark, incr count + r.trackPushNeg1(mark) // Save old mark + r.goTo(r.operand(0)) // Loop + continue + } else { // Max loops or empty match -> backtrack + r.stackPush2(r.trackPeek(), r.trackPeekN(1)) // Recall old mark, count + // backtrack + } + + case syntax.Lazybranchcount | syntax.Back2: + // r.trackPush: + // 0: Previous mark + // r.stackPush: + // 0: Mark (== current pos, discarded) + // 1: Count + r.trackPop() + r.stackPopN(2) + r.stackPush2(r.trackPeek(), r.stackPeekN(1)-1) // Recall old mark, count + // Backtrack + + case syntax.Setjump: + r.stackPush2(r.trackpos(), r.Crawlpos()) + r.trackPush() + r.advance(0) + continue + + case syntax.Setjump | syntax.Back: + r.stackPopN(2) + + case syntax.Backjump: + // r.stackPush: + // 0: Saved trackpos + // 1: r.crawlpos + r.stackPopN(2) + r.trackto(r.stackPeek()) + + for r.Crawlpos() != r.stackPeekN(1) { + r.uncapture() + } + + case syntax.Forejump: + // r.stackPush: + // 0: Saved trackpos + // 1: r.crawlpos + r.stackPopN(2) + r.trackto(r.stackPeek()) + r.trackPush1(r.stackPeekN(1)) + r.advance(0) + continue + + case syntax.Forejump | syntax.Back: + // r.trackPush: + // 0: r.crawlpos + r.trackPop() + + for r.Crawlpos() != r.trackPeek() { + r.uncapture() + } + + case syntax.Bol: + if r.leftchars() > 0 && r.charAt(r.textPos()-1) != '\n' { + break + } + r.advance(0) + continue + + case syntax.Eol: + if r.rightchars() > 0 && r.charAt(r.textPos()) != '\n' { + break + } + r.advance(0) + continue + + case syntax.Boundary: + if !r.IsBoundary(r.textPos()) { + break + } + r.advance(0) + continue + + case syntax.Nonboundary: + if r.IsBoundary(r.textPos()) { + break + } + r.advance(0) + continue + + case syntax.ECMABoundary: + if !r.IsECMABoundary(r.textPos()) { + break + } + r.advance(0) + continue + + case syntax.NonECMABoundary: + if r.IsECMABoundary(r.textPos()) { + break + } + r.advance(0) + continue + + case syntax.Beginning: + if r.leftchars() > 0 { + break + } + r.advance(0) + continue + + case syntax.Start: + if r.textPos() != r.textstart() { + break + } + r.advance(0) + continue + + case syntax.EndZ: + rchars := r.rightchars() + if rchars > 1 { + break + } + // RE2 and EcmaScript define $ as "asserts position at the end of the string" + // PCRE/.NET adds "or before the line terminator right at the end of the string (if any)" + if (r.re.options & (RE2 | ECMAScript)) != 0 { + // RE2/Ecmascript mode + if rchars > 0 { + break + } + } else if rchars == 1 && r.charAt(r.textPos()) != '\n' { + // "regular" mode + break + } + + r.advance(0) + continue + + case syntax.End: + if r.rightchars() > 0 { + break + } + r.advance(0) + continue + + case syntax.One: + if r.forwardchars() < 1 || r.forwardcharnext() != rune(r.operand(0)) { + break + } + + r.advance(1) + continue + + case syntax.Notone: + if r.forwardchars() < 1 || r.forwardcharnext() == rune(r.operand(0)) { + break + } + + r.advance(1) + continue + + case syntax.Set: + + if r.forwardchars() < 1 || !r.code.Sets[r.operand(0)].CharIn(r.forwardcharnext()) { + break + } + + r.advance(1) + continue + + case syntax.Multi: + if !r.runematch(r.code.Strings[r.operand(0)]) { + break + } + + r.advance(1) + continue + + case syntax.Ref: + + capnum := r.operand(0) + + if r.runmatch.isMatched(capnum) { + if !r.refmatch(r.runmatch.matchIndex(capnum), r.runmatch.matchLength(capnum)) { + break + } + } else { + if (r.re.options & ECMAScript) == 0 { + break + } + } + + r.advance(1) + continue + + case syntax.Onerep: + + c := r.operand(1) + + if r.forwardchars() < c { + break + } + + ch := rune(r.operand(0)) + + for c > 0 { + if r.forwardcharnext() != ch { + goto BreakBackward + } + c-- + } + + r.advance(2) + continue + + case syntax.Notonerep: + + c := r.operand(1) + + if r.forwardchars() < c { + break + } + ch := rune(r.operand(0)) + + for c > 0 { + if r.forwardcharnext() == ch { + goto BreakBackward + } + c-- + } + + r.advance(2) + continue + + case syntax.Setrep: + + c := r.operand(1) + + if r.forwardchars() < c { + break + } + + set := r.code.Sets[r.operand(0)] + + for c > 0 { + if !set.CharIn(r.forwardcharnext()) { + goto BreakBackward + } + c-- + } + + r.advance(2) + continue + + case syntax.Oneloop, syntax.Oneloopatomic: + + c := r.operand(1) + + if c > r.forwardchars() { + c = r.forwardchars() + } + + ch := rune(r.operand(0)) + i := c + + for ; i > 0; i-- { + if r.forwardcharnext() != ch { + r.backwardnext() + break + } + } + + if c > i && r.operator == syntax.Oneloop { + r.trackPush2(c-i-1, r.textPos()-r.bump()) + } + + r.advance(2) + continue + + case syntax.Notoneloop, syntax.Notoneloopatomic: + + c := r.operand(1) + + if c > r.forwardchars() { + c = r.forwardchars() + } + + ch := rune(r.operand(0)) + i := c + + for ; i > 0; i-- { + if r.forwardcharnext() == ch { + r.backwardnext() + break + } + } + + if c > i && r.operator == syntax.Notoneloop { + r.trackPush2(c-i-1, r.textPos()-r.bump()) + } + + r.advance(2) + continue + + case syntax.Setloop, syntax.Setloopatomic: + + c := r.operand(1) + + if c > r.forwardchars() { + c = r.forwardchars() + } + + set := r.code.Sets[r.operand(0)] + i := c + + for ; i > 0; i-- { + if !set.CharIn(r.forwardcharnext()) { + r.backwardnext() + break + } + } + + if c > i && r.operator == syntax.Setloop { + r.trackPush2(c-i-1, r.textPos()-r.bump()) + } + + r.advance(2) + continue + + case syntax.Oneloop | syntax.Back, syntax.Notoneloop | syntax.Back: + + r.trackPopN(2) + i := r.trackPeek() + pos := r.trackPeekN(1) + + r.textto(pos) + + if i > 0 { + r.trackPush2(i-1, pos-r.bump()) + } + + r.advance(2) + continue + + case syntax.Setloop | syntax.Back: + + r.trackPopN(2) + i := r.trackPeek() + pos := r.trackPeekN(1) + + r.textto(pos) + + if i > 0 { + r.trackPush2(i-1, pos-r.bump()) + } + + r.advance(2) + continue + + case syntax.Onelazy, syntax.Notonelazy: + + c := r.operand(1) + + if c > r.forwardchars() { + c = r.forwardchars() + } + + if c > 0 { + r.trackPush2(c-1, r.textPos()) + } + + r.advance(2) + continue + + case syntax.Setlazy: + + c := r.operand(1) + + if c > r.forwardchars() { + c = r.forwardchars() + } + + if c > 0 { + r.trackPush2(c-1, r.textPos()) + } + + r.advance(2) + continue + + case syntax.Onelazy | syntax.Back: + + r.trackPopN(2) + pos := r.trackPeekN(1) + r.textto(pos) + + if r.forwardcharnext() != rune(r.operand(0)) { + break + } + + i := r.trackPeek() + + if i > 0 { + r.trackPush2(i-1, pos+r.bump()) + } + + r.advance(2) + continue + + case syntax.Notonelazy | syntax.Back: + + r.trackPopN(2) + pos := r.trackPeekN(1) + r.textto(pos) + + if r.forwardcharnext() == rune(r.operand(0)) { + break + } + + i := r.trackPeek() + + if i > 0 { + r.trackPush2(i-1, pos+r.bump()) + } + + r.advance(2) + continue + + case syntax.Setlazy | syntax.Back: + + r.trackPopN(2) + pos := r.trackPeekN(1) + r.textto(pos) + + if !r.code.Sets[r.operand(0)].CharIn(r.forwardcharnext()) { + break + } + + i := r.trackPeek() + + if i > 0 { + r.trackPush2(i-1, pos+r.bump()) + } + + r.advance(2) + continue + + case syntax.UpdateBumpalong: + // UpdateBumpalong should only exist in the code stream at such a point where the root + // of the backtracking stack contains the runtextpos from the start of this Go call. Replace + // that tracking value with the current runtextpos value if it's greater. + trackingpos := r.runtrack[len(r.runtrack)-1] + if trackingpos < r.Runtextpos { + r.runtrack[len(r.runtrack)-1] = r.Runtextpos + } + r.advance(0) + continue + + default: + return fmt.Errorf("unknown state in regex runner: %v", r.operator) + } + + BreakBackward: + ; + + // "break Backward" comes here: + r.backtrack() + } +} + +// increase the size of stack and track storage +func (r *Runner) ensureStorage() { + if r.Runstackpos < r.runtrackcount*4 { + doubleIntSlice(&r.runstack, &r.Runstackpos) + } + if r.Runtrackpos < r.runtrackcount*4 { + doubleIntSlice(&r.runtrack, &r.Runtrackpos) + } +} + +func (r *Runner) ensureStack(plus int) { + if r.Runstackpos-plus < r.runtrackcount*4 { + doubleIntSlice(&r.runstack, &r.Runstackpos) + } +} + +func doubleIntSlice(s *[]int, pos *int) { + oldLen := len(*s) + newS := make([]int, oldLen*2) + + copy(newS[oldLen:], *s) + *pos += oldLen + *s = newS +} + +// Save a number on the longjump unrolling stack +func (r *Runner) crawl(i int) { + if r.runcrawlpos == 0 { + doubleIntSlice(&r.runcrawl, &r.runcrawlpos) + } + r.runcrawlpos-- + r.runcrawl[r.runcrawlpos] = i +} + +// Remove a number from the longjump unrolling stack +func (r *Runner) popcrawl() int { + val := r.runcrawl[r.runcrawlpos] + r.runcrawlpos++ + return val +} + +// Get the height of the stack +func (r *Runner) Crawlpos() int { + return len(r.runcrawl) - r.runcrawlpos +} + +func (r *Runner) advance(i int) { + r.codepos += (i + 1) + r.setOperator(r.code.Codes[r.codepos]) +} + +func (r *Runner) goTo(newpos int) { + // when branching backward or in place, ensure storage + if newpos <= r.codepos { + r.ensureStorage() + } + + r.setOperator(r.code.Codes[newpos]) + r.codepos = newpos +} + +func (r *Runner) textto(newpos int) { + r.Runtextpos = newpos +} + +func (r *Runner) trackto(newpos int) { + r.Runtrackpos = len(r.runtrack) - newpos +} + +func (r *Runner) textstart() int { + return r.Runtextstart +} + +func (r *Runner) textPos() int { + return r.Runtextpos +} + +// push onto the backtracking stack +func (r *Runner) trackpos() int { + return len(r.runtrack) - r.Runtrackpos +} + +func (r *Runner) trackPush() { + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = r.codepos +} + +func (r *Runner) trackPush1(I1 int) { + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = I1 + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = r.codepos +} + +func (r *Runner) trackPush2(I1, I2 int) { + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = I1 + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = I2 + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = r.codepos +} + +func (r *Runner) trackPush3(I1, I2, I3 int) { + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = I1 + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = I2 + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = I3 + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = r.codepos +} + +func (r *Runner) trackPushNeg1(I1 int) { + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = I1 + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = -r.codepos +} + +func (r *Runner) trackPushNeg2(I1, I2 int) { + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = I1 + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = I2 + r.Runtrackpos-- + r.runtrack[r.Runtrackpos] = -r.codepos +} + +func (r *Runner) backtrack() { + newpos := r.runtrack[r.Runtrackpos] + r.Runtrackpos++ + + if r.debug { + if newpos < 0 { + fmt.Printf(" Backtracking (back2) to code position %v\n", -newpos) + } else { + fmt.Printf(" Backtracking to code position %v\n", newpos) + } + } + + if newpos < 0 { + newpos = -newpos + r.setOperator(r.code.Codes[newpos] | int(syntax.Back2)) + } else { + r.setOperator(r.code.Codes[newpos] | int(syntax.Back)) + } + + // When branching backward, ensure storage + if newpos < r.codepos { + r.ensureStorage() + } + + r.codepos = newpos +} + +func (r *Runner) setOperator(op int) { + r.caseInsensitive = (0 != (op & int(syntax.Ci))) + r.rightToLeft = (0 != (op & int(syntax.Rtl))) + r.operator = syntax.InstOp(op & ^int(syntax.Rtl|syntax.Ci)) +} + +func (r *Runner) trackPop() { + r.Runtrackpos++ +} + +// pop framesize items from the backtracking stack +func (r *Runner) trackPopN(framesize int) { + r.Runtrackpos += framesize +} + +// Technically we are actually peeking at items already popped. So if you want to +// get and pop the top item from the stack, you do +// r.trackPop(); +// r.trackPeek(); +func (r *Runner) trackPeek() int { + return r.runtrack[r.Runtrackpos-1] +} + +// get the ith element down on the backtracking stack +func (r *Runner) trackPeekN(i int) int { + return r.runtrack[r.Runtrackpos-i-1] +} + +// Push onto the grouping stack +func (r *Runner) stackPush(I1 int) { + r.Runstackpos-- + r.runstack[r.Runstackpos] = I1 +} + +func (r *Runner) stackPush2(I1, I2 int) { + r.Runstackpos-- + r.runstack[r.Runstackpos] = I1 + r.Runstackpos-- + r.runstack[r.Runstackpos] = I2 +} + +func (r *Runner) stackPop() { + r.Runstackpos++ +} + +// pop framesize items from the grouping stack +func (r *Runner) stackPopN(framesize int) { + r.Runstackpos += framesize +} + +// Technically we are actually peeking at items already popped. So if you want to +// get and pop the top item from the stack, you do +// r.stackPop(); +// r.stackPeek(); +func (r *Runner) stackPeek() int { + return r.runstack[r.Runstackpos-1] +} + +// get the ith element down on the grouping stack +func (r *Runner) stackPeekN(i int) int { + return r.runstack[r.Runstackpos-i-1] +} + +func (r *Runner) operand(i int) int { + return r.code.Codes[r.codepos+i+1] +} + +func (r *Runner) leftchars() int { + return r.Runtextpos +} + +func (r *Runner) rightchars() int { + return r.Runtextend - r.Runtextpos +} + +func (r *Runner) bump() int { + if r.rightToLeft { + return -1 + } + return 1 +} + +func (r *Runner) forwardchars() int { + if r.rightToLeft { + return r.Runtextpos + } + return r.Runtextend - r.Runtextpos +} + +func (r *Runner) forwardcharnext() rune { + var ch rune + if r.rightToLeft { + r.Runtextpos-- + ch = r.Runtext[r.Runtextpos] + } else { + ch = r.Runtext[r.Runtextpos] + r.Runtextpos++ + } + + // move this to compile time for individual runes + /*if r.caseInsensitive { + return unicode.ToLower(ch) + }*/ + return ch +} + +func (r *Runner) runematch(str []rune) bool { + var pos int + + c := len(str) + if !r.rightToLeft { + if r.Runtextend-r.Runtextpos < c { + return false + } + + pos = r.Runtextpos + c + } else { + if r.Runtextpos-0 < c { + return false + } + + pos = r.Runtextpos + } + + if !r.caseInsensitive { + for c != 0 { + c-- + pos-- + if str[c] != r.Runtext[pos] { + return false + } + } + } else { + for c != 0 { + c-- + pos-- + if str[c] != unicode.ToLower(r.Runtext[pos]) { + return false + } + } + } + + if !r.rightToLeft { + pos += len(str) + } + + r.Runtextpos = pos + + return true +} + +func (r *Runner) refmatch(index, len int) bool { + var c, pos, cmpos int + + if !r.rightToLeft { + if r.Runtextend-r.Runtextpos < len { + return false + } + + pos = r.Runtextpos + len + } else { + if r.Runtextpos-0 < len { + return false + } + + pos = r.Runtextpos + } + cmpos = index + len + + c = len + + if !r.caseInsensitive { + for c != 0 { + c-- + cmpos-- + pos-- + if r.Runtext[cmpos] != r.Runtext[pos] { + return false + } + + } + } else { + for c != 0 { + c-- + cmpos-- + pos-- + + if unicode.ToLower(r.Runtext[cmpos]) != unicode.ToLower(r.Runtext[pos]) { + return false + } + } + } + + if !r.rightToLeft { + pos += len + } + + r.Runtextpos = pos + + return true +} + +func (r *Runner) backwardnext() { + if r.rightToLeft { + r.Runtextpos++ + } else { + r.Runtextpos-- + } +} + +func (r *Runner) charAt(j int) rune { + return r.Runtext[j] +} + +func findFirstCharDefault(r *Runner) bool { + if 0 != (r.code.Anchors & (syntax.AnchorBeginning | syntax.AnchorStart | syntax.AnchorEndZ | syntax.AnchorEnd)) { + if !r.code.RightToLeft { + if (0 != (r.code.Anchors&syntax.AnchorBeginning) && r.Runtextpos > 0) || + (0 != (r.code.Anchors&syntax.AnchorStart) && r.Runtextpos > r.Runtextstart) { + r.Runtextpos = r.Runtextend + return false + } + if 0 != (r.code.Anchors&syntax.AnchorEndZ) && r.Runtextpos < r.Runtextend-1 { + r.Runtextpos = r.Runtextend - 1 + } else if 0 != (r.code.Anchors&syntax.AnchorEnd) && r.Runtextpos < r.Runtextend { + r.Runtextpos = r.Runtextend + } + } else { + if (0 != (r.code.Anchors&syntax.AnchorEnd) && r.Runtextpos < r.Runtextend) || + (0 != (r.code.Anchors&syntax.AnchorEndZ) && (r.Runtextpos < r.Runtextend-1 || + (r.Runtextpos == r.Runtextend-1 && r.charAt(r.Runtextpos) != '\n'))) || + (0 != (r.code.Anchors&syntax.AnchorStart) && r.Runtextpos < r.Runtextstart) { + r.Runtextpos = 0 + return false + } + if 0 != (r.code.Anchors&syntax.AnchorBeginning) && r.Runtextpos > 0 { + r.Runtextpos = 0 + } + } + + if r.code.BmPrefix != nil { + return r.code.BmPrefix.IsMatch(r.Runtext, r.Runtextpos, 0, r.Runtextend) + } + + return true // found a valid start or end anchor + } else if r.code.BmPrefix != nil { + r.Runtextpos = r.code.BmPrefix.Scan(r.Runtext, r.Runtextpos, 0, r.Runtextend) + + if r.Runtextpos == -1 { + if r.code.RightToLeft { + r.Runtextpos = 0 + } else { + r.Runtextpos = r.Runtextend + } + return false + } + + return true + } + + if shouldUseFindFirstCharOptimized(r) { + if handled, found := findFirstCharOptimized(r); handled { + return found + } + } + + if r.code.FcPrefix == nil { + return true + } + + r.rightToLeft = r.code.RightToLeft + r.caseInsensitive = r.code.FcPrefix.CaseInsensitive + + set := r.code.FcPrefix.PrefixSet + if set.IsSingleton() { + ch := set.SingletonChar() + for i := r.forwardchars(); i > 0; i-- { + if ch == r.forwardcharnext() { + r.backwardnext() + return true + } + } + } else { + for i := r.forwardchars(); i > 0; i-- { + n := r.forwardcharnext() + //fmt.Printf("%v in %v: %v\n", string(n), set.String(), set.CharIn(n)) + if set.CharIn(n) { + r.backwardnext() + return true + } + } + } + + return false +} + +func shouldUseFindFirstCharOptimized(r *Runner) bool { + if r.code == nil || r.code.FindOptimizations == nil { + return false + } + + switch r.code.FindOptimizations.FindMode { + case syntax.TrailingAnchor_FixedLength_LeftToRight_End, + syntax.LeadingString_OrdinalIgnoreCase_LeftToRight, + syntax.LeadingStrings_LeftToRight, + syntax.LeadingStrings_OrdinalIgnoreCase_LeftToRight, + syntax.FixedDistanceChar_LeftToRight, + syntax.FixedDistanceString_LeftToRight, + syntax.FixedDistanceSets_LeftToRight, + syntax.LiteralAfterLoop_LeftToRight, + syntax.RequiredLandmarkChain_LeftToRight: + return true + default: + return false + } +} + +func findFirstCharOptimized(r *Runner) (handled bool, found bool) { + if r.code == nil || r.code.FindOptimizations == nil { + return false, false + } + + opts := r.code.FindOptimizations + switch opts.FindMode { + case syntax.NoSearch: + return false, false + case syntax.TrailingAnchor_FixedLength_LeftToRight_End: + return true, findTrailingFixedLengthEnd(r, opts.MinRequiredLength) + case syntax.LeadingString_LeftToRight: + return true, findLeadingStringLeftToRight(r, []rune(opts.LeadingPrefix), false) + case syntax.LeadingString_OrdinalIgnoreCase_LeftToRight: + return true, findLeadingStringLeftToRight(r, []rune(opts.LeadingPrefix), true) + case syntax.LeadingStrings_LeftToRight: + return true, findLeadingStringsLeftToRight(r, opts.LeadingPrefixes, false) + case syntax.LeadingStrings_OrdinalIgnoreCase_LeftToRight: + return true, findLeadingStringsLeftToRight(r, opts.LeadingPrefixes, true) + case syntax.FixedDistanceSets_LeftToRight: + return true, findFixedDistanceSetsLeftToRight(r, opts.FixedDistanceSets) + case syntax.FixedDistanceChar_LeftToRight: + return true, findFixedDistanceCharLeftToRight(r, opts.FixedDistanceLiteral.C, opts.FixedDistanceLiteral.Distance) + case syntax.FixedDistanceString_LeftToRight: + return true, findFixedDistanceStringLeftToRight(r, []rune(opts.FixedDistanceLiteral.S), opts.FixedDistanceLiteral.Distance) + case syntax.LiteralAfterLoop_LeftToRight: + return true, findLiteralAfterLoopLeftToRight(r, opts.LiteralAfterLoop) + case syntax.RequiredLandmarkChain_LeftToRight: + return true, findRequiredLandmarkChainLeftToRight(r, opts.LandmarkChain) + default: + return false, false + } +} + +func findTrailingFixedLengthEnd(r *Runner, fixedLength int) bool { + start := r.Runtextend - fixedLength + if start < r.Runtextpos || start < 0 { + r.Runtextpos = r.Runtextend + return false + } + r.Runtextpos = start + return true +} + +func findLeadingStringLeftToRight(r *Runner, prefix []rune, ignoreCase bool) bool { + if len(prefix) == 0 { + return true + } + + search := r.Runtext[r.Runtextpos:] + var offset int + if ignoreCase { + if isASCIIRunes(prefix) { + offset = helpers.IndexOfIgnoreCaseAscii(search, prefix) + } else { + offset = helpers.IndexOfIgnoreCase(search, prefix) + } + } else { + offset = helpers.IndexOf(search, prefix) + } + if offset < 0 { + r.Runtextpos = r.Runtextend + return false + } + + start := r.Runtextpos + offset + if !hasRequiredLengthAt(r, start) { + r.Runtextpos = r.Runtextend + return false + } + r.Runtextpos = start + return true +} + +func findLeadingStringsLeftToRight(r *Runner, prefixes []string, ignoreCase bool) bool { + if len(prefixes) == 0 { + return false + } + + for start := r.Runtextpos; start <= latestPossibleStart(r); start++ { + for _, prefix := range prefixes { + prefixRunes := []rune(prefix) + if ignoreCase { + if helpers.StartsWithIgnoreCase(r.Runtext[start:], prefixRunes) { + r.Runtextpos = start + return true + } + } else if helpers.StartsWith(r.Runtext[start:], prefixRunes) { + r.Runtextpos = start + return true + } + } + } + + r.Runtextpos = r.Runtextend + return false +} + +func findFixedDistanceCharLeftToRight(r *Runner, ch rune, distance int) bool { + searchStart := r.Runtextpos + distance + for searchStart < r.Runtextend { + offset := helpers.IndexOfAny1(r.Runtext[searchStart:], ch) + if offset < 0 { + r.Runtextpos = r.Runtextend + return false + } + literalIndex := searchStart + offset + start := literalIndex - distance + if start >= r.Runtextpos && hasRequiredLengthAt(r, start) { + r.Runtextpos = start + return true + } + if start > latestPossibleStart(r) { + break + } + searchStart = literalIndex + 1 + } + + r.Runtextpos = r.Runtextend + return false +} + +func findFixedDistanceStringLeftToRight(r *Runner, literal []rune, distance int) bool { + if len(literal) == 0 { + return true + } + + searchStart := r.Runtextpos + distance + for searchStart <= r.Runtextend-len(literal) { + offset := helpers.IndexOf(r.Runtext[searchStart:], literal) + if offset < 0 { + r.Runtextpos = r.Runtextend + return false + } + literalIndex := searchStart + offset + start := literalIndex - distance + if start >= r.Runtextpos && hasRequiredLengthAt(r, start) { + r.Runtextpos = start + return true + } + if start > latestPossibleStart(r) { + break + } + searchStart = literalIndex + 1 + } + + r.Runtextpos = r.Runtextend + return false +} + +func findFixedDistanceSetsLeftToRight(r *Runner, sets []syntax.FixedDistanceSet) bool { + if len(sets) == 0 || sets[0].Set == nil { + return false + } + + primary := sets[0] + searchStart := r.Runtextpos + primary.Distance + for searchStart < r.Runtextend { + offset := indexOfSet(r.Runtext[searchStart:], primary) + if offset < 0 { + r.Runtextpos = r.Runtextend + return false + } + + charIndex := searchStart + offset + start := charIndex - primary.Distance + if start > latestPossibleStart(r) { + break + } + if start >= r.Runtextpos && hasRequiredLengthAt(r, start) && fixedDistanceSetsMatchAt(r, sets, start) { + r.Runtextpos = start + return true + } + searchStart = charIndex + 1 + } + + r.Runtextpos = r.Runtextend + return false +} + +func findLiteralAfterLoopLeftToRight(r *Runner, literal *syntax.LiteralAfterLoop) bool { + if literal == nil || literal.LoopNode == nil || literal.LoopNode.Set == nil { + return false + } + + searchStart := r.Runtextpos + for searchStart < r.Runtextend { + literalIndex := indexOfLiteralAfterLoop(r, literal, searchStart) + if literalIndex < 0 { + r.Runtextpos = r.Runtextend + return false + } + + start := literalIndex + for start > r.Runtextpos && literal.LoopNode.Set.CharIn(r.Runtext[start-1]) { + start-- + } + if hasRequiredLengthAt(r, start) { + r.Runtextpos = start + return true + } + searchStart = literalIndex + 1 + } + + r.Runtextpos = r.Runtextend + return false +} + +func findRequiredLandmarkChainLeftToRight(r *Runner, chain *syntax.RequiredLandmarkChain) bool { + if chain == nil || chain.LeadingLoopSet == nil || len(chain.Landmarks) == 0 { + return false + } + + for searchStart := r.Runtextpos; searchStart <= latestPossibleStart(r); { + firstStart, firstEnd, ok := findNextRequiredLandmarkRunes(r.Runtext, searchStart, r.Runtextend, chain.Landmarks[0]) + if !ok { + r.Runtextpos = r.Runtextend + return false + } + + nextStart := firstEnd + for i := 1; i < len(chain.Landmarks); i++ { + _, landmarkEnd, ok := findNextRequiredLandmarkRunes(r.Runtext, nextStart, r.Runtextend, chain.Landmarks[i]) + if !ok { + r.Runtextpos = r.Runtextend + return false + } + nextStart = landmarkEnd + } + + candidate := firstStart + firstWhitespaceSet := firstLandmarkWhitespaceSet(chain.Landmarks[0]) + for candidate > r.Runtextpos && firstWhitespaceSet != nil && firstWhitespaceSet.CharIn(r.Runtext[candidate-1]) { + candidate-- + } + for candidate > r.Runtextpos && chain.LeadingLoopSet.CharIn(r.Runtext[candidate-1]) { + candidate-- + } + if hasRequiredLengthAt(r, candidate) { + r.Runtextpos = candidate + return true + } + + searchStart = firstStart + 1 + } + + r.Runtextpos = r.Runtextend + return false +} + +func findNextRequiredLandmarkRunes(input []rune, startAt, endAt int, landmark syntax.RequiredLandmark) (start int, end int, ok bool) { + for i := startAt; i < endAt; i++ { + for _, alt := range landmark.Alternatives { + if end, ok := requiredLandmarkAlternativeEnd(input, i, endAt, alt); ok { + return i, end, true + } + } + } + return 0, 0, false +} + +func requiredLandmarkAlternativeEnd(input []rune, start, endAt int, alt syntax.RequiredLandmarkAlternative) (int, bool) { + if alt.RequireWhitespaceBefore && + (start == 0 || alt.WhitespaceSet == nil || !alt.WhitespaceSet.CharIn(input[start-1])) { + return 0, false + } + + var end int + if alt.Literal != "" { + literal := []rune(alt.Literal) + if len(literal) == 0 || start+len(literal) > endAt || !helpers.StartsWith(input[start:], literal) { + return 0, false + } + end = start + len(literal) + } else if alt.Set != nil && alt.MinRepeat > 0 { + end = start + maxRepeat := alt.MaxRepeat + if maxRepeat <= 0 { + maxRepeat = alt.MinRepeat + } + for end < endAt && end-start < maxRepeat && alt.Set.CharIn(input[end]) { + end++ + } + if end-start < alt.MinRepeat { + return 0, false + } + } else { + return 0, false + } + + if alt.RequireWhitespaceAfter && + (end >= endAt || alt.WhitespaceSet == nil || !alt.WhitespaceSet.CharIn(input[end])) { + return 0, false + } + return end, true +} + +func firstLandmarkWhitespaceSet(landmark syntax.RequiredLandmark) *syntax.CharSet { + for _, alt := range landmark.Alternatives { + if alt.WhitespaceSet != nil { + return alt.WhitespaceSet + } + } + return nil +} + +func indexOfLiteralAfterLoop(r *Runner, literal *syntax.LiteralAfterLoop, searchStart int) int { + switch { + case literal.String != "": + needle := []rune(literal.String) + if literal.StringIgnoreCase { + var offset int + if isASCIIString(literal.String) { + offset = helpers.IndexOfIgnoreCaseAscii(r.Runtext[searchStart:], needle) + } else { + offset = helpers.IndexOfIgnoreCase(r.Runtext[searchStart:], needle) + } + if offset >= 0 { + return searchStart + offset + } + } else if offset := helpers.IndexOf(r.Runtext[searchStart:], needle); offset >= 0 { + return searchStart + offset + } + case len(literal.Chars) > 0: + if offset := helpers.IndexOfAny(r.Runtext[searchStart:], literal.Chars); offset >= 0 { + return searchStart + offset + } + default: + if offset := helpers.IndexOfAny1(r.Runtext[searchStart:], literal.Char); offset >= 0 { + return searchStart + offset + } + } + return -1 +} + +func isASCIIRunes(in []rune) bool { + for _, ch := range in { + if ch > unicode.MaxASCII { + return false + } + } + return true +} + +func indexOfSet(chars []rune, set syntax.FixedDistanceSet) int { + if len(set.Chars) > 0 && !set.Negated { + return helpers.IndexOfAny(chars, set.Chars) + } + if len(set.Chars) > 0 && set.Negated { + return helpers.IndexOfAnyExcept(chars, set.Chars) + } + if set.Range != nil { + if set.Negated { + return helpers.IndexOfAnyExceptInRange(chars, set.Range.First, set.Range.Last) + } + return helpers.IndexOfAnyInRange(chars, set.Range.First, set.Range.Last) + } + return helpers.IndexFunc(chars, func(ch rune) bool { + return charInFixedDistanceSet(set, ch) + }) +} + +func fixedDistanceSetsMatchAt(r *Runner, sets []syntax.FixedDistanceSet, start int) bool { + for _, set := range sets { + index := start + set.Distance + if index < 0 || index >= r.Runtextend || !charInFixedDistanceSet(set, r.Runtext[index]) { + return false + } + } + return true +} + +func charInFixedDistanceSet(set syntax.FixedDistanceSet, ch rune) bool { + if len(set.Chars) > 0 { + found := slices.Contains(set.Chars, ch) + if set.Negated { + return !found + } + return found + } + if set.Range != nil { + found := ch >= set.Range.First && ch <= set.Range.Last + if set.Negated { + return !found + } + return found + } + return set.Set != nil && set.Set.CharIn(ch) +} + +func latestPossibleStart(r *Runner) int { + if r.code == nil || r.code.FindOptimizations == nil { + return r.Runtextend + } + minRequiredLength := r.code.FindOptimizations.MinRequiredLength + if minRequiredLength <= 0 { + return r.Runtextend + } + return r.Runtextend - minRequiredLength +} + +func hasRequiredLengthAt(r *Runner, start int) bool { + return start >= 0 && start <= latestPossibleStart(r) +} + +func (r *Runner) initMatch(textInfo *matchText) { + // Use a hashtable'ed Match object if the capture numbers are sparse + + if r.runmatch == nil { + if r.re.caps != nil { + r.runmatch = newMatchSparse(r.re, r.re.caps, r.re.capsize, textInfo, r.Runtextstart) + } else { + r.runmatch = newMatch(r.re, r.re.capsize, textInfo, r.Runtextstart) + } + } else { + r.runmatch.reset(textInfo, r.Runtextstart) + } + + // note we test runcrawl, because it is the last one to be allocated + // If there is an alloc failure in the middle of the three allocations, + // we may still return to reuse this instance, and we want to behave + // as if the allocations didn't occur. (we used to test _trackcount != 0) + + if r.runcrawl != nil { + r.Runtrackpos = len(r.runtrack) + r.Runstackpos = len(r.runstack) + r.runcrawlpos = len(r.runcrawl) + return + } + + r.initTrackCount() + + tracksize := r.runtrackcount * 8 + stacksize := r.runtrackcount * 8 + + if tracksize < 64 { + tracksize = 64 + } + if stacksize < 32 { + stacksize = 32 + } + + r.runtrack = make([]int, tracksize) + r.Runtrackpos = tracksize + + r.runstack = make([]int, stacksize) + r.Runstackpos = stacksize + + r.runcrawl = make([]int, 32) + r.runcrawlpos = 32 +} + +func (r *Runner) tidyMatch(quick bool) *Match { + if !quick { + match := r.runmatch + + r.runmatch = nil + + match.tidy(r.Runtextpos) + return match + } else { + // send back our match -- it's not leaving the package, so it's safe to not clean it up + // this reduces allocs for frequent calls to the "IsMatch" bool-only functions + m := r.runmatch + if m == nil { + return nil + } + m.textpos = r.Runtextpos + if m.matchcount[0] > 0 { + interval := m.matches[0] + // bytes indices aren't used so just use fast path + m.RuneIndex = interval[0] + m.RuneLength = interval[1] + } + return m + } +} + +// Capture captures a subexpression. Note that the +// capnum used here has already been mapped to a non-sparse +// index (by the code generator RegexWriter). +func (r *Runner) Capture(capnum, start, end int) { + if end < start { + T := end + end = start + start = T + } + + r.crawl(capnum) + r.runmatch.addMatch(capnum, start, end-start) +} + +// transferCapture captures a subexpression. Note that the +// capnum used here has already been mapped to a non-sparse +// index (by the code generator RegexWriter). +func (r *Runner) transferCapture(capnum, uncapnum, start, end int) { + var start2, end2 int + + // these are the two intervals that are cancelling each other + + if end < start { + T := end + end = start + start = T + } + + start2 = r.runmatch.matchIndex(uncapnum) + end2 = start2 + r.runmatch.matchLength(uncapnum) + + // The new capture gets the innermost defined interval + + if start >= end2 { + end = start + start = end2 + } else if end <= start2 { + start = start2 + } else { + if end > end2 { + end = end2 + } + if start2 > start { + start = start2 + } + } + + r.crawl(uncapnum) + r.runmatch.balanceMatch(uncapnum) + + if capnum != -1 { + r.crawl(capnum) + r.runmatch.addMatch(capnum, start, end-start) + } +} + +// revert the last capture +func (r *Runner) uncapture() { + capnum := r.popcrawl() + r.runmatch.removeMatch(capnum) +} + +//debug + +func (r *Runner) dumpState() { + back := "" + if r.operator&syntax.Back != 0 { + back = " Back" + } + if r.operator&syntax.Back2 != 0 { + back += " Back2" + } + fmt.Printf("Text: %v\nTrack: %v\nStack: %v\n %s%s\n\n", + r.textposDescription(), + r.stackDescription(r.runtrack, r.Runtrackpos), + r.stackDescription(r.runstack, r.Runstackpos), + r.code.OpcodeDescription(r.codepos), + back) +} + +func (r *Runner) stackDescription(a []int, index int) string { + buf := &bytes.Buffer{} + + fmt.Fprintf(buf, "%v/%v", len(a)-index, len(a)) + if buf.Len() < 8 { + buf.WriteString(strings.Repeat(" ", 8-buf.Len())) + } + + buf.WriteRune('(') + for i := index; i < len(a); i++ { + if i > index { + buf.WriteRune(' ') + } + + buf.WriteString(strconv.Itoa(a[i])) + } + + buf.WriteRune(')') + + return buf.String() +} + +func (r *Runner) textposDescription() string { + buf := &bytes.Buffer{} + + buf.WriteString(strconv.Itoa(r.Runtextpos)) + + if buf.Len() < 8 { + buf.WriteString(strings.Repeat(" ", 8-buf.Len())) + } + + if r.Runtextpos > 0 { + buf.WriteString(syntax.CharDescription(r.Runtext[r.Runtextpos-1])) + } else { + buf.WriteRune('^') + } + + buf.WriteRune('>') + + for i := r.Runtextpos; i < r.Runtextend; i++ { + buf.WriteString(syntax.CharDescription(r.Runtext[i])) + } + if buf.Len() >= 64 { + buf.Truncate(61) + buf.WriteString("...") + } else { + buf.WriteRune('$') + } + + return buf.String() +} + +// decide whether the pos +// at the specified index is a boundary or not. It's just not worth +// emitting inline code for this logic. +func (r *Runner) IsBoundary(index int) bool { + return (index > 0 && syntax.IsWordChar(r.Runtext[index-1])) != + (index < r.Runtextend && syntax.IsWordChar(r.Runtext[index])) +} + +func (r *Runner) IsECMABoundary(index int) bool { + return (index > 0 && syntax.IsECMAWordChar(r.Runtext[index-1])) != + (index < r.Runtextend && syntax.IsECMAWordChar(r.Runtext[index])) +} + +func (r *Runner) startTimeoutWatch() { + if r.ignoreTimeout { + return + } + r.deadline = makeDeadline(r.timeout) +} + +func (r *Runner) CheckTimeout() error { + if r.ignoreTimeout || !r.deadline.reached() { + return nil + } + + return fmt.Errorf("match timeout after %v on input `%v`", r.timeout, string(r.Runtext)) +} + +func (r *Runner) initTrackCount() { + if r.code != nil { + r.runtrackcount = r.code.TrackCount + } +} + +// decodeString converts s to []rune using a shared size-classed buffer pool when +// allowed by the regexp optimization settings. Pooled slices must be returned +// after the runner is done with them. +func (r *Runner) decodeString(s string) ([]rune, *[]rune) { + buf, pooled := pooledRuneBuffers.get(len(s), r.re.optimizations.MaxCachedRuneBufferLength) + n := 0 + for _, ch := range s { + buf[n] = ch + n++ + } + return buf[:n], pooled +} + +func (r *Runner) decodeStringWithStart(s string, startAt int) (runes []rune, runeStart int, pooled *[]rune) { + buf, pooled := pooledRuneBuffers.get(len(s), r.re.optimizations.MaxCachedRuneBufferLength) + n := 0 + runeStart = -1 + for strIdx, ch := range s { + if startAt >= 0 && strIdx == startAt { + runeStart = n + } + buf[n] = ch + n++ + } + if startAt >= 0 && startAt == len(s) { + runeStart = n + } + return buf[:n], runeStart, pooled +} + +// getRunner returns a runner to use for matching re. +func (re *Regexp) getRunner() *Runner { + if re.runnerPool == nil { + re.initCaches() + } + return re.runnerPool.Get().(*Runner) +} + +// putRunner returns a runner to the re's pool cache. +func (re *Regexp) putRunner(r *Runner) { + r.Runtext = nil + if r.runmatch != nil { + r.runmatch.text = nil + } + re.runnerPool.Put(r) +} + +func (r *Runner) LastIndexOfRune(startIndex int, endIndex int, find rune) int { + for i := endIndex - 1; i >= startIndex; i-- { + if r.Runtext[i] == find { + return i + } + } + return -1 +} + +// Undo captures until it reaches the specified capture position +func (r *Runner) UncaptureUntil(capturePos int) { + for r.Crawlpos() > capturePos { + r.uncapture() + } +} + +func (r *Runner) StackPop() int { + //get it + val := r.runstack[r.Runstackpos] + // pop it + r.Runstackpos++ + // return it + return val +} + +func (r *Runner) StackPush(val int) { + // check if we need to size up stack + r.ensureStack(1) + r.Runstackpos-- + r.runstack[r.Runstackpos] = val +} +func (r *Runner) StackPush2(val1, val2 int) { + // check if we need to size up stack + r.ensureStack(2) + r.Runstackpos-- + r.runstack[r.Runstackpos] = val1 + r.Runstackpos-- + r.runstack[r.Runstackpos] = val2 +} +func (r *Runner) StackPush3(val1, val2, val3 int) { + // check if we need to size up stack + r.ensureStack(3) + r.Runstackpos-- + r.runstack[r.Runstackpos] = val1 + r.Runstackpos-- + r.runstack[r.Runstackpos] = val2 + r.Runstackpos-- + r.runstack[r.Runstackpos] = val3 +} +func (r *Runner) StackPush4(val1, val2, val3, val4 int) { + // check if we need to size up stack + r.ensureStack(4) + r.Runstackpos-- + r.runstack[r.Runstackpos] = val1 + r.Runstackpos-- + r.runstack[r.Runstackpos] = val2 + r.Runstackpos-- + r.runstack[r.Runstackpos] = val3 + r.Runstackpos-- + r.runstack[r.Runstackpos] = val4 +} +func (r *Runner) StackPush5(val1, val2, val3, val4, val5 int) { + // check if we need to size up stack + r.ensureStack(5) + r.Runstackpos-- + r.runstack[r.Runstackpos] = val1 + r.Runstackpos-- + r.runstack[r.Runstackpos] = val2 + r.Runstackpos-- + r.runstack[r.Runstackpos] = val3 + r.Runstackpos-- + r.runstack[r.Runstackpos] = val4 + r.Runstackpos-- + r.runstack[r.Runstackpos] = val5 +} +func (r *Runner) StackPushN(vals ...int) { + // check if we need to size up stack + r.ensureStack(len(vals)) + for _, val := range vals { + r.Runstackpos-- + r.runstack[r.Runstackpos] = val + } +} +func (r *Runner) IsMatched(cap int) bool { + return r.runmatch.isMatched(cap) +} +func (r *Runner) MatchLength(cap int) int { + return r.runmatch.matchLength(cap) +} +func (r *Runner) MatchIndex(cap int) int { + return r.runmatch.matchIndex(cap) +} diff --git a/vendor/github.com/dlclark/regexp2/v2/split.go b/vendor/github.com/dlclark/regexp2/v2/split.go new file mode 100644 index 000000000..1dbe0dcd1 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/split.go @@ -0,0 +1,67 @@ +package regexp2 + +import ( + "errors" + "math" +) + +// Split splits the given input string using the pattern and returns +// a slice of the parts. Count limits the number of matches to process. +// If Count is -1, then it will process the input fully. +// If Count is 0, returns nil. If Count is 1, returns the original input. +// The only expected error is a Timeout, if it's set. +// +// If capturing parentheses are used in the Regex expression, any captured +// text is included in the resulting string array +// For example, a pattern of "-" Split("a-b") will return ["a", "b"] +// but a pattern with "(-)" Split ("a-b") will return ["a", "-", "b"] +func (re *Regexp) Split(input string, count int) ([]string, error) { + if count < -1 { + return nil, errors.New("count too small") + } + if count == 0 { + return nil, nil + } + if count == 1 { + return []string{input}, nil + } + if count == -1 { + // no limit + count = math.MaxInt + } + + // iterate through the matches + priorIndex := 0 + var retVal []string + var txt []rune + + m, err := re.FindStringMatch(input) + + for ; m != nil && count > 0; m, err = re.FindNextMatch(m) { + txt = m.text.runes + // if we have an m, we don't have an err + // append our match + retVal = append(retVal, string(txt[priorIndex:m.RuneIndex])) + // append any capture groups, skipping group 0 + gs := m.Groups() + for i := 1; i < len(gs); i++ { + retVal = append(retVal, gs[i].String()) + } + priorIndex = m.RuneIndex + m.RuneLength + count-- + } + + if err != nil { + return nil, err + } + + if txt == nil { + // we never matched, return the original string + return []string{input}, nil + } + + // append our remainder + retVal = append(retVal, string(txt[priorIndex:])) + + return retVal, nil +} diff --git a/vendor/github.com/dlclark/regexp2/v2/stringprefixfilter.go b/vendor/github.com/dlclark/regexp2/v2/stringprefixfilter.go new file mode 100644 index 000000000..f8852f239 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/stringprefixfilter.go @@ -0,0 +1,295 @@ +package regexp2 + +import ( + "errors" + "strings" + "unicode/utf8" + + "github.com/dlclark/regexp2/v2/helpers" + "github.com/dlclark/regexp2/v2/syntax" +) + +const maxStringFilterLiteralLen = 8 + +var ( + errStringStartAtTooLarge = errors.New("startAt must be less than the length of the input string") + errStringStartAtNotRuneBoundary = errors.New("startAt must align to the start of a valid rune in the input string") +) + +// StringPrefixFilter optionally searches string input before the engine decodes it +// to runes. It returns a byte index for a candidate match start, or ok=false if +// the regex cannot match. The filter must be conservative: false positives are +// allowed, false negatives are not. +type StringPrefixFilter func(input string, startAt int) (candidateByteIndex int, ok bool) + +func newStringPrefixFilter(code *syntax.Code) StringPrefixFilter { + if code == nil || code.RightToLeft || code.FindOptimizations == nil { + return nil + } + + opts := code.FindOptimizations + minRequiredLength := opts.MinRequiredLength + + switch opts.FindMode { + case syntax.LeadingString_LeftToRight: + return stringIndexPrefixFilter(opts.LeadingPrefix, false, minRequiredLength) + case syntax.LeadingString_OrdinalIgnoreCase_LeftToRight: + return stringIndexPrefixFilter(opts.LeadingPrefix, true, minRequiredLength) + case syntax.LeadingStrings_LeftToRight: + return stringIndexPrefixesFilter(opts.LeadingPrefixes, false, minRequiredLength) + case syntax.LeadingStrings_OrdinalIgnoreCase_LeftToRight: + return stringIndexPrefixesFilter(opts.LeadingPrefixes, true, minRequiredLength) + case syntax.FixedDistanceChar_LeftToRight: + return stringFixedDistanceCharFilter(opts.FixedDistanceLiteral.C, opts.FixedDistanceLiteral.Distance, minRequiredLength) + case syntax.FixedDistanceString_LeftToRight: + return stringFixedDistanceStringFilter(opts.FixedDistanceLiteral.S, opts.FixedDistanceLiteral.Distance, minRequiredLength) + case syntax.LiteralAfterLoop_LeftToRight: + return stringLiteralAfterLoopFilter(opts.LiteralAfterLoop, minRequiredLength) + default: + return nil + } +} + +func stringIndexPrefixFilter(prefix string, ignoreCase bool, minRequiredLength int) StringPrefixFilter { + if prefix == "" { + return nil + } + if ignoreCase && !isASCIIString(prefix) { + return nil + } + + return func(input string, startAt int) (candidateByteIndex int, ok bool) { + if !hasMinRequiredBytes(input, startAt, minRequiredLength) { + return 0, false + } + + var offset int + if ignoreCase { + offset = helpers.IndexStringIgnoreCaseASCII(input[startAt:], prefix) + } else { + offset = strings.Index(input[startAt:], prefix) + } + if offset < 0 { + return 0, false + } + return startAt + offset, true + } +} + +func stringIndexPrefixesFilter(prefixes []string, ignoreCase bool, minRequiredLength int) StringPrefixFilter { + if len(prefixes) == 0 { + return nil + } + if ignoreCase { + for _, prefix := range prefixes { + if !isASCIIString(prefix) { + return nil + } + } + } + + prefixes = append([]string(nil), prefixes...) + return func(input string, startAt int) (candidateByteIndex int, ok bool) { + if !hasMinRequiredBytes(input, startAt, minRequiredLength) { + return 0, false + } + + best := -1 + remaining := input[startAt:] + for _, prefix := range prefixes { + var offset int + if ignoreCase { + offset = helpers.IndexStringIgnoreCaseASCII(remaining, prefix) + } else { + offset = strings.Index(remaining, prefix) + } + if offset >= 0 && (best < 0 || offset < best) { + best = offset + } + } + if best < 0 { + return 0, false + } + return startAt + best, true + } +} + +func stringFixedDistanceCharFilter(ch rune, distance, minRequiredLength int) StringPrefixFilter { + if distance < 0 { + return nil + } + + return func(input string, startAt int) (candidateByteIndex int, ok bool) { + if !hasMinRequiredBytes(input, startAt, minRequiredLength) { + return 0, false + } + + searchAt := startAt + for { + offset := strings.IndexRune(input[searchAt:], ch) + if offset < 0 { + return 0, false + } + byteIndex := searchAt + offset + candidateByteIndex, ok := stringFixedDistanceCandidateStart(input, startAt, byteIndex, distance) + if ok && hasMinRequiredBytes(input, candidateByteIndex, minRequiredLength) { + return candidateByteIndex, true + } + if ok { + return 0, false + } + _, size := utf8.DecodeRuneInString(input[byteIndex:]) + if size == 0 { + return 0, false + } + searchAt = byteIndex + size + } + } +} + +func stringFixedDistanceStringFilter(literal string, distance, minRequiredLength int) StringPrefixFilter { + if literal == "" || distance < 0 || len(literal) > maxStringFilterLiteralLen { + return nil + } + + return func(input string, startAt int) (candidateByteIndex int, ok bool) { + if !hasMinRequiredBytes(input, startAt, minRequiredLength) { + return 0, false + } + + searchAt := startAt + for searchAt <= len(input)-len(literal) { + offset := strings.Index(input[searchAt:], literal) + if offset < 0 { + return 0, false + } + literalIndex := searchAt + offset + candidateByteIndex, ok := stringFixedDistanceCandidateStart(input, startAt, literalIndex, distance) + if ok && hasMinRequiredBytes(input, candidateByteIndex, minRequiredLength) { + return candidateByteIndex, true + } + if ok { + return 0, false + } + searchAt = literalIndex + 1 + } + return 0, false + } +} + +func stringLiteralAfterLoopFilter(literal *syntax.LiteralAfterLoop, minRequiredLength int) StringPrefixFilter { + if literal == nil || literal.LoopNode == nil || literal.LoopNode.Set == nil { + return nil + } + if literal.StringIgnoreCase && (literal.String == "" || !isASCIIString(literal.String)) { + return nil + } + + return func(input string, startAt int) (candidateByteIndex int, ok bool) { + if !hasMinRequiredBytes(input, startAt, minRequiredLength) { + return 0, false + } + if !stringHasLiteralAfterLoop(input, startAt, literal) { + return 0, false + } + return startAt, true + } +} + +func stringHasLiteralAfterLoop(input string, searchAt int, literal *syntax.LiteralAfterLoop) bool { + switch { + case literal.String != "": + if literal.StringIgnoreCase { + return helpers.IndexStringIgnoreCaseASCII(input[searchAt:], literal.String) >= 0 + } + return strings.Contains(input[searchAt:], literal.String) + case len(literal.Chars) > 0: + needle := string(literal.Chars) + return strings.ContainsAny(input[searchAt:], needle) + default: + return strings.ContainsRune(input[searchAt:], literal.Char) + } +} + +func stringFixedDistanceCandidateStart(input string, startAt, byteIndex, distance int) (int, bool) { + candidateByteIndex := byteIndex + for i := 0; i < distance; i++ { + if candidateByteIndex <= startAt { + return 0, false + } + _, size := utf8.DecodeLastRuneInString(input[:candidateByteIndex]) + if size == 0 { + return 0, false + } + candidateByteIndex -= size + } + return candidateByteIndex, true +} + +func (re *Regexp) findStringPrefixCandidate(input string, startAt int) (candidateByteIndex int, ok bool) { + if re.stringPrefixFilter == nil || re.RightToLeft() { + return startAt, true + } + candidateByteIndex, ok = re.stringPrefixFilter(input, startAt) + if !ok { + return 0, false + } + if candidateByteIndex < startAt || candidateByteIndex > len(input) || !isStringRuneBoundary(input, candidateByteIndex) { + return startAt, true + } + return candidateByteIndex, true +} + +func (re *Regexp) findStringMatchStart(input string, startAt int) (candidateByteIndex int, ok bool, err error) { + if startAt > len(input) { + return 0, false, errStringStartAtTooLarge + } + if startAt >= 0 && !isStringRuneBoundary(input, startAt) { + return 0, false, errStringStartAtNotRuneBoundary + } + + if startAt < 0 { + if re.RightToLeft() { + startAt = len(input) + } else { + startAt = 0 + } + } + + candidateByteIndex, ok = re.findStringPrefixCandidate(input, startAt) + return candidateByteIndex, ok, nil +} + +func hasMinRequiredBytes(input string, startAt, minRequiredLength int) bool { + if startAt < 0 || startAt > len(input) { + return false + } + return minRequiredLength <= 0 || len(input)-startAt >= minRequiredLength +} + +func isStringRuneBoundary(s string, index int) bool { + if index == 0 || index == len(s) { + return true + } + if index < 0 || index > len(s) { + return false + } + for strIdx := range s { + if strIdx == index { + return true + } + if strIdx > index { + return false + } + } + return false +} + +func isASCIIString(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + return true +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/charclass.go b/vendor/github.com/dlclark/regexp2/v2/syntax/charclass.go new file mode 100644 index 000000000..8ddee2d24 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/charclass.go @@ -0,0 +1,1450 @@ +package syntax + +import ( + "bytes" + "encoding/binary" + "fmt" + "slices" + "sort" + "unicode" + "unicode/utf8" +) + +// CharSet combines start-end rune ranges and unicode categories representing a set of characters +type CharSet struct { + ranges []SingleRange + categories []Category + sub *CharSet //optional subtractor + negate bool + anything bool + + ascii *asciiBitmap +} + +type asciiBitmap struct { + bits [2]uint64 +} + +type Category struct { + Negate bool + Cat string +} + +type SingleRange struct { + First rune + Last rune +} + +const ( + SpaceCategoryText = " " + WordCategoryText = "W" +) + +var ( + ecmaSpace = []rune{0x0009, 0x000e, 0x0020, 0x0021, 0x00a0, 0x00a1, 0x1680, 0x1681, 0x2000, 0x200b, 0x2028, 0x202a, 0x202f, 0x2030, 0x205f, 0x2060, 0x3000, 0x3001, 0xfeff, 0xff00} + ecmaWord = []rune{0x0030, 0x003a, 0x0041, 0x005b, 0x005f, 0x0060, 0x0061, 0x007b} + ecmaDigit = []rune{0x0030, 0x003a} + + re2Space = []rune{0x0009, 0x000b, 0x000c, 0x000e, 0x0020, 0x0021} +) + +var ( + AnyClass = getCharSetFromOldString([]rune{0}, false) + ECMAAnyClass = getCharSetFromOldString([]rune{0, 0x000a, 0x000b, 0x000d, 0x000e}, false) + NoneClass = getCharSetFromOldString(nil, false) + ECMAWordClass = getCharSetFromOldString(ecmaWord, false) + NotECMAWordClass = getCharSetFromOldString(ecmaWord, true) + ECMASpaceClass = getCharSetFromOldString(ecmaSpace, false) + NotECMASpaceClass = getCharSetFromOldString(ecmaSpace, true) + ECMADigitClass = getCharSetFromOldString(ecmaDigit, false) + NotECMADigitClass = getCharSetFromOldString(ecmaDigit, true) + + WordClass = getCharSetFromCategoryString(false, false, WordCategoryText) + NotWordClass = getCharSetFromCategoryString(true, false, WordCategoryText) + SpaceClass = getCharSetFromCategoryString(false, false, SpaceCategoryText) + NotSpaceClass = getCharSetFromCategoryString(true, false, SpaceCategoryText) + DigitClass = getCharSetFromCategoryString(false, false, "Nd") + NotDigitClass = getCharSetFromCategoryString(false, true, "Nd") + + RE2SpaceClass = getCharSetFromOldString(re2Space, false) + NotRE2SpaceClass = getCharSetFromOldString(re2Space, true) + + NotNewLineClass = getCharSetFromOldString([]rune{0x0a, 0x0b}, true) +) + +var unicodeCategories = func() map[string]*unicode.RangeTable { + retVal := make(map[string]*unicode.RangeTable) + for k, v := range unicode.Scripts { + retVal[k] = v + } + for k, v := range unicode.Categories { + retVal[k] = v + } + // aliases are just pointers to the original keys + for k, v := range unicode.CategoryAliases { + retVal[k] = unicode.Categories[v] + } + for k, v := range unicode.Properties { + retVal[k] = v + } + return retVal +}() + +func getCharSetFromCategoryString(negateSet bool, negateCat bool, cats ...string) func() *CharSet { + if negateCat && negateSet { + panic("BUG! You should only negate the set OR the category in a constant setup, but not both") + } + + c := CharSet{negate: negateSet} + + c.categories = make([]Category, len(cats)) + for i, cat := range cats { + c.categories[i] = Category{Cat: cat, Negate: negateCat} + } + return func() *CharSet { + //make a copy each time + local := c + //return that address + return &local + } +} + +func getCharSetFromOldString(setText []rune, negate bool) func() *CharSet { + c := CharSet{} + if len(setText) > 0 { + fillFirst := false + l := len(setText) + if negate { + if setText[0] == 0 { + setText = setText[1:] + } else { + l++ + fillFirst = true + } + } + + if l%2 == 0 { + c.ranges = make([]SingleRange, l/2) + } else { + c.ranges = make([]SingleRange, l/2+1) + } + + first := true + if fillFirst { + c.ranges[0] = SingleRange{First: 0} + first = false + } + + i := 0 + for _, r := range setText { + if first { + // lower bound in a new range + c.ranges[i] = SingleRange{First: r} + first = false + } else { + c.ranges[i].Last = r - 1 + i++ + first = true + } + } + if !first { + c.ranges[i].Last = utf8.MaxRune + } + if len(c.ranges) == 1 && c.ranges[0].First == 0 && c.ranges[0].Last >= unicode.MaxRune { + // this is anything...or nothing + c.anything = !negate + } + } + + return func() *CharSet { + local := c + return &local + } +} + +// Copy makes a deep copy to prevent accidental mutation of a set +func (c CharSet) Copy() CharSet { + ret := CharSet{ + anything: c.anything, + negate: c.negate, + } + + ret.ranges = append(ret.ranges, c.ranges...) + ret.categories = append(ret.categories, c.categories...) + + if c.sub != nil { + sub := c.sub.Copy() + ret.sub = &sub + } + + return ret +} + +// gets a human-readable description for a set string +func (c CharSet) String() string { + buf := &bytes.Buffer{} + buf.WriteRune('[') + + if c.IsNegated() { + buf.WriteRune('^') + } + + for _, r := range c.ranges { + + buf.WriteString(CharDescription(r.First)) + if r.First != r.Last { + if r.Last-r.First != 1 { + //groups that are 1 char apart skip the dash + buf.WriteRune('-') + } + buf.WriteString(CharDescription(r.Last)) + } + } + + for _, c := range c.categories { + buf.WriteString(c.String()) + } + + if c.sub != nil { + buf.WriteRune('-') + buf.WriteString(c.sub.String()) + } + + buf.WriteRune(']') + + return buf.String() +} + +func b2i(b bool) byte { + if b { + return 1 + } + return 0 +} + +// mapHashFill converts a charset into a buffer for use in maps +func (c CharSet) mapHashFill(buf *bytes.Buffer) { + buf.WriteByte(b2i(c.negate) + b2i(c.anything)*2) + + _ = binary.Write(buf, binary.LittleEndian, int32(len(c.ranges))) + _ = binary.Write(buf, binary.LittleEndian, int32(len(c.categories))) + for _, r := range c.ranges { + buf.WriteRune(r.First) + buf.WriteRune(r.Last) + } + for _, ct := range c.categories { + // write the length of the cat and indicate if it's negated + if ct.Negate { + _ = binary.Write(buf, binary.LittleEndian, int8(-1*len(ct.Cat))) + } else { + _ = binary.Write(buf, binary.LittleEndian, int8(len(ct.Cat))) + } + buf.WriteString(ct.Cat) + } + + if c.sub != nil { + c.sub.mapHashFill(buf) + } +} + +func NewCharSetRuntime(buf string) CharSet { + retVal := CharSet{} + b := bytes.NewBufferString(buf) + val, _ := b.ReadByte() + //1s bit == negate, 2s bit == anything + retVal.negate = (val&0x1 == 0x1) + retVal.anything = (val&0x2 == 0x2) + var lenRanges, lenCats int32 + _ = binary.Read(b, binary.LittleEndian, &lenRanges) + _ = binary.Read(b, binary.LittleEndian, &lenCats) + + retVal.ranges = make([]SingleRange, lenRanges) + for i := 0; i < int(lenRanges); i++ { + r := SingleRange{} + r.First, _, _ = b.ReadRune() + r.Last, _, _ = b.ReadRune() + retVal.ranges[i] = r + } + + retVal.categories = make([]Category, lenCats) + for i := 0; i < int(lenCats); i++ { + var lenCat int8 + c := Category{} + _ = binary.Read(b, binary.LittleEndian, &lenCat) + if lenCat < 0 { + c.Negate = true + lenCat *= -1 + } + c.Cat = string(b.Next(int(lenCat))) + retVal.categories[i] = c + } + + //sub + if b.Len() > 0 { + sub := NewCharSetRuntime(b.String()) + retVal.sub = &sub + } + + return retVal +} + +// CharIn returns true if the rune is in our character set (either ranges or categories). +// It handles negations and subtracted sub-charsets. +func (c CharSet) CharIn(ch rune) bool { + if ch >= 0 && ch < 128 && c.ascii != nil { + return (c.ascii.bits[ch/64] & (1 << (uint(ch) % 64))) != 0 + } + return c.charInSlow(ch) +} + +func (c CharSet) charInSlow(ch rune) bool { + val := false + // in s && !s.subtracted + + //check ranges -- binary search for sets with many ranges, linear for small sets + n := len(c.ranges) + if n > 0 { + if n <= 4 { + for _, r := range c.ranges { + if ch < r.First { + break + } + if ch <= r.Last { + val = true + break + } + } + } else { + lo, hi := 0, n + for lo < hi { + mid := int(uint(lo+hi) >> 1) + if c.ranges[mid].First <= ch { + lo = mid + 1 + } else { + hi = mid + } + } + if lo > 0 && ch <= c.ranges[lo-1].Last { + val = true + } + } + } + + //check categories if we haven't already found a range + if !val && len(c.categories) > 0 { + val = c.charInCategories(ch) + } + + // negate the whole char set + if c.negate { + val = !val + } + + // get subtracted recurse + if val && c.sub != nil { + val = !c.sub.CharIn(ch) + } + + //log.Printf("Char '%v' in %v == %v", string(ch), c.String(), val) + return val +} + +func (c *CharSet) prepareASCIIBitmap() { + if c == nil || c.ascii != nil { + return + } + if c.sub != nil { + c.sub.prepareASCIIBitmap() + } + bm := &asciiBitmap{} + for i := range rune(128) { + if c.charInSlow(i) { + bm.bits[i/64] |= 1 << (uint(i) % 64) + } + } + c.ascii = bm +} + +func (c *CharSet) charInCategories(ch rune) bool { + for _, ct := range c.categories { + // special categories...then unicode + if ct.Cat == SpaceCategoryText { + if unicode.IsSpace(ch) { + // we found a space so we're done + // negate means this is a "bad" thing + return !ct.Negate + } else if ct.Negate { + return true + } + } else if ct.Cat == WordCategoryText { + if IsWordChar(ch) { + return !ct.Negate + } else if ct.Negate { + return true + } + } else if unicode.Is(unicodeCategories[ct.Cat], ch) { + // if we're in this unicode category then we're done + // if negate=true on this category then we "failed" our test + // otherwise we're good that we found it + return !ct.Negate + } else if ct.Negate { + return true + } + } + return false +} + +func (c Category) String() string { + switch c.Cat { + case SpaceCategoryText: + if c.Negate { + return "\\S" + } + return "\\s" + case WordCategoryText: + if c.Negate { + return "\\W" + } + return "\\w" + } + if _, ok := unicodeCategories[c.Cat]; ok { + + if c.Negate { + return "\\P{" + c.Cat + "}" + } + return "\\p{" + c.Cat + "}" + } + return "Unknown category: " + c.Cat +} + +// CharDescription Produces a human-readable description for a single character. +func CharDescription(ch rune) string { + /*if ch == '\\' { + return "\\\\" + } + + if ch > ' ' && ch <= '~' { + return string(ch) + } else if ch == '\n' { + return "\\n" + } else if ch == ' ' { + return "\\ " + }*/ + + b := &bytes.Buffer{} + escape(b, ch, false) //fmt.Sprintf("%U", ch) + return b.String() +} + +// According to UTS#18 Unicode Regular Expressions (http://www.unicode.org/reports/tr18/) +// RL 1.4 Simple Word Boundaries The class of includes all Alphabetic +// values from the Unicode character database, from UnicodeData.txt [UData], plus the U+200C +// ZERO WIDTH NON-JOINER and U+200D ZERO WIDTH JOINER. +func IsWordChar(r rune) bool { + //"L", "Mn", "Nd", "Pc" + return unicode.In(r, + unicode.Categories["L"], unicode.Categories["Mn"], + unicode.Categories["Nd"], unicode.Categories["Pc"]) || r == '\u200D' || r == '\u200C' + //return 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' || '0' <= r && r <= '9' || r == '_' +} + +func IsECMAWordChar(r rune) bool { + return unicode.In(r, + unicode.Categories["L"], unicode.Categories["Mn"], + unicode.Categories["Nd"], unicode.Categories["Pc"]) + + //return 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' || '0' <= r && r <= '9' || r == '_' +} + +func IsECMAIdentifierStartChar(r rune) bool { + return r == '$' || r == '_' || unicode.In(r, unicode.L, unicode.Nl, unicode.Other_ID_Start) +} + +func IsECMAIdentifierChar(r rune) bool { + return IsECMAIdentifierStartChar(r) || r == '\u200C' || r == '\u200D' || + unicode.In(r, unicode.Mn, unicode.Mc, unicode.Nd, unicode.Pc, unicode.Other_ID_Continue) +} + +// SingletonChar will return the char from the first range without validation. +// It assumes you have checked for IsSingleton or IsSingletonInverse and will panic given bad input +func (c CharSet) SingletonChar() rune { + return c.ranges[0].First +} + +func (c CharSet) IsSingleton() bool { + return !c.negate && //negated is multiple chars + len(c.categories) == 0 && len(c.ranges) == 1 && // multiple ranges and unicode classes represent multiple chars + c.sub == nil && // subtraction means we've got multiple chars + c.ranges[0].First == c.ranges[0].Last // first and last equal means we're just 1 char +} + +func (c CharSet) IsSingletonInverse() bool { + return c.negate && //same as above, but requires negated + len(c.categories) == 0 && len(c.ranges) == 1 && // multiple ranges and unicode classes represent multiple chars + c.sub == nil && // subtraction means we've got multiple chars + c.ranges[0].First == c.ranges[0].Last // first and last equal means we're just 1 char +} + +func (c CharSet) IsMergeable() bool { + return !c.IsNegated() && !c.HasSubtraction() +} + +func (c CharSet) IsNegated() bool { + return c.negate +} + +func (c CharSet) HasSubtraction() bool { + return c.sub != nil +} + +func (c CharSet) IsEmpty() bool { + return len(c.ranges) == 0 && len(c.categories) == 0 && c.sub == nil +} + +func (c CharSet) IsAnything() bool { + return c.anything +} + +func (c *CharSet) addDigit(ecma, negate bool) { + if ecma { + if negate { + c.addRanges(NotECMADigitClass().ranges) + } else { + c.addRanges(ECMADigitClass().ranges) + } + } else { + c.addCategories(Category{Cat: "Nd", Negate: negate}) + } +} + +func (c *CharSet) addChar(ch rune) { + c.addRange(ch, ch) +} + +func (c *CharSet) addSpace(ecma, re2, negate bool) { + if ecma { + if negate { + c.addRanges(NotECMASpaceClass().ranges) + } else { + c.addRanges(ECMASpaceClass().ranges) + } + } else if re2 { + if negate { + c.addRanges(NotRE2SpaceClass().ranges) + } else { + c.addRanges(RE2SpaceClass().ranges) + } + } else { + c.addCategories(Category{Cat: SpaceCategoryText, Negate: negate}) + } +} + +func (c *CharSet) addWord(ecma, negate bool) { + if ecma { + if negate { + c.addRanges(NotECMAWordClass().ranges) + } else { + c.addRanges(ECMAWordClass().ranges) + } + } else { + c.addCategories(Category{Cat: WordCategoryText, Negate: negate}) + } +} + +// Add set ranges and categories into ours -- no deduping or anything +func (c *CharSet) addSet(set CharSet) { + if c.anything { + return + } + if set.anything { + c.makeAnything() + return + } + // just append here to prevent double-canon + c.ranges = append(c.ranges, set.ranges...) + c.addCategories(set.categories...) + c.canonicalize() +} + +func (c *CharSet) makeAnything() { + c.anything = true + c.categories = []Category{} + c.ranges = []SingleRange{{First: 0, Last: unicode.MaxRune}} +} + +func (c *CharSet) addCategories(cats ...Category) { + // don't add dupes and remove positive+negative + if c.anything { + // if we've had a previous positive+negative group then + // just return, we're as broad as we can get + return + } + + for _, ct := range cats { + found := false + for _, ct2 := range c.categories { + if ct.Cat == ct2.Cat { + if ct.Negate != ct2.Negate { + // oposite negations...this mean we just + // take us as anything and move on + c.makeAnything() + return + } + found = true + break + } + } + + if !found { + c.categories = append(c.categories, ct) + } + } +} + +// Merges new ranges to our own +func (c *CharSet) addRanges(ranges []SingleRange) { + if c.anything { + return + } + c.ranges = append(c.ranges, ranges...) + c.canonicalize() +} + +// Merges everything but the new ranges into our own +func (c *CharSet) addNegativeRanges(ranges []SingleRange) { + if c.anything { + return + } + + var hi rune + + // convert incoming ranges into opposites, assume they are in order + for _, r := range ranges { + if hi < r.First { + c.ranges = append(c.ranges, SingleRange{hi, r.First - 1}) + } + hi = r.Last + 1 + } + + if hi < utf8.MaxRune { + c.ranges = append(c.ranges, SingleRange{hi, utf8.MaxRune}) + } + + c.canonicalize() +} + +func isValidUnicodeCat(catName string) bool { + _, ok := unicodeCategories[catName] + return ok +} + +func (c *CharSet) addCategory(categoryName string, negate, caseInsensitive bool) { + if !isValidUnicodeCat(categoryName) { + // unknown unicode category, script, or property "blah" + panic(fmt.Errorf("unknown unicode category, script, or property '%v'", categoryName)) + + } + + if caseInsensitive && (categoryName == "Ll" || categoryName == "Lu" || categoryName == "Lt") { + // when RegexOptions.IgnoreCase is specified then {Ll} {Lu} and {Lt} cases should all match + c.addCategories( + Category{Cat: "Ll", Negate: negate}, + Category{Cat: "Lu", Negate: negate}, + Category{Cat: "Lt", Negate: negate}) + } + c.addCategories(Category{Cat: categoryName, Negate: negate}) +} + +// Adds to the class any case-equivalence versions of characters already +// in the class. Used for case-insensitivity. +func (c *CharSet) addCaseEquivalences() { + // we already have all case equiv + if c.anything { + return + } + for i := 0; i < len(c.ranges); i++ { + r := c.ranges[i] + if r.First == r.Last { + equiv := tryFindCaseEquivalences(r.First) + for _, eq := range equiv { + c.addChar(eq) + } + } else { + c.addCaseEquivalenceRange(r.First, r.Last) + } + } +} + +// For a single range that's in the set, adds any additional ranges +// necessary to ensure that lowercase equivalents are also included. +func (c *CharSet) addCaseEquivalenceRange(chMin, chMax rune) { + for i := chMin; i <= chMax; i++ { + equiv := tryFindCaseEquivalences(i) + for _, eq := range equiv { + c.addChar(eq) + } + } +} + +// Performs a fast lookup which determines if a character is involved in case conversion, as well as +// returns the OTHER characters that should be considered equivalent in case it does participate in case conversion. +func tryFindCaseEquivalences(ch rune) []rune { + newCh := unicode.SimpleFold(ch) + if newCh == ch { + // no case support + return nil + } + equiv := []rune{newCh} + for { + newCh = unicode.SimpleFold(newCh) + if newCh == ch { + return equiv + } + equiv = append(equiv, newCh) + } +} + +func (c *CharSet) addSubtraction(sub *CharSet) { + c.sub = sub +} + +func (c *CharSet) addRange(chMin, chMax rune) { + c.ranges = append(c.ranges, SingleRange{First: chMin, Last: chMax}) + c.canonicalize() +} + +func (c *CharSet) addNamedASCII(name string, negate bool) bool { + var rs []SingleRange + + switch name { + case "alnum": + rs = []SingleRange{{'0', '9'}, {'A', 'Z'}, {'a', 'z'}} + case "alpha": + rs = []SingleRange{{'A', 'Z'}, {'a', 'z'}} + case "ascii": + rs = []SingleRange{{0, 0x7f}} + case "blank": + rs = []SingleRange{{'\t', '\t'}, {' ', ' '}} + case "cntrl": + rs = []SingleRange{{0, 0x1f}, {0x7f, 0x7f}} + case "digit": + c.addDigit(false, negate) + case "graph": + rs = []SingleRange{{'!', '~'}} + case "lower": + rs = []SingleRange{{'a', 'z'}} + case "print": + rs = []SingleRange{{' ', '~'}} + case "punct": //[!-/:-@[-`{-~] + rs = []SingleRange{{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}} + case "space": + c.addSpace(true, false, negate) + case "upper": + rs = []SingleRange{{'A', 'Z'}} + case "word": + c.addWord(true, negate) + case "xdigit": + rs = []SingleRange{{'0', '9'}, {'A', 'F'}, {'a', 'f'}} + default: + return false + } + + if len(rs) > 0 { + if negate { + c.addNegativeRanges(rs) + } else { + c.addRanges(rs) + } + } + + return true +} + +type singleRangeSorter []SingleRange + +func (p singleRangeSorter) Len() int { return len(p) } +func (p singleRangeSorter) Less(i, j int) bool { return p[i].First < p[j].First } +func (p singleRangeSorter) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +// Logic to reduce a character class to a unique, sorted form. +func (c *CharSet) canonicalize() { + var i, j int + var last rune + + if len(c.ranges) == 0 { + return + } + + // + // Find and eliminate overlapping or abutting ranges + // + + if len(c.ranges) > 1 { + sort.Sort(singleRangeSorter(c.ranges)) + + done := false + + for i, j = 1, 0; ; i++ { + for last = c.ranges[j].Last; ; i++ { + if i == len(c.ranges) || last >= unicode.MaxRune { + done = true + break + } + + CurrentRange := c.ranges[i] + if CurrentRange.First > last+1 { + break + } + + if last < CurrentRange.Last { + last = CurrentRange.Last + } + } + + c.ranges[j] = SingleRange{First: c.ranges[j].First, Last: last} + + j++ + + if done { + break + } + + if j < i { + c.ranges[j] = c.ranges[i] + } + } + + c.ranges = append(c.ranges[:j], c.ranges[len(c.ranges):]...) + } + + // If the class now represents a single negated range, but does so by including every + // other character, invert it to produce a normalized form with a single range. This + // is valuable for subsequent optimizations in most of the engines. + if !c.negate && c.sub == nil && len(c.categories) == 0 { + if len(c.ranges) == 2 { + // There are two ranges in the list. See if there's one missing range between them. + // Such a range might be as small as a single character. + if c.ranges[0].First == 0 && + c.ranges[1].Last >= unicode.MaxRune && + c.ranges[0].Last < c.ranges[1].First-1 { + c.ranges = []SingleRange{{c.ranges[0].Last + 1, c.ranges[1].First - 1}} + c.negate = true + } + } else if len(c.ranges) == 1 { + switch c.ranges[0].First { + case 0: + // There's only one range in the list. Does it include everything but the last char? + if c.ranges[0].Last == unicode.MaxRune-1 { + c.ranges[0] = SingleRange{unicode.MaxRune, unicode.MaxRune} + c.negate = true + } + case 1: + // Or everything but the first char? + if c.ranges[0].Last >= unicode.MaxRune { + c.ranges[0] = SingleRange{'\x00', '\x00'} + c.negate = true + } + } + } + } + + // If the class now has a range that includes everything, and if it doesn't have subtraction, + // we can remove all of its categories, as they're duplicative (the set already includes everything). + if !c.negate && + c.sub == nil && + len(c.ranges) == 1 && c.ranges[0].First == 0 && c.ranges[0].Last >= unicode.MaxRune { + + c.makeAnything() + } + + // If there's only a single character omitted from ranges, if there's no subtractor, and if there are categories, + // see if that character is in the categories. If it is, then we can replace whole thing with a complete "any" range. + // If it's not, then we can remove the categories, as they're only duplicating the rest of the range, turning the set + // into a "not one". This primarily helps in the case of a synthesized set from analysis that ends up combining '.' with + // categories, as we want to reduce that set down to either [^\n] or [\0-\uFFFF]. (This can be extrapolated to any number + // of missing characters; in fact, categories in general are superfluous and the entire set can be represented as ranges. + // But categories serve as a space optimization, and we strike a balance between testing many characters and the time/complexity + // it takes to do so. Thus, we limit this to the common case of a single missing character.) + if !c.negate && c.sub == nil && len(c.categories) > 0 && + len(c.ranges) == 2 && c.ranges[0].First == 0 && c.ranges[0].Last+2 == c.ranges[1].First && c.ranges[1].Last == unicode.MaxRune { + + if c.charInCategories(c.ranges[0].Last + 1) { + //c.ranges = []SingleRange{{'\x00', unicode.MaxRune}} + c.makeAnything() + } else { + c.negate = true + c.ranges = []SingleRange{{c.ranges[0].Last + 1, c.ranges[0].Last + 1}} + c.categories = []Category{} + } + } +} + +// Adds to the class any lowercase versions of characters already +// in the class. Used for case-insensitivity. +func (c *CharSet) addLowercase() { + if c.anything { + return + } + toAdd := []SingleRange{} + for i := 0; i < len(c.ranges); i++ { + r := c.ranges[i] + if r.First == r.Last { + lower := unicode.ToLower(r.First) + c.ranges[i] = SingleRange{First: lower, Last: lower} + } else { + toAdd = append(toAdd, r) + } + } + + for _, r := range toAdd { + c.addLowercaseRange(r.First, r.Last) + } + c.canonicalize() +} + +/************************************************************************** + Let U be the set of Unicode character values and let L be the lowercase + function, mapping from U to U. To perform case insensitive matching of + character sets, we need to be able to map an interval I in U, say + + I = [chMin, chMax] = { ch : chMin <= ch <= chMax } + + to a set A such that A contains L(I) and A is contained in the union of + I and L(I). + + The table below partitions U into intervals on which L is non-decreasing. + Thus, for any interval J = [a, b] contained in one of these intervals, + L(J) is contained in [L(a), L(b)]. + + It is also true that for any such J, [L(a), L(b)] is contained in the + union of J and L(J). This does not follow from L being non-decreasing on + these intervals. It follows from the nature of the L on each interval. + On each interval, L has one of the following forms: + + (1) L(ch) = constant (LowercaseSet) + (2) L(ch) = ch + offset (LowercaseAdd) + (3) L(ch) = ch | 1 (LowercaseBor) + (4) L(ch) = ch + (ch & 1) (LowercaseBad) + + It is easy to verify that for any of these forms [L(a), L(b)] is + contained in the union of [a, b] and L([a, b]). +***************************************************************************/ + +const ( + LowercaseSet = 0 // Set to arg. + LowercaseAdd = 1 // Add arg. + LowercaseBor = 2 // Bitwise or with 1. + LowercaseBad = 3 // Bitwise and with 1 and add original. +) + +type lcMap struct { + chMin, chMax rune + op, data int32 +} + +var lcTable = []lcMap{ + {'\u0041', '\u005A', LowercaseAdd, 32}, + {'\u00C0', '\u00DE', LowercaseAdd, 32}, + {'\u0100', '\u012E', LowercaseBor, 0}, + {'\u0130', '\u0130', LowercaseSet, 0x0069}, + {'\u0132', '\u0136', LowercaseBor, 0}, + {'\u0139', '\u0147', LowercaseBad, 0}, + {'\u014A', '\u0176', LowercaseBor, 0}, + {'\u0178', '\u0178', LowercaseSet, 0x00FF}, + {'\u0179', '\u017D', LowercaseBad, 0}, + {'\u0181', '\u0181', LowercaseSet, 0x0253}, + {'\u0182', '\u0184', LowercaseBor, 0}, + {'\u0186', '\u0186', LowercaseSet, 0x0254}, + {'\u0187', '\u0187', LowercaseSet, 0x0188}, + {'\u0189', '\u018A', LowercaseAdd, 205}, + {'\u018B', '\u018B', LowercaseSet, 0x018C}, + {'\u018E', '\u018E', LowercaseSet, 0x01DD}, + {'\u018F', '\u018F', LowercaseSet, 0x0259}, + {'\u0190', '\u0190', LowercaseSet, 0x025B}, + {'\u0191', '\u0191', LowercaseSet, 0x0192}, + {'\u0193', '\u0193', LowercaseSet, 0x0260}, + {'\u0194', '\u0194', LowercaseSet, 0x0263}, + {'\u0196', '\u0196', LowercaseSet, 0x0269}, + {'\u0197', '\u0197', LowercaseSet, 0x0268}, + {'\u0198', '\u0198', LowercaseSet, 0x0199}, + {'\u019C', '\u019C', LowercaseSet, 0x026F}, + {'\u019D', '\u019D', LowercaseSet, 0x0272}, + {'\u019F', '\u019F', LowercaseSet, 0x0275}, + {'\u01A0', '\u01A4', LowercaseBor, 0}, + {'\u01A7', '\u01A7', LowercaseSet, 0x01A8}, + {'\u01A9', '\u01A9', LowercaseSet, 0x0283}, + {'\u01AC', '\u01AC', LowercaseSet, 0x01AD}, + {'\u01AE', '\u01AE', LowercaseSet, 0x0288}, + {'\u01AF', '\u01AF', LowercaseSet, 0x01B0}, + {'\u01B1', '\u01B2', LowercaseAdd, 217}, + {'\u01B3', '\u01B5', LowercaseBad, 0}, + {'\u01B7', '\u01B7', LowercaseSet, 0x0292}, + {'\u01B8', '\u01B8', LowercaseSet, 0x01B9}, + {'\u01BC', '\u01BC', LowercaseSet, 0x01BD}, + {'\u01C4', '\u01C5', LowercaseSet, 0x01C6}, + {'\u01C7', '\u01C8', LowercaseSet, 0x01C9}, + {'\u01CA', '\u01CB', LowercaseSet, 0x01CC}, + {'\u01CD', '\u01DB', LowercaseBad, 0}, + {'\u01DE', '\u01EE', LowercaseBor, 0}, + {'\u01F1', '\u01F2', LowercaseSet, 0x01F3}, + {'\u01F4', '\u01F4', LowercaseSet, 0x01F5}, + {'\u01FA', '\u0216', LowercaseBor, 0}, + {'\u0386', '\u0386', LowercaseSet, 0x03AC}, + {'\u0388', '\u038A', LowercaseAdd, 37}, + {'\u038C', '\u038C', LowercaseSet, 0x03CC}, + {'\u038E', '\u038F', LowercaseAdd, 63}, + {'\u0391', '\u03AB', LowercaseAdd, 32}, + {'\u03E2', '\u03EE', LowercaseBor, 0}, + {'\u0401', '\u040F', LowercaseAdd, 80}, + {'\u0410', '\u042F', LowercaseAdd, 32}, + {'\u0460', '\u0480', LowercaseBor, 0}, + {'\u0490', '\u04BE', LowercaseBor, 0}, + {'\u04C1', '\u04C3', LowercaseBad, 0}, + {'\u04C7', '\u04C7', LowercaseSet, 0x04C8}, + {'\u04CB', '\u04CB', LowercaseSet, 0x04CC}, + {'\u04D0', '\u04EA', LowercaseBor, 0}, + {'\u04EE', '\u04F4', LowercaseBor, 0}, + {'\u04F8', '\u04F8', LowercaseSet, 0x04F9}, + {'\u0531', '\u0556', LowercaseAdd, 48}, + {'\u10A0', '\u10C5', LowercaseAdd, 48}, + {'\u1E00', '\u1EF8', LowercaseBor, 0}, + {'\u1F08', '\u1F0F', LowercaseAdd, -8}, + {'\u1F18', '\u1F1F', LowercaseAdd, -8}, + {'\u1F28', '\u1F2F', LowercaseAdd, -8}, + {'\u1F38', '\u1F3F', LowercaseAdd, -8}, + {'\u1F48', '\u1F4D', LowercaseAdd, -8}, + {'\u1F59', '\u1F59', LowercaseSet, 0x1F51}, + {'\u1F5B', '\u1F5B', LowercaseSet, 0x1F53}, + {'\u1F5D', '\u1F5D', LowercaseSet, 0x1F55}, + {'\u1F5F', '\u1F5F', LowercaseSet, 0x1F57}, + {'\u1F68', '\u1F6F', LowercaseAdd, -8}, + {'\u1F88', '\u1F8F', LowercaseAdd, -8}, + {'\u1F98', '\u1F9F', LowercaseAdd, -8}, + {'\u1FA8', '\u1FAF', LowercaseAdd, -8}, + {'\u1FB8', '\u1FB9', LowercaseAdd, -8}, + {'\u1FBA', '\u1FBB', LowercaseAdd, -74}, + {'\u1FBC', '\u1FBC', LowercaseSet, 0x1FB3}, + {'\u1FC8', '\u1FCB', LowercaseAdd, -86}, + {'\u1FCC', '\u1FCC', LowercaseSet, 0x1FC3}, + {'\u1FD8', '\u1FD9', LowercaseAdd, -8}, + {'\u1FDA', '\u1FDB', LowercaseAdd, -100}, + {'\u1FE8', '\u1FE9', LowercaseAdd, -8}, + {'\u1FEA', '\u1FEB', LowercaseAdd, -112}, + {'\u1FEC', '\u1FEC', LowercaseSet, 0x1FE5}, + {'\u1FF8', '\u1FF9', LowercaseAdd, -128}, + {'\u1FFA', '\u1FFB', LowercaseAdd, -126}, + {'\u1FFC', '\u1FFC', LowercaseSet, 0x1FF3}, + {'\u2160', '\u216F', LowercaseAdd, 16}, + {'\u24B6', '\u24D0', LowercaseAdd, 26}, + {'\uFF21', '\uFF3A', LowercaseAdd, 32}, +} + +func (c *CharSet) addLowercaseRange(chMin, chMax rune) { + var i, iMax, iMid int + var chMinT, chMaxT rune + var lc lcMap + + for i, iMax = 0, len(lcTable); i < iMax; { + iMid = (i + iMax) / 2 + if lcTable[iMid].chMax < chMin { + i = iMid + 1 + } else { + iMax = iMid + } + } + + for ; i < len(lcTable); i++ { + lc = lcTable[i] + if lc.chMin > chMax { + return + } + chMinT = lc.chMin + if chMinT < chMin { + chMinT = chMin + } + + chMaxT = lc.chMax + if chMaxT > chMax { + chMaxT = chMax + } + + switch lc.op { + case LowercaseSet: + chMinT = rune(lc.data) + chMaxT = rune(lc.data) + case LowercaseAdd: + chMinT += lc.data + chMaxT += lc.data + case LowercaseBor: + chMinT |= 1 + chMaxT |= 1 + case LowercaseBad: + chMinT += (chMinT & 1) + chMaxT += (chMaxT & 1) + } + + if chMinT < chMin || chMaxT > chMax { + c.addRange(chMinT, chMaxT) + } + } +} + +// Determines whether two sets could overlap. +func (set1 *CharSet) MayOverlap(set2 *CharSet) bool { + // If the sets are identical, there's obviously overlap. + if set1.Equals(set2) { + return true + } + + // If either set is all-inclusive, there's overlap by definition (unless + // the other set is empty, but that's so rare it's not worth checking.) + if set1.IsAnything() || set2.IsAnything() { + return true + } + + // If one set is negated and the other one isn't, we're in one of two situations: + // - The remainder of the sets are identical, in which case these are inverses of + // each other, and they don't overlap. + // - The remainder of the sets aren't identical, in which case there's very likely + // overlap, and it's not worth spending more time investigating. + set1Negated := set1.IsNegated() + set2Negated := set2.IsNegated() + if set1Negated != set2Negated { + + return !set1.equals(set2, true) + } + + // If the sets are negated, since they're not equal, there's almost certainly overlap. + if set1Negated { + return true + } + + // Special-case some known, common classes that don't overlap. + if knownDistinctSets(set1, set2) || knownDistinctSets(set2, set1) { + return false + } + + // If set2 can be easily enumerated (e.g. no unicode categories), then enumerate it and + // check if any of its members are in set1. Otherwise, the same for set1. + if !set2.HasSubtraction() && len(set2.categories) == 0 { + return mayOverlapByEnumeration(set1, set2) + } else if !set1.HasSubtraction() && len(set1.categories) == 0 { + return mayOverlapByEnumeration(set2, set1) + } + + // Assume that everything else might overlap. In the future if it proved impactful, we could be more accurate here, + // at the exense of more computation time. + return true +} + +func knownDistinctSets(set1, set2 *CharSet) bool { + + return (set1.Equals(SpaceClass()) || set1.Equals(ECMASpaceClass())) && + (set2.Equals(DigitClass()) || set2.Equals(WordClass()) || + set2.Equals(ECMADigitClass()) || set2.Equals(ECMAWordClass())) + +} + +func mayOverlapByEnumeration(set1, set2 *CharSet) bool { + for i := 0; i < len(set2.ranges); i++ { + for c := set2.ranges[i].First; c <= set2.ranges[i].Last; c++ { + if set1.CharIn(c) { + return true + } + } + } + + return false +} + +// Gets all of the characters in the specified set, storing them into the provided span. +// +// Only considers character classes that only contain sets (no categories), +// just simple sets containing starting/ending pairs (subtraction from those pairs +// is factored in, however).The returned characters may be negated: if IsNegated(set) +// is false, then the returned characters are the only ones that match; if it returns +// true, then the returned characters are the only ones that don't match. +func (c *CharSet) GetSetChars(maxChars int) []rune { + // don't support categories, just ranges + if len(c.categories) > 0 || len(c.ranges) > maxChars { + return nil + } + + // Negation with subtraction is too cumbersome to reason about efficiently. + if c.IsNegated() && c.HasSubtraction() { + return nil + } + + chars := make([]rune, 0, maxChars) + curWork := 0 + // Iterate through the pairs of ranges, storing each value in each range + // into the supplied span. If they all won't fit, we give up and return 0. + // Otherwise we return the number found. Note that we don't bother to handle + // the corner case where the last range's upper bound is LastChar (\uFFFF), + // based on it a) complicating things, and b) it being really unlikely to + // be part of a small set. + for _, r := range c.ranges { + // loop through each char in the range + for ch := r.First; ch <= r.Last; ch++ { + + // Keep track of how many characters we've checked. This could work + // just comparing count rather than evaluated, but we also want to + // limit how much work is done here, which we can do by constraining + // the number of checks to the size of the storage provided. + curWork++ + if curWork > maxChars { + return nil + } + + // If the set is all ranges but has a subtracted class, + // validate the char is actually in the set prior to storing it: + // it might be in the subtracted range. + if c.HasSubtraction() && !c.CharIn(ch) { + continue + } + chars = append(chars, ch) + } + } + return chars +} + +func (c *CharSet) Hash() []byte { + b := &bytes.Buffer{} + c.mapHashFill(b) + return b.Bytes() +} + +func (c *CharSet) Equals(c2 *CharSet) bool { + return c.equals(c2, false) +} + +func (c *CharSet) equals(c2 *CharSet, ignoreNegate bool) bool { + if c == nil && c2 == nil { + return true + } + if c == nil && c2 != nil || c2 == nil && c != nil { + return false + } + + if !ignoreNegate { + if c.negate != c2.negate { + return false + } + } + if c.anything != c2.anything { + return false + } + + if !slices.Equal(c.ranges, c2.ranges) { + return false + } + if !slices.Equal(c.categories, c2.categories) { + return false + } + + return c.sub.equals(c2.sub, false) +} + +var whitespaceChars = []rune{'\u0009', '\u000A', '\u000B', '\u000C', '\u000D', + '\u0020', '\u0085', '\u00A0', '\u1680', '\u2000', + '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', + '\u2006', '\u2007', '\u2008', '\u2009', '\u200A', + '\u2028', '\u2029', '\u202F', '\u205F', '\u3000'} + +// Gets whether the specified set is a named set with a reasonably small count +// of Unicode characters. Designed to help the regexp code generator choose a better +// search algo for finding chars +// Description is a short name that can be used as part of a var name in code gen +func (c *CharSet) IsUnicodeCategoryOfSmallCharCount() (isSmall bool, chars []rune, negated bool, desc string) { + // figure out if we're SpaceClass, RE2SpaceClass, ECMASpaceClass or inverse + // "hash" ourselves -- this is actually fully serialized, not just hashed + if c.IsSingleton() { + return true, []rune{c.SingletonChar()}, false, "" + } + if c.IsSingletonInverse() { + return true, []rune{c.SingletonChar()}, true, "" + } + + if c.Equals(SpaceClass()) { + // we're SpaceClass + return true, whitespaceChars, false, "whitespace" + } + if c.Equals(NotSpaceClass()) { + // we're NotSpaceClass + return true, whitespaceChars, true, "whitespace" + } + + return false, nil, false, "" +} + +// Gets whether the set description string is for two ASCII letters that case +// to each other under IgnoreCase rules. +func (c *CharSet) containsAsciiIgnoreCaseCharacter() (bool, []rune) { + if c.IsNegated() { + return false, nil + } + // get up to 3 chars, just to be able to error on both "too many" and "too few" + twoChars := c.GetSetChars(3) + return len(twoChars) == 2 && twoChars[0] < unicode.MaxASCII && twoChars[1] < unicode.MaxASCII && + (twoChars[0]|0x20) == (twoChars[1]|0x20) && + unicode.IsLetter(twoChars[0]) && unicode.IsLetter(twoChars[1]), twoChars +} + +func anyParticipateInCaseConversion(chars []rune) bool { + for _, c := range chars { + if participatesInCaseConversion(c) { + return true + } + } + return false +} + +func participatesInCaseConversion(ch rune) bool { + /* + case UnicodeCategory.ClosePunctuation: + case UnicodeCategory.ConnectorPunctuation: + case UnicodeCategory.Control: + case UnicodeCategory.DashPunctuation: + case UnicodeCategory.DecimalDigitNumber: + case UnicodeCategory.FinalQuotePunctuation: + case UnicodeCategory.InitialQuotePunctuation: + case UnicodeCategory.LineSeparator: + case UnicodeCategory.OpenPunctuation: + case UnicodeCategory.OtherNumber: + case UnicodeCategory.OtherPunctuation: + case UnicodeCategory.ParagraphSeparator: + case UnicodeCategory.SpaceSeparator: + */ + return !unicode.In(ch, unicode.Pe, unicode.Pc, unicode.Cc, unicode.Pd, unicode.Nd, unicode.Pf, + unicode.Pi, unicode.Zl, unicode.Ps, unicode.No, unicode.Po, unicode.Zp, unicode.Zs) +} + +func anyParticipatesInCaseConversion(str string) bool { + for _, c := range str { + if participatesInCaseConversion(c) { + return true + } + } + return false +} + +func isAsciiRunes(in []rune) bool { + for i := 0; i < len(in); i++ { + if in[i] > unicode.MaxASCII { + return false + } + } + return true +} + +func (c CharSet) GetIfNRanges(n int) []SingleRange { + if len(c.categories) > 0 { + return nil + } + if c.sub != nil { + return nil + } + if len(c.ranges) == n { + return c.ranges[:n] + } + return nil +} + +func (c *CharSet) GetIfOnlyUnicodeCategories() (cats []Category, negate bool) { + if c.sub != nil { + return nil, false + } + if len(c.ranges) > 0 { + return nil, false + } + // all cats need to be rationalized to the same negation or not + if len(c.categories) == 0 { + return nil, false + } + + neg := c.categories[0].Negate + for _, cat := range c.categories { + if neg != cat.Negate || cat.Cat == SpaceCategoryText || cat.Cat == WordCategoryText { + // negate some and not others...a problem + // or one of our non-unicode categories + return nil, false + } + } + + // tell the caller to negate if either all the categories + // are negated or the set as a whole is negated, but not + // both + return c.categories, neg != c.negate +} + +type CharClassAnalysisResults struct { + // true if the set contains only ranges; false if it contains Unicode categories and/or subtraction. + OnlyRanges bool + // true if we know for sure that the set contains only ASCII values; otherwise, false. + // This can only be true if OnlyRanges is true. + ContainsOnlyAscii bool + // true if we know for sure that the set doesn't contain any ASCII values; otherwise, false. + // This can only be true if OnlyRanges is true. + ContainsNoAscii bool + // true if we know for sure that all ASCII values are in the set; otherwise, false. + // This can only be true if OnlyRanges is true. + AllAsciiContained bool + // true if we know for sure that all non-ASCII values are in the set; otherwise, false. + // This can only be true if OnlyRanges is true. + AllNonAsciiContained bool + // The inclusive lower bound. + // This is only valid if OnlyRanges is true. + LowerBoundInclusiveIfOnlyRanges rune + // The exclusive upper bound. + // This is only valid if OnlyRanges is true. + UpperBoundExclusiveIfOnlyRanges rune +} + +// Analyzes the set to determine some basic properties that can be used to optimize usage. +func (set *CharSet) Analyze() CharClassAnalysisResults { + // The analysis is performed based entirely on ranges contained within the set. + // Thus, we require that it can be "easily enumerated", meaning it contains only + // ranges (and more specifically those with both the lower inclusive and upper + // exclusive bounds specified). We also permit the set to contain a subtracted + // character class, as for non-negated sets, that can only narrow what's permitted, + // and the analysis can be performed on the overestimate of the set prior to subtraction. + // However, negation is performed before subtraction, which means we can't trust + // the ranges to inform AllNonAsciiContained and AllAsciiContained, as the subtraction + // could create holes in those. As such, while we can permit subtraction for non-negated + // sets, for negated sets, we need to bail. + if (set.IsNegated() && set.HasSubtraction()) || len(set.categories) > 0 || len(set.ranges) == 0 { + // We can't make any strong claims about the set. + return CharClassAnalysisResults{} + } + + firstValueInclusive := set.ranges[0].First + lastValueExclusive := set.ranges[len(set.ranges)-1].Last + 1 + + if set.IsNegated() { + // We're negated: if the upper bound of the range is ASCII, that means everything + // above it is actually included, meaning all non-ASCII are in the class. + // Similarly if the lower bound is non-ASCII, that means in a negated world + // everything ASCII is included. + return CharClassAnalysisResults{ + OnlyRanges: true, + AllNonAsciiContained: lastValueExclusive <= unicode.MaxASCII, + AllAsciiContained: firstValueInclusive >= unicode.MaxASCII, + ContainsNoAscii: firstValueInclusive == 0 && set.ranges[0].Last >= unicode.MaxASCII, + ContainsOnlyAscii: false, + LowerBoundInclusiveIfOnlyRanges: firstValueInclusive, + UpperBoundExclusiveIfOnlyRanges: lastValueExclusive, + } + } + + // If the upper bound is ASCII, that means everything included in the class is ASCII. + // Similarly if the lower bound is non-ASCII, that means no ASCII is in the class. + return CharClassAnalysisResults{ + OnlyRanges: true, + AllNonAsciiContained: false, + AllAsciiContained: firstValueInclusive == 0 && set.ranges[0].Last >= unicode.MaxASCII && !set.HasSubtraction(), + ContainsOnlyAscii: lastValueExclusive <= unicode.MaxASCII, + ContainsNoAscii: firstValueInclusive >= unicode.MaxASCII, + LowerBoundInclusiveIfOnlyRanges: firstValueInclusive, + UpperBoundExclusiveIfOnlyRanges: lastValueExclusive, + } +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/code.go b/vendor/github.com/dlclark/regexp2/v2/syntax/code.go new file mode 100644 index 000000000..5862572c5 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/code.go @@ -0,0 +1,313 @@ +package syntax + +import ( + "bytes" + "fmt" + "math" +) + +// similar to prog.go in the go regex package...also with comment 'may not belong in this package' + +// File provides operator constants for use by the Builder and the Machine. + +// Implementation notes: +// +// Regexps are built into RegexCodes, which contain an operation array, +// a string table, and some constants. +// +// Each operation is one of the codes below, followed by the integer +// operands specified for each op. +// +// Strings and sets are indices into a string table. + +type InstOp int + +const ( + // lef/back operands description + + Onerep InstOp = 0 // lef,back char,min,max a {n} + Notonerep InstOp = 1 // lef,back char,min,max .{n} + Setrep InstOp = 2 // lef,back set,min,max [\d]{n} + + Oneloop InstOp = 3 // lef,back char,min,max a {,n} + Notoneloop InstOp = 4 // lef,back char,min,max .{,n} + Setloop InstOp = 5 // lef,back set,min,max [\d]{,n} + + Onelazy InstOp = 6 // lef,back char,min,max a {,n}? + Notonelazy InstOp = 7 // lef,back char,min,max .{,n}? + Setlazy InstOp = 8 // lef,back set,min,max [\d]{,n}? + + One InstOp = 9 // lef char a + Notone InstOp = 10 // lef char [^a] + Set InstOp = 11 // lef set [a-z\s] \w \s \d + + Multi InstOp = 12 // lef string abcd + Ref InstOp = 13 // lef group \# + + Bol InstOp = 14 // ^ + Eol InstOp = 15 // $ + Boundary InstOp = 16 // \b + Nonboundary InstOp = 17 // \B + Beginning InstOp = 18 // \A + Start InstOp = 19 // \G + EndZ InstOp = 20 // \Z + End InstOp = 21 // \Z + + Nothing InstOp = 22 // Reject! + + // Primitive control structures + + Lazybranch InstOp = 23 // back jump straight first + Branchmark InstOp = 24 // back jump branch first for loop + Lazybranchmark InstOp = 25 // back jump straight first for loop + Nullcount InstOp = 26 // back val set counter, null mark + Setcount InstOp = 27 // back val set counter, make mark + Branchcount InstOp = 28 // back jump,limit branch++ if zero<=c impl group slots + Capsize int // number of impl group slots + FcPrefix *Prefix // the set of candidate first characters (may be null) + BmPrefix *BmPrefix // the fixed prefix string as a Boyer-Moore machine (may be null) + Anchors AnchorLoc // the set of zero-length start anchors (RegexFCD.Bol, etc) + RightToLeft bool // true if right to left + FindOptimizations *FindOptimizations // analyzed candidate search strategy +} + +// PrepareCharSetASCIIBitmaps builds bounded ASCII lookup tables for compiled +// character classes before the regexp is shared across goroutines. +func (c *Code) PrepareCharSetASCIIBitmaps() { + if c == nil { + return + } + for _, set := range c.Sets { + set.prepareASCIIBitmap() + } + if c.FcPrefix != nil { + c.FcPrefix.PrefixSet.prepareASCIIBitmap() + } + if c.FindOptimizations != nil { + for _, set := range c.FindOptimizations.FixedDistanceSets { + set.Set.prepareASCIIBitmap() + } + if c.FindOptimizations.LiteralAfterLoop != nil && c.FindOptimizations.LiteralAfterLoop.LoopNode != nil { + c.FindOptimizations.LiteralAfterLoop.LoopNode.Set.prepareASCIIBitmap() + } + } +} + +func opcodeBacktracks(op InstOp) bool { + op &= Mask + + switch op { + case Oneloop, Notoneloop, Setloop, Onelazy, Notonelazy, Setlazy, Lazybranch, Branchmark, Lazybranchmark, + Nullcount, Setcount, Branchcount, Lazybranchcount, Setmark, Capturemark, Getmark, Setjump, Backjump, + Forejump, Goto: + return true + + default: + return false + } +} + +func opcodeSize(op InstOp) int { + op &= Mask + + switch op { + case Nothing, Bol, Eol, Boundary, Nonboundary, ECMABoundary, NonECMABoundary, Beginning, Start, EndZ, + End, Nullmark, Setmark, Getmark, Setjump, Backjump, Forejump, Stop, UpdateBumpalong: + return 1 + + case One, Notone, Multi, Ref, Testref, Goto, Nullcount, Setcount, Lazybranch, Branchmark, Lazybranchmark, + Prune, Set: + return 2 + + case Capturemark, Branchcount, Lazybranchcount, Onerep, Notonerep, Oneloop, Notoneloop, Onelazy, Notonelazy, + Setlazy, Setrep, Setloop, Oneloopatomic, Notoneloopatomic, Setloopatomic: + return 3 + + default: + panic(fmt.Errorf("unexpected op code: %v", op)) + } +} + +var codeStr = []string{ + "Onerep", "Notonerep", "Setrep", + "Oneloop", "Notoneloop", "Setloop", + "Onelazy", "Notonelazy", "Setlazy", + "One", "Notone", "Set", + "Multi", "Ref", + "Bol", "Eol", "Boundary", "Nonboundary", "Beginning", "Start", "EndZ", "End", + "Nothing", + "Lazybranch", "Branchmark", "Lazybranchmark", + "Nullcount", "Setcount", "Branchcount", "Lazybranchcount", + "Nullmark", "Setmark", "Capturemark", "Getmark", + "Setjump", "Backjump", "Forejump", "Testref", "Goto", + "Prune", "Stop", + "ECMABoundary", "NonECMABoundary", + "Oneloopatomic", "Notoneloopatomic", "Setloopatomic", + "Bumpalong", +} + +func operatorDescription(op InstOp) string { + desc := codeStr[op&Mask] + if (op & Ci) != 0 { + desc += "-Ci" + } + if (op & Rtl) != 0 { + desc += "-Rtl" + } + if (op & Back) != 0 { + desc += "-Back" + } + if (op & Back2) != 0 { + desc += "-Back2" + } + + return desc +} + +// OpcodeDescription is a humman readable string of the specific offset +func (c *Code) OpcodeDescription(offset int) string { + buf := &bytes.Buffer{} + + op := InstOp(c.Codes[offset]) + fmt.Fprintf(buf, "%06d ", offset) + + if opcodeBacktracks(op & Mask) { + buf.WriteString("*") + } else { + buf.WriteString(" ") + } + buf.WriteString(operatorDescription(op)) + buf.WriteString("(") + op &= Mask + + switch op { + case One, Notone, Onerep, Notonerep, Oneloop, Notoneloop, Onelazy, Notonelazy, + Oneloopatomic, Notoneloopatomic: + buf.WriteString("Ch = ") + buf.WriteString(CharDescription(rune(c.Codes[offset+1]))) + + case Set, Setrep, Setloop, Setlazy, Setloopatomic: + buf.WriteString("Set = ") + buf.WriteString(c.Sets[c.Codes[offset+1]].String()) + + case Multi: + fmt.Fprintf(buf, "String = %s", string(c.Strings[c.Codes[offset+1]])) + + case Ref, Testref: + fmt.Fprintf(buf, "Index = %d", c.Codes[offset+1]) + + case Capturemark: + fmt.Fprintf(buf, "Index = %d", c.Codes[offset+1]) + if c.Codes[offset+2] != -1 { + fmt.Fprintf(buf, ", Unindex = %d", c.Codes[offset+2]) + } + + case Nullcount, Setcount: + fmt.Fprintf(buf, "Value = %d", c.Codes[offset+1]) + + case Goto, Lazybranch, Branchmark, Lazybranchmark, Branchcount, Lazybranchcount: + fmt.Fprintf(buf, "Addr = %d", c.Codes[offset+1]) + } + + switch op { + case Onerep, Notonerep, Oneloop, Notoneloop, Onelazy, Notonelazy, Setrep, Setloop, Setlazy, + Oneloopatomic, Notoneloopatomic, Setloopatomic: + buf.WriteString(", Rep = ") + if c.Codes[offset+2] == math.MaxInt32 { + buf.WriteString("inf") + } else { + fmt.Fprintf(buf, "%d", c.Codes[offset+2]) + } + + case Branchcount, Lazybranchcount: + buf.WriteString(", Limit = ") + if c.Codes[offset+2] == math.MaxInt32 { + buf.WriteString("inf") + } else { + fmt.Fprintf(buf, "%d", c.Codes[offset+2]) + } + + } + + buf.WriteString(")") + + return buf.String() +} + +func (c *Code) Dump() string { + buf := &bytes.Buffer{} + + if c.RightToLeft { + fmt.Fprintln(buf, "Direction: right-to-left") + } else { + fmt.Fprintln(buf, "Direction: left-to-right") + } + if c.FcPrefix == nil { + fmt.Fprintln(buf, "Firstchars: n/a") + } else { + fmt.Fprintf(buf, "Firstchars: %v\n", c.FcPrefix.PrefixSet.String()) + } + + if c.BmPrefix == nil { + fmt.Fprintln(buf, "Prefix: n/a") + } else { + fmt.Fprintf(buf, "Prefix: %v\n", Escape(c.BmPrefix.String())) + } + + fmt.Fprintf(buf, "Anchors: %v\n", c.Anchors) + fmt.Fprintln(buf) + + if c.BmPrefix != nil { + fmt.Fprintln(buf, "BoyerMoore:") + fmt.Fprintln(buf, c.BmPrefix.Dump(" ")) + } + for i := 0; i < len(c.Codes); i += opcodeSize(InstOp(c.Codes[i])) { + fmt.Fprintln(buf, c.OpcodeDescription(i)) + } + + return buf.String() +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/escape.go b/vendor/github.com/dlclark/regexp2/v2/syntax/escape.go new file mode 100644 index 000000000..40dea1781 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/escape.go @@ -0,0 +1,94 @@ +package syntax + +import ( + "bytes" + "strconv" + "strings" + "unicode" +) + +func Escape(input string) string { + b := &bytes.Buffer{} + for _, r := range input { + escape(b, r, false) + } + return b.String() +} + +const meta = `\.+*?()|[]{}^$# ` + +func escape(b *bytes.Buffer, r rune, force bool) { + if unicode.IsPrint(r) { + if strings.ContainsRune(meta, r) || force { + b.WriteRune('\\') + } + b.WriteRune(r) + return + } + + switch r { + case '\a': + b.WriteString(`\a`) + case '\f': + b.WriteString(`\f`) + case '\n': + b.WriteString(`\n`) + case '\r': + b.WriteString(`\r`) + case '\t': + b.WriteString(`\t`) + case '\v': + b.WriteString(`\v`) + default: + if r < 0x100 { + b.WriteString(`\x`) + s := strconv.FormatInt(int64(r), 16) + if len(s) == 1 { + b.WriteRune('0') + } + b.WriteString(s) + break + } + b.WriteString(`\u`) + b.WriteString(strconv.FormatInt(int64(r), 16)) + } +} + +func Unescape(input string) (string, error) { + idx := strings.IndexRune(input, '\\') + // no slashes means no unescape needed + if idx == -1 { + return input, nil + } + + buf := bytes.NewBufferString(input[:idx]) + // get the runes for the rest of the string -- we're going full parser scan on this + + p := parser{} + p.setPattern(input[idx+1:]) + for { + if p.rightMost() { + return "", p.getErr(ErrIllegalEndEscape) + } + r, err := p.scanCharEscape() + if err != nil { + return "", err + } + buf.WriteRune(r) + // are we done? + if p.rightMost() { + return buf.String(), nil + } + + r = p.moveRightGetChar() + for r != '\\' { + buf.WriteRune(r) + if p.rightMost() { + // we're done, no more slashes + return buf.String(), nil + } + // keep scanning until we get another slash + r = p.moveRightGetChar() + } + } +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/fuzz.go b/vendor/github.com/dlclark/regexp2/v2/syntax/fuzz.go new file mode 100644 index 000000000..41cf8852b --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/fuzz.go @@ -0,0 +1,21 @@ +//go:build gofuzz +// +build gofuzz + +package syntax + +// Fuzz is the input point for go-fuzz +func Fuzz(data []byte) int { + sdata := string(data) + tree, err := Parse(sdata, ParseOptions{}) + if err != nil { + return 0 + } + + // translate it to code + _, err = Write(tree) + if err != nil { + panic(err) + } + + return 1 +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/optimizations.go b/vendor/github.com/dlclark/regexp2/v2/syntax/optimizations.go new file mode 100644 index 000000000..54f1252a0 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/optimizations.go @@ -0,0 +1,445 @@ +package syntax + +import ( + "bytes" + "cmp" + "slices" +) + +type FindOptimizations struct { + rightToLeft bool + asciiLookups [][]uint + + FindMode FindNextStartingPositionMode + LeadingAnchor NodeType + TrailingAnchor NodeType + MinRequiredLength int + MaxPossibleLength int + LeadingPrefix string + LeadingPrefixes []string + //LeadingStrings *helpers.StringSearchValues + + FixedDistanceLiteral FixedDistanceLiteral + FixedDistanceSets []FixedDistanceSet + LiteralAfterLoop *LiteralAfterLoop + LandmarkChain *RequiredLandmarkChain +} + +type LiteralAfterLoop struct { + String string + StringIgnoreCase bool + Char rune + Chars []rune + + LoopNode *RegexNode +} + +type FixedDistanceSet struct { + Set *CharSet + Chars []rune + Negated bool + Range *SingleRange + Distance int +} + +type FixedDistanceLiteral struct { + S string + C rune + Distance int +} + +type RequiredLandmarkChain struct { + LeadingLoopSet *CharSet + Landmarks []RequiredLandmark +} + +type RequiredLandmark struct { + Alternatives []RequiredLandmarkAlternative +} + +type RequiredLandmarkAlternative struct { + Literal string + Chars []rune + Set *CharSet + WhitespaceSet *CharSet + MinRepeat int + MaxRepeat int + RequireWhitespaceBefore bool + RequireWhitespaceAfter bool +} + +type FindNextStartingPositionMode int + +const ( + NoSearch FindNextStartingPositionMode = iota + // A "beginning" anchor at the beginning of the pattern. + LeadingAnchor_LeftToRight_Beginning + // A "start" anchor at the beginning of the pattern. + LeadingAnchor_LeftToRight_Start + // An "endz" anchor at the beginning of the pattern. This is rare. + LeadingAnchor_LeftToRight_EndZ + // An "end" anchor at the beginning of the pattern. This is rare. + LeadingAnchor_LeftToRight_End + // A "beginning" anchor at the beginning of the right-to-left pattern. + LeadingAnchor_RightToLeft_Beginning + // A "start" anchor at the beginning of the right-to-left pattern. + LeadingAnchor_RightToLeft_Start + // An "endz" anchor at the beginning of the right-to-left pattern. This is rare. + LeadingAnchor_RightToLeft_EndZ + // An "end" anchor at the beginning of the right-to-left pattern. This is rare. + LeadingAnchor_RightToLeft_End + // An "end" anchor at the end of the pattern, with the pattern always matching a fixed-length expression. + TrailingAnchor_FixedLength_LeftToRight_End + // An "endz" anchor at the end of the pattern, with the pattern always matching a fixed-length expression. + TrailingAnchor_FixedLength_LeftToRight_EndZ + // A multi-character substring at the beginning of the pattern. + LeadingString_LeftToRight + // A multi-character substring at the beginning of the right-to-left pattern. + LeadingString_RightToLeft + // A multi-character ordinal case-insensitive substring at the beginning of the pattern. + LeadingString_OrdinalIgnoreCase_LeftToRight + // Multiple leading prefix strings + LeadingStrings_LeftToRight + // Multiple leading ordinal case-insensitive prefix strings + LeadingStrings_OrdinalIgnoreCase_LeftToRight + + // A set starting the pattern. + LeadingSet_LeftToRight + // A set starting the right-to-left pattern. + LeadingSet_RightToLeft + + // A single character at the start of the right-to-left pattern. + LeadingChar_RightToLeft + + // A single character at a fixed distance from the start of the pattern. + FixedDistanceChar_LeftToRight + // A multi-character case-sensitive string at a fixed distance from the start of the pattern. + FixedDistanceString_LeftToRight + + // One or more sets at a fixed distance from the start of the pattern. + FixedDistanceSets_LeftToRight + + // A literal (single character, multi-char string, or set with small number of characters) after a non-overlapping set loop at the start of the pattern. + LiteralAfterLoop_LeftToRight + + // A sequence of required landmarks after a leading loop. + RequiredLandmarkChain_LeftToRight +) + +func newFindOptimizations(tree *RegexTree, opt ParseOptions) *FindOptimizations { + f := newFindOptimizationsForNode(tree.Root, opt, false) + + if !f.rightToLeft && !f.isUseful() { + if positiveLookahead, _ := findLeadingPositiveLookahead(tree.Root); positiveLookahead != nil { + positiveLookaheadOpts := newFindOptimizationsForNode(positiveLookahead.Children[0], opt, true) + + // Lookaheads don't currently factor into the whole-pattern length analysis, + // as they can overlap with the rest of the expression. Keep the whole-pattern + // minimum if it's larger, and preserve any max from the original expression. + positiveLookaheadOpts.MinRequiredLength = max(f.MinRequiredLength, positiveLookaheadOpts.MinRequiredLength) + positiveLookaheadOpts.MaxPossibleLength = f.MaxPossibleLength + f = positiveLookaheadOpts + } + } + + return f +} + +func newFindOptimizationsForNode(root *RegexNode, opt ParseOptions, isLeadingPartial bool) *FindOptimizations { + f := &FindOptimizations{ + rightToLeft: opt.RegexOptions&RightToLeft != 0, + MinRequiredLength: root.ComputeMinLength(), + LeadingAnchor: findLeadingOrTrailingAnchor(root, true), + MaxPossibleLength: -1, + } + + if f.rightToLeft && f.LeadingAnchor == NtBol { + // Filter out Bol for RightToLeft, as we don't currently optimize for it. + f.LeadingAnchor = NtUnknown + } + + f.FindMode = getFindMode(f.rightToLeft, f.LeadingAnchor) + if f.FindMode != NoSearch { + return f + } + + // Compute any anchor trailing the expression. If there is one, and we can also compute a fixed length + // for the whole expression, we can use that to quickly jump to the right location in the input. + if !f.rightToLeft && !isLeadingPartial { + f.TrailingAnchor = findLeadingOrTrailingAnchor(root, false) + if f.TrailingAnchor == NtEnd || f.TrailingAnchor == NtEndZ { + f.MaxPossibleLength = root.computeMaxLength() + if f.MinRequiredLength == f.MaxPossibleLength { + if f.TrailingAnchor == NtEnd { + f.FindMode = TrailingAnchor_FixedLength_LeftToRight_End + } else { + f.FindMode = TrailingAnchor_FixedLength_LeftToRight_EndZ + } + return f + } + } + } + + // If there's a leading substring, just use IndexOf and inherit all of its optimizations. + prefix := findPrefix(root) + if len(prefix) > 1 { + f.LeadingPrefix = prefix + if f.rightToLeft { + f.FindMode = LeadingString_RightToLeft + } else { + f.FindMode = LeadingString_LeftToRight + } + return f + } + + // At this point there are no fast-searchable anchors or case-sensitive prefixes. We can now analyze the + // pattern for sets and then use any found sets to determine what kind of search to perform. + + // If we're generating code, then the code generation process already handles sets that reduce to a single literal, + // so we can simplify and just always go for the sets. + dfa := false //(opt&NonBacktracking) != 0 + codeGen := opt.CodeGen && !dfa // for now, we never generate code for NonBacktracking, so treat it as non-codegen + interpreter := !codeGen && !dfa + //usesRfoTryFind := !codeGen + + // For interpreter, we want to employ optimizations, but we don't want to make construction significantly + // more expensive. regexp2cg can opt into more expensive analysis with OptionIsCodeGen. So for the + // interpreter we focus only on creating a set for the first character. Same for right-to-left, which + // is used very rarely and thus we don't need to invest in special-casing it. + if f.rightToLeft { + // Determine a set for anything that can possibly start the expression. + set := findFirstCharClass(root) + if set != nil { + var chars []rune + if !set.IsNegated() { + // See if the set is limited to holding only a few characters. + chars = set.GetSetChars(5) + } + + if !codeGen && len(chars) == 1 { + // The set contains one and only one character, meaning every match starts + // with the same literal value (potentially case-insensitive). Search for that. + f.FixedDistanceLiteral.C = chars[0] + f.FindMode = LeadingChar_RightToLeft + } else { + // The set may match multiple characters. Search for that. + f.FixedDistanceSets = []FixedDistanceSet{{ + Chars: chars, + Set: set, + Distance: 0, + }} + f.FindMode = LeadingSet_RightToLeft + f.asciiLookups = make([][]uint, 1) + } + } + return f + } + + // We're now left-to-right only. + + prefix = findPrefixOrdinalCaseInsensitive(root) + if len(prefix) > 1 { + f.LeadingPrefix = prefix + f.FindMode = LeadingString_OrdinalIgnoreCase_LeftToRight + return f + } + + // We're now left-to-right only and looking for multiple prefixes and/or sets. + + // If there are multiple leading strings, we can search for any of them. + // this works in the interpreter, but we avoid it due to additional cost during construction + + if !interpreter { + ciPrefixes := findPrefixes(root, true) + if len(ciPrefixes) > 1 { + f.LeadingPrefixes = ciPrefixes + f.FindMode = LeadingStrings_OrdinalIgnoreCase_LeftToRight + /*SYSTEM_TEXT_REGULAREXPRESSIONS + if usesRfoTryFind { + f.LeadingStrings = helpers.NewSearchValues(f.LeadingPrefixes, true) + }*/ + return f + } + } + + // Build up a list of all of the sets that are a fixed distance from the start of the expression. + fixedDistanceSets := findFixedDistanceSets(root, !interpreter) + + // See if we can make a string of at least two characters long out of those sets. We should have already caught + // one at the beginning of the pattern, but there may be one hiding at a non-zero fixed distance into the pattern. + if len(fixedDistanceSets) > 0 { + bestFixedDistanceString := findFixedDistanceString(fixedDistanceSets) + if bestFixedDistanceString != nil { + f.FindMode = FixedDistanceString_LeftToRight + f.FixedDistanceLiteral = *bestFixedDistanceString + return f + } + } + + // A landmark chain is more selective than a single leading set for shapes like + // /name-separator host-separator domain/. Prefer it before falling back to + // one-position or one-literal candidate searches. + if landmarkChain := findRequiredLandmarkChain(root); landmarkChain != nil { + f.FindMode = RequiredLandmarkChain_LeftToRight + f.LandmarkChain = landmarkChain + return f + } + + // As a backup, see if we can find a literal after a leading atomic loop. That might be better than whatever sets we find, so + // we want to know whether we have one in our pocket before deciding whether to use a leading set (we'll prefer a leading + // set if it's something for which we can search efficiently). + literalAfterLoop := findLiteralFollowingLeadingLoop(root) + + // If we got such sets, we'll likely use them. However, if the best of them is something that doesn't support an efficient + // search and we did successfully find a literal after an atomic loop we could search instead, we prefer the efficient search. + // For example, if we have a negated set, we will still prefer the literal-after-an-atomic-loop because negated sets typically + // contain _many_ characters (e.g. [^a] is everything but 'a') and are thus more likely to very quickly match, which means any + // vectorization employed is less likely to kick in and be worth the startup overhead. + if len(fixedDistanceSets) > 0 { + // Sort the sets by "quality", such that whatever set is first is the one deemed most efficient to use. + // In some searches, we may use multiple sets, so we want the subsequent ones to also be the efficiency runners-up. + slices.SortFunc(fixedDistanceSets, compareFixedDistanceSetsByQuality) + + // If the best fixed-distance set is composed of high-frequency characters, IndexOfAny on + // those characters is likely to match too many positions. Prefer a case-sensitive + // multi-prefix search when one is available. + if !interpreter && !mayContainCaseInsensitiveMatching(root) && hasHighFrequencyChars(fixedDistanceSets[0]) { + caseSensitivePrefixes := findPrefixes(root, false) + if len(caseSensitivePrefixes) > 1 { + f.LeadingPrefixes = caseSensitivePrefixes + f.FindMode = LeadingStrings_LeftToRight + return f + } + } + + // If there is no literal after the loop, use whatever set we got. + // If there is a literal after the loop, consider it to be better than a negated set and better than a set with many characters. + if literalAfterLoop == nil || (len(fixedDistanceSets[0].Chars) > 0 && !fixedDistanceSets[0].Negated) { + // Determine whether to do searching based on one or more sets or on a single literal. Code-generated engines + // don't need to special-case literals as they already do codegen to create the optimal lookup based on + // the set's characteristics. + if !codeGen && len(fixedDistanceSets) == 1 && len(fixedDistanceSets[0].Chars) == 1 && + !fixedDistanceSets[0].Negated { + f.FixedDistanceLiteral = FixedDistanceLiteral{ + C: fixedDistanceSets[0].Chars[0], + Distance: fixedDistanceSets[0].Distance, + } + f.FindMode = FixedDistanceChar_LeftToRight + } else { + // Limit how many sets we use to avoid doing lots of unnecessary work. The list was already + // sorted from best to worst, so just keep the first ones up to our limit. + const MaxSetsToUse = 3 // arbitrary tuned limit + if len(fixedDistanceSets) > MaxSetsToUse { + fixedDistanceSets = fixedDistanceSets[:MaxSetsToUse] + } + + // Store the sets, and compute which mode to use. + f.FixedDistanceSets = fixedDistanceSets + if len(fixedDistanceSets) == 1 && fixedDistanceSets[0].Distance == 0 { + f.FindMode = LeadingSet_LeftToRight + } else { + f.FindMode = FixedDistanceSets_LeftToRight + } + f.asciiLookups = make([][]uint, len(fixedDistanceSets)) + } + return f + } + } + + // If we found a literal we can search for after a leading set loop, use it. + if literalAfterLoop != nil { + f.FindMode = LiteralAfterLoop_LeftToRight + f.LiteralAfterLoop = literalAfterLoop + f.asciiLookups = make([][]uint, 1) + } + + return f +} + +func (f *FindOptimizations) isUseful() bool { + return f.FindMode != NoSearch || f.LeadingAnchor == NtBol +} + +func getFindMode(rtl bool, t NodeType) FindNextStartingPositionMode { + if rtl { + switch t { + case NtBeginning: + return LeadingAnchor_RightToLeft_Beginning + case NtStart: + return LeadingAnchor_RightToLeft_Start + case NtEnd: + return LeadingAnchor_RightToLeft_End + case NtEndZ: + return LeadingAnchor_RightToLeft_EndZ + } + } else { + switch t { + case NtBeginning: + return LeadingAnchor_LeftToRight_Beginning + case NtStart: + return LeadingAnchor_LeftToRight_Start + case NtEnd: + return LeadingAnchor_LeftToRight_End + case NtEndZ: + return LeadingAnchor_LeftToRight_EndZ + } + } + + return NoSearch +} + +// Analyzes a list of fixed-distance sets to extract a case-sensitive string at a fixed distance. +func findFixedDistanceString(fixedDistanceSets []FixedDistanceSet) *FixedDistanceLiteral { + var best *FixedDistanceLiteral + + // A result string must be at least two characters in length; therefore we require at least that many sets. + if len(fixedDistanceSets) >= 2 { + // We're walking the sets from beginning to end, so we need them sorted by distance. + slices.SortFunc(fixedDistanceSets, func(s1, s2 FixedDistanceSet) int { + return cmp.Compare(s1.Distance, s2.Distance) + }) + + vsb := &bytes.Buffer{} + + // Looking for strings of length >= 2 + start := -1 + for i := 0; i < len(fixedDistanceSets)+1; i++ { + chars := []rune(nil) + if i < len(fixedDistanceSets) { + chars = fixedDistanceSets[i].Chars + } + invalidChars := len(chars) != 1 || fixedDistanceSets[i].Negated + + // If the current set ends a sequence (or we've walked off the end), see whether + // what we've gathered constitues a valid string, and if it's better than the + // best we've already seen, store it. Regardless, reset the sequence in order + // to continue analyzing. + if invalidChars || (i > 0 && fixedDistanceSets[i].Distance != fixedDistanceSets[i-1].Distance+1) { + bestLen := 2 + if best != nil { + bestLen = len(best.S) + } + if start != -1 && i-start >= bestLen { + best = &FixedDistanceLiteral{ + S: vsb.String(), + Distance: fixedDistanceSets[start].Distance, + } + } + + vsb.Reset() + start = -1 + if invalidChars { + continue + } + } + + if start == -1 { + start = i + } + + vsb.WriteRune(chars[0]) + } + } + + return best +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/parser.go b/vendor/github.com/dlclark/regexp2/v2/syntax/parser.go new file mode 100644 index 000000000..3ce3667c6 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/parser.go @@ -0,0 +1,2527 @@ +package syntax + +import ( + "fmt" + "math" + "sort" + "strconv" + "strings" + "unicode" +) + +type RegexOptions int32 + +const ( + IgnoreCase RegexOptions = 0x0001 // "i" + Multiline RegexOptions = 0x0002 // "m" + ExplicitCapture RegexOptions = 0x0004 // "n" + Singleline RegexOptions = 0x0010 // "s" + IgnorePatternWhitespace RegexOptions = 0x0020 // "x" + RightToLeft RegexOptions = 0x0040 // "r" + ECMAScript RegexOptions = 0x0100 // "e" + RE2 RegexOptions = 0x0200 // RE2 compat mode + Unicode RegexOptions = 0x0400 // "u" +) + +func optionFromCode(ch rune) RegexOptions { + // case-insensitive + switch ch { + case 'i', 'I': + return IgnoreCase + case 'r', 'R': + return RightToLeft + case 'm', 'M': + return Multiline + case 'n', 'N': + return ExplicitCapture + case 's', 'S': + return Singleline + case 'x', 'X': + return IgnorePatternWhitespace + case 'e', 'E': + return ECMAScript + case 'u', 'U': + return Unicode + default: + return 0 + } +} + +// An Error describes a failure to parse a regular expression +// and gives the offending expression. +type Error struct { + Code ErrorCode + Expr string + Args []interface{} +} + +func (e *Error) Error() string { + if len(e.Args) == 0 { + return "error parsing regexp: " + e.Code.String() + " in `" + e.Expr + "`" + } + return "error parsing regexp: " + fmt.Sprintf(e.Code.String(), e.Args...) + " in `" + e.Expr + "`" +} + +// An ErrorCode describes a failure to parse a regular expression. +type ErrorCode string + +const ( + // internal issue + ErrInternalError ErrorCode = "regexp/syntax: internal error" + // Parser errors + ErrUnterminatedComment = "unterminated comment" + ErrInvalidCharRange = "invalid character class range" + ErrInvalidRepeatSize = "invalid repeat count" + ErrInvalidUTF8 = "invalid UTF-8" + ErrCaptureGroupOutOfRange = "capture group number out of range" + ErrUnexpectedParen = "unexpected )" + ErrMissingParen = "missing closing )" + ErrMissingBrace = "missing closing }" + ErrInvalidRepeatOp = "invalid nested repetition operator" + ErrMissingRepeatArgument = "missing argument to repetition operator" + ErrConditionalExpression = "illegal conditional (?(...)) expression" + ErrTooManyAlternates = "too many | in (?()|)" + ErrUnrecognizedGrouping = "unrecognized grouping construct: (%v" + ErrInvalidGroupName = "invalid group name: group names must begin with a word character and have a matching terminator" + ErrInvalidECMAGroupName = "invalid capture group name" + ErrDuplicateGroupName = "duplicate capture group name" + ErrCapNumNotZero = "capture number cannot be zero" + ErrUndefinedBackRef = "reference to undefined group number %v" + ErrUndefinedNameRef = "reference to undefined group name %v" + ErrAlternationCantCapture = "alternation conditions do not capture and cannot be named" + ErrAlternationCantHaveComment = "alternation conditions cannot be comments" + ErrMalformedReference = "(?(%v) ) malformed" + ErrUndefinedReference = "(?(%v) ) reference to undefined group" + ErrIllegalEndEscape = "illegal \\ at end of pattern" + ErrMalformedSlashP = "malformed \\p{X} character escape" + ErrIncompleteSlashP = "incomplete \\p{X} character escape" + ErrUnknownSlashP = "unknown unicode category, script, or property '%v'" + ErrUnrecognizedEscape = "unrecognized escape sequence \\%v" + ErrMissingControl = "missing control character" + ErrUnrecognizedControl = "unrecognized control character" + ErrTooFewHex = "insufficient hexadecimal digits" + ErrInvalidHex = "hex values may not be larger than 0x10FFFF" + ErrMalformedNameRef = "malformed \\k<...> named back reference" + ErrBadClassInCharRange = "cannot include class \\%v in character range" + ErrShorthandClassInCharRange = "cannot create range with shorthand escape sequence \\%v" + ErrUnterminatedBracket = "unterminated [] set" + ErrSubtractionMustBeLast = "a subtraction must be the last element in a character class" + ErrReversedCharRange = "[%c-%c] range in reverse order" +) + +func (e ErrorCode) String() string { + return string(e) +} + +type parser struct { + stack *RegexNode + group *RegexNode + alternation *RegexNode + concatenation *RegexNode + unit *RegexNode + + patternRaw string + pattern []rune + + currentPos int + + autocap int + capcount int + captop int + capsize int + + caps map[int]int + capnames map[string]int + + maintainCaptureOrder bool + + capnumlist []int + capnamelist []string + + options RegexOptions + optionsStack []RegexOptions + ignoreNextParen bool +} + +type ParseOptions struct { + RegexOptions RegexOptions + MaintainCaptureOrder bool + CodeGen bool +} + +const ( + maxValueDiv10 int = math.MaxInt32 / 10 + maxValueMod10 = math.MaxInt32 % 10 +) + +// Parse converts a regex string into a parse tree +func Parse(re string, op ParseOptions) (*RegexTree, error) { + p := parser{ + options: op.RegexOptions, + caps: make(map[int]int), + maintainCaptureOrder: op.MaintainCaptureOrder || (op.RegexOptions&ECMAScript) != 0, + } + p.setPattern(re) + + if err := p.countCaptures(); err != nil { + return nil, err + } + + p.reset(op.RegexOptions) + root, err := p.scanRegex() + + if err != nil { + return nil, err + } + tree := &RegexTree{ + Root: root, + Caps: p.caps, + Capnumlist: p.capnumlist, + Captop: p.captop, + Capnames: p.capnames, + Caplist: p.capnamelist, + Options: op.RegexOptions, + } + tree.FindOptimizations = newFindOptimizations(tree, op) + + return tree, nil +} + +func (p *parser) setPattern(pattern string) { + p.patternRaw = pattern + p.pattern = make([]rune, 0, len(pattern)) + + //populate our rune array to handle utf8 encoding + for _, r := range pattern { + p.pattern = append(p.pattern, r) + } +} +func (p *parser) getErr(code ErrorCode, args ...interface{}) error { + return &Error{Code: code, Expr: p.patternRaw, Args: args} +} + +func (p *parser) noteCaptureSlot(i, pos int) { + if _, ok := p.caps[i]; !ok { + // the rhs of the hashtable isn't used in the parser + p.caps[i] = pos + p.capcount++ + + if p.captop <= i { + if i == math.MaxInt32 { + p.captop = i + } else { + p.captop = i + 1 + } + } + } +} + +func (p *parser) noteCaptureName(name string, pos int) error { + if p.capnames == nil { + p.capnames = make(map[string]int) + } + + if _, ok := p.capnames[name]; !ok { + if p.maintainCaptureOrder { + slot := p.consumeAutocap() + p.capnames[name] = slot + p.noteCaptureSlot(slot, pos) + } else { + p.capnames[name] = pos + } + p.capnamelist = append(p.capnamelist, name) + } else if p.useOptionE() { + return p.getErr(ErrDuplicateGroupName) + } + return nil +} + +func (p *parser) assignNameSlots() { + if p.maintainCaptureOrder { + p.assignOrderedNameSlots() + return + } + + if p.capnames != nil { + for _, name := range p.capnamelist { + for p.isCaptureSlot(p.autocap) { + p.autocap++ + } + pos := p.capnames[name] + p.capnames[name] = p.autocap + p.noteCaptureSlot(p.autocap, pos) + + p.autocap++ + } + } + + // if the caps array has at least one gap, construct the list of used slots + if p.capcount < p.captop { + p.capnumlist = make([]int, p.capcount) + i := 0 + + for k := range p.caps { + p.capnumlist[i] = k + i++ + } + + sort.Ints(p.capnumlist) + } + + // merge capsnumlist into capnamelist + if p.capnames != nil || p.capnumlist != nil { + var oldcapnamelist []string + var next int + var k int + + if p.capnames == nil { + oldcapnamelist = nil + p.capnames = make(map[string]int) + p.capnamelist = []string{} + next = -1 + } else { + oldcapnamelist = p.capnamelist + p.capnamelist = []string{} + next = p.capnames[oldcapnamelist[0]] + } + + for i := 0; i < p.capcount; i++ { + j := i + if p.capnumlist != nil { + j = p.capnumlist[i] + } + + if next == j { + p.capnamelist = append(p.capnamelist, oldcapnamelist[k]) + k++ + + if k == len(oldcapnamelist) { + next = -1 + } else { + next = p.capnames[oldcapnamelist[k]] + } + + } else { + //feature: culture? + str := strconv.Itoa(j) + p.capnamelist = append(p.capnamelist, str) + p.capnames[str] = j + } + } + } +} + +func (p *parser) assignOrderedNameSlots() { + if !p.useOptionE() && p.capnames == nil && p.capcount == p.captop { + return + } + + if p.capcount < p.captop { + p.capnumlist = make([]int, p.capcount) + i := 0 + for k := range p.caps { + p.capnumlist[i] = k + i++ + } + sort.Ints(p.capnumlist) + } + + names := p.capnamelist + p.capnamelist = make([]string, p.capcount) + if p.capnames == nil { + p.capnames = make(map[string]int, p.capcount) + } + + for _, name := range names { + slot := p.capnames[name] + index := slot + if p.capnumlist != nil { + for i, capnum := range p.capnumlist { + if capnum == slot { + index = i + break + } + } + } + p.capnamelist[index] = name + } + + for i := 0; i < p.capcount; i++ { + slot := i + if p.capnumlist != nil { + slot = p.capnumlist[i] + } + if p.useOptionE() { + continue + } + if p.capnamelist[i] == "" { + p.capnamelist[i] = strconv.Itoa(slot) + } + if _, ok := p.capnames[p.capnamelist[i]]; !ok { + p.capnames[p.capnamelist[i]] = slot + } + } +} + +func (p *parser) consumeAutocap() int { + r := p.autocap + p.autocap++ + return r +} + +func (p *parser) consumeCaptureSlot(capnum int) { + if p.maintainCaptureOrder && capnum == p.autocap { + p.consumeAutocap() + } +} + +// CountCaptures is a prescanner for deducing the slots used for +// captures by doing a partial tokenization of the pattern. +func (p *parser) countCaptures() error { + var ch rune + + p.noteCaptureSlot(0, 0) + + p.autocap = 1 + + for p.charsRight() > 0 { + pos := p.textpos() + ch = p.moveRightGetChar() + switch ch { + case '\\': + if p.charsRight() > 0 { + _, _ = p.scanBackslash(true) + } + + case '#': + if p.useOptionX() { + p.moveLeft() + _ = p.scanBlank() + } + + case '[': + _, _ = p.scanCharSet(false, true) + + case ')': + if !p.emptyOptionsStack() { + p.popOptions() + } + + case '(': + if p.charsRight() >= 2 && p.rightChar(1) == '#' && p.rightChar(0) == '?' { + p.moveLeft() + _ = p.scanBlank() + } else { + p.pushOptions() + if p.charsRight() > 0 && p.rightChar(0) == '?' { + // we have (?... + p.moveRight(1) + + if p.charsRight() > 1 && (p.rightChar(0) == '<' || p.rightChar(0) == '\'') { + // named group: (?<... or (?'... + + p.moveRight(1) + ch = p.rightChar(0) + + if ch != '0' && p.isGroupNameStartChar(ch) { + if ch >= '1' && ch <= '9' && !p.useOptionE() { + dec, err := p.scanDecimal() + if err != nil { + return err + } + if p.maintainCaptureOrder { + if err = p.noteCaptureName(strconv.Itoa(dec), pos); err != nil { + return err + } + } else { + p.noteCaptureSlot(dec, pos) + } + } else { + capname, err := p.scanCapname() + if err != nil { + return err + } + if err = p.noteCaptureName(capname, pos); err != nil { + return err + } + } + } + } else if p.useRE2() && p.charsRight() > 2 && (p.rightChar(0) == 'P' && p.rightChar(1) == '<') { + // RE2-compat (?P<) + p.moveRight(2) + ch = p.rightChar(0) + if IsWordChar(ch) { + capname, err := p.scanCapname() + if err != nil { + return err + } + if err = p.noteCaptureName(capname, pos); err != nil { + return err + } + } + + } else { + // (?... + + // get the options if it's an option construct (?cimsx-cimsx...) + p.scanOptions() + + if p.charsRight() > 0 { + if p.rightChar(0) == ')' { + // (?cimsx-cimsx) + p.moveRight(1) + p.popKeepOptions() + } else if p.rightChar(0) == '(' { + // alternation construct: (?(foo)yes|no) + // ignore the next paren so we don't capture the condition + p.ignoreNextParen = true + + // break from here so we don't reset ignoreNextParen + continue + } + } + } + } else { + if !p.useOptionN() && !p.ignoreNextParen { + p.noteCaptureSlot(p.consumeAutocap(), pos) + } + } + } + + p.ignoreNextParen = false + + } + } + + p.assignNameSlots() + return nil +} + +func (p *parser) reset(topopts RegexOptions) { + p.currentPos = 0 + p.autocap = 1 + p.ignoreNextParen = false + + if len(p.optionsStack) > 0 { + p.optionsStack = p.optionsStack[:0] + } + + p.options = topopts + p.stack = nil +} + +func (p *parser) scanRegex() (*RegexNode, error) { + var ch rune // nonspecial ch, means at beginning + isQuant := false + + p.startGroup(newRegexNodeMN(NtCapture, p.options, 0, -1)) + + for p.charsRight() > 0 { + wasPrevQuantifier := isQuant + isQuant = false + + if err := p.scanBlank(); err != nil { + return nil, err + } + + startpos := p.textpos() + + // move past all of the normal characters. We'll stop when we hit some kind of control character, + // or if IgnorePatternWhiteSpace is on, we'll stop when we see some whitespace. + if p.useOptionX() { + for p.charsRight() > 0 { + ch = p.rightChar(0) + if isStopperX(ch) && (ch != '{' || p.isTrueQuantifier()) { + break + } + p.moveRight(1) + } + } else { + for p.charsRight() > 0 { + ch = p.rightChar(0) + if isSpecial(ch) && (ch != '{' || p.isTrueQuantifier()) { + break + } + p.moveRight(1) + } + } + + endpos := p.textpos() + + if err := p.scanBlank(); err != nil { + return nil, err + } + + if p.charsRight() == 0 { + ch = '!' // nonspecial, means at end + } else if ch = p.rightChar(0); isSpecial(ch) { + isQuant = isQuantifier(ch) + p.moveRight(1) + } else { + ch = ' ' // nonspecial, means at ordinary char + } + + if startpos < endpos { + cchUnquantified := endpos - startpos + if isQuant { + cchUnquantified-- + } + wasPrevQuantifier = false + + if cchUnquantified > 0 { + p.addToConcatenate(startpos, cchUnquantified, false) + } + + if isQuant { + p.addUnitOne(p.charAt(endpos - 1)) + } + } + + switch ch { + case '!': + goto BreakOuterScan + + case ' ': + goto ContinueOuterScan + + case '[': + cc, err := p.scanCharSet(p.useOptionI(), false) + if err != nil { + return nil, err + } + p.addUnitSet(cc) + + case '(': + if p.useRE2() && p.charsRight() >= 3 && p.rightChar(0) == '?' && p.rightChar(1) == 'P' && p.rightChar(2) == '=' { + n, err := p.scanPythonNamedBackref() + if err != nil { + return nil, err + } + p.addUnitNode(n) + break + } + + p.pushOptions() + + if grouper, err := p.scanGroupOpen(); err != nil { + return nil, err + } else if grouper == nil { + p.popKeepOptions() + } else { + p.pushGroup() + p.startGroup(grouper) + } + + continue + + case '|': + p.addAlternate() + goto ContinueOuterScan + + case ')': + if p.emptyStack() { + return nil, p.getErr(ErrUnexpectedParen) + } + + if err := p.addGroup(); err != nil { + return nil, err + } + if err := p.popGroup(); err != nil { + return nil, err + } + p.popOptions() + + if p.unit == nil { + goto ContinueOuterScan + } + + case '\\': + n, err := p.scanBackslash(false) + if err != nil { + return nil, err + } + p.addUnitNode(n) + + case '^': + if p.useOptionM() { + p.addUnitType(NtBol) + } else { + p.addUnitType(NtBeginning) + } + + case '$': + if p.useOptionM() { + p.addUnitType(NtEol) + } else if p.useRE2() || p.useOptionE() { + // RE2 and EcmaScript define $ as "asserts position at the end of the string" + // PCRE/.NET adds "or before the line terminator right at the end of the string (if any)" + // which is NtEnd, not NtEndZ + p.addUnitType(NtEnd) + } else { + p.addUnitType(NtEndZ) + } + + case '.': + if p.useOptionS() { + p.addUnitSet(AnyClass()) + } else if p.useOptionE() { + p.addUnitSet(ECMAAnyClass()) + } else { + p.addUnitNotone('\n') + } + + case '{', '*', '+', '?': + if p.unit == nil { + if wasPrevQuantifier { + return nil, p.getErr(ErrInvalidRepeatOp) + } else { + return nil, p.getErr(ErrMissingRepeatArgument) + } + } + p.moveLeft() + + default: + return nil, p.getErr(ErrInternalError) + } + + if err := p.scanBlank(); err != nil { + return nil, err + } + + if p.charsRight() > 0 { + isQuant = p.isTrueQuantifier() + } + if p.charsRight() == 0 || !isQuant { + //maintain odd C# assignment order -- not sure if required, could clean up? + p.addConcatenate() + goto ContinueOuterScan + } + + ch = p.moveRightGetChar() + + // Handle quantifiers + for p.unit != nil { + var min, max int + var lazy bool + + switch ch { + case '*': + min = 0 + max = math.MaxInt32 + + case '?': + min = 0 + max = 1 + + case '+': + min = 1 + max = math.MaxInt32 + + case '{': + { + var err error + startpos = p.textpos() + if min, err = p.scanDecimal(); err != nil { + return nil, err + } + max = min + if startpos < p.textpos() { + if p.charsRight() > 0 && p.rightChar(0) == ',' { + p.moveRight(1) + if p.charsRight() == 0 || p.rightChar(0) == '}' { + max = math.MaxInt32 + } else { + if max, err = p.scanDecimal(); err != nil { + return nil, err + } + } + } + } + + if startpos == p.textpos() || p.charsRight() == 0 || p.moveRightGetChar() != '}' { + p.addConcatenate() + p.textto(startpos - 1) + goto ContinueOuterScan + } + } + + default: + return nil, p.getErr(ErrInternalError) + } + + if err := p.scanBlank(); err != nil { + return nil, err + } + + if p.charsRight() == 0 || p.rightChar(0) != '?' { + lazy = false + } else { + p.moveRight(1) + lazy = true + } + + if min > max { + return nil, p.getErr(ErrInvalidRepeatSize) + } + + p.addConcatenate3(lazy, min, max) + } + + ContinueOuterScan: + } + +BreakOuterScan: + ; + + if !p.emptyStack() { + return nil, p.getErr(ErrMissingParen) + } + + if err := p.addGroup(); err != nil { + return nil, err + } + + return p.unit.finalOptimize(), nil + +} + +/* + * Simple parsing for replacement patterns + */ +func (p *parser) scanReplacement() (*RegexNode, error) { + var c, startpos int + + p.concatenation = newRegexNode(NtConcatenate, p.options) + + for { + c = p.charsRight() + if c == 0 { + break + } + + startpos = p.textpos() + + for c > 0 && p.rightChar(0) != '$' { + p.moveRight(1) + c-- + } + + p.addToConcatenate(startpos, p.textpos()-startpos, true) + + if c > 0 { + if p.moveRightGetChar() == '$' { + n, err := p.scanDollar() + if err != nil { + return nil, err + } + p.addUnitNode(n) + } + p.addConcatenate() + } + } + + return p.concatenation, nil +} + +/* + * Scans $ patterns recognized within replacement patterns + */ +func (p *parser) scanDollar() (*RegexNode, error) { + if p.charsRight() == 0 { + return newRegexNodeCh(NtOne, p.options, '$'), nil + } + + ch := p.rightChar(0) + angled := false + backpos := p.textpos() + lastEndPos := backpos + + // Note angle + + if ch == '{' && p.charsRight() > 1 { + angled = true + p.moveRight(1) + ch = p.rightChar(0) + } + + // Try to parse backreference: \1 or \{1} or \{cap} + + if ch >= '0' && ch <= '9' { + if !angled && p.useOptionE() { + capnum := -1 + newcapnum := int(ch - '0') + p.moveRight(1) + if p.isCaptureSlot(newcapnum) { + capnum = newcapnum + lastEndPos = p.textpos() + } + + for p.charsRight() > 0 { + ch = p.rightChar(0) + if ch < '0' || ch > '9' { + break + } + digit := int(ch - '0') + if newcapnum > maxValueDiv10 || (newcapnum == maxValueDiv10 && digit > maxValueMod10) { + return nil, p.getErr(ErrCaptureGroupOutOfRange) + } + + newcapnum = newcapnum*10 + digit + + p.moveRight(1) + if p.isCaptureSlot(newcapnum) { + capnum = newcapnum + lastEndPos = p.textpos() + } + } + p.textto(lastEndPos) + if capnum >= 0 { + return newRegexNodeM(NtRef, p.options, capnum), nil + } + } else { + capnum, err := p.scanDecimal() + if err != nil { + return nil, err + } + if !angled || p.charsRight() > 0 && p.moveRightGetChar() == '}' { + if p.isCaptureSlot(capnum) { + return newRegexNodeM(NtRef, p.options, capnum), nil + } + } + } + } else if angled && p.isGroupNameStartChar(ch) { + capname, err := p.scanCapname() + if err != nil { + return nil, err + } + + if p.charsRight() > 0 && p.moveRightGetChar() == '}' { + if p.isCaptureName(capname) { + return newRegexNodeM(NtRef, p.options, p.captureSlotFromName(capname)), nil + } + } + } else if !angled { + capnum := 1 + + switch ch { + case '$': + p.moveRight(1) + return newRegexNodeCh(NtOne, p.options, '$'), nil + case '&': + capnum = 0 + case '`': + capnum = replaceLeftPortion + case '\'': + capnum = replaceRightPortion + case '+': + capnum = replaceLastGroup + case '_': + capnum = replaceWholeString + } + + if capnum != 1 { + p.moveRight(1) + return newRegexNodeM(NtRef, p.options, capnum), nil + } + } + + // unrecognized $: literalize + + p.textto(backpos) + return newRegexNodeCh(NtOne, p.options, '$'), nil +} + +func (p *parser) isGroupNameStartChar(ch rune) bool { + if p.useOptionE() { + return IsECMAIdentifierStartChar(ch) || ch == '\\' + } + return IsWordChar(ch) +} + +func (p *parser) scanPythonNamedBackref() (*RegexNode, error) { + p.moveRight(3) + if p.charsRight() == 0 { + return nil, p.getErr(ErrMalformedNameRef) + } + + ch := p.rightChar(0) + if !p.isGroupNameStartChar(ch) { + return nil, p.getErr(ErrInvalidGroupName) + } + + capname, err := p.scanCapname() + if err != nil { + return nil, err + } + + if capname == "" || p.charsRight() == 0 || p.moveRightGetChar() != ')' { + return nil, p.getErr(ErrMalformedNameRef) + } + + if !p.isCaptureName(capname) { + return nil, p.getErr(ErrUndefinedNameRef, capname) + } + + return newRegexNodeM(NtRef, p.options, p.captureSlotFromName(capname)), nil +} + +// scanGroupOpen scans chars following a '(' (not counting the '('), and returns +// a RegexNode for the type of group scanned, or nil if the group +// simply changed options (?cimsx-cimsx) or was a comment (#...). +func (p *parser) scanGroupOpen() (*RegexNode, error) { + var ch rune + var nt NodeType + var err error + close := '>' + start := p.textpos() + + // just return a RegexNode if we have: + // 1. "(" followed by nothing + // 2. "(x" where x != ? + // 3. "(?)" + if p.charsRight() == 0 || p.rightChar(0) != '?' || (p.rightChar(0) == '?' && (p.charsRight() > 1 && p.rightChar(1) == ')')) { + if p.useOptionN() || p.ignoreNextParen { + p.ignoreNextParen = false + return newRegexNode(NtGroup, p.options), nil + } + return newRegexNodeMN(NtCapture, p.options, p.consumeAutocap(), -1), nil + } + + p.moveRight(1) + + for p.charsRight() > 0 { + switch ch = p.moveRightGetChar(); ch { + case ':': + nt = NtGroup + + case '=': + p.options &= ^RightToLeft + nt = NtPosLook + + case '!': + p.options &= ^RightToLeft + nt = NtNegLook + + case '>': + nt = NtAtomic + + case '\'': + close = '\'' + fallthrough + + case '<': + if p.charsRight() == 0 { + goto BreakRecognize + } + + switch ch = p.moveRightGetChar(); ch { + case '=': + if close == '\'' { + goto BreakRecognize + } + + p.options |= RightToLeft + nt = NtPosLook + + case '!': + if close == '\'' { + goto BreakRecognize + } + + p.options |= RightToLeft + nt = NtNegLook + + default: + p.moveLeft() + capnum := -1 + uncapnum := -1 + proceed := false + + // grab part before - + + if ch >= '0' && ch <= '9' && !p.useOptionE() { + if capnum, err = p.scanDecimal(); err != nil { + return nil, err + } + + if !p.isCaptureSlot(capnum) { + capnum = -1 + } + + // check if we have bogus characters after the number + if p.charsRight() > 0 && p.rightChar(0) != close && p.rightChar(0) != '-' { + return nil, p.getErr(ErrInvalidGroupName) + } + if capnum == 0 { + return nil, p.getErr(ErrCapNumNotZero) + } + } else if p.isGroupNameStartChar(ch) { + capname, err := p.scanCapname() + if err != nil { + return nil, err + } + + if p.isCaptureName(capname) { + capnum = p.captureSlotFromName(capname) + } + + // check if we have bogus character after the name + if p.charsRight() > 0 && p.rightChar(0) != close && p.rightChar(0) != '-' { + if p.useOptionE() { + return nil, p.getErr(ErrInvalidECMAGroupName) + } + return nil, p.getErr(ErrInvalidGroupName) + } + } else if ch == '-' { + proceed = true + } else { + // bad group name - starts with something other than a word character and isn't a number + if p.useOptionE() { + return nil, p.getErr(ErrInvalidECMAGroupName) + } + return nil, p.getErr(ErrInvalidGroupName) + } + + // grab part after - if any + + if !p.useOptionE() && (capnum != -1 || proceed) && p.charsRight() > 0 && p.rightChar(0) == '-' { + p.moveRight(1) + + //no more chars left, no closing char, etc + if p.charsRight() == 0 { + return nil, p.getErr(ErrInvalidGroupName) + } + + ch = p.rightChar(0) + if ch >= '0' && ch <= '9' { + if uncapnum, err = p.scanDecimal(); err != nil { + return nil, err + } + + if !p.isCaptureSlot(uncapnum) { + return nil, p.getErr(ErrUndefinedBackRef, uncapnum) + } + + // check if we have bogus characters after the number + if p.charsRight() > 0 && p.rightChar(0) != close { + return nil, p.getErr(ErrInvalidGroupName) + } + } else if IsWordChar(ch) { + uncapname, err := p.scanCapname() + if err != nil { + return nil, err + } + + if !p.isCaptureName(uncapname) { + return nil, p.getErr(ErrUndefinedNameRef, uncapname) + } + uncapnum = p.captureSlotFromName(uncapname) + + // check if we have bogus character after the name + if p.charsRight() > 0 && p.rightChar(0) != close { + return nil, p.getErr(ErrInvalidGroupName) + } + } else { + // bad group name - starts with something other than a word character and isn't a number + return nil, p.getErr(ErrInvalidGroupName) + } + } + + // actually make the node + + if (capnum != -1 || uncapnum != -1) && p.charsRight() > 0 && p.moveRightGetChar() == close { + p.consumeCaptureSlot(capnum) + return newRegexNodeMN(NtCapture, p.options, capnum, uncapnum), nil + } + goto BreakRecognize + } + + case '(': + // alternation construct (?(...) | ) + + parenPos := p.textpos() + if p.charsRight() > 0 { + ch = p.rightChar(0) + + // check if the alternation condition is a backref + if ch >= '0' && ch <= '9' { + var capnum int + if capnum, err = p.scanDecimal(); err != nil { + return nil, err + } + if p.charsRight() > 0 && p.moveRightGetChar() == ')' { + if p.isCaptureSlot(capnum) { + return newRegexNodeM(NtBackRefCond, p.options, capnum), nil + } + return nil, p.getErr(ErrUndefinedReference, capnum) + } + + return nil, p.getErr(ErrMalformedReference, capnum) + + } else if IsWordChar(ch) { + capname, err := p.scanCapname() + if err != nil { + return nil, err + } + + if p.isCaptureName(capname) && p.charsRight() > 0 && p.moveRightGetChar() == ')' { + return newRegexNodeM(NtBackRefCond, p.options, p.captureSlotFromName(capname)), nil + } + } + } + // not a backref + nt = NtExprCond + p.textto(parenPos - 1) // jump to the start of the parentheses + p.ignoreNextParen = true // but make sure we don't try to capture the insides + + charsRight := p.charsRight() + if charsRight >= 3 && p.rightChar(1) == '?' { + rightchar2 := p.rightChar(2) + // disallow comments in the condition + if rightchar2 == '#' { + return nil, p.getErr(ErrAlternationCantHaveComment) + } + + // disallow named capture group (?<..>..) in the condition + if rightchar2 == '\'' { + return nil, p.getErr(ErrAlternationCantCapture) + } + + if charsRight >= 4 && (rightchar2 == '<' && p.rightChar(3) != '!' && p.rightChar(3) != '=') { + return nil, p.getErr(ErrAlternationCantCapture) + } + } + + case 'P': + if p.useRE2() { + // support for P syntax + if p.charsRight() < 3 { + goto BreakRecognize + } + + ch = p.moveRightGetChar() + if ch != '<' { + goto BreakRecognize + } + + ch = p.moveRightGetChar() + p.moveLeft() + + if IsWordChar(ch) { + capnum := -1 + capname, err := p.scanCapname() + if err != nil { + return nil, err + } + + if p.isCaptureName(capname) { + capnum = p.captureSlotFromName(capname) + } + + // check if we have bogus character after the name + if p.charsRight() > 0 && p.rightChar(0) != '>' { + return nil, p.getErr(ErrInvalidGroupName) + } + + // actually make the node + + if capnum != -1 && p.charsRight() > 0 && p.moveRightGetChar() == '>' { + p.consumeCaptureSlot(capnum) + return newRegexNodeMN(NtCapture, p.options, capnum, -1), nil + } + goto BreakRecognize + + } else { + // bad group name - starts with something other than a word character and isn't a number + return nil, p.getErr(ErrInvalidGroupName) + } + } + // if we're not using RE2 compat mode then + // we just behave like normal + fallthrough + + default: + p.moveLeft() + + nt = NtGroup + // disallow options in the children of a testgroup node + if p.group.T != NtExprCond { + p.scanOptions() + } + if p.charsRight() == 0 { + goto BreakRecognize + } + + if ch = p.moveRightGetChar(); ch == ')' { + return nil, nil + } + + if ch != ':' { + goto BreakRecognize + } + + } + + return newRegexNode(nt, p.options), nil + } + +BreakRecognize: + + // break Recognize comes here + + return nil, p.getErr(ErrUnrecognizedGrouping, string(p.pattern[start:p.textpos()])) +} + +// scans backslash specials and basics +func (p *parser) scanBackslash(scanOnly bool) (*RegexNode, error) { + + if p.charsRight() == 0 { + return nil, p.getErr(ErrIllegalEndEscape) + } + + switch ch := p.rightChar(0); ch { + case 'b', 'B', 'A', 'G', 'Z', 'z': + p.moveRight(1) + return newRegexNode(p.typeFromCode(ch), p.options), nil + + case 'w': + p.moveRight(1) + if p.useOptionE() || p.useRE2() { + return newRegexNodeSet(NtSet, p.options, ECMAWordClass()), nil + } + return newRegexNodeSet(NtSet, p.options, WordClass()), nil + + case 'W': + p.moveRight(1) + if p.useOptionE() || p.useRE2() { + return newRegexNodeSet(NtSet, p.options, NotECMAWordClass()), nil + } + return newRegexNodeSet(NtSet, p.options, NotWordClass()), nil + + case 's': + p.moveRight(1) + if p.useOptionE() { + return newRegexNodeSet(NtSet, p.options, ECMASpaceClass()), nil + } else if p.useRE2() { + return newRegexNodeSet(NtSet, p.options, RE2SpaceClass()), nil + } + return newRegexNodeSet(NtSet, p.options, SpaceClass()), nil + + case 'S': + p.moveRight(1) + if p.useOptionE() { + return newRegexNodeSet(NtSet, p.options, NotECMASpaceClass()), nil + } else if p.useRE2() { + return newRegexNodeSet(NtSet, p.options, NotRE2SpaceClass()), nil + } + return newRegexNodeSet(NtSet, p.options, NotSpaceClass()), nil + + case 'd': + p.moveRight(1) + if p.useOptionE() || p.useRE2() { + return newRegexNodeSet(NtSet, p.options, ECMADigitClass()), nil + } + return newRegexNodeSet(NtSet, p.options, DigitClass()), nil + + case 'D': + p.moveRight(1) + if p.useOptionE() || p.useRE2() { + return newRegexNodeSet(NtSet, p.options, NotECMADigitClass()), nil + } + return newRegexNodeSet(NtSet, p.options, NotDigitClass()), nil + + case 'p', 'P': + // skip if we're in ECMAScript mode WITHOUT unicode flag + if p.useOptionE() && !p.useOptionU() { + return p.scanBasicBackslash(scanOnly) + } + + p.moveRight(1) + prop, err := p.parseProperty() + if err != nil { + return nil, err + } + cc := &CharSet{} + cc.addCategory(prop, (ch != 'p'), p.useOptionI()) + if p.useOptionI() { + cc.addLowercase() + } + + return newRegexNodeSet(NtSet, p.options, cc), nil + + default: + return p.scanBasicBackslash(scanOnly) + } +} + +// Scans \-style backreferences and character escapes +func (p *parser) scanBasicBackslash(scanOnly bool) (*RegexNode, error) { + if p.charsRight() == 0 { + return nil, p.getErr(ErrIllegalEndEscape) + } + angled := false + k := false + close := '\x00' + + backpos := p.textpos() + ch := p.rightChar(0) + + // Allow \k instead of \, which is now deprecated. + + // According to ECMAScript specification, \k is only parsed as a named group reference if + // there is at least one group name in the regexp. + // See https://www.ecma-international.org/ecma-262/#sec-isvalidregularexpressionliteral, step 7. + // Note, during the first (scanOnly) run we may not have all group names scanned, but that's ok. + if ch == 'k' && (!p.useOptionE() || p.useOptionU() || len(p.capnames) > 0) { + if p.charsRight() >= 2 { + p.moveRight(1) + ch = p.moveRightGetChar() + + if ch == '<' || (!p.useOptionE() && ch == '\'') { // No support for \k'name' in ECMAScript + angled = true + if ch == '\'' { + close = '\'' + } else { + close = '>' + } + } + } + + if !angled || p.charsRight() <= 0 { + return nil, p.getErr(ErrMalformedNameRef) + } + + ch = p.rightChar(0) + k = true + + } else if !p.useOptionE() && (ch == '<' || ch == '\'') && p.charsRight() > 1 { // Note angle without \g + angled = true + if ch == '\'' { + close = '\'' + } else { + close = '>' + } + + p.moveRight(1) + ch = p.rightChar(0) + } + + // Try to parse backreference: \<1> or \ + + if angled && ch >= '0' && ch <= '9' { + capnum, err := p.scanDecimal() + if err != nil { + return nil, err + } + + if p.charsRight() > 0 && p.moveRightGetChar() == close { + if p.isCaptureSlot(capnum) { + return newRegexNodeM(NtRef, p.options, capnum), nil + } + return nil, p.getErr(ErrUndefinedBackRef, capnum) + } + } else if !angled && ch >= '1' && ch <= '9' { // Try to parse backreference or octal: \1 + capnum, err := p.scanDecimal() + if err != nil { + return nil, err + } + + if scanOnly { + return nil, nil + } + + if p.isCaptureSlot(capnum) { + return newRegexNodeM(NtRef, p.options, capnum), nil + } + if capnum <= 9 && !p.useOptionE() { + return nil, p.getErr(ErrUndefinedBackRef, capnum) + } + + } else if angled { + capname, err := p.scanCapname() + if err != nil { + return nil, err + } + + if capname != "" && p.charsRight() > 0 && p.moveRightGetChar() == close { + + if scanOnly { + return nil, nil + } + + if p.isCaptureName(capname) { + return newRegexNodeM(NtRef, p.options, p.captureSlotFromName(capname)), nil + } + return nil, p.getErr(ErrUndefinedNameRef, capname) + } else { + if k { + return nil, p.getErr(ErrMalformedNameRef) + } + } + } + + // Not backreference: must be char code + + p.textto(backpos) + ch, err := p.scanCharEscape() + if err != nil { + return nil, err + } + + if scanOnly { + return nil, nil + } + + if p.useOptionI() { + ch = unicode.ToLower(ch) + } + + return newRegexNodeCh(NtOne, p.options, ch), nil +} + +// Scans X for \p{X} or \P{X} +func (p *parser) parseProperty() (string, error) { + // RE2 and PCRE supports \pX syntax (no {} and only 1 letter unicode cats supported) + // since this is purely additive syntax it's not behind a flag + if p.charsRight() >= 1 && p.rightChar(0) != '{' && (!p.useOptionE() || !p.useOptionU()) { + ch := string(p.moveRightGetChar()) + // check if it's a valid cat + if !isValidUnicodeCat(ch) { + return "", p.getErr(ErrUnknownSlashP, ch) + } + return ch, nil + } + + if p.charsRight() < 3 { + return "", p.getErr(ErrIncompleteSlashP) + } + ch := p.moveRightGetChar() + if ch != '{' { + return "", p.getErr(ErrMalformedSlashP) + } + + startpos := p.textpos() + for p.charsRight() > 0 { + ch = p.moveRightGetChar() + if !IsWordChar(ch) && ch != '-' { + p.moveLeft() + break + } + } + capname := string(p.pattern[startpos:p.textpos()]) + + if p.charsRight() == 0 || p.moveRightGetChar() != '}' { + return "", p.getErr(ErrIncompleteSlashP) + } + + if !isValidUnicodeCat(capname) { + return "", p.getErr(ErrUnknownSlashP, capname) + } + + return capname, nil +} + +// Returns ReNode type for zero-length assertions with a \ code. +func (p *parser) typeFromCode(ch rune) NodeType { + switch ch { + case 'b': + if p.useOptionE() { + return NtECMABoundary + } + return NtBoundary + case 'B': + if p.useOptionE() { + return NtNonECMABoundary + } + return NtNonboundary + case 'A': + return NtBeginning + case 'G': + return NtStart + case 'Z': + return NtEndZ + case 'z': + return NtEnd + default: + return NtNothing + } +} + +// Scans whitespace or x-mode comments. +func (p *parser) scanBlank() error { + if p.useOptionX() { + for { + for p.charsRight() > 0 && isSpace(p.rightChar(0)) { + p.moveRight(1) + } + + if p.charsRight() == 0 { + break + } + + if p.rightChar(0) == '#' { + for p.charsRight() > 0 && p.rightChar(0) != '\n' { + p.moveRight(1) + } + } else if p.charsRight() >= 3 && p.rightChar(2) == '#' && + p.rightChar(1) == '?' && p.rightChar(0) == '(' { + for p.charsRight() > 0 && p.rightChar(0) != ')' { + p.moveRight(1) + } + if p.charsRight() == 0 { + return p.getErr(ErrUnterminatedComment) + } + p.moveRight(1) + } else { + break + } + } + } else { + for { + if p.charsRight() < 3 || p.rightChar(2) != '#' || + p.rightChar(1) != '?' || p.rightChar(0) != '(' { + return nil + } + + for p.charsRight() > 0 && p.rightChar(0) != ')' { + p.moveRight(1) + } + if p.charsRight() == 0 { + return p.getErr(ErrUnterminatedComment) + } + p.moveRight(1) + } + } + return nil +} + +func (p *parser) scanECMACapname() (string, error) { + startpos := p.textpos() + var sb strings.Builder + hasEscape := false + index := 0 + + for p.charsRight() > 0 { + savedpos := p.textpos() + ch := p.moveRightGetChar() + var err error + escaped := false + + if ch == '\\' { + if p.charsRight() == 0 || p.rightChar(0) != 'u' { + return "", p.getErr(ErrInvalidECMAGroupName) + } + escaped = true + p.moveRight(1) + if p.charsRight() > 0 && p.rightChar(0) == '{' { + if !p.useOptionU() { + return "", p.getErr(ErrInvalidECMAGroupName) + } + p.moveRight(1) + ch, err = p.scanHexUntilBrace() + } else { + ch, err = p.scanHex(4) + } + if err != nil { + return "", err + } + if !hasEscape { + sb.WriteString(string(p.pattern[startpos:savedpos])) + hasEscape = true + } + } + + valid := IsECMAIdentifierChar(ch) + if index == 0 { + valid = IsECMAIdentifierStartChar(ch) + } + if !valid { + if escaped { + return "", p.getErr(ErrInvalidECMAGroupName) + } + p.textto(savedpos) + break + } + if hasEscape { + sb.WriteRune(ch) + } + index++ + } + + if hasEscape { + return sb.String(), nil + } + return string(p.pattern[startpos:p.textpos()]), nil +} + +func (p *parser) scanWord() string { + startpos := p.textpos() + + for p.charsRight() > 0 { + if !IsWordChar(p.moveRightGetChar()) { + p.moveLeft() + break + } + } + + return string(p.pattern[startpos:p.textpos()]) +} + +func (p *parser) scanCapname() (string, error) { + if p.useOptionE() { + return p.scanECMACapname() + } + + return p.scanWord(), nil +} + +// Scans contents of [] (not including []'s), and converts to a set. +func (p *parser) scanCharSet(caseInsensitive, scanOnly bool) (*CharSet, error) { + var ch rune + chPrev := '\x00' + inRange := false + firstChar := true + closed := false + + var cc *CharSet + if !scanOnly { + cc = &CharSet{} + } + + if p.charsRight() > 0 && p.rightChar(0) == '^' { + p.moveRight(1) + if !scanOnly { + cc.negate = true + } + } + + for ; p.charsRight() > 0; firstChar = false { + fTranslatedChar := false + ch = p.moveRightGetChar() + if ch == ']' { + if !firstChar { + closed = true + break + } else if p.useOptionE() { + if !scanOnly { + cc.addRanges(NoneClass().ranges) + } + closed = true + break + } + + } else if ch == '\\' && p.charsRight() > 0 { + switch ch = p.moveRightGetChar(); ch { + case 'D', 'd': + if !scanOnly { + if inRange { + if !p.useOptionE() { + return nil, p.getErr(ErrBadClassInCharRange, ch) + } + cc.addChar(chPrev) + cc.addChar('-') + inRange = false + } + cc.addDigit(p.useOptionE() || p.useRE2(), ch == 'D') + } + continue + + case 'S', 's': + if !scanOnly { + if inRange { + if !p.useOptionE() { + return nil, p.getErr(ErrBadClassInCharRange, ch) + } + cc.addChar(chPrev) + cc.addChar('-') + inRange = false + } + cc.addSpace(p.useOptionE(), p.useRE2(), ch == 'S') + } + continue + + case 'W', 'w': + if !scanOnly { + if inRange { + if !p.useOptionE() { + return nil, p.getErr(ErrBadClassInCharRange, ch) + } + cc.addChar(chPrev) + cc.addChar('-') + inRange = false + } + + cc.addWord(p.useOptionE() || p.useRE2(), ch == 'W') + } + continue + + case 'p', 'P': + if p.useOptionE() && !p.useOptionU() && ch == 'P' && inRange { + return nil, p.getErr(ErrShorthandClassInCharRange, string(ch)) + } + if p.useOptionE() && !p.useOptionU() && ch == 'p' { + if !scanOnly { + if inRange { + if chPrev > ch { + return nil, p.getErr(ErrReversedCharRange, chPrev, ch) + } + cc.addRange(chPrev, ch) + inRange = false + } else if p.charsRight() >= 2 && p.rightChar(0) == '-' && p.rightChar(1) != ']' { + cc.addChar('-') + p.moveRight(1) + chLast := p.moveRightGetChar() + if ch > chLast { + return nil, p.getErr(ErrReversedCharRange, ch, chLast) + } + cc.addRange(ch, chLast) + } else { + cc.addChar(ch) + } + } + continue + } + if !scanOnly { + prop, err := p.parseProperty() + if err != nil { + return nil, err + } + if inRange { + return nil, p.getErr(ErrShorthandClassInCharRange, string(ch)) + } + cc.addCategory(prop, (ch != 'p'), caseInsensitive) + } else { + if _, err := p.parseProperty(); err != nil { + return nil, err + } + } + + continue + + case '-': + if !scanOnly { + cc.addRange(ch, ch) + } + continue + + default: + p.moveLeft() + var err error + ch, err = p.scanCharEscape() // non-literal character + if err != nil { + return nil, err + } + fTranslatedChar = true + } + } else if ch == '[' { + // This is code for Posix style properties - [:Ll:] or [:IsTibetan:]. + // It currently doesn't do anything other than skip the whole thing! + if p.charsRight() > 0 && p.rightChar(0) == ':' && !inRange { + savePos := p.textpos() + + p.moveRight(1) + negate := false + if p.charsRight() > 1 && p.rightChar(0) == '^' { + negate = true + p.moveRight(1) + } + + nm := p.scanWord() // snag the name + if !scanOnly && p.useRE2() { + // look up the name since these are valid for RE2 + // add the group based on the name + if ok := cc.addNamedASCII(nm, negate); !ok { + return nil, p.getErr(ErrInvalidCharRange) + } + } + if p.charsRight() < 2 || p.moveRightGetChar() != ':' || p.moveRightGetChar() != ']' { + p.textto(savePos) + } else if p.useRE2() { + // move on + continue + } + } + } + + if inRange { + inRange = false + if !scanOnly { + if ch == '[' && !fTranslatedChar && !firstChar { + // We thought we were in a range, but we're actually starting a subtraction. + // In that case, we'll add chPrev to our char class, skip the opening [, and + // scan the new character class recursively. + cc.addChar(chPrev) + sub, err := p.scanCharSet(caseInsensitive, false) + if err != nil { + return nil, err + } + cc.addSubtraction(sub) + + if p.charsRight() > 0 && p.rightChar(0) != ']' { + return nil, p.getErr(ErrSubtractionMustBeLast) + } + } else { + // a regular range, like a-z + if chPrev > ch { + return nil, p.getErr(ErrReversedCharRange, chPrev, ch) + } + cc.addRange(chPrev, ch) + } + } + } else if p.charsRight() >= 2 && p.rightChar(0) == '-' && p.rightChar(1) != ']' { + // this could be the start of a range + chPrev = ch + inRange = true + p.moveRight(1) + } else if p.charsRight() >= 1 && ch == '-' && !fTranslatedChar && p.rightChar(0) == '[' && !firstChar { + // we aren't in a range, and now there is a subtraction. Usually this happens + // only when a subtraction follows a range, like [a-z-[b]] + if !scanOnly { + p.moveRight(1) + sub, err := p.scanCharSet(caseInsensitive, false) + if err != nil { + return nil, err + } + cc.addSubtraction(sub) + + if p.charsRight() > 0 && p.rightChar(0) != ']' { + return nil, p.getErr(ErrSubtractionMustBeLast) + } + } else { + p.moveRight(1) + _, _ = p.scanCharSet(caseInsensitive, true) + } + } else { + if !scanOnly { + cc.addRange(ch, ch) + } + } + } + + if !closed { + return nil, p.getErr(ErrUnterminatedBracket) + } + + if !scanOnly && caseInsensitive { + cc.addLowercase() + } + + return cc, nil +} + +// Scans any number of decimal digits (pegs value at 2^31-1 if too large) +func (p *parser) scanDecimal() (int, error) { + i := 0 + var d int + + for p.charsRight() > 0 { + d = int(p.rightChar(0) - '0') + if d < 0 || d > 9 { + break + } + p.moveRight(1) + + if i > maxValueDiv10 || (i == maxValueDiv10 && d > maxValueMod10) { + return 0, p.getErr(ErrCaptureGroupOutOfRange) + } + + i *= 10 + i += d + } + + return int(i), nil +} + +// Returns true for options allowed only at the top level +func isOnlyTopOption(option RegexOptions) bool { + return option == RightToLeft || option == ECMAScript || option == RE2 +} + +// Scans imnsxu-imnsxu option string, stops at the first unrecognized char. +func (p *parser) scanOptions() { + + for off := false; p.charsRight() > 0; p.moveRight(1) { + ch := p.rightChar(0) + + switch ch { + case '-': + off = true + case '+': + off = false + default: + option := optionFromCode(ch) + if option == 0 || isOnlyTopOption(option) { + return + } + + if off { + p.options &= ^option + } else { + p.options |= option + } + } + } +} + +// Scans \ code for escape codes that map to single unicode chars. +func (p *parser) scanCharEscape() (r rune, err error) { + + ch := p.moveRightGetChar() + + if ch >= '0' && ch <= '7' { + p.moveLeft() + return p.scanOctal(), nil + } + + pos := p.textpos() + + switch ch { + case 'x': + // support for \x{HEX} syntax from Perl and PCRE + if p.charsRight() > 0 && p.rightChar(0) == '{' { + if p.useOptionE() { + return ch, nil + } + p.moveRight(1) + return p.scanHexUntilBrace() + } else { + r, err = p.scanHex(2) + } + case 'u': + // ECMAscript suppot \u{HEX} only if `u` is also set + if p.useOptionE() && p.useOptionU() && p.charsRight() > 0 && p.rightChar(0) == '{' { + p.moveRight(1) + return p.scanHexUntilBrace() + } else { + r, err = p.scanHex(4) + } + case 'a': + return '\u0007', nil + case 'b': + return '\b', nil + case 'e': + return '\u001B', nil + case 'f': + return '\f', nil + case 'n': + return '\n', nil + case 'r': + return '\r', nil + case 't': + return '\t', nil + case 'v': + return '\u000B', nil + case 'c': + r, err = p.scanControl() + default: + if !p.useOptionE() && !p.useRE2() && IsWordChar(ch) { + return 0, p.getErr(ErrUnrecognizedEscape, string(ch)) + } + return ch, nil + } + if err != nil && p.useOptionE() { + p.textto(pos) + return ch, nil + } + return +} + +// Grabs and converts an ascii control character +func (p *parser) scanControl() (rune, error) { + if p.charsRight() <= 0 { + return 0, p.getErr(ErrMissingControl) + } + + ch := p.moveRightGetChar() + + // \ca interpreted as \cA + + if ch >= 'a' && ch <= 'z' { + ch = (ch - ('a' - 'A')) + } + ch = (ch - '@') + if ch >= 0 && ch < ' ' { + return ch, nil + } + + return 0, p.getErr(ErrUnrecognizedControl) + +} + +// Scan hex digits until we hit a closing brace. +// Non-hex digits, hex value too large for UTF-8, or running out of chars are errors +func (p *parser) scanHexUntilBrace() (rune, error) { + // PCRE spec reads like unlimited hex digits are allowed, but unicode has a limit + // so we can enforce that + i := 0 + hasContent := false + + for p.charsRight() > 0 { + ch := p.moveRightGetChar() + if ch == '}' { + // hit our close brace, we're done here + // prevent \x{} + if !hasContent { + return 0, p.getErr(ErrTooFewHex) + } + return rune(i), nil + } + hasContent = true + // no brace needs to be hex digit + d := hexDigit(ch) + if d < 0 { + return 0, p.getErr(ErrMissingBrace) + } + + i *= 0x10 + i += d + + if i > unicode.MaxRune { + return 0, p.getErr(ErrInvalidHex) + } + } + + // we only make it here if we run out of digits without finding the brace + return 0, p.getErr(ErrMissingBrace) +} + +// Scans exactly c hex digits (c=2 for \xFF, c=4 for \uFFFF) +func (p *parser) scanHex(c int) (rune, error) { + + i := 0 + + if p.charsRight() >= c { + for c > 0 { + d := hexDigit(p.moveRightGetChar()) + if d < 0 { + break + } + i *= 0x10 + i += d + c-- + } + } + + if c > 0 { + return 0, p.getErr(ErrTooFewHex) + } + + return rune(i), nil +} + +// Returns n <= 0xF for a hex digit. +func hexDigit(ch rune) int { + + if d := uint(ch - '0'); d <= 9 { + return int(d) + } + + if d := uint(ch - 'a'); d <= 5 { + return int(d + 0xa) + } + + if d := uint(ch - 'A'); d <= 5 { + return int(d + 0xa) + } + + return -1 +} + +// Scans up to three octal digits (stops before exceeding 0377). +func (p *parser) scanOctal() rune { + // Consume octal chars only up to 3 digits and value 0377 + + c := 3 + + if c > p.charsRight() { + c = p.charsRight() + } + + //we know the first char is good because the caller had to check + i := 0 + d := int(p.rightChar(0) - '0') + for c > 0 && d <= 7 && d >= 0 { + if i >= 0x20 && p.useOptionE() { + break + } + i *= 8 + i += d + c-- + + p.moveRight(1) + if !p.rightMost() { + d = int(p.rightChar(0) - '0') + } + } + + // Octal codes only go up to 255. Any larger and the behavior that Perl follows + // is simply to truncate the high bits. + i &= 0xFF + + return rune(i) +} + +// Returns the current parsing position. +func (p *parser) textpos() int { + return p.currentPos +} + +// Zaps to a specific parsing position. +func (p *parser) textto(pos int) { + p.currentPos = pos +} + +// Returns the char at the right of the current parsing position and advances to the right. +func (p *parser) moveRightGetChar() rune { + ch := p.pattern[p.currentPos] + p.currentPos++ + return ch +} + +// Moves the current position to the right. +func (p *parser) moveRight(i int) { + // default would be 1 + p.currentPos += i +} + +// Moves the current parsing position one to the left. +func (p *parser) moveLeft() { + p.currentPos-- +} + +// Returns the char left of the current parsing position. +func (p *parser) charAt(i int) rune { + return p.pattern[i] +} + +// Returns the char i chars right of the current parsing position. +func (p *parser) rightChar(i int) rune { + // default would be 0 + return p.pattern[p.currentPos+i] +} + +// Number of characters to the right of the current parsing position. +func (p *parser) charsRight() int { + return len(p.pattern) - p.currentPos +} + +func (p *parser) rightMost() bool { + return p.currentPos == len(p.pattern) +} + +// Looks up the slot number for a given name +func (p *parser) captureSlotFromName(capname string) int { + return p.capnames[capname] +} + +// True if the capture slot was noted +func (p *parser) isCaptureSlot(i int) bool { + if p.caps != nil { + _, ok := p.caps[i] + return ok + } + + return (i >= 0 && i < p.capsize) +} + +// Looks up the slot number for a given name +func (p *parser) isCaptureName(capname string) bool { + if p.capnames == nil { + return false + } + + _, ok := p.capnames[capname] + return ok +} + +// option shortcuts + +// True if N option disabling '(' autocapture is on. +func (p *parser) useOptionN() bool { + return (p.options & ExplicitCapture) != 0 +} + +// True if I option enabling case-insensitivity is on. +func (p *parser) useOptionI() bool { + return (p.options & IgnoreCase) != 0 +} + +// True if M option altering meaning of $ and ^ is on. +func (p *parser) useOptionM() bool { + return (p.options & Multiline) != 0 +} + +// True if S option altering meaning of . is on. +func (p *parser) useOptionS() bool { + return (p.options & Singleline) != 0 +} + +// True if X option enabling whitespace/comment mode is on. +func (p *parser) useOptionX() bool { + return (p.options & IgnorePatternWhitespace) != 0 +} + +// True if E option enabling ECMAScript behavior on. +func (p *parser) useOptionE() bool { + return (p.options & ECMAScript) != 0 +} + +// true to use RE2 compatibility parsing behavior. +func (p *parser) useRE2() bool { + return (p.options & RE2) != 0 +} + +// True if U option enabling ECMAScript's Unicode behavior on. +func (p *parser) useOptionU() bool { + return (p.options & Unicode) != 0 +} + +// True if options stack is empty. +func (p *parser) emptyOptionsStack() bool { + return len(p.optionsStack) == 0 +} + +// Finish the current quantifiable (when a quantifier is not found or is not possible) +func (p *parser) addConcatenate() { + // The first (| inside a Testgroup group goes directly to the group + p.concatenation.addChild(p.unit) + p.unit = nil +} + +// Finish the current quantifiable (when a quantifier is found) +func (p *parser) addConcatenate3(lazy bool, min, max int) { + p.concatenation.addChild(p.unit.makeQuantifier(lazy, min, max)) + p.unit = nil +} + +// Sets the current unit to a single char node +func (p *parser) addUnitOne(ch rune) { + p.unit = newRegexNodeCh(NtOne, p.options, ch) +} + +// Sets the current unit to a single inverse-char node +func (p *parser) addUnitNotone(ch rune) { + p.unit = newRegexNodeCh(NtNotone, p.options, ch) +} + +// Sets the current unit to a single set node +func (p *parser) addUnitSet(set *CharSet) { + p.unit = newRegexNodeSet(NtSet, p.options, set) +} + +// Sets the current unit to a subtree +func (p *parser) addUnitNode(node *RegexNode) { + p.unit = node +} + +// Sets the current unit to an assertion of the specified type +func (p *parser) addUnitType(t NodeType) { + p.unit = newRegexNode(t, p.options) +} + +// Finish the current group (in response to a ')' or end) +func (p *parser) addGroup() error { + if p.group.T == NtExprCond || p.group.T == NtBackRefCond { + p.group.addChild(p.concatenation.reverseLeft()) + if (p.group.T == NtBackRefCond && len(p.group.Children) > 2) || len(p.group.Children) > 3 { + return p.getErr(ErrTooManyAlternates) + } + } else { + p.alternation.addChild(p.concatenation.reverseLeft()) + p.group.addChild(p.alternation) + } + + p.unit = p.group + return nil +} + +// Pops the option stack, but keeps the current options unchanged. +func (p *parser) popKeepOptions() { + lastIdx := len(p.optionsStack) - 1 + p.optionsStack = p.optionsStack[:lastIdx] +} + +// Recalls options from the stack. +func (p *parser) popOptions() { + lastIdx := len(p.optionsStack) - 1 + // get the last item on the stack and then remove it by reslicing + p.options = p.optionsStack[lastIdx] + p.optionsStack = p.optionsStack[:lastIdx] +} + +// Saves options on a stack. +func (p *parser) pushOptions() { + p.optionsStack = append(p.optionsStack, p.options) +} + +// Add a string to the last concatenate. +func (p *parser) addToConcatenate(pos, cch int, isReplacement bool) { + if cch == 0 { + return + } + if cch == 1 { + var node *RegexNode + + if isReplacement { + node = newRegexNodeCh(NtOne, p.options&^IgnoreCase, p.pattern[pos]) + } else { + node = newRegexNodeCh(NtOne, p.options, p.pattern[pos]) + } + p.concatenation.addChild(node) + return + } + + if cch > 1 && (!p.useOptionI() || isReplacement || !anyParticipateInCaseConversion(p.pattern[pos:pos+cch])) { + str := make([]rune, cch) + copy(str, p.pattern[pos:pos+cch]) + p.concatenation.addChild(newRegexNodeStr(NtMulti, p.options&^IgnoreCase, str)) + return + } + + // each letter gets an upper-lower range + for i := pos; i < pos+cch; i++ { + p.concatenation.addChild(newRegexNodeCh(NtOne, p.options, p.pattern[i])) + } +} + +// Push the parser state (in response to an open paren) +func (p *parser) pushGroup() { + p.group.Parent = p.stack + p.alternation.Parent = p.group + p.concatenation.Parent = p.alternation + p.stack = p.concatenation +} + +// Remember the pushed state (in response to a ')') +func (p *parser) popGroup() error { + p.concatenation = p.stack + p.alternation = p.concatenation.Parent + p.group = p.alternation.Parent + p.stack = p.group.Parent + + // The first () inside a Testgroup group goes directly to the group + if p.group.T == NtExprCond && len(p.group.Children) == 0 { + if p.unit == nil { + return p.getErr(ErrConditionalExpression) + } + + p.group.addChild(p.unit) + p.unit = nil + } + return nil +} + +// True if the group stack is empty. +func (p *parser) emptyStack() bool { + return p.stack == nil +} + +// Start a new round for the parser state (in response to an open paren or string start) +func (p *parser) startGroup(openGroup *RegexNode) { + p.group = openGroup + p.alternation = newRegexNode(NtAlternate, p.options) + p.concatenation = newRegexNode(NtConcatenate, p.options) +} + +// Finish the current concatenation (in response to a |) +func (p *parser) addAlternate() { + // The | parts inside a Testgroup group go directly to the group + + if p.group.T == NtExprCond || p.group.T == NtBackRefCond { + p.group.addChild(p.concatenation.reverseLeft()) + } else { + p.alternation.addChild(p.concatenation.reverseLeft()) + } + + p.concatenation = newRegexNode(NtConcatenate, p.options) +} + +// For categorizing ascii characters. + +const ( + Q byte = 5 // quantifier + S byte = 4 // ordinary stopper + Z byte = 3 // ScanBlank stopper + X byte = 2 // whitespace + E byte = 1 // should be escaped +) + +var _category = []byte{ + //01 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 0, 0, 0, 0, 0, 0, 0, 0, X, X, X, X, X, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + X, 0, 0, Z, S, 0, 0, 0, S, S, Q, Q, 0, 0, S, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Q, + //@A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, S, 0, + //'a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Q, S, 0, 0, 0, +} + +func isSpace(ch rune) bool { + return (ch <= ' ' && _category[ch] == X) +} + +// Returns true for those characters that terminate a string of ordinary chars. +func isSpecial(ch rune) bool { + return (ch <= '|' && _category[ch] >= S) +} + +// Returns true for those characters that terminate a string of ordinary chars. +func isStopperX(ch rune) bool { + return (ch <= '|' && _category[ch] >= X) +} + +// Returns true for those characters that begin a quantifier. +func isQuantifier(ch rune) bool { + return (ch <= '{' && _category[ch] >= Q) +} + +func (p *parser) isTrueQuantifier() bool { + nChars := p.charsRight() + if nChars == 0 { + return false + } + + startpos := p.textpos() + ch := p.charAt(startpos) + if ch != '{' { + return ch <= '{' && _category[ch] >= Q + } + + //UGLY: this is ugly -- the original code was ugly too + pos := startpos + for { + nChars-- + if nChars <= 0 { + break + } + pos++ + ch = p.charAt(pos) + if ch < '0' || ch > '9' { + break + } + } + + if nChars == 0 || pos-startpos == 1 { + return false + } + if ch == '}' { + return true + } + if ch != ',' { + return false + } + for { + nChars-- + if nChars <= 0 { + break + } + pos++ + ch = p.charAt(pos) + if ch < '0' || ch > '9' { + break + } + } + + return nChars > 0 && ch == '}' +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/prefix.go b/vendor/github.com/dlclark/regexp2/v2/syntax/prefix.go new file mode 100644 index 000000000..af5a8c06b --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/prefix.go @@ -0,0 +1,943 @@ +package syntax + +import ( + "bytes" + "fmt" + "strconv" + "unicode" + "unicode/utf8" +) + +type Prefix struct { + PrefixStr []rune + PrefixSet CharSet + CaseInsensitive bool +} + +// It takes a RegexTree and computes the set of chars that can start it. +func getFirstCharsPrefix(tree *RegexTree) *Prefix { + s := regexFcd{ + fcStack: make([]regexFc, 32), + intStack: make([]int, 32), + } + fc := s.regexFCFromRegexTree(tree) + + if fc == nil || fc.nullable || fc.cc.IsEmpty() { + return nil + } + fcSet := fc.getFirstChars() + return &Prefix{PrefixSet: fcSet, CaseInsensitive: fc.caseInsensitive} +} + +type regexFcd struct { + intStack []int + intDepth int + fcStack []regexFc + fcDepth int + skipAllChildren bool // don't process any more children at the current level + skipchild bool // don't process the current child. + failed bool +} + +/* + * The main FC computation. It does a shortcutted depth-first walk + * through the tree and calls CalculateFC to emits code before + * and after each child of an interior node, and at each leaf. + */ +func (s *regexFcd) regexFCFromRegexTree(tree *RegexTree) *regexFc { + curNode := tree.Root + curChild := 0 + + for { + if len(curNode.Children) == 0 { + // This is a leaf node + s.calculateFC(curNode.T, curNode, 0) + } else if curChild < len(curNode.Children) && !s.skipAllChildren { + // This is an interior node, and we have more children to analyze + s.calculateFC(curNode.T|BeforeChild, curNode, curChild) + + if !s.skipchild { + curNode = curNode.Children[curChild] + // this stack is how we get a depth first walk of the tree. + s.pushInt(curChild) + curChild = 0 + } else { + curChild++ + s.skipchild = false + } + continue + } + + // This is an interior node where we've finished analyzing all the children, or + // the end of a leaf node. + s.skipAllChildren = false + + if s.intIsEmpty() { + break + } + + curChild = s.popInt() + curNode = curNode.Parent + + s.calculateFC(curNode.T|AfterChild, curNode, curChild) + if s.failed { + return nil + } + + curChild++ + } + + if s.fcIsEmpty() { + return nil + } + + return s.popFC() +} + +// To avoid recursion, we use a simple integer stack. +// This is the push. +func (s *regexFcd) pushInt(I int) { + if s.intDepth >= len(s.intStack) { + expanded := make([]int, s.intDepth*2) + copy(expanded, s.intStack) + s.intStack = expanded + } + + s.intStack[s.intDepth] = I + s.intDepth++ +} + +// True if the stack is empty. +func (s *regexFcd) intIsEmpty() bool { + return s.intDepth == 0 +} + +// This is the pop. +func (s *regexFcd) popInt() int { + s.intDepth-- + return s.intStack[s.intDepth] +} + +// We also use a stack of RegexFC objects. +// This is the push. +func (s *regexFcd) pushFC(fc regexFc) { + if s.fcDepth >= len(s.fcStack) { + expanded := make([]regexFc, s.fcDepth*2) + copy(expanded, s.fcStack) + s.fcStack = expanded + } + + s.fcStack[s.fcDepth] = fc + s.fcDepth++ +} + +// True if the stack is empty. +func (s *regexFcd) fcIsEmpty() bool { + return s.fcDepth == 0 +} + +// This is the pop. +func (s *regexFcd) popFC() *regexFc { + s.fcDepth-- + return &s.fcStack[s.fcDepth] +} + +// This is the top. +func (s *regexFcd) topFC() *regexFc { + return &s.fcStack[s.fcDepth-1] +} + +// Called in Beforechild to prevent further processing of the current child +func (s *regexFcd) skipChild() { + s.skipchild = true +} + +// FC computation and shortcut cases for each node type +func (s *regexFcd) calculateFC(nt NodeType, node *RegexNode, CurIndex int) { + //fmt.Printf("NodeType: %v, CurIndex: %v, Desc: %v\n", nt, CurIndex, node.description()) + ci := false + rtl := false + + if nt <= NtRef { + if (node.Options & IgnoreCase) != 0 { + ci = true + } + if (node.Options & RightToLeft) != 0 { + rtl = true + } + } + + switch nt { + case NtConcatenate | BeforeChild, NtAlternate | BeforeChild, NtBackRefCond | BeforeChild, NtLoop | BeforeChild, NtLazyloop | BeforeChild: + + case NtExprCond | BeforeChild: + if CurIndex == 0 { + s.skipChild() + } + + case NtEmpty: + s.pushFC(regexFc{nullable: true}) + + case NtConcatenate | AfterChild: + if CurIndex != 0 { + child := s.popFC() + cumul := s.topFC() + + s.failed = !cumul.addFC(*child, true) + } + + fc := s.topFC() + if !fc.nullable { + s.skipAllChildren = true + } + + case NtExprCond | AfterChild: + if CurIndex > 1 { + child := s.popFC() + cumul := s.topFC() + + s.failed = !cumul.addFC(*child, false) + } + + case NtAlternate | AfterChild, NtBackRefCond | AfterChild: + if CurIndex != 0 { + child := s.popFC() + cumul := s.topFC() + + s.failed = !cumul.addFC(*child, false) + } + + case NtLoop | AfterChild, NtLazyloop | AfterChild: + if node.M == 0 { + fc := s.topFC() + fc.nullable = true + } + + case NtGroup | BeforeChild, NtGroup | AfterChild, NtCapture | BeforeChild, NtCapture | AfterChild, NtAtomic | BeforeChild, NtAtomic | AfterChild: + + case NtPosLook | BeforeChild, NtNegLook | BeforeChild: + s.skipChild() + s.pushFC(regexFc{nullable: true}) + + case NtPosLook | AfterChild, NtNegLook | AfterChild: + + case NtOne, NtNotone: + s.pushFC(newRegexFc(node.Ch, nt == NtNotone, false, ci)) + + case NtOneloop, NtOnelazy, NtOneloopatomic: + s.pushFC(newRegexFc(node.Ch, false, node.M == 0, ci)) + + case NtNotoneloop, NtNotonelazy, NtNotoneloopatomic: + s.pushFC(newRegexFc(node.Ch, true, node.M == 0, ci)) + + case NtMulti: + if len(node.Str) == 0 { + s.pushFC(regexFc{nullable: true}) + } else if !rtl { + s.pushFC(newRegexFc(node.Str[0], false, false, ci)) + } else { + s.pushFC(newRegexFc(node.Str[len(node.Str)-1], false, false, ci)) + } + + case NtSet: + s.pushFC(regexFc{cc: node.Set.Copy(), nullable: false, caseInsensitive: ci}) + + case NtSetloop, NtSetlazy, NtSetloopatomic: + s.pushFC(regexFc{cc: node.Set.Copy(), nullable: node.M == 0, caseInsensitive: ci}) + + case NtRef: + s.pushFC(regexFc{cc: *AnyClass(), nullable: true, caseInsensitive: false}) + + case NtNothing, NtBol, NtEol, NtBoundary, NtNonboundary, NtECMABoundary, NtNonECMABoundary, NtBeginning, NtStart, NtEndZ, NtEnd, NtUpdateBumpalong: + s.pushFC(regexFc{nullable: true}) + + default: + panic(fmt.Sprintf("unexpected op code: %v", nt)) + } +} + +type regexFc struct { + cc CharSet + nullable bool + caseInsensitive bool +} + +func newRegexFc(ch rune, not, nullable, caseInsensitive bool) regexFc { + r := regexFc{ + caseInsensitive: caseInsensitive, + nullable: nullable, + } + if not { + if ch > 0 { + r.cc.addRange('\x00', ch-1) + } + if ch < 0xFFFF { + r.cc.addRange(ch+1, utf8.MaxRune) + } + } else { + r.cc.addRange(ch, ch) + } + return r +} + +func (r *regexFc) getFirstChars() CharSet { + if r.caseInsensitive { + r.cc.addLowercase() + } + + return r.cc +} + +func (r *regexFc) addFC(fc regexFc, concatenate bool) bool { + if !r.cc.IsMergeable() || !fc.cc.IsMergeable() { + return false + } + + if concatenate { + if !r.nullable { + return true + } + + if !fc.nullable { + r.nullable = false + } + } else { + if fc.nullable { + r.nullable = true + } + } + + r.caseInsensitive = r.caseInsensitive || fc.caseInsensitive + r.cc.addSet(fc.cc) + + return true +} + +// This is a related computation: it takes a RegexTree and computes the +// leading substring if it sees one. It's quite trivial and gives up easily. +func getPrefix(tree *RegexTree) *Prefix { + var concatNode *RegexNode + nextChild := 0 + + curNode := tree.Root + + for { + switch curNode.T { + case NtConcatenate: + if len(curNode.Children) > 0 { + concatNode = curNode + nextChild = 0 + } + + case NtAtomic, NtCapture: + curNode = curNode.Children[0] + concatNode = nil + continue + + case NtOneloop, NtOnelazy: + if curNode.M > 0 { + return &Prefix{ + PrefixStr: repeat(curNode.Ch, curNode.M), + CaseInsensitive: (curNode.Options & IgnoreCase) != 0, + } + } + return nil + + case NtOne: + return &Prefix{ + PrefixStr: []rune{curNode.Ch}, + CaseInsensitive: (curNode.Options & IgnoreCase) != 0, + } + + case NtMulti: + return &Prefix{ + PrefixStr: curNode.Str, + CaseInsensitive: (curNode.Options & IgnoreCase) != 0, + } + + case NtBol, NtEol, NtBoundary, NtECMABoundary, NtBeginning, NtStart, + NtEndZ, NtEnd, NtEmpty, NtPosLook, NtNegLook: + + default: + return nil + } + + if concatNode == nil || nextChild >= len(concatNode.Children) { + return nil + } + + curNode = concatNode.Children[nextChild] + nextChild++ + } +} + +// repeat the rune r, c times... up to the max of MaxPrefixSize +func repeat(r rune, c int) []rune { + if c > MaxPrefixSize { + c = MaxPrefixSize + } + + ret := make([]rune, c) + + // binary growth using copy for speed + ret[0] = r + bp := 1 + for bp < len(ret) { + copy(ret[bp:], ret[:bp]) + bp *= 2 + } + + return ret +} + +// BmPrefix precomputes the Boyer-Moore +// tables for fast string scanning. These tables allow +// you to scan for the first occurrence of a string within +// a large body of text without examining every character. +// The performance of the heuristic depends on the actual +// string and the text being searched, but usually, the longer +// the string that is being searched for, the fewer characters +// need to be examined. +type BmPrefix struct { + positive []int + negativeASCII []int + negativeUnicode [][]int + pattern []rune + lowASCII rune + highASCII rune + rightToLeft bool + caseInsensitive bool +} + +func newBmPrefix(pattern []rune, caseInsensitive, rightToLeft bool) *BmPrefix { + + b := &BmPrefix{ + rightToLeft: rightToLeft, + caseInsensitive: caseInsensitive, + pattern: pattern, + } + + if caseInsensitive { + for i := 0; i < len(b.pattern); i++ { + // We do the ToLower character by character for consistency. With surrogate chars, doing + // a ToLower on the entire string could actually change the surrogate pair. This is more correct + // linguistically, but since Regex doesn't support surrogates, it's more important to be + // consistent. + + b.pattern[i] = unicode.ToLower(b.pattern[i]) + } + } + + var beforefirst, last, bump int + var scan, match int + + if !rightToLeft { + beforefirst = -1 + last = len(b.pattern) - 1 + bump = 1 + } else { + beforefirst = len(b.pattern) + last = 0 + bump = -1 + } + + // PART I - the good-suffix shift table + // + // compute the positive requirement: + // if char "i" is the first one from the right that doesn't match, + // then we know the matcher can advance by _positive[i]. + // + // This algorithm is a simplified variant of the standard + // Boyer-Moore good suffix calculation. + + b.positive = make([]int, len(b.pattern)) + + examine := last + ch := b.pattern[examine] + b.positive[examine] = bump + examine -= bump + +Outerloop: + for { + // find an internal char (examine) that matches the tail + + for { + if examine == beforefirst { + break Outerloop + } + if b.pattern[examine] == ch { + break + } + examine -= bump + } + + match = last + scan = examine + + // find the length of the match + for { + if scan == beforefirst || b.pattern[match] != b.pattern[scan] { + // at the end of the match, note the difference in _positive + // this is not the length of the match, but the distance from the internal match + // to the tail suffix. + if b.positive[match] == 0 { + b.positive[match] = match - scan + } + + // System.Diagnostics.Debug.WriteLine("Set positive[" + match + "] to " + (match - scan)); + + break + } + + scan -= bump + match -= bump + } + + examine -= bump + } + + match = last - bump + + // scan for the chars for which there are no shifts that yield a different candidate + + // The inside of the if statement used to say + // "_positive[match] = last - beforefirst;" + // This is slightly less aggressive in how much we skip, but at worst it + // should mean a little more work rather than skipping a potential match. + for match != beforefirst { + if b.positive[match] == 0 { + b.positive[match] = bump + } + + match -= bump + } + + // PART II - the bad-character shift table + // + // compute the negative requirement: + // if char "ch" is the reject character when testing position "i", + // we can slide up by _negative[ch]; + // (_negative[ch] = str.Length - 1 - str.LastIndexOf(ch)) + // + // the lookup table is divided into ASCII and Unicode portions; + // only those parts of the Unicode 16-bit code set that actually + // appear in the string are in the table. (Maximum size with + // Unicode is 65K; ASCII only case is 512 bytes.) + + b.negativeASCII = make([]int, 128) + + for i := 0; i < len(b.negativeASCII); i++ { + b.negativeASCII[i] = last - beforefirst + } + + b.lowASCII = 127 + b.highASCII = 0 + + for examine = last; examine != beforefirst; examine -= bump { + ch = b.pattern[examine] + + switch { + case ch < 128: + if b.lowASCII > ch { + b.lowASCII = ch + } + + if b.highASCII < ch { + b.highASCII = ch + } + + if b.negativeASCII[ch] == last-beforefirst { + b.negativeASCII[ch] = last - examine + } + case ch <= 0xffff: + i, j := ch>>8, ch&0xFF + + if b.negativeUnicode == nil { + b.negativeUnicode = make([][]int, 256) + } + + if b.negativeUnicode[i] == nil { + newarray := make([]int, 256) + + for k := 0; k < len(newarray); k++ { + newarray[k] = last - beforefirst + } + + if i == 0 { + copy(newarray, b.negativeASCII) + //TODO: this line needed? + b.negativeASCII = newarray + } + + b.negativeUnicode[i] = newarray + } + + if b.negativeUnicode[i][j] == last-beforefirst { + b.negativeUnicode[i][j] = last - examine + } + default: + // we can't do the filter because this algo doesn't support + // unicode chars >0xffff + return nil + } + } + + return b +} + +func (b *BmPrefix) String() string { + return string(b.pattern) +} + +// Dump returns the contents of the filter as a human readable string +func (b *BmPrefix) Dump(indent string) string { + buf := &bytes.Buffer{} + + fmt.Fprintf(buf, "%sBM Pattern: %s\n%sPositive: ", indent, string(b.pattern), indent) + for i := 0; i < len(b.positive); i++ { + buf.WriteString(strconv.Itoa(b.positive[i])) + buf.WriteRune(' ') + } + buf.WriteRune('\n') + + if b.negativeASCII != nil { + buf.WriteString(indent) + buf.WriteString("Negative table\n") + for i := 0; i < len(b.negativeASCII); i++ { + if b.negativeASCII[i] != len(b.pattern) { + fmt.Fprintf(buf, "%s %s %s\n", indent, Escape(string(rune(i))), strconv.Itoa(b.negativeASCII[i])) + } + } + } + + return buf.String() +} + +// Scan uses the Boyer-Moore algorithm to find the first occurrence +// of the specified string within text, beginning at index, and +// constrained within beglimit and endlimit. +// +// The direction and case-sensitivity of the match is determined +// by the arguments to the RegexBoyerMoore constructor. +func (b *BmPrefix) Scan(text []rune, index, beglimit, endlimit int) int { + var ( + defadv, test, test2 int + match, startmatch, endmatch int + bump, advance int + chTest rune + unicodeLookup []int + ) + + if !b.rightToLeft { + defadv = len(b.pattern) + startmatch = len(b.pattern) - 1 + endmatch = 0 + test = index + defadv - 1 + bump = 1 + } else { + defadv = -len(b.pattern) + startmatch = 0 + endmatch = -defadv - 1 + test = index + defadv + bump = -1 + } + + chMatch := b.pattern[startmatch] + + for { + if test >= endlimit || test < beglimit { + return -1 + } + + chTest = text[test] + + if b.caseInsensitive { + chTest = unicode.ToLower(chTest) + } + + if chTest != chMatch { + if chTest < 128 { + advance = b.negativeASCII[chTest] + } else if chTest < 0xffff && len(b.negativeUnicode) > 0 { + unicodeLookup = b.negativeUnicode[chTest>>8] + if len(unicodeLookup) > 0 { + advance = unicodeLookup[chTest&0xFF] + } else { + advance = defadv + } + } else { + advance = defadv + } + + test += advance + } else { // if (chTest == chMatch) + test2 = test + match = startmatch + + for { + if match == endmatch { + if b.rightToLeft { + return test2 + 1 + } else { + return test2 + } + } + + match -= bump + test2 -= bump + + chTest = text[test2] + + if b.caseInsensitive { + chTest = unicode.ToLower(chTest) + } + + if chTest != b.pattern[match] { + advance = b.positive[match] + if chTest < 128 { + test2 = (match - startmatch) + b.negativeASCII[chTest] + } else if chTest < 0xffff && len(b.negativeUnicode) > 0 { + unicodeLookup = b.negativeUnicode[chTest>>8] + if len(unicodeLookup) > 0 { + test2 = (match - startmatch) + unicodeLookup[chTest&0xFF] + } else { + test += advance + break + } + } else { + test += advance + break + } + + if b.rightToLeft { + if test2 < advance { + advance = test2 + } + } else if test2 > advance { + advance = test2 + } + + test += advance + break + } + } + } + } +} + +// When a regex is anchored, we can do a quick IsMatch test instead of a Scan +func (b *BmPrefix) IsMatch(text []rune, index, beglimit, endlimit int) bool { + if !b.rightToLeft { + if index < beglimit || endlimit-index < len(b.pattern) { + return false + } + + return b.matchPattern(text, index) + } else { + if index > endlimit || index-beglimit < len(b.pattern) { + return false + } + + return b.matchPattern(text, index-len(b.pattern)) + } +} + +func (b *BmPrefix) matchPattern(text []rune, index int) bool { + if len(text)-index < len(b.pattern) { + return false + } + + if b.caseInsensitive { + for i := 0; i < len(b.pattern); i++ { + //Debug.Assert(textinfo.ToLower(_pattern[i]) == _pattern[i], "pattern should be converted to lower case in constructor!"); + if unicode.ToLower(text[index+i]) != b.pattern[i] { + return false + } + } + return true + } else { + for i := 0; i < len(b.pattern); i++ { + if text[index+i] != b.pattern[i] { + return false + } + } + return true + } +} + +type AnchorLoc int16 + +// where the regex can be pegged +const ( + AnchorBeginning AnchorLoc = 0x0001 + AnchorBol AnchorLoc = 0x0002 + AnchorStart AnchorLoc = 0x0004 + AnchorEol AnchorLoc = 0x0008 + AnchorEndZ AnchorLoc = 0x0010 + AnchorEnd AnchorLoc = 0x0020 + AnchorBoundary AnchorLoc = 0x0040 + AnchorECMABoundary AnchorLoc = 0x0080 +) + +func getAnchors(tree *RegexTree) AnchorLoc { + + var concatNode *RegexNode + nextChild, result := 0, AnchorLoc(0) + + curNode := tree.Root + + for { + switch curNode.T { + case NtConcatenate: + if len(curNode.Children) > 0 { + concatNode = curNode + nextChild = 0 + } + + case NtAtomic, NtCapture: + curNode = curNode.Children[0] + concatNode = nil + continue + + case NtBol, NtEol, NtBoundary, NtECMABoundary, NtBeginning, + NtStart, NtEndZ, NtEnd: + return result | anchorFromType(curNode.T) + + case NtEmpty, NtPosLook, NtNegLook: + + default: + return result + } + + if concatNode == nil || nextChild >= len(concatNode.Children) { + return result + } + + curNode = concatNode.Children[nextChild] + nextChild++ + } +} + +func anchorFromType(t NodeType) AnchorLoc { + switch t { + case NtBol: + return AnchorBol + case NtEol: + return AnchorEol + case NtBoundary: + return AnchorBoundary + case NtECMABoundary: + return AnchorECMABoundary + case NtBeginning: + return AnchorBeginning + case NtStart: + return AnchorStart + case NtEndZ: + return AnchorEndZ + case NtEnd: + return AnchorEnd + default: + return 0 + } +} + +// anchorDescription returns a human-readable description of the anchors +func (anchors AnchorLoc) String() string { + buf := &bytes.Buffer{} + + if (anchors & AnchorBeginning) != 0 { + buf.WriteString(", Beginning") + } + if (anchors & AnchorStart) != 0 { + buf.WriteString(", Start") + } + if (anchors & AnchorBol) != 0 { + buf.WriteString(", Bol") + } + if (anchors & AnchorBoundary) != 0 { + buf.WriteString(", Boundary") + } + if (anchors & AnchorECMABoundary) != 0 { + buf.WriteString(", ECMABoundary") + } + if (anchors & AnchorEol) != 0 { + buf.WriteString(", Eol") + } + if (anchors & AnchorEnd) != 0 { + buf.WriteString(", End") + } + if (anchors & AnchorEndZ) != 0 { + buf.WriteString(", EndZ") + } + + // trim off comma + if buf.Len() >= 2 { + return buf.String()[2:] + } + return "None" +} + +func findLeadingOrTrailingAnchor(node *RegexNode, leading bool) NodeType { + for { + switch node.T { + case NtBol, NtEol, NtBeginning, NtStart, NtEndZ, NtEnd, NtBoundary, NtECMABoundary: + // anchor found + return node.T + case NtAtomic, NtCapture: + // For groups, continue exploring the sole child. + node = node.Children[0] + continue + case NtConcatenate: + // For concatenations, we expect primarily to explore its first (for leading) or last (for trailing) child, + // but we can also skip over certain kinds of nodes (e.g. Empty), and thus iterate through its children backward + // looking for the last we shouldn't skip. + var child *RegexNode + + if leading { + for i := 0; i < len(node.Children); i++ { + t := node.Children[i].T + if t != NtEmpty && t != NtPosLook && t != NtNegLook { + child = node.Children[i] + break + } + } + } else { + for i := len(node.Children) - 1; i >= 0; i-- { + t := node.Children[i].T + if t != NtEmpty && t != NtPosLook && t != NtNegLook { + child = node.Children[i] + break + } + } + } + if child != nil { + node = child + continue + } + + case NtAlternate: + // For alternations, every branch needs to lead or trail with the same anchor. + + // Get the leading/trailing anchor of the first branch. If there isn't one, bail. + anchor := findLeadingOrTrailingAnchor(node.Children[0], leading) + if anchor == NtUnknown { + return NtUnknown + } + + // Look at each subsequent branch and validate it has the same leading or trailing + // anchor. If any doesn't, bail. + for i := 1; i < len(node.Children); i++ { + if findLeadingOrTrailingAnchor(node.Children[i], leading) != anchor { + return NtUnknown + } + } + + // All branches have the same leading/trailing anchor. Return it. + return anchor + + } + + // no anchor + return NtUnknown + } +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/prefixanalyzer.go b/vendor/github.com/dlclark/regexp2/v2/syntax/prefixanalyzer.go new file mode 100644 index 000000000..09bfc8456 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/prefixanalyzer.go @@ -0,0 +1,1467 @@ +package syntax + +import ( + "bytes" + "cmp" + "math" + "slices" + "strings" + "unicode" + "unicode/utf8" + "unsafe" +) + +// Computes a character class for the first character in tree. This uses a more robust algorithm +// than is used by TryFindFixedLiterals and thus can find starting sets it couldn't. For example, +// fixed literals won't find the starting set for a*b, as the a isn't guaranteed and the b is at a +// variable position, but this will find [ab] as it's instead looking for anything that under any +// circumstance could possibly start a match. +func findFirstCharClass(root *RegexNode) *CharSet { + // Explore the graph, adding found chars into a result set, which is lazily initialized so that + // we can initialize it to a parsed set if we discover one first (this is helpful not just for allocation + // but because it enables supporting starting negated sets, which wouldn't work if they had to be merged + // into a non-negated default set). If the operation returns true, we successfully explore all relevant nodes + // in the graph. If it returns false, we were unable to successfully explore all relevant nodes, typically + // due to conflicts when trying to add characters into the result set, e.g. we may have read a negated set + // and were then unable to merge into that a subsequent non-negated set. If it returns null, it means the + // whole pattern was nullable such that it could match an empty string, in which case we + // can't make any statements about what begins a match. + var cc *CharSet + if tryFindFirstCharClass(root, &cc) == 1 { + return cc + } + return nil +} + +// Walks the nodes of the expression looking for any node that could possibly match the first +// character of a match, e.g. in `a*b*c+d`, we'd find [abc], or in `(abc|d*e)?[fgh]`, we'd find +// [adefgh]. The function is called for each node, recurring into children where appropriate, +// and returns: +// - 1 if the child was successfully processed and represents a stopping point, e.g. a single +// char loop with a minimum bound greater than 0 such that nothing after that node in a +// concatenation could possibly match the first character. +// - 0 if the child failed to be processed but needed to be, such that the results can't +// be trusted. If any node returns false, the whole operation fails. +// - -1 if the child was successfully processed but doesn't represent a stopping point, i.e. +// it's zero-width (e.g. empty, a lookaround, an anchor, etc.) or it could be zero-width +// (e.g. a loop with a min bound of 0). A concatenation processing a child that returns +// -1 needs to keep processing the next child. +func tryFindFirstCharClass(node *RegexNode, ccIn **CharSet) int { + cc := *ccIn + + switch node.T { + // Base cases where we have results to add to the result set. Add the values into the result set, if possible. + // If this is a loop and it has a lower bound of 0, then it's zero-width, so return null. + case NtOne, NtOneloop, NtOnelazy, NtOneloopatomic: + if cc == nil { + cc = &CharSet{} + *ccIn = cc + } + if cc.IsMergeable() { + cc.addChar(node.Ch) + if node.T == NtOne || node.M > 0 { + return 1 + } + return -1 + } + return 0 + + case NtNotone, NtNotoneloop, NtNotonelazy, NtNotoneloopatomic: + if cc == nil { + cc = &CharSet{} + *ccIn = cc + } + if cc.IsMergeable() { + cc.addChar(node.Ch) + cc.negate = true + /*if node.Ch > 0 { + // Add the range before the excluded char. + cc.addRange(0, (node.Ch - 1)) + } + if node.Ch < unicode.MaxRune { + // Add the range after the excluded char. + cc.addRange(node.Ch+1, unicode.MaxRune) + }*/ + if node.T == NtNotone || node.M > 0 { + return 1 + } + return -1 + } + return 0 + + case NtSet, NtSetloop, NtSetlazy, NtSetloopatomic: + { + setSuccess := false + if cc == nil { + cp := node.Set.Copy() + *ccIn = &cp + setSuccess = true + } else if cc.IsMergeable() && node.Set.IsMergeable() { + cc.addSet(*node.Set) + setSuccess = true + } + if !setSuccess { + return 0 + } else if node.T == NtSet || node.M > 0 { + return 1 + } + return -1 + } + + case NtMulti: + if cc == nil { + cc = &CharSet{} + *ccIn = cc + } + if cc.IsMergeable() { + if node.Options&RightToLeft != 0 { + cc.addChar(node.Str[len(node.Str)-1]) + } else { + cc.addChar(node.Str[0]) + } + return 1 + } + return 0 + + // Zero-width elements. These don't contribute to the starting set, so return null to indicate a caller + // should keep looking past them. + case NtEmpty, NtNothing, NtBol, NtEol, NtBoundary, NtNonboundary, NtECMABoundary, NtNonECMABoundary, + NtBeginning, NtStart, NtEndZ, NtEnd, NtUpdateBumpalong, NtPosLook, NtNegLook: + return -1 + + // Groups. These don't contribute anything of their own, and are just pass-throughs to their children. + case NtAtomic, NtCapture: + return tryFindFirstCharClass(node.Children[0], ccIn) + + // Loops. Like groups, these are mostly pass-through: if the child fails, then the whole operation needs + // to fail, and if the child is nullable, then the loop is as well. However, if the child succeeds but + // the loop has a lower bound of 0, then the loop is still nullable. + case NtLoop, NtLazyloop: + val := tryFindFirstCharClass(node.Children[0], ccIn) + if val <= 0 || node.M != 0 { + return val + } + return -1 + + // Concatenation. Loop through the children as long as they're nullable. The moment a child returns true, + // we don't need or want to look further, as that child represents non-zero-width and nothing beyond it can + // contribute to the starting character set. The moment a child returns false, we need to fail the whole thing. + // If every child is nullable, then the concatenation is also nullable. + case NtConcatenate: + for i := 0; i < len(node.Children); i++ { + childResult := tryFindFirstCharClass(node.Children[i], ccIn) + if childResult != -1 { + return childResult + } + } + return -1 + + // Alternation. Every child is its own fork/branch and contributes to the starting set. As with concatenation, + // the moment any child fails, fail. And if any child is nullable, the alternation is also nullable (since that + // zero-width path could be taken). Otherwise, if every branch returns true, so too does the alternation. + case NtAlternate: + anyChildWasNull := false + for i := 0; i < len(node.Children); i++ { + childResult := tryFindFirstCharClass(node.Children[i], ccIn) + switch childResult { + case -1: + anyChildWasNull = true + case 0: + return 0 + } + } + if anyChildWasNull { + return -1 + } + return 1 + + // Conditionals. Just like alternation for their "yes"/"no" child branches. If either returns false, return false. + // If either is nullable, this is nullable. If both return true, return true. + case NtBackRefCond, NtExprCond: + branchStart := 1 + if node.T == NtBackRefCond { + branchStart = 0 + } + // conditional without all the branches + if branchStart+1 >= len(node.Children) { + return -1 + } + start := tryFindFirstCharClass(node.Children[branchStart], ccIn) + next := tryFindFirstCharClass(node.Children[branchStart+1], ccIn) + if start == -1 || next == -1 { + return -1 + } + if start == 0 || next == 0 { + return 0 + } + return 1 + + // Backreferences. We can't easily make any claims about what content they might match, so just give up. + case NtRef: + return 0 + } + + // Unknown node. + return 0 +} + +// Computes the leading ordinal case-insensitive substring in node +func findPrefixOrdinalCaseInsensitive(node *RegexNode) string { + for { + // Search down the left side of the tree looking for a concatenation. If we find one, + // ask it for any ordinal case-insensitive prefix it has. + switch node.T { + case NtLoop, NtLazyloop: + if node.M <= 0 { + return "" + } + fallthrough + case NtAtomic, NtCapture: + node = node.Children[0] + continue + + case NtConcatenate: + _, _, caseInsensitiveString := node.TryGetOrdinalCaseInsensitiveString(0, len(node.Children), true) + return caseInsensitiveString + + default: + return "" + } + } +} + +// Computes the leading substring in node may be empty. +func findPrefix(node *RegexNode) string { + vsb := &bytes.Buffer{} + tryFindPrefix(node, vsb) + return vsb.String() +} + +// Processes the node, adding any prefix text to the builder. +// Returns whether processing should continue with subsequent nodes. +func tryFindPrefix(node *RegexNode, vsb *bytes.Buffer) bool { + // We don't bother to handle reversed input, so process at most one node + // when handling RightToLeft. + rtl := (node.Options & RightToLeft) != 0 + + switch node.T { + // Concatenation + case NtConcatenate: + for i := 0; i < len(node.Children); i++ { + if !tryFindPrefix(node.Children[i], vsb) { + return false + } + } + return !rtl + // Alternation: find a string that's a shared prefix of all branches + case NtAlternate: + // for RTL we'd need to be matching the suffixes of the alternation cases + if rtl { + return false + } + + // Store the initial branch into the target builder, keeping track + // of how much was appended. Any of this contents that doesn't overlap + // will every other branch will be removed before returning. + initialLength := vsb.Len() + tryFindPrefix(node.Children[0], vsb) + addedLength := vsb.Len() - initialLength + + // Then explore the rest of the branches, finding the length + // of prefix they all share in common with the initial branch. + if addedLength != 0 { + alternateSb := &bytes.Buffer{} + vsbSlice := vsb.Bytes()[initialLength : initialLength+addedLength] + + // Process each branch. If we reach a point where we've proven there's + // no overlap, we can bail early. + for i := 1; i < len(node.Children) && addedLength != 0; i++ { + alternateSb.Reset() + + // Process the branch into a temporary builder. + tryFindPrefix(node.Children[i], alternateSb) + + // Find how much overlap there is between this branch's prefix + // and the smallest amount of prefix that overlapped with all + // the previously seen branches. + addedLength = commonPrefixLen(vsbSlice, alternateSb.Bytes()) + } + + // Then cull back on what was added based on the other branches. + vsb.Truncate(initialLength + addedLength) + } + + // Don't explore anything after the alternation. We could make this work if desirable, + // but it's currently not worth the extra complication. The entire contents of every + // branch would need to be identical other than zero-width anchors/assertions. + return false + + // One character + case NtOne: + vsb.WriteRune(node.Ch) + return !rtl + + // Multiple characters + case NtMulti: + vsb.WriteString(string(node.Str)) + return !rtl + + // Loop of one character + case NtOneloop /*NtOneloopatomic,*/, NtOnelazy: + if node.M <= 0 { + return false + } + // arbitrary cut-off to avoid creating super long strings unnecessarily + count := 32 + if node.M < count { + count = node.M + } + vsb.WriteString(strings.Repeat(string(node.Ch), count)) + return count == node.N && !rtl + + // Loop of a node + case NtLoop, NtLazyloop: + if node.M <= 0 { + return false + } + + // arbitrary cut-off to avoid creating super long strings unnecessarily + limit := 4 + if node.M < limit { + limit = node.M + } + for i := 0; i < limit; i++ { + if tryFindPrefix(node.Children[0], vsb) { + return false + } + } + return limit == node.N && !rtl + + // Grouping nodes for which we only care about their single child + case NtAtomic, NtCapture: + return tryFindPrefix(node.Children[0], vsb) + + // Zero-width anchors and assertions + case NtBol, NtEol, NtBoundary, NtECMABoundary, NtNonboundary, NtNonECMABoundary, NtBeginning, + NtStart, NtEndZ, NtEnd, NtEmpty, NtUpdateBumpalong, NtPosLook, NtNegLook: + return true + } + // Give up for anything else + return false +} + +// commonPrefixLen returns the length of the common prefix of two strings. +func commonPrefixLen(a, b []byte) int { + commonLen := min(len(a), len(b)) + i := 0 + // Optimization: load and compare word-sized chunks at a time. + // This is about 6x faster than the naive approach when len > 64. + // + // TODO(adonovan): further optimizations are possible, + // at the cost of portability, for example by: + // - better elimination of bounds checks; + // - use of uint64 instead of an array may result in better + // registerization, and allows computing the final portion + // from the bitmask: + // + // cmp := load64le(a, i) ^ load64le(b, i) + // if cmp != 0 { + // return i + bits.LeadingZeros64(cmp)/8 + // } + // + // - use of vector instructions in the manner of + // runtime.cmpstring, which is expected to achieve 3x + // further improvement when len > 32. + // + const wordsize = int(unsafe.Sizeof(uint(0))) + var aword, bword [wordsize]byte + for i+wordsize <= commonLen { + copy(aword[:], a[i:i+wordsize]) + copy(bword[:], b[i:i+wordsize]) + if aword != bword { + break + } + i += wordsize + } + // naive implementation + for i < commonLen { + if a[i] != b[i] { + return i + } + i++ + } + return i +} +func min(x, y int) int { + if x < y { + return x + } else { + return y + } +} +func max(x, y int) int { + if x > y { + return x + } else { + return y + } +} + +// Minimum string length for prefixes to be useful. If any prefix has length 1, +// then we're generally better off just using IndexOfAny with chars. +const minPrefixLength = 2 + +// Arbitrary string length limit (with some wiggle room) to avoid creating strings that are longer than is useful and consuming too much memory. +const maxPrefixLength = 8 + +// Arbitrary limit on the number of prefixes to find. If we find more than this, we're likely to be spending too much time finding prefixes that won't be useful. +const maxPrefixes = 16 + +// Finds an array of multiple prefixes that a node can begin with. +// If a fixed set of prefixes is found, such that a match for this node is guaranteed to begin +// with one of those prefixes, an array of those prefixes is returned. Otherwise, null. +func findPrefixes(node *RegexNode, ignoreCase bool) []string { + + // Analyze the node to find prefixes. + results := []*bytes.Buffer{{}} + findPrefixesCore(node, &results, ignoreCase) + + // If we found too many prefixes or if any found is too short, fail. + if len(results) > maxPrefixes || slices.ContainsFunc(results, func(sb *bytes.Buffer) bool { return sb.Len() < minPrefixLength }) { + return nil + } + + // Return the prefixes. + resultStrings := make([]string, len(results)) + for i := 0; i < len(results); i++ { + resultStrings[i] = results[i].String() + } + return resultStrings +} + +// Updates the results list with found prefixes. All existing strings in the list are treated as existing +// discovered prefixes prior to the node being processed. The method returns true if subsequent nodes after +// this one should be examined, or returns false if they shouldn't be because the node wasn't guaranteed +// to be fully processed. +func findPrefixesCore(node *RegexNode, res *[]*bytes.Buffer, ignoreCase bool) bool { + results := *res + // If we're too deep to analyze further, we can't trust what we've already computed, so stop iterating. + // Also bail if any of our results is already hitting the threshold, or if this node is RTL, which is + // not worth the complexity of handling. + // Or if we've already discovered more than the allowed number of prefixes. + + if slices.ContainsFunc(results, func(sb *bytes.Buffer) bool { return sb.Len() >= maxPrefixLength }) || + (node.Options&RightToLeft) != 0 || + len(results) > maxPrefixes { + return false + } + + // These limits are approximations. We'll stop trying to make strings longer once we exceed the max length, + // and if we exceed the max number of prefixes by a non-trivial amount, we'll fail the operation. + // limit how many chars we get from a set based on the max prefixes we care about + //setChars := make([]rune, 0, maxPrefixes) + + // Loop down the left side of the tree, looking for a starting node we can handle. We only loop through + // atomic and capture nodes, as the child is guaranteed to execute once, as well as loops with a positive + // minimum and thus at least one guaranteed iteration. + for { + switch node.T { + // These nodes are all guaranteed to execute at least once, so we can just + // skip through them to their child. + case NtAtomic, NtCapture: + node = node.Children[0] + continue + + // Zero-width anchors and assertions don't impact a prefix and may be skipped over. + case NtBol, NtEol, NtBoundary, NtECMABoundary, NtNonboundary, NtNonECMABoundary, + NtBeginning, NtStart, NtEndZ, NtEnd, NtEmpty, NtUpdateBumpalong, + NtPosLook, NtNegLook: + return true + + // If we hit a single character, we can just return that character. + // This is only relevant for case-sensitive searches, as for case-insensitive we'd have sets for anything + // that produces a different result when case-folded, or for strings composed entirely of characters that + // don't participate in case conversion. Single character loops are handled the same as single characters + // up to the min iteration limit. We can continue processing after them as well if they're repeaters such + // that their min and max are the same. + case NtOne, NtOneloop, NtOnelazy, NtOneloopatomic: + if !ignoreCase || !participatesInCaseConversion(node.Ch) { + reps := maxPrefixLength + if node.T == NtOne { + reps = 1 + } else if node.M < reps { + reps = node.M + } + if reps == 1 { + for _, sb := range results { + sb.WriteRune(node.Ch) + } + } else { + for _, sb := range results { + sb.WriteString(strings.Repeat(string(node.Ch), reps)) + } + } + return node.T == NtOne || reps == node.N + } + + // If we hit a string, we can just return that string. + // As with One above, this is only relevant for case-sensitive searches. + case NtMulti: + if !ignoreCase { + for _, sb := range results { + sb.WriteString(string(node.Str)) + } + } else { + // If we're ignoring case, then only append up through characters that don't participate in case conversion. + // If there are any beyond that, we can't go further and need to stop with what we have. + for _, c := range node.Str { + if participatesInCaseConversion(c) { + return false + } + + for _, sb := range results { + sb.WriteRune(c) + } + } + } + return true + + // For case-sensitive, try to extract the characters that comprise it, and if there are + // any and there aren't more than the max number of prefixes, we can return + // them each as a prefix. Effectively, this is an alternation of the characters + // that comprise the set. For case-insensitive, we need the set to be two ASCII letters that case fold to the same thing. + // As with One and loops, set loops are handled the same as sets up to the min iteration limit. + case NtSet, NtSetloop, NtSetlazy, NtSetloopatomic: + + setChars := node.Set.GetSetChars(maxPrefixes) + + if len(setChars) == 0 { + return false + } + + reps := maxPrefixLength + if node.T == NtSet { + reps = 1 + } else if node.M < reps { + reps = node.M + } + if !ignoreCase { + for rep := 0; rep < reps; rep++ { + existingCount := len(results) + if existingCount*len(setChars) > maxPrefixes { + return false + } + + // Duplicate all of the existing strings for all of the new suffixes, other than the first. + for _, suffix := range setChars[1:] { + for existing := 0; existing < existingCount; existing++ { + newSb := bytes.NewBuffer(slices.Clone(results[existing].Bytes())) + newSb.WriteRune(suffix) + results = append(results, newSb) + } + } + *res = results + + // Then append the first suffix to all of the existing strings. + for existing := 0; existing < existingCount; existing++ { + results[existing].WriteRune(setChars[0]) + } + } + } else { + // For ignore-case, we currently only handle the simple (but common) case of a single + // ASCII character that case folds to the same char. + ok, setChars := node.Set.containsAsciiIgnoreCaseCharacter() + if !ok { + return false + } + + // Append it to each. + for _, sb := range results { + sb.WriteString(strings.Repeat(string(setChars[1]), reps)) + } + } + + *res = results + return node.T == NtSet || reps == node.N + + case NtConcatenate: + for i := 0; i < len(node.Children); i++ { + if !findPrefixesCore(node.Children[i], res, ignoreCase) { + return false + } + } + + return true + + // We can append any guaranteed iterations as if they were a concatenation. + case NtLoop, NtLazyloop: + if node.M > 0 { + // MaxPrefixLength here is somewhat arbitrary, as a single loop iteration could yield multiple chars + limit := maxPrefixLength + if node.M < limit { + limit = node.M + } + for i := 0; i < limit; i++ { + if !findPrefixesCore(node.Children[0], res, ignoreCase) { + return false + } + } + return limit == node.N + } + + // For alternations, we need to find a prefix for every branch; if we can't compute a + // prefix for any one branch, we can't trust the results and need to give up, since we don't + // know if our set of prefixes is complete. + case NtAlternate: + + // If there are more children than our maximum, just give up immediately, as we + // won't be able to get a prefix for every branch and have it be within our max. + if len(node.Children) > maxPrefixes { + return false + } + + // Build up the list of all prefixes across all branches. + var allBranchResults []*bytes.Buffer + alternateBranchResults := []*bytes.Buffer{{}} + for i := 0; i < len(node.Children); i++ { + findPrefixesCore(node.Children[i], &alternateBranchResults, ignoreCase) + + // If we now have too many results, bail. + if len(allBranchResults)+len(alternateBranchResults) > maxPrefixes { + return false + } + + for _, sb := range alternateBranchResults { + // If a branch yields an empty prefix, then none of the other branches + // matter, e.g. if the pattern is abc(def|ghi|), then this would result + // in prefixes abcdef, abcghi, and abc, and since abc is a prefix of both + // abcdef and abcghi, the former two would never be used. + if sb.Len() == 0 { + return false + } + } + + if allBranchResults == nil { + allBranchResults = alternateBranchResults + alternateBranchResults = []*bytes.Buffer{{}} + } else { + allBranchResults = append(allBranchResults, alternateBranchResults...) + alternateBranchResults = []*bytes.Buffer{{}} + } + } + + // At this point, we know we can successfully incorporate the alternation's results + // into the main results. + + // If the results are currently empty (meaning a single empty StringBuilder), we can remove + // that builder and just replace the results with the alternation's results. We would otherwise + // be creating a dot product of every builder in the results with every branch's result, which + // is logically the same thing. + if len(results) == 1 && results[0].Len() == 0 { + results = allBranchResults + } else { + existingCount := len(results) + + // Duplicate all of the existing strings for all of the new suffixes, other than the first. + for i := 1; i < len(allBranchResults); i++ { + suffix := allBranchResults[i] + for existing := 0; existing < existingCount; existing++ { + newSb := &bytes.Buffer{} + newSb.Write(results[existing].Bytes()) + newSb.Write(suffix.Bytes()) + results = append(results, newSb) + } + } + + // Then append the first suffix to all of the existing strings. + for existing := 0; existing < existingCount; existing++ { + results[existing].Write(allBranchResults[0].Bytes()) + } + } + *res = results + + // We don't know that we fully processed every branch, so we can't iterate through what comes after this node. + // The results were successfully updated, but return false to indicate that nothing after this node should be examined. + } + return false + } +} + +const maxLoopExpansion = 20 // arbitrary cut-off to avoid loops adding significant overhead to processing +const maxFixedResults = 50 // arbitrary cut-off to avoid generating lots of sets unnecessarily + +// Finds sets at fixed-offsets from the beginning of the pattern/ +// set "thorough" to true to spend more time finding sets (e.g. through alternations); false to do a faster analysis that's potentially more incomplete. +// Returns the array of found sets, or null if there aren't any. +func findFixedDistanceSets(root *RegexNode, thorough bool) []FixedDistanceSet { + + // Find all fixed-distance sets. + results := []FixedDistanceSet{} + distance := 0 + tryFindRawFixedSets(root, &results, &distance, thorough) + + // Remove any sets that match everything; they're not helpful. (This check exists primarily to weed + // out use of . in Singleline mode, but also filters out explicit sets like [\s\S].) + results = slices.DeleteFunc(results, func(s FixedDistanceSet) bool { return s.Set.IsAnything() }) + + // If we don't have any results, try harder to compute one for the starting character. + // This is a more involved computation that can find things the fixed-distance investigation + // doesn't. + if len(results) == 0 { + // weed out match-all, same as above + c := findFirstCharClass(root) + if c == nil || c.IsAnything() { + return nil + } + + results = append(results, FixedDistanceSet{Set: c, Distance: 0}) + } + + // For every entry, try to get the chars that make up the set, if there are few enough. + // For any for which we couldn't get the small chars list, see if we can get other useful info. + //scratch := make([]rune, 0, 128) + for i := 0; i < len(results); i++ { + result := results[i] + result.Negated = result.Set.IsNegated() + + // prefer IndexOfAny for tiny sets of 1 or 2 elements + if r := result.Set.GetIfNRanges(1); len(r) == 1 && r[0].Last-r[0].First > 1 { + result.Range = &r[0] + } else { + scratch := result.Set.GetSetChars(128) + if len(scratch) > 0 { + result.Chars = scratch + } + } + + results[i] = result + } + + return results +} + +// Starting from the specified root node, populates results with any characters at a fixed distance +// from the node's starting position. The function returns true if the entire contents of the node +// is at a fixed distance, in which case distance will have been updated to include the full length +// of the node. If it returns false, the node isn't entirely fixed, in which case subsequent nodes +// shouldn't be examined and distance should no longer be trusted. However, regardless of whether it +// returns true or false, it may have populated results, and all populated results are valid. All +// FixedDistanceSet result will only have its Set string and Distance populated; the rest is left +// to be populated by FindFixedDistanceSets after this returns. +func tryFindRawFixedSets(node *RegexNode, res *[]FixedDistanceSet, distance *int, thorough bool) bool { + results := *res + if node.Options&RightToLeft != 0 { + return false + } + + switch node.T { + case NtOne: + if len(results) < maxFixedResults { + set := &CharSet{} + set.addChar(node.Ch) + results = append(results, FixedDistanceSet{Set: set, Distance: *distance}) + *res = results + *distance++ + return true + } + return false + + case NtOnelazy, NtOneloop, NtOneloopatomic: + if node.M > 0 { + set := &CharSet{} + set.addChar(node.Ch) + minIterations := maxLoopExpansion + if node.M < minIterations { + minIterations = node.M + } + i := 0 + for ; i < minIterations && len(results) < maxFixedResults; i++ { + results = append(results, FixedDistanceSet{Set: set, Distance: *distance}) + *distance++ + } + *res = results + return i == node.M && i == node.N + } + + case NtMulti: + i := 0 + for ; i < len(node.Str) && len(results) < maxFixedResults; i++ { + set := &CharSet{} + set.addChar(node.Str[i]) + results = append(results, FixedDistanceSet{Set: set, Distance: *distance}) + *distance++ + } + *res = results + return i == len(node.Str) + + case NtSet: + if len(results) < maxFixedResults { + results = append(results, FixedDistanceSet{Set: node.Set, Distance: *distance}) + *res = results + *distance++ + return true + } + return false + + case NtSetlazy, NtSetloop, NtSetloopatomic: + if node.M > 0 { + minIterations := maxLoopExpansion + if node.M < minIterations { + minIterations = node.M + } + i := 0 + for ; i < minIterations && len(results) < maxFixedResults; i++ { + results = append(results, FixedDistanceSet{Set: node.Set, Distance: *distance}) + *distance++ + } + *res = results + return i == node.M && i == node.N + } + + case NtNotone: + // We could create a set out of Notone, but it will be of little value in helping to improve + // the speed of finding the first place to match, as almost every character will match it. + *distance++ + return true + + case NtNotonelazy, NtNotoneloop, NtNotoneloopatomic: + if node.M == node.N { + *distance += node.M + return true + } + case NtBeginning, NtBol, NtBoundary, NtECMABoundary, NtEmpty, NtEnd, NtEndZ, NtEol, + NtNonboundary, NtNonECMABoundary, NtUpdateBumpalong, + NtStart, NtNegLook, NtPosLook: + // Zero-width anchors and assertions. In theory, for PositiveLookaround and NegativeLookaround we could also + // investigate them and use the learned knowledge to impact the generated sets, at least for lookaheads. + // For now, we don't bother. + return true + + case NtAtomic, NtGroup, NtCapture: + return tryFindRawFixedSets(node.Children[0], res, distance, thorough) + + case NtLazyloop, NtLoop: + if node.M > 0 { + // This effectively only iterates the loop once. If deemed valuable, + // it could be updated in the future to duplicate the found results + // (updated to incorporate distance from previous iterations) and + // summed distance for all node.M iterations. If node.M == node.N, + // this would then also allow continued evaluation of the rest of the + // expression after the loop. + tryFindRawFixedSets(node.Children[0], res, distance, thorough) + return false + } + + case NtConcatenate: + for i := 0; i < len(node.Children); i++ { + if !tryFindRawFixedSets(node.Children[i], res, distance, thorough) { + return false + } + } + return true + + case NtAlternate: + if thorough { + allSameSize := true + sameDistance := -1 + var combined = make(map[int]struct { + Set *CharSet + Count int + }) + var localResults []FixedDistanceSet + + for i := 0; i < len(node.Children); i++ { + localResults = []FixedDistanceSet{} + localDistance := 0 + allSameSize = allSameSize && tryFindRawFixedSets(node.Children[i], &localResults, &localDistance, thorough) + + if len(localResults) == 0 { + return false + } + + if allSameSize { + if sameDistance == -1 { + sameDistance = localDistance + } else if sameDistance != localDistance { + allSameSize = false + } + } + + for _, fixedSet := range localResults { + if v, ok := combined[fixedSet.Distance]; ok { + if v.Set.IsMergeable() && fixedSet.Set.IsMergeable() { + v.Set.addSet(*fixedSet.Set) + v.Count++ + combined[fixedSet.Distance] = v + } + } else { + combined[fixedSet.Distance] = struct { + Set *CharSet + Count int + }{Set: fixedSet.Set, Count: 1} + } + } + } + + for k, v := range combined { + if len(results) >= maxFixedResults { + allSameSize = false + break + } + + if v.Count == len(node.Children) { + results = append(results, FixedDistanceSet{Set: v.Set, Distance: k + *distance}) + } + } + *res = results + + if allSameSize { + *distance += sameDistance + return true + } + + return false + } + } + return false +} + +// CompareFunc for a set of fixed-distance set results from best to worst quality. +func compareFixedDistanceSetsByQuality(s1, s2 FixedDistanceSet) int { + // Finally, try to move the "best" results to be earlier. "best" here are ones we're able to search + // for the fastest and that have the best chance of matching as few false positives as possible. + s1RangeLength := getRangeLength(s1.Range, s1.Negated) + s2RangeLength := getRangeLength(s2.Range, s2.Negated) + + // If one set is negated and the other isn't, prefer the non-negated set. In general, negated + // sets are large and thus likely to match more frequently, making them slower to search for. + if s1.Negated != s2.Negated { + if s2.Negated { + return -1 + } + return 1 + } + + // If we extracted only a few chars and the sets are negated, they both represent very large + // sets that are difficult to compare for quality. + if !s1.Negated { + // If both have chars, prioritize the one with the smaller frequency for those chars. + if len(s1.Chars) > 0 && len(s2.Chars) > 0 { + // Prefer sets with less frequent values. The frequency is only an approximation, + // used as a tie-breaker when we'd otherwise effectively be picking randomly. + // True frequencies will vary widely based on the actual data being searched, the language of the data, etc. + s1Frequency := sumFrequencies(s1.Chars) + s2Frequency := sumFrequencies(s2.Chars) + + if s1Frequency != s2Frequency { + return cmp.Compare(s1Frequency, s2Frequency) + } + + if !isAsciiRunes(s1.Chars) && !isAsciiRunes(s2.Chars) { + // Prefer the set with fewer values. + return cmp.Compare(len(s1.Chars), len(s2.Chars)) + } + } + + // If one has chars and the other has a range, prefer the shorter set. + if (len(s1.Chars) > 0 && s2RangeLength > 0) || (s1RangeLength > 0 && len(s2.Chars) > 0) { + s1Len := s1RangeLength + if len(s1.Chars) > s1Len { + s1Len = len(s1.Chars) + } + s2Len := s2RangeLength + if len(s2.Chars) > s2Len { + s2Len = len(s2.Chars) + } + c := cmp.Compare(s1Len, s2Len) + if c != 0 { + return c + } + + // If lengths are the same, prefer the chars. + if len(s1.Chars) > 0 { + return -1 + } + return 1 + } + + // If one has chars and the other doesn't, prioritize the one with chars. + if (len(s1.Chars) > 0) != (len(s2.Chars) > 0) { + if len(s1.Chars) > 0 { + return -1 + } + return 1 + } + } + + // If one has a range and the other doesn't, prioritize the one with a range. + if (s1RangeLength > 0) != (s2RangeLength > 0) { + if s1RangeLength > 0 { + return -1 + } + return 1 + } + + // If both have ranges, prefer the one that includes fewer characters. + if s1RangeLength > 0 { + return cmp.Compare(s1RangeLength, s2RangeLength) + } + + // As a tiebreaker, prioritize the earlier one. + return cmp.Compare(s1.Distance, s2.Distance) +} + +func getRangeLength(r *SingleRange, negated bool) int { + if r == nil { + return 0 + } + if negated { + return int(unicode.MaxRune - (r.Last - r.First)) + } + return int(r.Last - r.First + 1) +} + +func sumFrequencies(chars []rune) float32 { + var sum float32 + for _, c := range chars { + // Lookup each character in the table. Values >= 128 are ignored + // and thus we'll get skew in the data. It's already a gross approximation, though, + // and it is primarily meant for disambiguation of ASCII letters. + if c < unicode.MaxASCII { + sum += frequency[c] + } + } + return sum +} + +func hasHighFrequencyChars(set FixedDistanceSet) bool { + if set.Negated { + return true + } + + // Sets without extracted chars can't be frequency-analyzed. + // Single-char sets use IndexOf, which is a strong filter regardless of frequency. + if len(set.Chars) <= 1 { + return false + } + + totalFrequency := sumFrequencies(set.Chars) + + // If the average frequency of the set's chars exceeds this threshold, the + // characters are common enough that a multi-string search may be a better filter. + const highFrequencyThreshold = 0.6 + return totalFrequency >= highFrequencyThreshold*float32(len(set.Chars)) +} + +func mayContainCaseInsensitiveMatching(node *RegexNode) bool { + if node.Options&IgnoreCase != 0 { + return true + } + + if node.Set != nil { + chars := node.Set.GetSetChars(maxPrefixes) + for _, ch := range chars { + if participatesInCaseConversion(ch) && + slices.Contains(chars, unicode.ToLower(ch)) && + slices.Contains(chars, unicode.ToUpper(ch)) { + return true + } + } + } + + for _, child := range node.Children { + if mayContainCaseInsensitiveMatching(child) { + return true + } + } + + return false +} + +// Percent occurrences in source text (100 * char count / total count) +var frequency = []float32{ + 0.000 /* '\x00' */, 0.000 /* '\x01' */, 0.000 /* '\x02' */, 0.000 /* '\x03' */, 0.000 /* '\x04' */, 0.000 /* '\x05' */, 0.000 /* '\x06' */, 0.000, /* '\x07' */ + 0.000 /* '\x08' */, 0.001 /* '\x09' */, 0.000 /* '\x0A' */, 0.000 /* '\x0B' */, 0.000 /* '\x0C' */, 0.000 /* '\x0D' */, 0.000 /* '\x0E' */, 0.000, /* '\x0F' */ + 0.000 /* '\x10' */, 0.000 /* '\x11' */, 0.000 /* '\x12' */, 0.000 /* '\x13' */, 0.003 /* '\x14' */, 0.000 /* '\x15' */, 0.000 /* '\x16' */, 0.000, /* '\x17' */ + 0.000 /* '\x18' */, 0.004 /* '\x19' */, 0.000 /* '\x1A' */, 0.000 /* '\x1B' */, 0.006 /* '\x1C' */, 0.006 /* '\x1D' */, 0.000 /* '\x1E' */, 0.000, /* '\x1F' */ + 8.952 /* ' ' */, 0.065 /* ' !' */, 0.420 /* ' "' */, 0.010 /* ' #' */, 0.011 /* ' $' */, 0.005 /* ' %' */, 0.070 /* ' &' */, 0.050, /* ' '' */ + 3.911 /* ' (' */, 3.910 /* ' )' */, 0.356 /* ' *' */, 2.775 /* ' +' */, 1.411 /* ' ,' */, 0.173 /* ' -' */, 2.054 /* ' .' */, 0.677, /* ' /' */ + 1.199 /* ' 0' */, 0.870 /* ' 1' */, 0.729 /* ' 2' */, 0.491 /* ' 3' */, 0.335 /* ' 4' */, 0.269 /* ' 5' */, 0.435 /* ' 6' */, 0.240, /* ' 7' */ + 0.234 /* ' 8' */, 0.196 /* ' 9' */, 0.144 /* ' :' */, 0.983 /* ' ;' */, 0.357 /* ' <' */, 0.661 /* ' =' */, 0.371 /* ' >' */, 0.088, /* ' ?' */ + 0.007 /* ' @' */, 0.763 /* ' A' */, 0.229 /* ' B' */, 0.551 /* ' C' */, 0.306 /* ' D' */, 0.449 /* ' E' */, 0.337 /* ' F' */, 0.162, /* ' G' */ + 0.131 /* ' H' */, 0.489 /* ' I' */, 0.031 /* ' J' */, 0.035 /* ' K' */, 0.301 /* ' L' */, 0.205 /* ' M' */, 0.253 /* ' N' */, 0.228, /* ' O' */ + 0.288 /* ' P' */, 0.034 /* ' Q' */, 0.380 /* ' R' */, 0.730 /* ' S' */, 0.675 /* ' T' */, 0.265 /* ' U' */, 0.309 /* ' V' */, 0.137, /* ' W' */ + 0.084 /* ' X' */, 0.023 /* ' Y' */, 0.023 /* ' Z' */, 0.591 /* ' [' */, 0.085 /* ' \' */, 0.590 /* ' ]' */, 0.013 /* ' ^' */, 0.797, /* ' _' */ + 0.001 /* ' `' */, 4.596 /* ' a' */, 1.296 /* ' b' */, 2.081 /* ' c' */, 2.005 /* ' d' */, 6.903 /* ' e' */, 1.494 /* ' f' */, 1.019, /* ' g' */ + 1.024 /* ' h' */, 3.750 /* ' i' */, 0.286 /* ' j' */, 0.439 /* ' k' */, 2.913 /* ' l' */, 1.459 /* ' m' */, 3.908 /* ' n' */, 3.230, /* ' o' */ + 1.444 /* ' p' */, 0.231 /* ' q' */, 4.220 /* ' r' */, 3.924 /* ' s' */, 5.312 /* ' t' */, 2.112 /* ' u' */, 0.737 /* ' v' */, 0.573, /* ' w' */ + 0.992 /* ' x' */, 1.067 /* ' y' */, 0.181 /* ' z' */, 0.391 /* ' {' */, 0.056 /* ' |' */, 0.391 /* ' }' */, 0.002 /* ' ~' */, 0.000, /* '\x7F' */ +} + +// The above table was generated programmatically with the following. This can be augmented to incorporate additional data sources, +// though it is only intended to be a rough approximation use when tie-breaking and we'd otherwise be picking randomly, so, it's something. +// The frequencies may be wildly inaccurate when used with data sources different in nature than the training set, in which case we shouldn't +// be much worse off than just picking randomly: +// +// using System.Runtime.InteropServices; +// +// var counts = new Dictionary(); +// +// (string, string)[] rootsAndExtensions = new[] +// { +// (@"d:\repos\runtime\src\", "*.cs"), // C# files in dotnet/runtime +// (@"d:\Top25GutenbergBooks", "*.txt"), // Top 25 most popular books on Project Gutenberg +// }; +// +// foreach ((string root, string ext) in rootsAndExtensions) +// foreach (string path in Directory.EnumerateFiles(root, ext, SearchOption.AllDirectories)) +// foreach (string line in File.ReadLines(path)) +// foreach (char c in line.AsSpan().Trim()) +// CollectionsMarshal.GetValueRefOrAddDefault(counts, (byte)c, out _)++; +// +// long total = counts.Sum(i => i.Value); +// +// Console.WriteLine("/// Percent occurrences in source text (100 * char count / total count)."); +// Console.WriteLine("private static ReadOnlySpan Frequency =>"); +// Console.WriteLine("["); +// int i = 0; +// for (int row = 0; row < 16; row++) +// { +// Console.Write(" "); +// for (int col = 0; col < 8; col++) +// { +// counts.TryGetValue((byte)i, out long charCount); +// float frequency = (float)(charCount / (double)total) * 100; +// Console.Write($" {frequency:N3}f /* '{(i >= 32 && i < 127 ? $" {(char)i}" : $"\\x{i:X2}")}' */,"); +// i++; +// } +// Console.WriteLine(); +// } +// Console.WriteLine("];"); + +// / +// / Analyzes the pattern for a leading set loop followed by a non-overlapping literal. If such a pattern is found, an implementation +// / can search for the literal and then walk backward through all matches for the loop until the beginning is found. +// / +func findLiteralFollowingLeadingLoop(node *RegexNode) *LiteralAfterLoop { + if (node.Options & RightToLeft) != 0 { + // As a simplification, ignore RightToLeft. + return nil + } + + // Find the first concatenation. We traverse through atomic and capture nodes as they don't effect flow control. (We don't + // want to explore loops, even if they have a guaranteed iteration, because we may use information about the node to then + // skip the node's execution in the matching algorithm, and we would need to special-case only skipping the first iteration.) + for node.T == NtAtomic || node.T == NtCapture { + node = node.Children[0] + } + if node.T != NtConcatenate { + return nil + } + + // Bail if the first node isn't a set loop. We treat any kind of set loop (Setloop, Setloopatomic, and Setlazy) + // the same because of two important constraints: the loop must not have an upper bound, and the literal we look + // for immediately following it must not overlap. With those constraints, all three of these kinds of loops will + // end up having the same semantics; in fact, if atomic optimizations are used, we will have converted Setloop + // into a Setloopatomic (but those optimizations are disabled for NonBacktracking in general). This + // could also be made to support Oneloopatomic and Notoneloopatomic, but the scenarios for that are rare. + firstChild := node.Children[0] + for firstChild.T == NtAtomic || firstChild.T == NtCapture { + firstChild = firstChild.Children[0] + } + if (firstChild.T != NtSetloop && firstChild.T != NtSetloopatomic && firstChild.T != NtSetlazy) || + firstChild.N != math.MaxInt32 { + return nil + } + + // Get the subsequent node. An UpdateBumpalong may have been added as an optimization, but it doesn't have an + // impact on semantics and we can skip it. + nextChild := node.Children[1] + if nextChild.T == NtUpdateBumpalong { + if len(node.Children) == 2 { + // If the UpdateBumpalong is the last node, nothing meaningful follows the set loop. + return nil + } + nextChild = node.Children[2] + } + + // Is the set loop followed by a case-sensitive string we can search for? + if prefix := findPrefix(nextChild); len(prefix) >= 1 { + // The literal can be searched for as either a single char or as a string. + // But we need to make sure that its starting character isn't part of the preceding + // set, as then we can't know for certain where the set loop ends. + if firstChild.Set.CharIn(rune(prefix[0])) { + return nil + } else if len(prefix) == 1 { + return &LiteralAfterLoop{ + LoopNode: firstChild, + Char: rune(prefix[0]), + } + } + return &LiteralAfterLoop{ + LoopNode: firstChild, + String: prefix, + } + } + + // Is the set loop followed by an ordinal case-insensitive string we can search for? We could + // search for a string with at least one char, but if it has only one, we're better off just + // searching as a set, so we look for strings with at least two chars. + if ordinalCaseInsensitivePrefix := findPrefixOrdinalCaseInsensitive(nextChild); len(ordinalCaseInsensitivePrefix) >= 2 { + // The literal can be searched for as a case-insensitive string. As with ordinal above, + // though, we need to make sure its starting character isn't part of the previous set. + // If that starting character participates in case conversion, then we need to test out + // both casings (FindPrefixOrdinalCaseInsensitive will only return strings composed of + // characters that either are ASCII or that don't participate in case conversion). + ch, _ := utf8.DecodeRuneInString(ordinalCaseInsensitivePrefix) + if participatesInCaseConversion(ch) { + if firstChild.Set.CharIn(ch|0x20) || + firstChild.Set.CharIn(ch&^0x20) { + return nil + } + } else if firstChild.Set.CharIn(ch) { + return nil + } + + return &LiteralAfterLoop{ + LoopNode: firstChild, + String: ordinalCaseInsensitivePrefix, + StringIgnoreCase: true, + } + } + + // Is the set loop followed by a set we can search for? Whereas the above helpers will drill down into + // children as is appropriate, to examine a set here, we need to drill in ourselves. We can drill through + // atomic and capture nodes, as they don't affect flow control, and into the left-most node of a concatenate, + // as the first child is guaranteed next. We can also drill into a loop or lazy loop that has a guaranteed + // iteration, for the same reason as with concatenate. + for nextChild.T == NtAtomic || nextChild.T == NtCapture || nextChild.T == NtConcatenate || + (nextChild.T == NtLoop || nextChild.T == NtLazyloop && nextChild.M >= 1) { + nextChild = nextChild.Children[0] + } + + // If the resulting node is a set with at least one iteration, we can search for it. + if nextChild.IsSetFamily() && + !nextChild.Set.IsNegated() && + (nextChild.T == NtSet || nextChild.M >= 1) { + // maximum number of chars optimized by IndexOfAny + chars := nextChild.Set.GetSetChars(5) + if len(chars) > 0 { + for _, c := range chars { + if firstChild.Set.CharIn(c) { + return nil + } + } + + return &LiteralAfterLoop{ + LoopNode: firstChild, + Chars: chars, + } + } + } + + // Otherwise, we couldn't find the pattern of an atomic set loop followed by a literal. + return nil +} + +func findRequiredLandmarkChain(node *RegexNode) *RequiredLandmarkChain { + if (node.Options & RightToLeft) != 0 { + return nil + } + + node = unwrapTransparentNodes(node) + if node.T != NtConcatenate || len(node.Children) < 4 { + return nil + } + + firstChild := unwrapTransparentNodes(node.Children[0]) + if !isUnboundedSetLoop(firstChild) { + return nil + } + + // Collect landmarks that must appear in order later in the match. This is a + // conservative prefilter: if any child cannot be described as a detectable + // landmark, we skip that child rather than making it part of the chain. + landmarks := make([]RequiredLandmark, 0, 2) + for _, child := range node.Children[1:] { + if landmark, ok := extractRequiredLandmark(child); ok { + landmarks = append(landmarks, landmark) + } + } + if len(landmarks) < 2 { + return nil + } + + return &RequiredLandmarkChain{ + LeadingLoopSet: firstChild.Set, + Landmarks: landmarks, + } +} + +func unwrapTransparentNodes(node *RegexNode) *RegexNode { + for node != nil && (node.T == NtAtomic || node.T == NtCapture || node.T == NtGroup) && len(node.Children) == 1 { + node = node.Children[0] + } + return node +} + +func isUnboundedSetLoop(node *RegexNode) bool { + return node != nil && + (node.T == NtSetloop || node.T == NtSetloopatomic || node.T == NtSetlazy) && + node.Set != nil && + node.N == math.MaxInt32 +} + +func extractRequiredLandmark(node *RegexNode) (RequiredLandmark, bool) { + node = unwrapTransparentNodes(node) + if node == nil { + return RequiredLandmark{}, false + } + + if node.T != NtAlternate { + alt, ok := extractRequiredLandmarkAlternative(node) + if !ok { + return RequiredLandmark{}, false + } + return RequiredLandmark{Alternatives: []RequiredLandmarkAlternative{alt}}, true + } + + landmark := RequiredLandmark{Alternatives: make([]RequiredLandmarkAlternative, 0, len(node.Children))} + for _, child := range node.Children { + alt, ok := extractRequiredLandmarkAlternative(child) + if !ok { + return RequiredLandmark{}, false + } + landmark.Alternatives = append(landmark.Alternatives, alt) + } + return landmark, len(landmark.Alternatives) > 0 +} + +func extractRequiredLandmarkAlternative(node *RegexNode) (RequiredLandmarkAlternative, bool) { + node = unwrapTransparentNodes(node) + if node == nil { + return RequiredLandmarkAlternative{}, false + } + + children := []*RegexNode{node} + if node.T == NtConcatenate { + children = node.Children + } + + alt := RequiredLandmarkAlternative{} + i := 0 + if i < len(children) { + if whitespaceSet, min, ok := whitespaceLoop(children[i]); ok { + alt.WhitespaceSet = whitespaceSet + alt.RequireWhitespaceBefore = min > 0 + i++ + } + } + if i >= len(children) { + return RequiredLandmarkAlternative{}, false + } + + // The core of a landmark must be a literal or a bounded, enumerable set + // repetition. The VM still validates the full regex after this prefilter + // returns a candidate. + core := unwrapTransparentNodes(children[i]) + switch core.T { + case NtOne: + alt.Literal = string(core.Ch) + alt.MinRepeat = 1 + alt.MaxRepeat = 1 + case NtMulti: + alt.Literal = string(core.Str) + alt.MinRepeat = 1 + alt.MaxRepeat = 1 + case NtSet, NtSetloop, NtSetloopatomic, NtSetlazy: + if core.Set == nil { + return RequiredLandmarkAlternative{}, false + } + if core.T == NtSet { + alt.MinRepeat = 1 + alt.MaxRepeat = 1 + } else if core.M > 0 && core.N != math.MaxInt32 { + alt.MinRepeat = core.M + alt.MaxRepeat = core.N + } else { + return RequiredLandmarkAlternative{}, false + } + chars := core.Set.GetSetChars(8) + if len(chars) == 0 || core.Set.IsNegated() { + return RequiredLandmarkAlternative{}, false + } + alt.Set = core.Set + alt.Chars = chars + default: + return RequiredLandmarkAlternative{}, false + } + i++ + + if i < len(children) { + if whitespaceSet, min, ok := whitespaceLoop(children[i]); ok { + if alt.WhitespaceSet == nil { + alt.WhitespaceSet = whitespaceSet + } + alt.RequireWhitespaceAfter = min > 0 + i++ + } + } + if i != len(children) { + return RequiredLandmarkAlternative{}, false + } + return alt, true +} + +func whitespaceLoop(node *RegexNode) (*CharSet, int, bool) { + node = unwrapTransparentNodes(node) + if node != nil && + (node.T == NtSetloop || node.T == NtSetloopatomic || node.T == NtSetlazy) && + node.Set != nil && + node.N == math.MaxInt32 && + (node.Set.Equals(SpaceClass()) || node.Set.Equals(ECMASpaceClass()) || node.Set.Equals(RE2SpaceClass())) { + return node.Set, node.M, true + } + return nil, 0, false +} + +// Returns a leading positive lookahead if found and whether to keep examining subsequent nodes in a concatenation. +func findLeadingPositiveLookahead(node *RegexNode) (*RegexNode, bool) { + for { + if node.Options&RightToLeft != 0 { + return nil, false + } + + switch node.T { + case NtPosLook: + return node, false + + case NtBol, NtEol, NtBeginning, NtStart, NtEndZ, NtEnd, + NtBoundary, NtECMABoundary, NtNegLook, NtEmpty: + return nil, true + + case NtAtomic, NtCapture: + node = node.Children[0] + continue + + case NtLoop, NtLazyloop: + if node.M < 1 { + return nil, false + } + lookahead, _ := findLeadingPositiveLookahead(node.Children[0]) + return lookahead, false + + case NtConcatenate: + for i := 0; i < len(node.Children); i++ { + lookahead, keepLooking := findLeadingPositiveLookahead(node.Children[i]) + if lookahead != nil || !keepLooking { + return lookahead, false + } + } + return nil, true + + default: + return nil, false + } + } +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/replacerdata.go b/vendor/github.com/dlclark/regexp2/v2/syntax/replacerdata.go new file mode 100644 index 000000000..ea1b6ab3d --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/replacerdata.go @@ -0,0 +1,87 @@ +package syntax + +import ( + "bytes" + "errors" +) + +type ReplacerData struct { + Rep string + Strings []string + Rules []int +} + +const ( + replaceSpecials = 4 + replaceLeftPortion = -1 + replaceRightPortion = -2 + replaceLastGroup = -3 + replaceWholeString = -4 +) + +// ErrReplacementError is a general error during parsing the replacement text +var ErrReplacementError = errors.New("replacement pattern error") + +// NewReplacerData will populate a reusable replacer data struct based on the given replacement string +// and the capture group data from a regexp +func NewReplacerData(rep string, caps map[int]int, capsize int, capnames map[string]int, op RegexOptions) (*ReplacerData, error) { + p := parser{ + options: op, + caps: caps, + capsize: capsize, + capnames: capnames, + } + p.setPattern(rep) + concat, err := p.scanReplacement() + if err != nil { + return nil, err + } + + if concat.T != NtConcatenate { + panic(ErrReplacementError) + } + + sb := &bytes.Buffer{} + var ( + strings []string + rules []int + ) + + for _, child := range concat.Children { + switch child.T { + case NtMulti: + child.writeStrToBuf(sb) + + case NtOne: + sb.WriteRune(child.Ch) + + case NtRef: + if sb.Len() > 0 { + rules = append(rules, len(strings)) + strings = append(strings, sb.String()) + sb.Reset() + } + slot := child.M + + if len(caps) > 0 && slot >= 0 { + slot = caps[slot] + } + + rules = append(rules, -replaceSpecials-1-slot) + + default: + panic(ErrReplacementError) + } + } + + if sb.Len() > 0 { + rules = append(rules, len(strings)) + strings = append(strings, sb.String()) + } + + return &ReplacerData{ + Rep: rep, + Strings: strings, + Rules: rules, + }, nil +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/tree.go b/vendor/github.com/dlclark/regexp2/v2/syntax/tree.go new file mode 100644 index 000000000..f4b8b6a26 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/tree.go @@ -0,0 +1,2362 @@ +package syntax + +import ( + "bytes" + "fmt" + "math" + "strconv" + "strings" + "unicode" + + "slices" +) + +// Arbitrary number of repetitions of the same character when we'd prefer to represent that as a repeater of that character rather than a string. +const MultiVsRepeaterLimit = 64 + +type RegexTree struct { + Root *RegexNode + Caps map[int]int + Capnumlist []int + Captop int + Capnames map[string]int + Caplist []string + Options RegexOptions + FindOptimizations *FindOptimizations +} + +// It is built into a parsed tree for a regular expression. + +// Implementation notes: +// +// Since the node tree is a temporary data structure only used +// during compilation of the regexp to integer codes, it's +// designed for clarity and convenience rather than +// space efficiency. +// +// RegexNodes are built into a tree, linked by the n.children list. +// Each node also has a n.parent and n.ichild member indicating +// its parent and which child # it is in its parent's list. +// +// RegexNodes come in as many types as there are constructs in +// a regular expression, for example, "concatenate", "alternate", +// "one", "rept", "group". There are also node types for basic +// peephole optimizations, e.g., "onerep", "notsetrep", etc. +// +// Because perl 5 allows "lookback" groups that scan backwards, +// each node also gets a "direction". Normally the value of +// boolean n.backward = false. +// +// On the parse stack, each tree has a "role" - basically, the +// nonterminal in the grammar that the parser has currently +// assigned to the tree. That code is stored in n.role. +// +// Finally, some of the different kinds of nodes have data. +// Two integers (for the looping constructs) are stored in +// n.operands, an an object (either a string or a set) +// is stored in n.data +type RegexNode struct { + T NodeType + Children []*RegexNode + Str []rune + Set *CharSet + Ch rune + M int + N int + Options RegexOptions + Parent *RegexNode +} + +type NodeType int32 + +const ( + // The following are leaves, and correspond to primitive operations + NtUnknown NodeType = -1 + //NtOnerep NodeType = 0 // lef,back char,min,max a {n} + //NtNotonerep NodeType = 1 // lef,back char,min,max .{n} + //NtSetrep NodeType = 2 // lef,back set,min,max [\d]{n} + NtOneloop NodeType = 3 // lef,back char,min,max a {,n} + NtNotoneloop NodeType = 4 // lef,back char,min,max .{,n} + NtSetloop NodeType = 5 // lef,back set,min,max [\d]{,n} + NtOnelazy NodeType = 6 // lef,back char,min,max a {,n}? + NtNotonelazy NodeType = 7 // lef,back char,min,max .{,n}? + NtSetlazy NodeType = 8 // lef,back set,min,max [\d]{,n}? + NtOne NodeType = 9 // lef char a + NtNotone NodeType = 10 // lef char [^a] + NtSet NodeType = 11 // lef set [a-z\s] \w \s \d + NtMulti NodeType = 12 // lef string abcd + NtRef NodeType = 13 // lef group \# + NtBol NodeType = 14 // ^ + NtEol NodeType = 15 // $ + NtBoundary NodeType = 16 // \b + NtNonboundary NodeType = 17 // \B + NtBeginning NodeType = 18 // \A + NtStart NodeType = 19 // \G + NtEndZ NodeType = 20 // \Z + NtEnd NodeType = 21 // \Z + + // Interior nodes do not correspond to primitive operations, but + // control structures compositing other operations + + // Concat and alternate take n children, and can run forward or backwards + + NtNothing NodeType = 22 // [] + NtEmpty NodeType = 23 // () + NtAlternate NodeType = 24 // a|b + NtConcatenate NodeType = 25 // ab + NtLoop NodeType = 26 // m,x * + ? {,} + NtLazyloop NodeType = 27 // m,x *? +? ?? {,}? + NtCapture NodeType = 28 // n () + NtGroup NodeType = 29 // (?:) + NtPosLook NodeType = 30 // (?=) (?<=) + NtNegLook NodeType = 31 // (?!) (?) (?<) + NtBackRefCond NodeType = 33 // (?(n) | ) + NtExprCond NodeType = 34 // (?(...) | ) + + NtECMABoundary NodeType = 41 // \b + NtNonECMABoundary NodeType = 42 // \B + + // Atomic loop of the specified character. + // Operand 0 is the character. Operand 1 is the max iteration count. + NtOneloopatomic NodeType = 43 + // Atomic loop of a single character other than the one specified. + // Operand 0 is the character. Operand 1 is the max iteration count. + NtNotoneloopatomic NodeType = 44 + // Atomic loop of a single character matching the specified set + // Operand 0 is index into the strings table of the character class description. Operand 1 is the repetition count. + NtSetloopatomic NodeType = 45 + // Updates the bumpalong position to the current position. + NtUpdateBumpalong NodeType = 46 +) + +func newRegexNode(t NodeType, opt RegexOptions) *RegexNode { + return &RegexNode{ + T: t, + Options: opt, + } +} + +func newRegexNodeCh(t NodeType, opt RegexOptions, ch rune) *RegexNode { + return nodeWithCaseConversion(&RegexNode{ + T: t, + Options: opt, + Ch: ch, + }) +} + +func newRegexNodeStr(t NodeType, opt RegexOptions, str []rune) *RegexNode { + return &RegexNode{ + T: t, + Options: opt, + Str: str, + } +} + +func newRegexNodeSet(t NodeType, opt RegexOptions, set *CharSet) *RegexNode { + return nodeWithCaseConversion(&RegexNode{ + T: t, + Options: opt, + Set: set, + }) +} + +func newRegexNodeM(t NodeType, opt RegexOptions, m int) *RegexNode { + return &RegexNode{ + T: t, + Options: opt, + M: m, + } +} +func newRegexNodeMN(t NodeType, opt RegexOptions, m, n int) *RegexNode { + return &RegexNode{ + T: t, + Options: opt, + M: m, + N: n, + } +} + +func nodeWithCaseConversion(n *RegexNode) *RegexNode { + // if opts are ignore case and our rune is impacted by casing + // then we need to switch our type to the set version + // NtOne = NtSet + if n.Options&IgnoreCase == 0 { + return n + } + + if n.Ch > 0 { + ch := n.Ch + if isLow, isUp := unicode.IsLower(ch), unicode.IsUpper(ch); isLow || isUp { + /*var upper, lower rune + // it's a capitalizable char + if isUp { + upper = ch + lower = unicode.ToLower(ch) + } else { + lower = ch + upper = unicode.ToUpper(ch) + }*/ + set := &CharSet{} + set.addChar(ch) + set.addCaseEquivalences() + t := NtSet + + switch n.T { + case NtOneloop, NtNotoneloop: + t = NtSetloop + case NtOnelazy, NtNotonelazy: + t = NtSetlazy + } + set.negate = n.IsNotoneFamily() + + return &RegexNode{ + T: t, + Options: n.Options & ^IgnoreCase, + Set: set, + } + } + } else if n.Set != nil { + // just to be safe we don't modify the original set pointer + // just in case it's used in a case-sensitive area + s := n.Set.Copy() + s.addCaseEquivalences() + n.Set = &s + n.Options &= ^IgnoreCase + } + + return n +} + +func (n *RegexNode) IsSetFamily() bool { + return n.T == NtSet || n.T == NtSetloop || n.T == NtSetlazy || n.T == NtSetloopatomic +} +func (n *RegexNode) IsOneFamily() bool { + return n.T == NtOne || n.T == NtOneloop || n.T == NtOnelazy || n.T == NtOneloopatomic +} +func (n *RegexNode) IsNotoneFamily() bool { + return n.T == NtNotone || n.T == NtNotoneloop || n.T == NtNotonelazy || n.T == NtNotoneloopatomic +} + +func (n *RegexNode) IsSetloopFamily() bool { + return n.T == NtSetloop || n.T == NtSetlazy || n.T == NtSetloopatomic +} +func (n *RegexNode) IsOneloopFamily() bool { + return n.T == NtOneloop || n.T == NtOnelazy || n.T == NtOneloopatomic +} +func (n *RegexNode) IsNotoneloopFamily() bool { + return n.T == NtNotoneloop || n.T == NtNotonelazy || n.T == NtNotoneloopatomic +} + +func (n *RegexNode) IsAtomicloopFamily() bool { + return n.T == NtOneloopatomic || n.T == NtNotoneloopatomic || n.T == NtSetloopatomic +} + +func (n *RegexNode) writeStrToBuf(buf *bytes.Buffer) { + for i := 0; i < len(n.Str); i++ { + buf.WriteRune(n.Str[i]) + } +} + +func (n *RegexNode) addChild(child *RegexNode) { + child.Parent = n + reduced := child.reduce() + reduced.Parent = n + n.Children = append(n.Children, reduced) + reduced.Parent = n +} + +func (n *RegexNode) insertChildren(afterIndex int, nodes []*RegexNode) { + for _, c := range nodes { + c.Parent = n + } + n.Children = slices.Insert(n.Children, afterIndex, nodes...) + //newChildren := make([]*RegexNode, 0, len(n.Children)+len(nodes)) + //n.Children = append(append(append(newChildren, n.Children[:afterIndex]...), nodes...), n.Children[afterIndex:]...) +} + +// removes children including the start but not the end index +func (n *RegexNode) removeChildren(startIndex, endIndex int) { + n.Children = append(n.Children[:startIndex], n.Children[endIndex:]...) +} + +func (n *RegexNode) ReplaceChild(index int, newChild *RegexNode) { + newChild.Parent = n // so that the child can see its parent while being reduced + newChild = newChild.reduce() + newChild.Parent = n // in case Reduce returns a different node that needs to be reparented + + n.Children[index] = newChild +} + +// Pass type as OneLazy or OneLoop +func (n *RegexNode) makeRep(t NodeType, min, max int) { + n.T += (t - NtOne) + n.M = min + n.N = max +} + +// Performs additional optimizations on an entire tree prior to being used. +// +// Some optimizations are performed by the parser while parsing, and others are performed +// as nodes are being added to the tree. The optimizations here expect the tree to be fully +// formed, as they inspect relationships between nodes that may not have been in place as +// individual nodes were being processed/added to the tree. +func (n *RegexNode) finalOptimize() *RegexNode { + rootNode := n + + // Only apply optimization when LTR to avoid needing additional code for the much rarer RTL case. + // Also only apply these optimizations when not using NonBacktracking, as these optimizations are + // all about avoiding things that are impactful for the backtracking engines but nops for non-backtracking. + if n.Options&RightToLeft == 0 { + // Optimization: eliminate backtracking for loops. + // For any single-character loop (Oneloop, Notoneloop, Setloop), see if we can automatically convert + // that into its atomic counterpart (Oneloopatomic, Notoneloopatomic, Setloopatomic) based on what + // comes after it in the expression tree. + rootNode.findAndMakeLoopsAtomic() + + // Optimization: backtracking removal at expression end. + // If we find backtracking construct at the end of the regex, we can instead make it non-backtracking, + // since nothing would ever backtrack into it anyway. Doing this then makes the construct available + // to implementations that don't support backtracking. + rootNode.eliminateEndingBacktracking() + + // Optimization: unnecessary re-processing of starting loops. + // If an expression is guaranteed to begin with a single-character unbounded loop that isn't part of an alternation (in which case it + // wouldn't be guaranteed to be at the beginning) or a capture (in which case a back reference could be influenced by its length), then we + // can update the tree with a temporary node to indicate that the implementation should use that node's ending position in the input text + // as the next starting position at which to start the next match. This avoids redoing matches we've already performed, e.g. matching + // "\w+@dot.net" against "is this a valid address@dot.net", the \w+ will initially match the "is" and then will fail to match the "@". + // Rather than bumping the scan loop by 1 and trying again to match at the "s", we can instead start at the " ". For functional correctness + // we can only consider unbounded loops, as to be able to start at the end of the loop we need the loop to have consumed all possible matches; + // otherwise, you could end up with a pattern like "a{1,3}b" matching against "aaaabc", which should match, but if we pre-emptively stop consuming + // after the first three a's and re-start from that position, we'll end up failing the match even though it should have succeeded. We can also + // apply this optimization to non-atomic loops: even though backtracking could be necessary, such backtracking would be handled within the processing + // of a single starting position. Lazy loops similarly benefit, as a failed match will result in exploring the exact same search space as with + // a greedy loop, just in the opposite order (and a successful match will overwrite the bumpalong position); we need to avoid atomic lazy loops, + // however, as they will only end up as a repeater for the minimum length and thus will effectively end up with a non-infinite upper bound, which + // we've already outlined is problematic. + node := rootNode.Children[0] // skip implicit root capture node + atomicByAncestry := true // the root is implicitly atomic because nothing comes after it (same for the implicit root capture) + for { + if node.T == NtAtomic { + node = node.Children[0] + continue + } else if node.T == NtConcatenate { + atomicByAncestry = false + node = node.Children[0] + continue + } else if node.N == math.MaxInt32 && + ((node.T == NtOneloop || node.T == NtOneloopatomic || node.T == NtNotoneloop || node.T == NtNotoneloopatomic || node.T == NtSetloop || node.T == NtSetloopatomic) || + ((node.T == NtOnelazy || node.T == NtNotonelazy || node.T == NtSetlazy) && !atomicByAncestry)) { + + if node.Parent != nil && node.Parent.T == NtConcatenate { + node.Parent.Children = slices.Insert(node.Parent.Children, 1, &RegexNode{T: NtUpdateBumpalong, Options: node.Options, Parent: node.Parent}) + } + } + + break + } + + } + + //debug helper + //rootNode.ValidateFinalTreeInvariants() + + // Done optimizing. Return the final tree. + return rootNode +} + +// Finds {one/notone/set}loop nodes in the concatenation that can be automatically upgraded +// to {one/notone/set}loopatomic nodes. Such changes avoid potential useless backtracking. +// e.g. A*B (where sets A and B don't overlap) => (?>A*)B. +func (n *RegexNode) findAndMakeLoopsAtomic() { + if n.Options&RightToLeft != 0 { + // RTL is so rare, we don't need to spend additional time/code optimizing for it. + return + } + + // For all node types that have children, recur into each of those children. + for i := 0; i < len(n.Children); i++ { + n.Children[i].findAndMakeLoopsAtomic() + } + + // If this isn't a concatenation, nothing more to do. + if n.T != NtConcatenate { + return + } + + // This is a concatenation. Iterate through each pair of nodes in the concatenation seeing whether we can + // make the first node (or its right-most child) atomic based on the second node (or its left-most child). + for i := 0; i < len(n.Children)-1; i++ { + n.Children[i].processNode(n.Children[i+1]) + } +} + +func (node *RegexNode) processNode(subsequent *RegexNode) { + // Skip down the node past irrelevant nodes. + for { + // We can always recur into captures and into the last node of concatenations. + if node.T == NtCapture || node.T == NtConcatenate { + node = node.Children[len(node.Children)-1] + continue + } + + // For loops with at least one guaranteed iteration, we can recur into them, but + // we need to be careful not to just always do so; the ending node of a loop can only + // be made atomic if what comes after the loop but also the beginning of the loop are + // compatible for the optimization. + if node.T == NtLoop { + loopDescendent := node.FindLastExpressionInLoopForAutoAtomic() + if loopDescendent != nil { + node = loopDescendent + continue + } + } + + // Can't skip any further. + break + } + + // If the node can be changed to atomic based on what comes after it, do so. + switch node.T { + case NtOneloop, NtNotoneloop, NtSetloop: + if node.canBeMadeAtomic(subsequent, true, false) { + // The greedy loop doesn't overlap with what comes after it, which means giving anything it matches back will not + // help the overall match to succeed, which means it can simply become atomic to match as much as possible. The call + // to CanBeMadeAtomic passes iterateNullableSubsequent=true because, in a pattern like a*b*c*, when analyzing a*, we + // want to examine the b* and the c* rather than just giving up after seeing that b* is nullable; in order to make + // the a* atomic, we need to know that anything that could possibly come after the loop doesn't overlap. + node.makeLoopAtomic() + } + + case NtOnelazy, NtNotonelazy, NtSetlazy: + if node.canBeMadeAtomic(subsequent, false, true) { + // The lazy loop doesn't overlap with what comes after it, which means it needs to match as much as its allowed + // to match in order for there to be a possibility that what comes next matches (if it doesn't match as much + // as it's allowed and there was still more it could match, then what comes next is guaranteed to not match, + // since it doesn't match any of the same things the loop matches). We don't want to just make the lazy loop + // atomic, as an atomic lazy loop matches as little as possible, not as much as possible. Instead, we want to + // make the lazy loop into an atomic greedy loop. Note that when we check CanBeMadeAtomic, we need to set + // "iterateNullableSubsequent" to false so that we only inspect non-nullable subsequent nodes. For example, + // given a pattern like a*?b, we want to upgrade that loop to being greedy atomic, e.g. (?>a*)b. But given a + // pattern like a*?b*, the subsequent node is nullable, which means it doesn't have to be part of a match, which + // means the a*? could match by itself, in which case as it's lazy it needs to match as few a's as possible, e.g. + // a+?b* against the input "aaaab" should match "a", not "aaaa" nor "aaaab". (Technically for lazy, we only need to prevent + // walking off the end of the pattern, but it's not currently worth complicating the implementation for that case.) + // allowLazy is set to true so that the implementation will analyze rather than ignore this node; generally lazy nodes + // are ignored due to making them atomic not generally being a sound change, but here we're explicitly choosing to + // given the circumstances. + node.T -= NtOnelazy - NtOneloop // lazy to greedy + node.makeLoopAtomic() + } + + case NtAlternate, NtBackRefCond, NtExprCond: + // In the case of alternation, we can't change the alternation node itself + // based on what comes after it (at least not with more complicated analysis + // that factors in all branches together), but we can look at each individual + // branch, and analyze ending loops in each branch individually to see if they + // can be made atomic. Then if we do end up backtracking into the alternation, + // we at least won't need to backtrack into that loop. The same is true for + // conditionals, though we don't want to process the condition expression + // itself, as it's already considered atomic and handled as part of ReduceExpressionConditional. + b := 0 + if node.T == NtExprCond { + b = 1 + } + for ; b < len(node.Children); b++ { + node.Children[b].processNode(subsequent) + } + + } +} + +func (n *RegexNode) reduce() *RegexNode { + // Remove IgnoreCase option from everything except a Backreference + if n.T != NtRef { + n.Options &= ^IgnoreCase + } + switch n.T { + case NtAlternate: + return n.reduceAlternation() + case NtAtomic: + return n.reduceAtomic() + case NtConcatenate: + return n.reduceConcatenation() + case NtGroup: + return n.reduceGroup() + case NtLoop, NtLazyloop: + return n.reduceRep() + case NtPosLook, NtNegLook: + return n.reduceLookaround() + case NtSet, NtSetloop, NtSetlazy, NtSetloopatomic: + return n.reduceSet() + case NtExprCond: + return n.reduceExpressionConditional() + case NtBackRefCond: + return n.reduceBackreferenceConditional() + default: + return n + } +} + +// / Optimizations for positive and negative lookaheads/behinds. +func (n *RegexNode) reduceLookaround() *RegexNode { + // A lookaround is a zero-width atomic assertion. + // As it's atomic, nothing will backtrack into it, and we can + // eliminate any ending backtracking from it. + n.eliminateEndingBacktracking() + + // A positive lookaround wrapped around an empty is a nop, and we can reduce it + // to simply Empty. A developer typically doesn't write this, but rather it evolves + // due to optimizations resulting in empty. + + // A negative lookaround wrapped around an empty child, i.e. (?!), is + // sometimes used as a way to insert a guaranteed no-match into the expression, + // often as part of a conditional. We can reduce it to simply Nothing. + + if n.Children[0].T == NtEmpty { + if n.T == NtPosLook { + n.T = NtEmpty + } else { + n.T = NtNothing + } + n.Children = nil + } + + return n +} + +// Optimizations for backreference conditionals. +func (n *RegexNode) reduceBackreferenceConditional() *RegexNode { + // This isn't so much an optimization as it is changing the tree for consistency. We want + // all engines to be able to trust that every backreference conditional will have two children, + // even though it's optional in the syntax. If it's missing a "not matched" branch, + // we add one that will match empty. + if len(n.Children) == 1 { + n.addChild(&RegexNode{T: NtEmpty, Options: n.Options}) + } + + return n +} + +// / Optimizations for expression conditionals. +func (n *RegexNode) reduceExpressionConditional() *RegexNode { + // This isn't so much an optimization as it is changing the tree for consistency. We want + // all engines to be able to trust that every expression conditional will have three children, + // even though it's optional in the syntax. If it's missing a "not matched" branch, + // we add one that will match empty. + if len(n.Children) == 2 { + n.addChild(&RegexNode{T: NtEmpty, Options: n.Options}) + } + + // It's common for the condition to be an explicit positive lookahead, as specifying + // that eliminates any ambiguity in syntax as to whether the expression is to be matched + // as an expression or to be a reference to a capture group. After parsing, however, + // there's no ambiguity, and we can remove an extra level of positive lookahead, as the + // engines need to treat the condition as a zero-width positive, atomic assertion regardless. + condition := n.Children[0] + if condition.T == NtPosLook && (condition.Options&RightToLeft) == 0 { + n.ReplaceChild(0, condition.Children[0]) + } + + // We can also eliminate any ending backtracking in the condition, as the condition + // is considered to be a positive lookahead, which is an atomic zero-width assertion. + condition = n.Children[0] + condition.eliminateEndingBacktracking() + + return n +} + +// Remove unnecessary atomic nodes, and make appropriate descendents of the atomic node themselves atomic. +// e.g. (?>(?>(?>a*))) => (?>a*) +// e.g. (?>(abc*)*) => (?>(abc(?>c*))*) +func (n *RegexNode) reduceAtomic() *RegexNode { + atomic := n + child := n.Children[0] + for child.T == NtAtomic { + atomic = child + child = atomic.Children[0] + } + + switch child.T { + // If the child is empty/nothing, there's nothing to be made atomic so the Atomic + // node can simply be removed. + case NtEmpty, NtNothing: + return child + + // If the child is already atomic, we can just remove the atomic node. + case NtOneloopatomic, NtNotoneloopatomic, NtSetloopatomic: + return child + + // If an atomic subexpression contains only a {one/notone/set}{loop/lazy}, + // change it to be an {one/notone/set}loopatomic and remove the atomic node. + case NtOneloop, NtNotoneloop, NtSetloop, NtOnelazy, NtNotonelazy, NtSetlazy: + child.makeLoopAtomic() + return child + + // Alternations have a variety of possible optimizations that can be applied + // iff they're atomic. + case NtAlternate: + if (n.Options & RightToLeft) == 0 { + branches := child.Children + + // If an alternation is atomic and its first branch is Empty, the whole thing + // is a nop, as Empty will match everything trivially, and no backtracking + // into the node will be performed, making the remaining branches irrelevant. + if branches[0].T == NtEmpty { + return &RegexNode{T: NtEmpty, Options: child.Options} + } + + // Similarly, we can trim off any branches after an Empty, as they'll never be used. + // An Empty will match anything, and thus branches after that would only be used + // if we backtracked into it and advanced passed the Empty after trying the Empty... + // but if the alternation is atomic, such backtracking won't happen. + for i := 1; i < len(branches)-1; i++ { + if branches[i].T == NtEmpty { + branches = slices.Delete(branches, i+1, len(branches)) + break + } + } + + // If an alternation is atomic, we won't ever backtrack back into it, which + // means order matters but not repetition. With backtracking, it would be incorrect + // to convert an expression like "hi|there|hello" into "hi|hello|there", as doing + // so could then change the order of results if we matched "hi" and then failed + // based on what came after it, and both "hello" and "there" could be successful + // with what came later. But without backtracking, we can reorder "hi|there|hello" + // to instead be "hi|hello|there", as "hello" and "there" can't match the same text, + // and once this atomic alternation has matched, we won't try another branch. This + // reordering is valuable as it then enables further optimizations, e.g. + // "hi|there|hello" => "hi|hello|there" => "h(?:i|ello)|there", which means we only + // need to check the 'h' once in case it's not an 'h', and it's easier to employ different + // code gen that, for example, switches on first character of the branches, enabling faster + // choice of branch without always having to walk through each. + reordered := false + for start := 0; start < len(branches); start++ { + // Get the node that may start our range. If it's a one, multi, or concat of those, proceed. + startNode := branches[start] + if startNode.findBranchOneOrMultiStart() == nil { + continue + } + + // Find the contiguous range of nodes from this point that are similarly one, multi, or concat of those. + endExclusive := start + 1 + for endExclusive < len(branches) && branches[endExclusive].findBranchOneOrMultiStart() != nil { + endExclusive++ + } + + // If there's at least 3, there may be something to reorder (we won't reorder anything + // before the starting position, and so only 2 items is considered ordered). + if endExclusive-start >= 3 { + compare := start + for compare < endExclusive { + // Get the starting character + c := branches[compare].findBranchOneOrMultiStart().FirstCharOfOneOrMulti() + + // Move compare to point to the last branch that has the same starting value. + for compare < endExclusive && branches[compare].findBranchOneOrMultiStart().FirstCharOfOneOrMulti() == c { + compare++ + } + + // Compare now points to the first node that doesn't match the starting node. + // If we've walked off our range, there's nothing left to reorder. + if compare < endExclusive { + // There may be something to reorder. See if there are any other nodes that begin with the same character. + for next := compare + 1; next < endExclusive; next++ { + nextChild := branches[next] + if nextChild.findBranchOneOrMultiStart().FirstCharOfOneOrMulti() == c { + branches = slices.Delete(branches, next, next+1) + branches = slices.Insert(branches, compare, nextChild) + compare++ + reordered = true + } + } + } + } + } + + // Move to the end of the range we've now explored. endExclusive is not a viable + // starting position either, and the start++ for the loop will thus take us to + // the next potential place to start a range. + start = endExclusive + } + child.Children = branches + // If anything was reordered, there may be new optimization opportunities inside + // of the alternation, so reduce it again. + if reordered { + atomic.ReplaceChild(0, child) + child = atomic.Children[0] + } + } + fallthrough + + // For everything else, try to reduce ending backtracking of the last contained expression. + default: + child.eliminateEndingBacktracking() + return atomic + } +} + +func (n *RegexNode) makeLoopAtomic() { + + switch n.T { + case NtOneloop, NtNotoneloop, NtSetloop: + // For loops, we simply change the Type to the atomic variant. + // Atomic greedy loops should consume as many values as they can. + n.T += NtOneloopatomic - NtOneloop + + case NtOnelazy, NtNotonelazy, NtSetlazy: + // For lazy, we not only change the Type, we also lower the max number of iterations + // to the minimum number of iterations, creating a repeater, as they should end up + // matching as little as possible. + n.T += NtOneloopatomic - NtOnelazy + n.N = n.M + if n.N == 0 { + // If moving the max to be the same as the min dropped it to 0, there's no + // work to be done for this node, and we can make it Empty. + n.T = NtEmpty + n.Str = nil + n.Ch = 0x0 + } else if n.T == NtOneloopatomic && n.N >= 2 && n.N <= MultiVsRepeaterLimit { + // If this is now a One repeater with a small enough length, + // make it a Multi instead, as they're better optimized down the line. + n.T = NtMulti + n.Str = []rune(strings.Repeat(string(n.Ch), n.N)) + n.Ch = 0x0 + n.M = 0 + n.N = 0 + } + } +} + +// Converts nodes at the end of the node tree to be atomic. +// The correctness of this optimization depends on nothing being able to backtrack into +// the provided node. That means it must be at the root of the overall expression, or +// it must be an Atomic node that nothing will backtrack into by the very nature of Atomic. +func (n *RegexNode) eliminateEndingBacktracking() { + // Walk the tree starting from the current node. + node := n + for { + switch node.T { + // {One/Notone/Set}loops can be upgraded to {One/Notone/Set}loopatomic nodes, e.g. [abc]* => (?>[abc]*). + // And {One/Notone/Set}lazys can similarly be upgraded to be atomic, which really makes them into repeaters + // or even empty nodes. + case NtOneloop, NtNotoneloop, NtSetloop, NtOnelazy, NtNotonelazy, NtSetlazy: + node.makeLoopAtomic() + + // Just because a particular node is atomic doesn't mean all its descendants are. + // Process them as well. Lookarounds are implicitly atomic. + case NtAtomic, NtPosLook, NtNegLook: + node = node.Children[0] + continue + + case NtCapture, NtConcatenate: + // For Capture and Concatenate, we just recur into their last child (only child in the case + // of Capture). However, if the child is an alternation or loop, we can also make the + // node itself atomic by wrapping it in an Atomic node. Since we later check to see whether a + // node is atomic based on its parent or grandparent, we don't bother wrapping such a node in + // an Atomic one if its grandparent is already Atomic. + // e.g. [xyz](?:abc|def) => [xyz](?>abc|def) + + // validate grandparent isn't atomic + existingChild := node.Children[len(node.Children)-1] + if (existingChild.T == NtAlternate || existingChild.T == NtBackRefCond || + existingChild.T == NtExprCond || existingChild.T == NtLoop || + existingChild.T == NtLazyloop) && + (node.Parent == nil || node.Parent.T != NtAtomic) { + + atomic := &RegexNode{T: NtAtomic, Options: existingChild.Options} + atomic.addChild(existingChild) + node.ReplaceChild(len(node.Children)-1, atomic) + } + node = existingChild + continue + + // For alternate, we can recur into each branch separately. We use this iteration for the first branch. + // Conditionals are just like alternations in this regard. + // e.g. abc*|def* => ab(?>c*)|de(?>f*) + case NtAlternate, NtBackRefCond, NtExprCond: + + branches := len(node.Children) + for i := 1; i < branches; i++ { + node.Children[i].eliminateEndingBacktracking() + } + + // ReduceExpressionConditional will have already applied ending backtracking removal + if node.T != NtExprCond { + node = node.Children[0] + continue + } + + // For {Lazy}Loop, we search to see if there's a viable last expression, and iff there + // is we recur into processing it. Also, as with the single-char lazy loops, LazyLoop + // can have its max iteration count dropped to its min iteration count, as there's no + // reason for it to match more than the minimal at the end; that in turn makes it a + // repeater, which results in better code generation. + // e.g. (?:abc*)* => (?:ab(?>c*))* + // e.g. (abc*?)+? => (ab){1} + case NtLazyloop: + node.N = node.M + fallthrough + case NtLoop: + if node.N == 1 { + // If the loop has a max iteration count of 1 (e.g. it's an optional node), + // there's no possibility for conflict between multiple iterations, so + // we can process it. + node = node.Children[0] + continue + } + + loopDescendent := node.FindLastExpressionInLoopForAutoAtomic() + if loopDescendent != nil { + node = loopDescendent + continue // loop around to process node + } + + } + + break + } +} + +// Recurs into the last expression of a loop node, looking to see if it can find a node +// that could be made atomic _assuming_ the conditions exist for it with the loop's ancestors. +// Returns The found node that should be explored further for auto-atomicity; null if it doesn't exist. +func (n *RegexNode) FindLastExpressionInLoopForAutoAtomic() *RegexNode { + node := n + + // Start by looking at the loop's sole child. + node = node.Children[0] + + // Skip past captures. + for node.T == NtCapture { + node = node.Children[0] + } + + // If the loop's body is a concatenate, we can skip to its last child iff that + // last child doesn't conflict with the first child, since this whole concatenation + // could be repeated, such that the first node ends up following the last. For + // example, in the expression (a+[def])*, the last child is [def] and the first is + // a+, which can't possibly overlap with [def]. In contrast, if we had (a+[ade])*, + // [ade] could potentially match the starting 'a'. + if node.T == NtConcatenate { + concatCount := len(node.Children) + lastConcatChild := node.Children[concatCount-1] + if lastConcatChild.canBeMadeAtomic(node.Children[0], false, false) { + return lastConcatChild + } + } + + // Otherwise, the loop has nothing that can participate in auto-atomicity. + return nil +} + +// Determines whether a node can be switched to an atomic loop. +// +// The node following is subsequent, used to determine whether it overlaps. +// iterateNullableSubsequent is whether to allow examining nodes beyond subsequent. +// allowLazy is whether lazy loops in addition to greedy loops should be considered for atomicity. +func (n *RegexNode) canBeMadeAtomic(subsequent *RegexNode, iterateNullableSubsequent, allowLazy bool) bool { + // In most case, we'll simply check the node against whatever subsequent is. However, in case + // subsequent ends up being a loop with a min bound of 0, we'll also need to evaluate the node + // against whatever comes after subsequent. In that case, we'll walk the tree to find the + // next subsequent, and we'll loop around against to perform the comparison again. + for { + // Skip the successor down to the closest node that's guaranteed to follow it. + childCount := len(subsequent.Children) + for ; childCount > 0; childCount = len(subsequent.Children) { + if subsequent.T == NtConcatenate || subsequent.T == NtCapture || + subsequent.T == NtAtomic || + (subsequent.T == NtPosLook && subsequent.Options&RightToLeft == 0) || + ((subsequent.T == NtLoop || subsequent.T == NtLazyloop) && subsequent.M > 0) { + subsequent = subsequent.Children[0] + continue + } + + break + } + + // If the current node's options don't match the subsequent node, then we cannot make it atomic. + // This applies to RightToLeft for lookbehinds, as well as patterns that enable/disable global flags in the middle of the pattern. + if n.Options != subsequent.Options { + return false + } + + // If the successor is an alternation, all of its children need to be evaluated, since any of them + // could come after this node. If any of them fail the optimization, then the whole node fails. + // This applies to expression conditionals as well, as long as they have both a yes and a no branch (if there's + // only a yes branch, we'd need to also check whatever comes after the conditional). It doesn't apply to + // backreference conditionals, as the condition itself is unknown statically and could overlap with the + // loop being considered for atomicity. + if subsequent.T == NtAlternate || (subsequent.T == NtExprCond && childCount == 3) { + // condition, yes, and no branch + for i := 0; i < childCount; i++ { + if !n.canBeMadeAtomic(subsequent.Children[i], iterateNullableSubsequent, false) { + return false + } + } + return true + } + + // If this node is a {one/notone/set}loop, see if it overlaps with its successor in the concatenation. + // If it doesn't, then we can upgrade it to being a {one/notone/set}loopatomic. + // Doing so avoids unnecessary backtracking. + if n.T == NtOneloop || (n.T == NtOnelazy && allowLazy) { + + if (subsequent.T == NtOne && n.Ch != subsequent.Ch) || + (subsequent.T == NtNotone && n.Ch == subsequent.Ch) || + (subsequent.T == NtSet && !subsequent.Set.CharIn(n.Ch)) || + (subsequent.IsOneFamily() && subsequent.M > 0 && n.Ch != subsequent.Ch) || + (subsequent.IsNotoneFamily() && subsequent.M > 0 && n.Ch == subsequent.Ch) || + (subsequent.IsSetFamily() && subsequent.M > 0 && !subsequent.Set.CharIn(n.Ch)) || + (subsequent.T == NtMulti && n.Ch != subsequent.Str[0]) || + (subsequent.T == NtEnd) || + (subsequent.T == NtEndZ && n.Ch != '\n') || + (subsequent.T == NtEol && n.Ch != '\n') { + return true + } + + // The loop can be made atomic based on this subsequent node, but we'll need to evaluate the next one as well. + if (subsequent.IsOneloopFamily() && subsequent.M == 0 && n.Ch != subsequent.Ch) || + (subsequent.IsNotoneloopFamily() && subsequent.M == 0 && n.Ch == subsequent.Ch) || + (subsequent.IsSetloopFamily() && subsequent.M == 0 && !subsequent.Set.CharIn(n.Ch)) || + (subsequent.T == NtBoundary && n.M > 0 && IsWordChar(n.Ch)) || + (subsequent.T == NtNonboundary && n.M > 0 && !IsWordChar(n.Ch)) || + (subsequent.T == NtECMABoundary && n.M > 0 && IsECMAWordChar(n.Ch)) || + (subsequent.T == NtNonECMABoundary && n.M > 0 && !IsECMAWordChar(n.Ch)) { + goto end + } + + return false + + } else if n.T == NtNotoneloop || (n.T == NtNotonelazy && allowLazy) { + if (subsequent.T == NtOne && n.Ch == subsequent.Ch) || + (subsequent.IsOneFamily() && subsequent.M > 0 && n.Ch == subsequent.Ch) || + (subsequent.T == NtMulti && n.Ch == subsequent.Str[0]) || + (subsequent.T == NtEnd) { + return true + } + + // The loop can be made atomic based on this subsequent node, but we'll need to evaluate the next one as well. + if subsequent.IsOneloopFamily() && subsequent.M == 0 && n.Ch == subsequent.Ch { + goto end + } + + return false + } else if n.T == NtSetloop || (n.T == NtSetlazy && allowLazy) { + if (subsequent.T == NtOne && !n.Set.CharIn(subsequent.Ch)) || + (subsequent.T == NtSet && !n.Set.MayOverlap(subsequent.Set)) || + (subsequent.IsOneloopFamily() && subsequent.M > 0 && !n.Set.CharIn(subsequent.Ch)) || + (subsequent.IsSetloopFamily() && subsequent.M > 0 && !n.Set.MayOverlap(subsequent.Set)) || + (subsequent.T == NtMulti && !n.Set.CharIn(subsequent.Str[0])) || + (subsequent.T == NtEnd) || + (subsequent.T == NtEndZ && !n.Set.CharIn('\n')) || + (subsequent.T == NtEol && !n.Set.CharIn('\n')) { + return true + } + + if (subsequent.IsOneloopFamily() && subsequent.M == 0 && !n.Set.CharIn(subsequent.Ch)) || + (subsequent.IsSetloopFamily() && subsequent.M == 0 && subsequent.Set.MayOverlap(n.Set)) || + (subsequent.T == NtBoundary && n.M > 0 && (n.Set.Equals(WordClass()) || n.Set.Equals(DigitClass()))) || + (subsequent.T == NtNonboundary && n.M > 0 && (n.Set.Equals(NotWordClass()) || n.Set.Equals(NotDigitClass()))) || + (subsequent.T == NtECMABoundary && n.M > 0 && (n.Set.Equals(ECMAWordClass()) || n.Set.Equals(ECMADigitClass()))) || + (subsequent.T == NtNonECMABoundary && n.M > 0 && (n.Set.Equals(NotECMAWordClass()) || n.Set.Equals(NotDigitClass()))) { + // The loop can be made atomic based on this subsequent node, but we'll need to evaluate the next one as well. + goto end + } + return false + } else { + return false + } + + end: + // We only get here if the node could be made atomic based on subsequent but subsequent has a lower bound of zero + // and thus we need to move subsequent to be the next node in sequence and loop around to try again. + if !iterateNullableSubsequent { + return false + } + + // To be conservative, we only walk up through a very limited set of constructs (even though we may have walked + // down through more, like loops), looking for the next concatenation that we're not at the end of, at + // which point subsequent becomes whatever node is next in that concatenation. + for { + parent := subsequent.Parent + if parent == nil { + // If we hit the root, we're at the end of the expression, at which point nothing could backtrack + // in and we can declare success. + return true + } + + switch parent.T { + case NtAtomic, NtAlternate, NtCapture: + subsequent = parent + continue + + case NtConcatenate: + peers := parent.Children + currentIndex := slices.Index(peers, subsequent) + + if currentIndex+1 == len(peers) { + subsequent = parent + continue + } else { + subsequent = peers[currentIndex+1] + } + + default: + // Anything else, we don't know what to do, so we have to assume it could conflict with the loop. + return false + } + + break + } + } +} + +// Basic optimization. Single-letter alternations can be replaced +// by faster set specifications, and nested alternations with no +// intervening operators can be flattened: +// +// a|b|c|def|g|h -> [a-c]|def|[gh] +// apple|(?:orange|pear)|grape -> apple|orange|pear|grape +func (n *RegexNode) reduceAlternation() *RegexNode { + if len(n.Children) == 0 { + return newRegexNode(NtNothing, n.Options) + } + if len(n.Children) == 1 { + return n.Children[0] + } + n.reduceSingleLetterAndNestedAlternations() + + node := n.replaceNodeIfUnnecessary() + if node.T == NtAlternate { + node = node.extractCommonPrefixText() + if node.T == NtAlternate { + node = node.extractCommonPrefixOneNotoneSet() + if node.T == NtAlternate { + node = node.removeRedundantEmptiesAndNothings() + } + } + } + return node +} + +// Analyzes all the branches of the alternation for text that's identical at the beginning +// of every branch. That text is then pulled out into its own one or multi node in a +// concatenation with the alternation (whose branches are updated to remove that prefix). +// This is valuable for a few reasons. One, it exposes potentially more text to the +// expression prefix analyzer used to influence FindFirstChar. Second, it exposes more +// potential alternation optimizations, e.g. if the same prefix is followed in two branches +// by sets that can be merged. Third, it reduces the amount of duplicated comparisons required +// if we end up backtracking into subsequent branches. +// e.g. abc|ade => a(?bc|de) +func (n *RegexNode) extractCommonPrefixText() *RegexNode { + // To keep things relatively simple, we currently only handle: + // - Left to right (e.g. we don't process alternations in lookbehinds) + // - Branches that are one or multi nodes, or that are concatenations beginning with one or multi nodes. + // - All branches having the same options. + + // Only extract left-to-right prefixes. + if (n.Options & RightToLeft) != 0 { + return n + } + + for startingIndex := 0; startingIndex < len(n.Children)-1; startingIndex++ { + // Process the first branch to get the maximum possible common string. + startingNode := n.Children[startingIndex].findBranchOneOrMultiStart() + if startingNode == nil { + return n + } + + startingNodeOptions := startingNode.Options + startingSpan := startingNode.Str + if startingNode.T == NtOne { + startingSpan = []rune{startingNode.Ch} + } + + // Now compare the rest of the branches against it. + endingIndex := startingIndex + 1 + for ; endingIndex < len(n.Children); endingIndex++ { + // Get the starting node of the next branch. + startingNode = n.Children[endingIndex].findBranchOneOrMultiStart() + if startingNode == nil || startingNode.Options != startingNodeOptions { + break + } + + // See if the new branch's prefix has a shared prefix with the current one. + // If it does, shorten to that; if it doesn't, bail. + if startingNode.T == NtOne { + if startingSpan[0] != startingNode.Ch { + break + } + + if len(startingSpan) != 1 { + startingSpan = startingSpan[0:1] + } + } else { + minLength := len(startingSpan) + if len(startingNode.Str) < minLength { + minLength = len(startingNode.Str) + } + c := 0 + for c < minLength && startingSpan[c] == startingNode.Str[c] { + c++ + } + + if c == 0 { + break + } + + startingSpan = startingSpan[0:c] + } + } + + // When we get here, we have a starting string prefix shared by all branches + // in the range [startingIndex, endingIndex). + if endingIndex-startingIndex <= 1 { + // There's nothing to consolidate for this starting node. + continue + } + + // We should be able to consolidate something for the nodes in the range [startingIndex, endingIndex). + + // Create a new node of the form: + // Concatenation(prefix, Alternation(each | node | with | prefix | removed)) + // that replaces all these branches in this alternation. + var prefix *RegexNode + if len(startingSpan) == 1 { + prefix = &RegexNode{T: NtOne, Options: startingNodeOptions, Ch: startingSpan[0]} + } else { + prefix = &RegexNode{T: NtMulti, Options: startingNodeOptions, Str: slices.Clone(startingSpan)} + } + + newAlternate := &RegexNode{T: NtAlternate, Options: startingNodeOptions} + for i := startingIndex; i < endingIndex; i++ { + branch := n.Children[i] + if branch.T == NtConcatenate { + branch.Children[0].processOneOrMulti(startingSpan) + } else { + branch.processOneOrMulti(startingSpan) + } + branch = branch.reduce() + newAlternate.addChild(branch) + } + + if n.Parent != nil && n.Parent.T == NtAtomic { + var atomic = &RegexNode{T: NtAtomic, Options: startingNodeOptions} + atomic.addChild(newAlternate) + newAlternate = atomic + } + + newConcat := &RegexNode{T: NtConcatenate, Options: startingNodeOptions} + newConcat.addChild(prefix) + newConcat.addChild(newAlternate) + n.ReplaceChild(startingIndex, newConcat) + n.Children = slices.Delete(n.Children, startingIndex+1, endingIndex) + } + + if len(n.Children) == 1 { + return n.Children[0] + } + + return n +} + +// This function optimizes out prefix nodes from alternation branches that are +// the same across multiple contiguous branches. +// e.g. \w12|\d34|\d56|\w78|\w90 => \w12|\d(?:34|56)|\w(?:78|90) +func (n *RegexNode) extractCommonPrefixOneNotoneSet() *RegexNode { + // Only process left-to-right prefixes. + if (n.Options & RightToLeft) != 0 { + return n + } + + // Only handle the case where each branch is a concatenation + for _, child := range n.Children { + if child.T != NtConcatenate || len(child.Children) < 2 { + return n + } + } + + for startingIndex := 0; startingIndex < len(n.Children)-1; startingIndex++ { + // Only handle the case where each branch begins with the same One, Notone, or Set (individual or loop). + // Note that while we can do this for individual characters, fixed length loops, and atomic loops, doing + // it for non-atomic variable length loops could change behavior as each branch could otherwise have a + // different number of characters consumed by the loop based on what's after it. + required := n.Children[startingIndex].Children[0] + + if (!required.IsOneFamily() && !required.IsNotoneFamily() && !required.IsSetFamily()) || + required.M != required.N { + // skip if it's not one of these scenarios + continue + } + + // Only handle the case where each branch begins with the exact same node value + endingIndex := startingIndex + 1 + for ; endingIndex < len(n.Children); endingIndex++ { + other := n.Children[endingIndex].Children[0] + if required.T != other.T || + required.Options != other.Options || + required.M != other.M || + required.N != other.N || + required.Ch != other.Ch || + !slices.Equal(required.Str, other.Str) || + !required.Set.Equals(other.Set) { + break + } + } + + if endingIndex-startingIndex <= 1 { + // Nothing to extract from this starting index. + continue + } + + // Remove the prefix node from every branch, adding it to a new alternation + newAlternate := &RegexNode{T: NtAlternate, Options: n.Options} + for i := startingIndex; i < endingIndex; i++ { + n.Children[i].Children = slices.Delete(n.Children[i].Children, 0, 1) + newAlternate.addChild(n.Children[i]) + } + + // If this alternation is wrapped as atomic, we need to do the same for the new alternation. + if n.Parent != nil && n.Parent.T == NtAtomic { + atomic := &RegexNode{T: NtAtomic, Options: n.Options} + atomic.addChild(newAlternate) + newAlternate = atomic + } + + // Now create a concatenation of the prefix node with the new alternation for the combined + // branches, and replace all of the branches in this alternation with that new concatenation. + newConcat := &RegexNode{T: NtConcatenate, Options: n.Options} + newConcat.addChild(required) + newConcat.addChild(newAlternate) + n.ReplaceChild(startingIndex, newConcat) + n.Children = slices.Delete(n.Children, startingIndex+1, endingIndex) + } + + return n.replaceNodeIfUnnecessary() +} + +// Removes unnecessary Empty and Nothing nodes from the alternation. A Nothing will never +// match, so it can be removed entirely, and an Empty can be removed if there's a previous +// Empty in the alternation: it's an extreme case of just having a repeated branch in an +// alternation, and while we don't check for all duplicates, checking for empty is easy. +func (n *RegexNode) removeRedundantEmptiesAndNothings() *RegexNode { + children := n.Children + + i, j := 0, 0 + seenEmpty := false + for i < len(children) { + child := children[i] + if child.T == NtNothing || (child.T == NtEmpty && seenEmpty) { + i++ + continue + } + if child.T == NtEmpty { + seenEmpty = true + } + children[j] = children[i] + i++ + j++ + } + + n.Children = slices.Delete(children, j, len(children)) + return n.replaceNodeIfUnnecessary() +} + +// Remove the starting text from the one or multi node. This may end up changing +// the type of the node to be Empty if the starting text matches the node's full value. +func (n *RegexNode) processOneOrMulti(startingSpan []rune) { + if n.T == NtOne { + n.T = NtEmpty + n.Ch = 0x0 + } else { + if len(n.Str) == len(startingSpan) { + n.T = NtEmpty + n.Str = nil + } else if len(n.Str)-1 == len(startingSpan) { + n.T = NtOne + n.Ch = n.Str[len(n.Str)-1] + n.Str = nil + } else { + n.Str = n.Str[len(startingSpan):] + } + } +} + +// Finds the starting one or multi of the branch, if it has one; otherwise, returns null. +// For simplicity, this only considers branches that are One or Multi, or a Concatenation +// beginning with a One or Multi. We don't traverse more than one level to avoid the +// complication of then having to later update that hierarchy when removing the prefix, +// but it could be done in the future if proven beneficial enough. +func (n *RegexNode) findBranchOneOrMultiStart() *RegexNode { + branch := n + if n.T == NtConcatenate { + branch = n.Children[0] + } + if branch.T == NtOne || branch.T == NtMulti { + return branch + } + return nil +} + +func (n *RegexNode) reduceSingleLetterAndNestedAlternations() { + + wasLastSet := false + lastNodeCannotMerge := false + var optionsLast RegexOptions + var i, j int + + for i, j = 0, 0; i < len(n.Children); i, j = i+1, j+1 { + at := n.Children[i] + + if j < i { + n.Children[j] = at + } + + for { + if at.T == NtAlternate { + n.insertChildren(i+1, at.Children) + j-- + } else if at.T == NtSet || at.T == NtOne { + // Cannot merge sets if L or I options differ, or if either are negated. + optionsAt := at.Options & (RightToLeft | IgnoreCase) + + if at.T == NtSet { + if !wasLastSet || optionsLast != optionsAt || lastNodeCannotMerge || !at.Set.IsMergeable() { + wasLastSet = true + lastNodeCannotMerge = !at.Set.IsMergeable() + optionsLast = optionsAt + break + } + } else if !wasLastSet || optionsLast != optionsAt || lastNodeCannotMerge { + wasLastSet = true + lastNodeCannotMerge = false + optionsLast = optionsAt + break + } + + // The last node was a Set or a One, we're a Set or One and our options are the same. + // Merge the two nodes. + j-- + prev := n.Children[j] + + var prevCharClass *CharSet + if prev.T == NtOne { + prevCharClass = &CharSet{} + prevCharClass.addChar(prev.Ch) + } else { + prevCharClass = prev.Set + } + + if at.T == NtOne { + prevCharClass.addChar(at.Ch) + } else { + prevCharClass.addSet(*at.Set) + } + + prev.T = NtSet + prev.Set = prevCharClass + if prev.Options&IgnoreCase != 0 { + prev.Options &= ^IgnoreCase + } + } else if at.T == NtNothing { + j-- + } else { + wasLastSet = false + lastNodeCannotMerge = false + } + break + } + } + + if j < i { + n.removeChildren(j, i) + } +} + +func (n *RegexNode) reduceConcatenation() *RegexNode { + // Eliminate empties and concat adjacent strings/chars + + if len(n.Children) == 0 { + return newRegexNode(NtEmpty, n.Options) + } + // remove concat + if len(n.Children) == 1 { + return n.Children[0] + } + + // If any node in the concatenation is a Nothing, the concatenation itself is a Nothing. + for i := 0; i < len(n.Children); i++ { + child := n.Children[i] + if child.T == NtNothing { + return child + } + } + + // Coalesce adjacent loops. This helps to minimize work done by the interpreter, minimize code gen, + // and also help to reduce catastrophic backtracking. + n.reduceConcatenationWithAdjacentLoops() + + // Coalesce adjacent characters/strings. This is done after the adjacent loop coalescing so that + // a One adjacent to both a Multi and a Loop prefers being folded into the Loop rather than into + // the Multi. Doing so helps with auto-atomicity when it's later applied. + n.reduceConcatenationWithAdjacentStrings() + + // If the concatenation is now empty, return an empty node, or if it's got a single child, return that child. + // Otherwise, return this. + return n.replaceNodeIfUnnecessary() +} + +func addMinLength(x, y int) int { + const maxMinLength = math.MaxInt32 - 1 + if x >= maxMinLength || y >= maxMinLength || x > maxMinLength-y { + return maxMinLength + } + return x + y +} + +func multiplyMinLength(x, y int) int { + const maxMinLength = math.MaxInt32 - 1 + if x == 0 || y == 0 { + return 0 + } + if x >= maxMinLength || y >= maxMinLength || x > maxMinLength/y { + return maxMinLength + } + return x * y +} + +func addMaxLength(x, y int) int { + if x < 0 || y < 0 || x >= math.MaxInt32 || y >= math.MaxInt32 || x > (math.MaxInt32-1)-y { + return -1 + } + return x + y +} + +func multiplyMaxLength(x, y int) int { + if x < 0 || y < 0 { + return -1 + } + if x == 0 || y == 0 { + return 0 + } + if x >= math.MaxInt32 || y >= math.MaxInt32 || x > (math.MaxInt32-1)/y { + return -1 + } + return x * y +} + +func maxLessThanTwiceMin(max, min int) bool { + if min <= math.MaxInt32/2 { + return max < min*2 + } + return max != math.MaxInt32 +} + +func canCombineCounts(nodeMin, nodeMax, nextMin, nextMax int) bool { + // We shouldn't have an infinite minimum; bail if we find one. Also check for the + // degenerate case where we'd make the min overflow or go infinite when it wasn't already. + if nodeMin == math.MaxInt32 || + nextMin == math.MaxInt32 || + addMaxLength(nodeMin, nextMin) < 0 { + return false + } + + // Similar overflow / go infinite check for max (which can be infinite). + if nodeMax != math.MaxInt32 && + nextMax != math.MaxInt32 && + addMaxLength(nodeMax, nextMax) < 0 { + return false + } + + return true +} + +// Combine adjacent loops. +// e.g. a*a*a* => a* +// e.g. a+ab => a{2,}b +func (n *RegexNode) reduceConcatenationWithAdjacentLoops() { + current, next, nextSave := 0, 1, 1 + + for next < len(n.Children) { + currentNode := n.Children[current] + nextNode := n.Children[next] + + if currentNode.Options == nextNode.Options { + // Coalescing a loop with its same type + if ((currentNode.IsOneloopFamily() || currentNode.IsNotoneloopFamily()) && nextNode.T == currentNode.T && currentNode.Ch == nextNode.Ch) || + (currentNode.IsSetloopFamily() && currentNode.T == nextNode.T && currentNode.Set.Equals(nextNode.Set)) { + if nextNode.M > 0 && currentNode.IsAtomicloopFamily() { + // Atomic loops can only be combined if the second loop has no lower bound, as if it has a lower bound, + // combining them changes behavior. Uncombined, the first loop can consume all matching items; + // the second loop might then not be able to meet its minimum and fail. But if they're combined, the combined + // minimum of the sole loop could now be met, introducing matches where there shouldn't have been any. + goto End + } + + if !canCombineCounts(currentNode.M, currentNode.N, nextNode.M, nextNode.N) { + goto End + } + currentNode.M += nextNode.M + if currentNode.N != math.MaxInt32 { + if nextNode.N == math.MaxInt32 { + currentNode.N = math.MaxInt32 + } else { + currentNode.N += nextNode.N + } + } + next++ + continue + + } else if ((currentNode.T == NtOneloop || currentNode.T == NtOnelazy) && nextNode.T == NtOne && currentNode.Ch == nextNode.Ch) || + ((currentNode.T == NtNotoneloop || currentNode.T == NtNotonelazy) && nextNode.T == NtNotone && currentNode.Ch == nextNode.Ch) || + ((currentNode.T == NtSetloop || currentNode.T == NtSetlazy) && nextNode.T == NtSet && currentNode.Set.Equals(nextNode.Set)) { + // Coalescing a loop with an additional item of the same type + if canCombineCounts(currentNode.M, currentNode.N, 1, 1) { + currentNode.M++ + if currentNode.N != math.MaxInt32 { + currentNode.N++ + } + next++ + continue + } + } else if (currentNode.T == NtOneloop || currentNode.T == NtOnelazy) && nextNode.T == NtMulti && currentNode.Ch == nextNode.Str[0] { + // Coalescing a loop with a subsequent string + // Determine how many of the multi's characters can be combined. + // We already checked for the first, so we know it's at least one. + matchingCharsInMulti := 1 + for matchingCharsInMulti < len(nextNode.Str) && currentNode.Ch == nextNode.Str[matchingCharsInMulti] { + matchingCharsInMulti++ + } + + if canCombineCounts(currentNode.M, currentNode.N, matchingCharsInMulti, matchingCharsInMulti) { + // Update the loop's bounds to include those characters from the multi + currentNode.M += matchingCharsInMulti + if currentNode.N != math.MaxInt32 { + currentNode.N += matchingCharsInMulti + } + + // If it was the full multi, skip/remove the multi and continue processing this loop. + if len(nextNode.Str) == matchingCharsInMulti { + next++ + continue + } + + // Otherwise, trim the characters from the multiple that were absorbed into the loop. + // If it now only has a single character, it becomes a One. + if len(nextNode.Str)-matchingCharsInMulti == 1 { + nextNode.T = NtOne + nextNode.Ch = nextNode.Str[len(nextNode.Str)-1] + nextNode.Str = nil + } else { + nextNode.Str = nextNode.Str[matchingCharsInMulti:] + } + } + + // NOTE: We could add support for coalescing a string with a subsequent loop, but the benefits of that + // are limited. Pulling a subsequent string's prefix back into the loop helps with making the loop atomic, + // but if the loop is after the string, pulling the suffix of the string forward into the loop may actually + // be a deoptimization as those characters could end up matching more slowly as part of loop matching. + + } else if (currentNode.T == NtOne && nextNode.IsOneloopFamily() && currentNode.Ch == nextNode.Ch) || + (currentNode.T == NtNotone && nextNode.IsNotoneloopFamily() && currentNode.Ch == nextNode.Ch) || + (currentNode.T == NtSet && nextNode.IsSetloopFamily() && currentNode.Set.Equals(nextNode.Set)) { + // Coalescing an individual item with a loop. + if canCombineCounts(1, 1, nextNode.M, nextNode.N) { + currentNode.T = nextNode.T + currentNode.M = nextNode.M + 1 + if nextNode.N == math.MaxInt32 { + currentNode.N = math.MaxInt32 + } else { + currentNode.N = nextNode.N + 1 + } + next++ + continue + } + } else if (currentNode.T == NtNotone && nextNode.T == NtNotone && currentNode.Ch == nextNode.Ch) || + (currentNode.T == NtSet && nextNode.T == NtSet && currentNode.Set.Equals(nextNode.Set)) { + // Coalescing an individual item with another individual item. + // We don't coalesce adjacent One nodes into a Oneloop as we'd rather they be joined into a Multi. + currentNode.makeRep(NtOneloop, 2, 2) + next++ + continue + } + + End: + } + + n.Children[nextSave] = n.Children[next] + nextSave++ + current = next + next++ + } + + if nextSave < len(n.Children) { + n.Children = slices.Delete(n.Children, nextSave, len(n.Children)) + } +} + +// Basic optimization. Adjacent strings can be concatenated. +// +// (?:abc)(?:def) -> abcdef +func (n *RegexNode) reduceConcatenationWithAdjacentStrings() { + var optionsLast RegexOptions + var optionsAt RegexOptions + var i, j int + + wasLastString := false + + for i, j = 0, 0; i < len(n.Children); i, j = i+1, j+1 { + var at, prev *RegexNode + + at = n.Children[i] + + if j < i { + n.Children[j] = at + } + + if at.T == NtConcatenate && + ((at.Options & RightToLeft) == (n.Options & RightToLeft)) { + for k := 0; k < len(at.Children); k++ { + at.Children[k].Parent = n + } + + //insert at.children at i+1 index in n.children + n.insertChildren(i+1, at.Children) + + j-- + } else if at.T == NtMulti || at.T == NtOne { + // Cannot merge strings if L or I options differ + optionsAt = at.Options & (RightToLeft | IgnoreCase) + + if !wasLastString || optionsLast != optionsAt { + wasLastString = true + optionsLast = optionsAt + continue + } + + j-- + prev = n.Children[j] + + if prev.T == NtOne { + prev.T = NtMulti + prev.Str = []rune{prev.Ch} + } + + if (optionsAt & RightToLeft) == 0 { + if at.T == NtOne { + prev.Str = append(prev.Str, at.Ch) + } else { + prev.Str = append(prev.Str, at.Str...) + } + } else { + if at.T == NtOne { + // insert at the front by expanding our slice, copying the data over, and then setting the value + prev.Str = append(prev.Str, 0) + copy(prev.Str[1:], prev.Str) + prev.Str[0] = at.Ch + } else { + //insert at the front...this one we'll make a new slice and copy both into it + merge := make([]rune, len(prev.Str)+len(at.Str)) + copy(merge, at.Str) + copy(merge[len(at.Str):], prev.Str) + prev.Str = merge + } + } + } else if at.T == NtEmpty { + j-- + } else { + wasLastString = false + } + } + + if j < i { + // remove indices j through i from the children + n.removeChildren(j, i) + } +} + +// Nested repeaters just get multiplied with each other if they're not +// too lumpy +func (n *RegexNode) reduceRep() *RegexNode { + + u := n + t := n.T + min := n.M + max := n.N + + for len(u.Children) > 0 { + child := u.Children[0] + + // multiply reps of the same type only + if child.T != t { + valid := false + if t == NtLoop { + switch child.T { + case NtOneloop, NtOneloopatomic, NtNotoneloop, + NtNotoneloopatomic, NtSetloop, NtSetloopatomic: + valid = true + } + } else { + switch child.T { + case NtOnelazy, NtNotonelazy, NtSetlazy: + valid = true + } + } + if !valid { + break + } + } + + // child can be too lumpy to blur, e.g., (a {100,105}) {3} or (a {2,})? + // [but things like (a {2,})+ are not too lumpy...] + if u.M == 0 && child.M > 1 || maxLessThanTwiceMin(child.N, child.M) { + break + } + + u = child + if u.M > 0 { + if (math.MaxInt32-1)/u.M < min { + u.M = math.MaxInt32 + } else { + u.M *= min + } + } + if u.N > 0 { + if (math.MaxInt32-1)/u.N < max { + u.N = math.MaxInt32 + } else { + u.N *= max + } + } + } + + if min == math.MaxInt32 { + return newRegexNode(NtNothing, n.Options) + } + + // If the Loop or Lazyloop now only has one child node and its a Set, One, or Notone, + // reduce to just Setloop/lazy, Oneloop/lazy, or Notoneloop/lazy. The parser will + // generally have only produced the latter, but other reductions could have exposed + // this. + if len(u.Children) == 1 { + child := u.Children[0] + switch child.T { + case NtOne, NtNotone, NtSet: + if u.T == NtLazyloop { + child.makeRep(NtOnelazy, u.M, u.N) + } else { + child.makeRep(NtOneloop, u.M, u.N) + } + u = child + } + } + + return u + +} + +// Simple optimization. If a concatenation or alternation has only +// one child strip out the intermediate node. If it has zero children, +// turn it into an empty. +func (n *RegexNode) replaceNodeIfUnnecessary() *RegexNode { + switch len(n.Children) { + case 0: + emptyType := NtEmpty + if n.T == NtAlternate { + emptyType = NtNothing + } + return newRegexNode(emptyType, n.Options) + case 1: + return n.Children[0] + default: + return n + } +} + +func (n *RegexNode) reduceGroup() *RegexNode { + u := n + + for u.T == NtGroup { + u = u.Children[0] + } + + return u +} + +// Simple optimization. If a set is a singleton, an inverse singleton, +// or empty, it's transformed accordingly. +func (n *RegexNode) reduceSet() *RegexNode { + // Extract empty-set, one and not-one case as special + + if n.Set == nil { + n.T = NtNothing + } else if n.Set.IsSingleton() { + n.Ch = n.Set.SingletonChar() + n.Set = nil + n.T += (NtOne - NtSet) + } else if n.Set.IsSingletonInverse() { + n.Ch = n.Set.SingletonChar() + n.Set = nil + n.T += (NtNotone - NtSet) + } + + return n +} + +func (n *RegexNode) reverseLeft() *RegexNode { + if n.Options&RightToLeft != 0 && n.T == NtConcatenate && len(n.Children) > 0 { + //reverse children order + for left, right := 0, len(n.Children)-1; left < right; left, right = left+1, right-1 { + n.Children[left], n.Children[right] = n.Children[right], n.Children[left] + } + } + + return n +} + +func (n *RegexNode) makeQuantifier(lazy bool, min, max int) *RegexNode { + // Certain cases of repeaters (min == max) can be handled specially + if min == 0 && max == 0 { + // The node is repeated 0 times, so it's actually empty. + return newRegexNode(NtEmpty, n.Options) + } + + if min == 1 && max == 1 { + // The node is repeated 1 time, so it's not actually a repeater. + return n + } + + if min == max && max <= MultiVsRepeaterLimit && n.T == NtOne { + // The same character is repeated a fixed number of times, so it's actually a multi. + // While this could remain a repeater, multis are more readily optimized later in + // processing. The counts used here in real-world expressions are invariably small (e.g. 4), + // but we set an upper bound just to avoid creating really large strings. + n.T = NtMulti + n.Str = []rune(strings.Repeat(string(n.Ch), max)) + n.Ch = 0 + return n + } + + switch n.T { + case NtOne, NtNotone, NtSet: + if lazy { + n.makeRep(NtOnelazy, min, max) + } else { + n.makeRep(NtOneloop, min, max) + } + return n + + default: + var t NodeType + if lazy { + t = NtLazyloop + } else { + t = NtLoop + } + result := newRegexNodeMN(t, n.Options, min, max) + result.addChild(n) + return result + } +} + +// Computes a min bound on the required length of any string that could possibly match. +// If the result is 0, there is no minimum we can enforce. +func (n *RegexNode) ComputeMinLength() int { + switch n.T { + case NtOne, NtNotone, NtSet: + // single char + return 1 + case NtMulti: + // Every character in the string needs to match. + return len(n.Str) + case NtNotonelazy, NtNotoneloop, NtNotoneloopatomic, + NtOnelazy, NtOneloop, NtOneloopatomic, NtSetlazy, NtSetloop, NtSetloopatomic: + // One character repeated at least M times. + return n.M + case NtLazyloop, NtLoop: + // A node graph repeated at least M times. + return multiplyMinLength(n.M, n.Children[0].ComputeMinLength()) + case NtAlternate: + // The minimum required length for any of the alternation's branches. + childCount := len(n.Children) + min := n.Children[0].ComputeMinLength() + for i := 1; i < childCount && min > 0; i++ { + newMin := n.Children[i].ComputeMinLength() + if newMin < min { + min = newMin + } + } + return min + case NtBackRefCond: + // Minimum of its yes and no branches. The backreference doesn't add to the length. + b1 := n.Children[0].ComputeMinLength() + if len(n.Children) == 1 { + return b1 + } + b2 := n.Children[1].ComputeMinLength() + if b1 < b2 { + return b1 + } + return b2 + case NtExprCond: + // Minimum of its yes and no branches. The condition is a zero-width assertion. + if len(n.Children) == 2 { + return n.Children[1].ComputeMinLength() + } + b1 := n.Children[1].ComputeMinLength() + b2 := n.Children[2].ComputeMinLength() + if b1 < b2 { + return b1 + } + return b2 + case NtConcatenate: + // The sum of all of the concatenation's children. + sum := 0 + for i := 0; i < len(n.Children); i++ { + sum = addMinLength(sum, n.Children[i].ComputeMinLength()) + } + return sum + case NtAtomic, NtCapture, NtGroup: + // For groups, we just delegate to the sole child. + return n.Children[0].ComputeMinLength() + case NtEmpty, NtNothing, + NtBeginning, NtBol, NtBoundary, NtECMABoundary, NtEnd, NtEndZ, NtEol, + NtNonboundary, NtNonECMABoundary, NtStart, NtNegLook, NtPosLook, NtRef: + // Nothing to match. In the future, we could potentially use Nothing to say that the min length + // is infinite, but that would require a different structure, as that would only apply if the + // Nothing match is required in all cases (rather than, say, as one branch of an alternation). + } + return 0 +} + +// Computes a maximum length of any string that could possibly match. +// or -1 if the length may not always be the same. +func (n *RegexNode) computeMaxLength() int { + switch n.T { + case NtOne, NtNotone, NtSet: + return 1 + case NtMulti: + return len(n.Str) + case NtNotonelazy, NtNotoneloop, NtNotoneloopatomic, + NtOnelazy, NtOneloop, NtOneloopatomic, + NtSetlazy, NtSetloop, NtSetloopatomic: + // Return the max number of iterations if there's an upper bound, or null if it's infinite + if n.N == math.MaxInt32 { + return -1 + } + return n.N + case NtLazyloop, NtLoop: + if n.N == math.MaxInt32 { + return -1 + } + // A node graph repeated a fixed number of times + if c := n.Children[0].computeMaxLength(); c >= 0 { + return multiplyMaxLength(n.N, c) + } + case NtAlternate: + // The maximum length of any child branch, as long as they all have one. + c := n.Children[0].computeMaxLength() + + if c < 0 { + return -1 + } + for i := 1; i < len(n.Children); i++ { + c2 := n.Children[i].computeMaxLength() + if c2 < 0 { + return -1 + } + + c = max(c, c2) + } + return c + case NtBackRefCond: + // The maximum length of either child branch, as long as they both have one. + b1 := n.Children[0].computeMaxLength() + if b1 < 0 { + return -1 + } + b2 := n.Children[1].computeMaxLength() + if b2 < 0 { + return -1 + } + return max(b1, b2) + + case NtExprCond: + // The condition for an expression conditional is a zero-width assertion. + b1 := n.Children[1].computeMaxLength() + if b1 < 0 { + return -1 + } + b2 := n.Children[2].computeMaxLength() + if b2 < 0 { + return -1 + } + return max(b1, b2) + + case NtConcatenate: + // The sum of all of the concatenation's children's max lengths, as long as they all have one. + sum := 0 + for i := 0; i < len(n.Children); i++ { + c := n.Children[i].computeMaxLength() + if c < 0 { + return -1 + } + sum = addMaxLength(sum, c) + if sum < 0 { + return -1 + } + } + return sum + + case NtAtomic, NtCapture: + // For groups, we just delegate to the sole child. + return n.Children[0].computeMaxLength() + case NtEmpty, NtNothing, NtUpdateBumpalong, + NtBeginning, NtBol, NtBoundary, NtECMABoundary, NtEnd, NtEndZ, NtEol, + NtNonboundary, NtNonECMABoundary, NtStart, NtNegLook, NtPosLook: + //zero-width + return 0 + + case NtRef: + // Requires matching data available only at run-time. In the future, we could choose to find + // and follow the capture group this aligns with, while being careful not to end up in an + // infinite cycle. + return -1 + } + + return -1 +} + +// debug functions + +var typeStr = []string{ + "Onerep", "Notonerep", "Setrep", + "Oneloop", "Notoneloop", "Setloop", + "Onelazy", "Notonelazy", "Setlazy", + "One", "Notone", "Set", + "Multi", "Ref", + "Bol", "Eol", "Boundary", "Nonboundary", + "Beginning", "Start", "EndZ", "End", + "Nothing", "Empty", + "Alternate", "Concatenate", + "Loop", "Lazyloop", + "Capture", "Group", "PosLook", "NegLook", "Atomic", + "BackRefCond", "ExprCond", + "Unknown", "Unknown", "Unknown", + "Unknown", "Unknown", "Unknown", + "ECMABoundary", "NonECMABoundary", + "OneloopAtomic", "NotoneloopAtomic", "SetloopAtomic", + "UpdateBumpalong", +} + +func (n *RegexNode) Description() string { + buf := &bytes.Buffer{} + + buf.WriteString(typeStr[n.T]) + + if (n.Options & ExplicitCapture) != 0 { + buf.WriteString("-C") + } + if (n.Options & IgnoreCase) != 0 { + buf.WriteString("-I") + } + if (n.Options & RightToLeft) != 0 { + buf.WriteString("-L") + } + if (n.Options & Multiline) != 0 { + buf.WriteString("-M") + } + if (n.Options & Singleline) != 0 { + buf.WriteString("-S") + } + if (n.Options & IgnorePatternWhitespace) != 0 { + buf.WriteString("-X") + } + if (n.Options & ECMAScript) != 0 { + buf.WriteString("-E") + } + + switch n.T { + case NtOneloop, NtOneloopatomic, NtNotoneloop, NtOnelazy, NtNotonelazy, NtOne, NtNotone, NtNotoneloopatomic: + buf.WriteString("(Ch = " + CharDescription(n.Ch) + ")") + case NtCapture: + buf.WriteString("(index = " + strconv.Itoa(n.M) + ", unindex = " + strconv.Itoa(n.N) + ")") + case NtRef, NtBackRefCond: + buf.WriteString("(index = " + strconv.Itoa(n.M) + ")") + case NtMulti: + fmt.Fprintf(buf, "(String = %#v)", string(n.Str)) + case NtSet, NtSetloop, NtSetlazy, NtSetloopatomic: + buf.WriteString("(Set = " + n.Set.String() + ")") + } + + switch n.T { + case NtOneloop, NtNotoneloop, NtOnelazy, NtNotonelazy, NtSetloop, NtSetlazy, NtLoop, NtLazyloop, + NtOneloopatomic, NtNotoneloopatomic, NtSetloopatomic: + + buf.WriteString("(Min = ") + buf.WriteString(strconv.Itoa(n.M)) + buf.WriteString(", Max = ") + if n.N == math.MaxInt32 { + buf.WriteString("inf") + } else { + buf.WriteString(strconv.Itoa(n.N)) + } + buf.WriteString(")") + } + + return buf.String() +} + +var padSpace = []byte(" ") + +func (t *RegexTree) Dump() string { + return t.Root.dump() +} + +func (n *RegexNode) dump() string { + var stack []int + CurNode := n + CurChild := 0 + + buf := bytes.NewBufferString(CurNode.Description()) + buf.WriteRune('\n') + + for { + if CurNode.Children != nil && CurChild < len(CurNode.Children) { + stack = append(stack, CurChild+1) + CurNode = CurNode.Children[CurChild] + CurChild = 0 + + Depth := len(stack) + if Depth > 32 { + Depth = 32 + } + buf.Write(padSpace[:Depth]) + buf.WriteString(CurNode.Description()) + buf.WriteRune('\n') + } else { + if len(stack) == 0 { + break + } + + CurChild = stack[len(stack)-1] + stack = stack[:len(stack)-1] + CurNode = CurNode.Parent + } + } + return buf.String() +} + +// Determines whether the specified child index of a concatenation begins a sequence whose values +// should be used to perform an ordinal case-insensitive comparison. +// +// When consumeZeroWidthNodes is false, the consumer needs the semantics of matching the produced string to fully represent +// the semantics of all the consumed nodes, which means nodes can be consumed iff they produce text that's represented +// by the resulting string. When true, the resulting string needs to fully represent all valid matches at that position, +// but it can have false positives, which means the resulting string doesn't need to fully represent all zero-width nodes +// consumed. true is only valid when used as part of a search to determine where to try a full match, not as part of +// actual matching logic. +// consumeZeroWidthNodes = false +func (n *RegexNode) TryGetOrdinalCaseInsensitiveString(childIndex int, exclusiveChildBound int, consumeZeroWidthNodes bool) (success bool, nodesConsumed int, caseInsensitiveString string) { + vsb := &strings.Builder{} + + // We're looking in particular for sets of ASCII characters, so we focus only on sets with two characters in them, e.g. [Aa]. + //twoChars := make([]rune, 0, 2) + + // Iterate from the child index to the exclusive upper bound. + var i int + for i = childIndex; i < exclusiveChildBound; i++ { + child := n.Children[i] + + if child.T == NtOne { + // We only want to include ASCII characters, and only if they don't participate in case conversion + // such that they only case to themselves and nothing other cases to them. Otherwise, including + // them would potentially cause us to match against things not allowed by the pattern. + if child.Ch >= unicode.MaxASCII || participatesInCaseConversion(child.Ch) { + break + } + + vsb.WriteRune(child.Ch) + } else if child.T == NtMulti { + // As with NtOne, the string needs to be composed solely of ASCII characters that + // don't participate in case conversion. + hasNonAscii := slices.ContainsFunc(child.Str, func(ch rune) bool { return ch > unicode.MaxASCII }) + if hasNonAscii || anyParticipatesInCaseConversion(string(child.Str)) { + break + } + + vsb.WriteString(string(child.Str)) + } else if child.T == NtSet || + ((child.T == NtSetloop || child.T == NtSetlazy || child.T == NtSetloopatomic) && child.M == child.N) { + // In particular we want to look for sets that contain only the upper and lowercase variant + // of the same ASCII letter. + ok, twoChars := child.Set.containsAsciiIgnoreCaseCharacter() + if !ok { + break + } + + count := child.M + if child.T == NtSet { + count = 1 + } + vsb.WriteString(strings.Repeat(string(twoChars[0]|0x20), count)) + } else if child.T == NtEmpty { + // Skip over empty nodes, as they're pure nops. They would ideally have been optimized away, + // but can still remain in some situations. + } else if consumeZeroWidthNodes && + // anchors + (child.T == NtBeginning || child.T == NtBol || child.T == NtStart || + // boundaries + child.T == NtBoundary || child.T == NtECMABoundary || child.T == NtNonboundary || child.T == NtNonECMABoundary || + // lookarounds + child.T == NtNegLook || child.T == NtPosLook || + // logic + child.T == NtUpdateBumpalong) { + // Skip over zero-width nodes that might be reasonable at the beginning of or within a substring. + // We can only do these if consumeZeroWidthNodes is true, as otherwise we'd be producing a string that + // may not fully represent the semantics of this portion of the pattern. + } else { + break + } + } + + // If we found at least two characters, consider it a sequence found. It's possible + // they all came from the same node, so this could be a sequence of just one node. + if vsb.Len() >= 2 { + return true, i - childIndex, vsb.String() + } + + // No sequence found. + return false, 0, "" +} + +func (child *RegexNode) canJoinLengthCheck() bool { + if child.T == NtOne || child.T == NtNotone || child.T == NtSet || child.T == NtMulti { + return true + } + if (child.IsSetloopFamily() || child.IsNotoneloopFamily() || child.IsOneloopFamily()) && + child.M == child.N { + return true + } + + return false +} + +// Determine whether the specified child node is the beginning of a sequence that can +// trivially have length checks combined in order to avoid bounds checks. +// requiredLength is The sum of all the fixed lengths for the nodes in the sequence. +// exclusiveEnd is The index of the node just after the last one in the sequence. +// returns true if more than one node can have their length checks combined; otherwise, false. +// +// There are additional node types for which we can prove a fixed length, e.g. examining all branches +// of an alternation and returning true if all their lengths are equal. However, the primary purpose +// of this method is to avoid bounds checks by consolidating length checks that guard accesses to +// strings/spans for which the JIT can see a fixed index within bounds, and alternations employ +// patterns that defeat that (e.g. reassigning the span in question). As such, the implementation +// remains focused on only a core subset of nodes that are a) likely to be used in concatenations and +// b) employ simple patterns of checks. +func (n *RegexNode) TryGetJoinableLengthCheckChildRange(childIndex int, requiredLength *int, exclusiveEnd *int) bool { + + child := n.Children[childIndex] + if child.canJoinLengthCheck() { + *requiredLength = child.ComputeMinLength() + + for *exclusiveEnd = childIndex + 1; *exclusiveEnd < len(n.Children); *exclusiveEnd++ { + child = n.Children[*exclusiveEnd] + if !child.canJoinLengthCheck() { + break + } + + *requiredLength += child.ComputeMinLength() + } + + if *exclusiveEnd-childIndex > 1 { + return true + } + } + + *requiredLength = 0 + *exclusiveEnd = 0 + return false +} + +type StartingLiteral struct { + Range SingleRange + String []rune + SetChars []rune + Negated bool +} + +func (n *RegexNode) FindStartingLiteral() *StartingLiteral { + node := n.FindStartingLiteralNode(true) + if node == nil { + return nil + } + + if node.IsOneFamily() { + return &StartingLiteral{Range: SingleRange{node.Ch, node.Ch}, Negated: false} + } + if node.IsNotoneFamily() { + return &StartingLiteral{Range: SingleRange{node.Ch, node.Ch}, Negated: true} + } + if node.IsSetFamily() { + ranges := node.Set.GetIfNRanges(1) + if len(ranges) == 1 && ranges[0].Last-ranges[0].First > 1 { + return &StartingLiteral{Range: ranges[0], Negated: node.Set.IsNegated()} + } + setChars := node.Set.GetSetChars(128) + if len(setChars) > 0 { + return &StartingLiteral{SetChars: setChars, Negated: node.Set.IsNegated()} + } + } + if node.T == NtMulti { + return &StartingLiteral{String: node.Str} + } + + return nil +} + +// Finds the guaranteed beginning literal(s) of the node, or null if none exists. +// allowZeroWidth = true +func (n *RegexNode) FindStartingLiteralNode(allowZeroWidth bool) *RegexNode { + node := n + for { + if node != nil && node.Options&RightToLeft == 0 { + switch node.T { + case NtOne, NtNotone, NtMulti, NtSet: + return node + + case NtOneloop, NtOneloopatomic, NtOnelazy, + NtNotoneloop, NtNotoneloopatomic, NtNotonelazy, + NtSetloop, NtSetloopatomic, NtSetlazy: + if node.M > 0 { + return node + } + + case NtAtomic, NtConcatenate, NtCapture, NtGroup: + node = node.Children[0] + continue + case NtLoop, NtLazyloop: + node = node.Children[0] + continue + case NtPosLook: + if allowZeroWidth { + node = node.Children[0] + continue + } + } + } + + return nil + } +} + +// Gets the character that begins a One or Multi. +func (n *RegexNode) FirstCharOfOneOrMulti() rune { + if n.IsOneFamily() { + return n.Ch + } + return n.Str[0] +} diff --git a/vendor/github.com/dlclark/regexp2/v2/syntax/writer.go b/vendor/github.com/dlclark/regexp2/v2/syntax/writer.go new file mode 100644 index 000000000..818b4b56c --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/syntax/writer.go @@ -0,0 +1,500 @@ +package syntax + +import ( + "bytes" + "fmt" + "math" +) + +func Write(tree *RegexTree) (*Code, error) { + w := writer{ + intStack: make([]int, 0, 32), + emitted: make([]int, 2), + stringhash: make(map[string]int), + sethash: make(map[string]int), + } + + code, err := w.codeFromTree(tree) + + return code, err +} + +type writer struct { + emitted []int + + intStack []int + curpos int + stringhash map[string]int + stringtable [][]rune + sethash map[string]int + settable []*CharSet + counting bool + count int + trackcount int + caps map[int]int +} + +const ( + BeforeChild NodeType = 64 + AfterChild NodeType = 128 + //MaxPrefixSize is the largest number of runes we'll use for a BoyerMoyer prefix + MaxPrefixSize = 50 +) + +// The top level RegexCode generator. It does a depth-first walk +// through the tree and calls EmitFragment to emits code before +// and after each child of an interior node, and at each leaf. +// +// It runs two passes, first to count the size of the generated +// code, and second to generate the code. +// +// We should time it against the alternative, which is +// to just generate the code and grow the array as we go. +func (w *writer) codeFromTree(tree *RegexTree) (*Code, error) { + var ( + curNode *RegexNode + curChild int + capsize int + ) + // construct sparse capnum mapping if some numbers are unused + + if tree.Capnumlist == nil || tree.Captop == len(tree.Capnumlist) { + capsize = tree.Captop + w.caps = nil + } else { + capsize = len(tree.Capnumlist) + w.caps = tree.Caps + for i := 0; i < len(tree.Capnumlist); i++ { + w.caps[tree.Capnumlist[i]] = i + } + } + + w.counting = true + + for { + if !w.counting { + w.emitted = make([]int, w.count) + } + + curNode = tree.Root + curChild = 0 + + w.emit1(Lazybranch, 0) + + for { + if len(curNode.Children) == 0 { + if err := w.emitFragment(curNode.T, curNode, 0); err != nil { + return nil, err + } + } else if curChild < len(curNode.Children) { + if err := w.emitFragment(curNode.T|BeforeChild, curNode, curChild); err != nil { + return nil, err + } + + curNode = curNode.Children[curChild] + + w.pushInt(curChild) + curChild = 0 + continue + } + + if w.emptyStack() { + break + } + + curChild = w.popInt() + curNode = curNode.Parent + + if err := w.emitFragment(curNode.T|AfterChild, curNode, curChild); err != nil { + return nil, err + } + curChild++ + } + + w.patchJump(0, w.curPos()) + w.emit(Stop) + + if !w.counting { + break + } + + w.counting = false + } + + fcPrefix := getFirstCharsPrefix(tree) + prefix := getPrefix(tree) + rtl := (tree.Options & RightToLeft) != 0 + + var bmPrefix *BmPrefix + //TODO: benchmark string prefixes + if prefix != nil && len(prefix.PrefixStr) > 0 && MaxPrefixSize > 0 { + if len(prefix.PrefixStr) > MaxPrefixSize { + // limit prefix changes to 10k + prefix.PrefixStr = prefix.PrefixStr[:MaxPrefixSize] + } + bmPrefix = newBmPrefix(prefix.PrefixStr, prefix.CaseInsensitive, rtl) + } else { + bmPrefix = nil + } + + return &Code{ + Codes: w.emitted, + Strings: w.stringtable, + Sets: w.settable, + TrackCount: w.trackcount, + Caps: w.caps, + Capsize: capsize, + FcPrefix: fcPrefix, + BmPrefix: bmPrefix, + Anchors: getAnchors(tree), + RightToLeft: rtl, + FindOptimizations: tree.FindOptimizations, + }, nil +} + +// The main RegexCode generator. It does a depth-first walk +// through the tree and calls EmitFragment to emits code before +// and after each child of an interior node, and at each leaf. +func (w *writer) emitFragment(nodetype NodeType, node *RegexNode, curIndex int) error { + bits := InstOp(0) + + if (node.Options & RightToLeft) != 0 { + bits |= Rtl + } + if (node.Options & IgnoreCase) != 0 { + bits |= Ci + } + + ntBits := NodeType(bits) + + switch nodetype { + case NtConcatenate | BeforeChild, NtConcatenate | AfterChild, NtEmpty: + + case NtAlternate | BeforeChild: + if curIndex < len(node.Children)-1 { + w.pushInt(w.curPos()) + w.emit1(Lazybranch, 0) + } + + case NtAlternate | AfterChild: + if curIndex < len(node.Children)-1 { + lbPos := w.popInt() + w.pushInt(w.curPos()) + w.emit1(Goto, 0) + w.patchJump(lbPos, w.curPos()) + } else { + for i := 0; i < curIndex; i++ { + w.patchJump(w.popInt(), w.curPos()) + } + } + + case NtBackRefCond | BeforeChild: + if curIndex == 0 { + w.emit(Setjump) + w.pushInt(w.curPos()) + w.emit1(Lazybranch, 0) + w.emit1(Testref, w.mapCapnum(node.M)) + w.emit(Forejump) + } + + case NtBackRefCond | AfterChild: + switch curIndex { + case 0: + branchpos := w.popInt() + w.pushInt(w.curPos()) + w.emit1(Goto, 0) + w.patchJump(branchpos, w.curPos()) + w.emit(Forejump) + if len(node.Children) <= 1 { + w.patchJump(w.popInt(), w.curPos()) + } + case 1: + w.patchJump(w.popInt(), w.curPos()) + } + + case NtExprCond | BeforeChild: + if curIndex == 0 { + w.emit(Setjump) + w.emit(Setmark) + w.pushInt(w.curPos()) + w.emit1(Lazybranch, 0) + } + + case NtExprCond | AfterChild: + switch curIndex { + case 0: + w.emit(Getmark) + w.emit(Forejump) + case 1: + branchpos := w.popInt() + w.pushInt(w.curPos()) + w.emit1(Goto, 0) + w.patchJump(branchpos, w.curPos()) + w.emit(Getmark) + w.emit(Forejump) + if len(node.Children) <= 2 { + w.patchJump(w.popInt(), w.curPos()) + } + case 2: + w.patchJump(w.popInt(), w.curPos()) + } + + case NtLoop | BeforeChild, NtLazyloop | BeforeChild: + + if node.N < math.MaxInt32 || node.M > 1 { + if node.M == 0 { + w.emit1(Nullcount, 0) + } else { + w.emit1(Setcount, 1-node.M) + } + } else if node.M == 0 { + w.emit(Nullmark) + } else { + w.emit(Setmark) + } + + if node.M == 0 { + w.pushInt(w.curPos()) + w.emit1(Goto, 0) + } + w.pushInt(w.curPos()) + + case NtLoop | AfterChild, NtLazyloop | AfterChild: + + startJumpPos := w.curPos() + lazy := InstOp(nodetype - (NtLoop | AfterChild)) + + if node.N < math.MaxInt32 || node.M > 1 { + if node.N == math.MaxInt32 { + w.emit2(Branchcount+lazy, w.popInt(), math.MaxInt32) + } else { + w.emit2(Branchcount+lazy, w.popInt(), node.N-node.M) + } + } else { + w.emit1(Branchmark+lazy, w.popInt()) + } + + if node.M == 0 { + w.patchJump(w.popInt(), startJumpPos) + } + + case NtGroup | BeforeChild, NtGroup | AfterChild: + + case NtCapture | BeforeChild: + w.emit(Setmark) + + case NtCapture | AfterChild: + w.emit2(Capturemark, w.mapCapnum(node.M), w.mapCapnum(node.N)) + + case NtPosLook | BeforeChild: + // NOTE: the following line causes lookahead/lookbehind to be + // NON-BACKTRACKING. It can be commented out with (*) + w.emit(Setjump) + + w.emit(Setmark) + + case NtPosLook | AfterChild: + w.emit(Getmark) + + // NOTE: the following line causes lookahead/lookbehind to be + // NON-BACKTRACKING. It can be commented out with (*) + w.emit(Forejump) + + case NtNegLook | BeforeChild: + w.emit(Setjump) + w.pushInt(w.curPos()) + w.emit1(Lazybranch, 0) + + case NtNegLook | AfterChild: + w.emit(Backjump) + w.patchJump(w.popInt(), w.curPos()) + w.emit(Forejump) + + case NtAtomic | BeforeChild: + w.emit(Setjump) + + case NtAtomic | AfterChild: + w.emit(Forejump) + + case NtOne, NtNotone: + w.emit1(InstOp(node.T|ntBits), int(node.Ch)) + + case NtNotoneloop, NtNotoneloopatomic, NtNotonelazy, NtOneloop, NtOneloopatomic, NtOnelazy: + if node.M > 0 { + if node.T == NtOneloop || node.T == NtOnelazy || node.T == NtOneloopatomic { + w.emit2(Onerep|bits, int(node.Ch), node.M) + } else { + w.emit2(Notonerep|bits, int(node.Ch), node.M) + } + } + if node.N > node.M { + if node.N == math.MaxInt32 { + w.emit2(InstOp(node.T|ntBits), int(node.Ch), math.MaxInt32) + } else { + w.emit2(InstOp(node.T|ntBits), int(node.Ch), node.N-node.M) + } + } + + case NtSetloop, NtSetlazy, NtSetloopatomic: + if node.M > 0 { + w.emit2(Setrep|bits, w.setCode(node.Set), node.M) + } + if node.N > node.M { + if node.N == math.MaxInt32 { + w.emit2(InstOp(node.T|ntBits), w.setCode(node.Set), math.MaxInt32) + } else { + w.emit2(InstOp(node.T|ntBits), w.setCode(node.Set), node.N-node.M) + } + } + + case NtMulti: + w.emit1(InstOp(node.T|ntBits), w.stringCode(node.Str)) + + case NtSet: + w.emit1(InstOp(node.T|ntBits), w.setCode(node.Set)) + + case NtRef: + w.emit1(InstOp(node.T|ntBits), w.mapCapnum(node.M)) + + case NtNothing, NtBol, NtEol, NtBoundary, NtNonboundary, NtECMABoundary, NtNonECMABoundary, NtBeginning, NtStart, NtEndZ, NtEnd, NtUpdateBumpalong: + w.emit(InstOp(node.T)) + + default: + return fmt.Errorf("unexpected opcode in regular expression generation: %v", nodetype) + } + + return nil +} + +// To avoid recursion, we use a simple integer stack. +// This is the push. +func (w *writer) pushInt(i int) { + w.intStack = append(w.intStack, i) +} + +// Returns true if the stack is empty. +func (w *writer) emptyStack() bool { + return len(w.intStack) == 0 +} + +// This is the pop. +func (w *writer) popInt() int { + //get our item + idx := len(w.intStack) - 1 + i := w.intStack[idx] + //trim our slice + w.intStack = w.intStack[:idx] + return i +} + +// Returns the current position in the emitted code. +func (w *writer) curPos() int { + return w.curpos +} + +// Fixes up a jump instruction at the specified offset +// so that it jumps to the specified jumpDest. +func (w *writer) patchJump(offset, jumpDest int) { + w.emitted[offset+1] = jumpDest +} + +// Returns an index in the set table for a charset +// uses a map to eliminate duplicates. +func (w *writer) setCode(set *CharSet) int { + if w.counting { + return 0 + } + + buf := &bytes.Buffer{} + + set.mapHashFill(buf) + hash := buf.String() + i, ok := w.sethash[hash] + if !ok { + i = len(w.sethash) + w.sethash[hash] = i + w.settable = append(w.settable, set) + } + return i +} + +// Returns an index in the string table for a string. +// uses a map to eliminate duplicates. +func (w *writer) stringCode(str []rune) int { + if w.counting { + return 0 + } + + hash := string(str) + i, ok := w.stringhash[hash] + if !ok { + i = len(w.stringhash) + w.stringhash[hash] = i + w.stringtable = append(w.stringtable, str) + } + + return i +} + +// When generating code on a regex that uses a sparse set +// of capture slots, we hash them to a dense set of indices +// for an array of capture slots. Instead of doing the hash +// at match time, it's done at compile time, here. +func (w *writer) mapCapnum(capnum int) int { + if capnum == -1 { + return -1 + } + + if w.caps != nil { + return w.caps[capnum] + } + + return capnum +} + +// Emits a zero-argument operation. Note that the emit +// functions all run in two modes: they can emit code, or +// they can just count the size of the code. +func (w *writer) emit(op InstOp) { + if w.counting { + w.count++ + if opcodeBacktracks(op) { + w.trackcount++ + } + return + } + w.emitted[w.curpos] = int(op) + w.curpos++ +} + +// Emits a one-argument operation. +func (w *writer) emit1(op InstOp, opd1 int) { + if w.counting { + w.count += 2 + if opcodeBacktracks(op) { + w.trackcount++ + } + return + } + w.emitted[w.curpos] = int(op) + w.curpos++ + w.emitted[w.curpos] = opd1 + w.curpos++ +} + +// Emits a two-argument operation. +func (w *writer) emit2(op InstOp, opd1, opd2 int) { + if w.counting { + w.count += 3 + if opcodeBacktracks(op) { + w.trackcount++ + } + return + } + w.emitted[w.curpos] = int(op) + w.curpos++ + w.emitted[w.curpos] = opd1 + w.curpos++ + w.emitted[w.curpos] = opd2 + w.curpos++ +} diff --git a/vendor/github.com/dlclark/regexp2/v2/testoutput1 b/vendor/github.com/dlclark/regexp2/v2/testoutput1 new file mode 100644 index 000000000..fbf63fdf2 --- /dev/null +++ b/vendor/github.com/dlclark/regexp2/v2/testoutput1 @@ -0,0 +1,7061 @@ +# This set of tests is for features that are compatible with all versions of +# Perl >= 5.10, in non-UTF mode. It should run clean for the 8-bit, 16-bit, and +# 32-bit PCRE libraries, and also using the perltest.pl script. + +#forbid_utf +#newline_default lf any anycrlf +#perltest + +/the quick brown fox/ + the quick brown fox + 0: the quick brown fox + What do you know about the quick brown fox? + 0: the quick brown fox +\= Expect no match + The quick brown FOX +No match + What do you know about THE QUICK BROWN FOX? +No match + +/The quick brown fox/i + the quick brown fox + 0: the quick brown fox + The quick brown FOX + 0: The quick brown FOX + What do you know about the quick brown fox? + 0: the quick brown fox + What do you know about THE QUICK BROWN FOX? + 0: THE QUICK BROWN FOX + +/abcd\t\n\r\f\a\e\071\x3b\$\\\?caxyz/ + abcd\t\n\r\f\a\e9;\$\\?caxyz + 0: abcd\x09\x0a\x0d\x0c\x07\x1b9;$\?caxyz + +/a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz/ + abxyzpqrrrabbxyyyypqAzz + 0: abxyzpqrrrabbxyyyypqAzz + abxyzpqrrrabbxyyyypqAzz + 0: abxyzpqrrrabbxyyyypqAzz + aabxyzpqrrrabbxyyyypqAzz + 0: aabxyzpqrrrabbxyyyypqAzz + aaabxyzpqrrrabbxyyyypqAzz + 0: aaabxyzpqrrrabbxyyyypqAzz + aaaabxyzpqrrrabbxyyyypqAzz + 0: aaaabxyzpqrrrabbxyyyypqAzz + abcxyzpqrrrabbxyyyypqAzz + 0: abcxyzpqrrrabbxyyyypqAzz + aabcxyzpqrrrabbxyyyypqAzz + 0: aabcxyzpqrrrabbxyyyypqAzz + aaabcxyzpqrrrabbxyyyypAzz + 0: aaabcxyzpqrrrabbxyyyypAzz + aaabcxyzpqrrrabbxyyyypqAzz + 0: aaabcxyzpqrrrabbxyyyypqAzz + aaabcxyzpqrrrabbxyyyypqqAzz + 0: aaabcxyzpqrrrabbxyyyypqqAzz + aaabcxyzpqrrrabbxyyyypqqqAzz + 0: aaabcxyzpqrrrabbxyyyypqqqAzz + aaabcxyzpqrrrabbxyyyypqqqqAzz + 0: aaabcxyzpqrrrabbxyyyypqqqqAzz + aaabcxyzpqrrrabbxyyyypqqqqqAzz + 0: aaabcxyzpqrrrabbxyyyypqqqqqAzz + aaabcxyzpqrrrabbxyyyypqqqqqqAzz + 0: aaabcxyzpqrrrabbxyyyypqqqqqqAzz + aaaabcxyzpqrrrabbxyyyypqAzz + 0: aaaabcxyzpqrrrabbxyyyypqAzz + abxyzzpqrrrabbxyyyypqAzz + 0: abxyzzpqrrrabbxyyyypqAzz + aabxyzzzpqrrrabbxyyyypqAzz + 0: aabxyzzzpqrrrabbxyyyypqAzz + aaabxyzzzzpqrrrabbxyyyypqAzz + 0: aaabxyzzzzpqrrrabbxyyyypqAzz + aaaabxyzzzzpqrrrabbxyyyypqAzz + 0: aaaabxyzzzzpqrrrabbxyyyypqAzz + abcxyzzpqrrrabbxyyyypqAzz + 0: abcxyzzpqrrrabbxyyyypqAzz + aabcxyzzzpqrrrabbxyyyypqAzz + 0: aabcxyzzzpqrrrabbxyyyypqAzz + aaabcxyzzzzpqrrrabbxyyyypqAzz + 0: aaabcxyzzzzpqrrrabbxyyyypqAzz + aaaabcxyzzzzpqrrrabbxyyyypqAzz + 0: aaaabcxyzzzzpqrrrabbxyyyypqAzz + aaaabcxyzzzzpqrrrabbbxyyyypqAzz + 0: aaaabcxyzzzzpqrrrabbbxyyyypqAzz + aaaabcxyzzzzpqrrrabbbxyyyyypqAzz + 0: aaaabcxyzzzzpqrrrabbbxyyyyypqAzz + aaabcxyzpqrrrabbxyyyypABzz + 0: aaabcxyzpqrrrabbxyyyypABzz + aaabcxyzpqrrrabbxyyyypABBzz + 0: aaabcxyzpqrrrabbxyyyypABBzz + >>>aaabxyzpqrrrabbxyyyypqAzz + 0: aaabxyzpqrrrabbxyyyypqAzz + >aaaabxyzpqrrrabbxyyyypqAzz + 0: aaaabxyzpqrrrabbxyyyypqAzz + >>>>abcxyzpqrrrabbxyyyypqAzz + 0: abcxyzpqrrrabbxyyyypqAzz +\= Expect no match + abxyzpqrrabbxyyyypqAzz +No match + abxyzpqrrrrabbxyyyypqAzz +No match + abxyzpqrrrabxyyyypqAzz +No match + aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz +No match + aaaabcxyzzzzpqrrrabbbxyyypqAzz +No match + aaabcxyzpqrrrabbxyyyypqqqqqqqAzz +No match + +/^(abc){1,2}zz/ + abczz + 0: abczz + 1: abc + abcabczz + 0: abcabczz + 1: abc +\= Expect no match + zz +No match + abcabcabczz +No match + >>abczz +No match + +/^(b+?|a){1,2}?c/ + bc + 0: bc + 1: b + bbc + 0: bbc + 1: b + bbbc + 0: bbbc + 1: bb + bac + 0: bac + 1: a + bbac + 0: bbac + 1: a + aac + 0: aac + 1: a + abbbbbbbbbbbc + 0: abbbbbbbbbbbc + 1: bbbbbbbbbbb + bbbbbbbbbbbac + 0: bbbbbbbbbbbac + 1: a +\= Expect no match + aaac +No match + abbbbbbbbbbbac +No match + +/^(b+|a){1,2}c/ + bc + 0: bc + 1: b + bbc + 0: bbc + 1: bb + bbbc + 0: bbbc + 1: bbb + bac + 0: bac + 1: a + bbac + 0: bbac + 1: a + aac + 0: aac + 1: a + abbbbbbbbbbbc + 0: abbbbbbbbbbbc + 1: bbbbbbbbbbb + bbbbbbbbbbbac + 0: bbbbbbbbbbbac + 1: a +\= Expect no match + aaac +No match + abbbbbbbbbbbac +No match + +/^(b+|a){1,2}?bc/ + bbc + 0: bbc + 1: b + +/^(b*|ba){1,2}?bc/ + babc + 0: babc + 1: ba + bbabc + 0: bbabc + 1: ba + bababc + 0: bababc + 1: ba +\= Expect no match + bababbc +No match + babababc +No match + +/^(ba|b*){1,2}?bc/ + babc + 0: babc + 1: ba + bbabc + 0: bbabc + 1: ba + bababc + 0: bababc + 1: ba +\= Expect no match + bababbc +No match + babababc +No match + +#/^\ca\cA\c[;\c:/ +# \x01\x01\e;z +# 0: \x01\x01\x1b;z + +/^[ab\]cde]/ + athing + 0: a + bthing + 0: b + ]thing + 0: ] + cthing + 0: c + dthing + 0: d + ething + 0: e +\= Expect no match + fthing +No match + [thing +No match + \\thing +No match + +/^[]cde]/ + ]thing + 0: ] + cthing + 0: c + dthing + 0: d + ething + 0: e +\= Expect no match + athing +No match + fthing +No match + +/^[^ab\]cde]/ + fthing + 0: f + [thing + 0: [ + \\thing + 0: \ +\= Expect no match + athing +No match + bthing +No match + ]thing +No match + cthing +No match + dthing +No match + ething +No match + +/^[^]cde]/ + athing + 0: a + fthing + 0: f +\= Expect no match + ]thing +No match + cthing +No match + dthing +No match + ething +No match + +# DLC - I don't get this one +#/^\/ +#  +# 0: \x81 + +#updated to handle 16-bits utf8 +/^ÿ/ + ÿ + 0: \xc3\xbf + +/^[0-9]+$/ + 0 + 0: 0 + 1 + 0: 1 + 2 + 0: 2 + 3 + 0: 3 + 4 + 0: 4 + 5 + 0: 5 + 6 + 0: 6 + 7 + 0: 7 + 8 + 0: 8 + 9 + 0: 9 + 10 + 0: 10 + 100 + 0: 100 +\= Expect no match + abc +No match + +/^.*nter/ + enter + 0: enter + inter + 0: inter + uponter + 0: uponter + +/^xxx[0-9]+$/ + xxx0 + 0: xxx0 + xxx1234 + 0: xxx1234 +\= Expect no match + xxx +No match + +/^.+[0-9][0-9][0-9]$/ + x123 + 0: x123 + x1234 + 0: x1234 + xx123 + 0: xx123 + 123456 + 0: 123456 +\= Expect no match + 123 +No match + +/^.+?[0-9][0-9][0-9]$/ + x123 + 0: x123 + x1234 + 0: x1234 + xx123 + 0: xx123 + 123456 + 0: 123456 +\= Expect no match + 123 +No match + +/^([^!]+)!(.+)=apquxz\.ixr\.zzz\.ac\.uk$/ + abc!pqr=apquxz.ixr.zzz.ac.uk + 0: abc!pqr=apquxz.ixr.zzz.ac.uk + 1: abc + 2: pqr +\= Expect no match + !pqr=apquxz.ixr.zzz.ac.uk +No match + abc!=apquxz.ixr.zzz.ac.uk +No match + abc!pqr=apquxz:ixr.zzz.ac.uk +No match + abc!pqr=apquxz.ixr.zzz.ac.ukk +No match + +/:/ + Well, we need a colon: somewhere + 0: : +\= Expect no match + Fail without a colon +No match + +/([\da-f:]+)$/i + 0abc + 0: 0abc + 1: 0abc + abc + 0: abc + 1: abc + fed + 0: fed + 1: fed + E + 0: E + 1: E + :: + 0: :: + 1: :: + 5f03:12C0::932e + 0: 5f03:12C0::932e + 1: 5f03:12C0::932e + fed def + 0: def + 1: def + Any old stuff + 0: ff + 1: ff +\= Expect no match + 0zzz +No match + gzzz +No match + fed\x20 +No match + Any old rubbish +No match + +/^.*\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ + .1.2.3 + 0: .1.2.3 + 1: 1 + 2: 2 + 3: 3 + A.12.123.0 + 0: A.12.123.0 + 1: 12 + 2: 123 + 3: 0 +\= Expect no match + .1.2.3333 +No match + 1.2.3 +No match + 1234.2.3 +No match + +/^(\d+)\s+IN\s+SOA\s+(\S+)\s+(\S+)\s*\(\s*$/ + 1 IN SOA non-sp1 non-sp2( + 0: 1 IN SOA non-sp1 non-sp2( + 1: 1 + 2: non-sp1 + 3: non-sp2 + 1 IN SOA non-sp1 non-sp2 ( + 0: 1 IN SOA non-sp1 non-sp2 ( + 1: 1 + 2: non-sp1 + 3: non-sp2 +\= Expect no match + 1IN SOA non-sp1 non-sp2( +No match + +/^[a-zA-Z\d][a-zA-Z\d\-]*(\.[a-zA-Z\d][a-zA-z\d\-]*)*\.$/ + a. + 0: a. + Z. + 0: Z. + 2. + 0: 2. + ab-c.pq-r. + 0: ab-c.pq-r. + 1: .pq-r + sxk.zzz.ac.uk. + 0: sxk.zzz.ac.uk. + 1: .uk + x-.y-. + 0: x-.y-. + 1: .y- +\= Expect no match + -abc.peq. +No match + +/^\*\.[a-z]([a-z\-\d]*[a-z\d]+)?(\.[a-z]([a-z\-\d]*[a-z\d]+)?)*$/ + *.a + 0: *.a + *.b0-a + 0: *.b0-a + 1: 0-a + *.c3-b.c + 0: *.c3-b.c + 1: 3-b + 2: .c + *.c-a.b-c + 0: *.c-a.b-c + 1: -a + 2: .b-c + 3: -c +\= Expect no match + *.0 +No match + *.a- +No match + *.a-b.c- +No match + *.c-a.0-c +No match + +/^(?=ab(de))(abd)(e)/ + abde + 0: abde + 1: de + 2: abd + 3: e + +/^(?!(ab)de|x)(abd)(f)/ + abdf + 0: abdf + 1: + 2: abd + 3: f + +/^(?=(ab(cd)))(ab)/ + abcd + 0: ab + 1: abcd + 2: cd + 3: ab + +/^[\da-f](\.[\da-f])*$/i + a.b.c.d + 0: a.b.c.d + 1: .d + A.B.C.D + 0: A.B.C.D + 1: .D + a.b.c.1.2.3.C + 0: a.b.c.1.2.3.C + 1: .C + +/^\".*\"\s*(;.*)?$/ + \"1234\" + 0: "1234" + \"abcd\" ; + 0: "abcd" ; + 1: ; + \"\" ; rhubarb + 0: "" ; rhubarb + 1: ; rhubarb +\= Expect no match + \"1234\" : things +No match + +/^$/ + \ + 0: +\= Expect no match + A non-empty line +No match + +/ ^ a (?# begins with a) b\sc (?# then b c) $ (?# then end)/x + ab c + 0: ab c +\= Expect no match + abc +No match + ab cde +No match + +/(?x) ^ a (?# begins with a) b\sc (?# then b c) $ (?# then end)/ + ab c + 0: ab c +\= Expect no match + abc +No match + ab cde +No match + +/^ a\ b[c ]d $/x + a bcd + 0: a bcd + a b d + 0: a b d +\= Expect no match + abcd +No match + ab d +No match + +/^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$/ + abcdefhijklm + 0: abcdefhijklm + 1: abc + 2: bc + 3: c + 4: def + 5: ef + 6: f + 7: hij + 8: ij + 9: j +10: klm +11: lm +12: m + +/^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$/ + abcdefhijklm + 0: abcdefhijklm + 1: bc + 2: c + 3: ef + 4: f + 5: ij + 6: j + 7: lm + 8: m + +#/^[\w][\W][\s][\S][\d][\D][\b][\n][\c]][\022]/ +# a+ Z0+\x08\n\x1d\x12 +# 0: a+ Z0+\x08\x0a\x1d\x12 + +/^[.^$|()*+?{,}]+/ + .^\$(*+)|{?,?} + 0: .^$(*+)|{?,?} + +/^a*\w/ + z + 0: z + az + 0: az + aaaz + 0: aaaz + a + 0: a + aa + 0: aa + aaaa + 0: aaaa + a+ + 0: a + aa+ + 0: aa + +/^a*?\w/ + z + 0: z + az + 0: a + aaaz + 0: a + a + 0: a + aa + 0: a + aaaa + 0: a + a+ + 0: a + aa+ + 0: a + +/^a+\w/ + az + 0: az + aaaz + 0: aaaz + aa + 0: aa + aaaa + 0: aaaa + aa+ + 0: aa + +/^a+?\w/ + az + 0: az + aaaz + 0: aa + aa + 0: aa + aaaa + 0: aa + aa+ + 0: aa + +/^\d{8}\w{2,}/ + 1234567890 + 0: 1234567890 + 12345678ab + 0: 12345678ab + 12345678__ + 0: 12345678__ +\= Expect no match + 1234567 +No match + +/^[aeiou\d]{4,5}$/ + uoie + 0: uoie + 1234 + 0: 1234 + 12345 + 0: 12345 + aaaaa + 0: aaaaa +\= Expect no match + 123456 +No match + +/^[aeiou\d]{4,5}?/ + uoie + 0: uoie + 1234 + 0: 1234 + 12345 + 0: 1234 + aaaaa + 0: aaaa + 123456 + 0: 1234 + +/\A(abc|def)=(\1){2,3}\Z/ + abc=abcabc + 0: abc=abcabc + 1: abc + 2: abc + def=defdefdef + 0: def=defdefdef + 1: def + 2: def +\= Expect no match + abc=defdef +No match + +/^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\11*(\3\4)\1(?#)2$/ + abcdefghijkcda2 + 0: abcdefghijkcda2 + 1: a + 2: b + 3: c + 4: d + 5: e + 6: f + 7: g + 8: h + 9: i +10: j +11: k +12: cd + abcdefghijkkkkcda2 + 0: abcdefghijkkkkcda2 + 1: a + 2: b + 3: c + 4: d + 5: e + 6: f + 7: g + 8: h + 9: i +10: j +11: k +12: cd + +/(cat(a(ract|tonic)|erpillar)) \1()2(3)/ + cataract cataract23 + 0: cataract cataract23 + 1: cataract + 2: aract + 3: ract + 4: + 5: 3 + catatonic catatonic23 + 0: catatonic catatonic23 + 1: catatonic + 2: atonic + 3: tonic + 4: + 5: 3 + caterpillar caterpillar23 + 0: caterpillar caterpillar23 + 1: caterpillar + 2: erpillar + 3: + 4: + 5: 3 + + +/^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]/ + From abcd Mon Sep 01 12:33:02 1997 + 0: From abcd Mon Sep 01 12:33 + 1: abcd + +/^From\s+\S+\s+([a-zA-Z]{3}\s+){2}\d{1,2}\s+\d\d:\d\d/ + From abcd Mon Sep 01 12:33:02 1997 + 0: From abcd Mon Sep 01 12:33 + 1: Sep + From abcd Mon Sep 1 12:33:02 1997 + 0: From abcd Mon Sep 1 12:33 + 1: Sep +\= Expect no match + From abcd Sep 01 12:33:02 1997 +No match + +/^12.34/s + 12\n34 + 0: 12\x0a34 + 12\r34 + 0: 12\x0d34 + +/\w+(?=\t)/ + the quick brown\t fox + 0: brown + +/foo(?!bar)(.*)/ + foobar is foolish see? + 0: foolish see? + 1: lish see? + +/(?:(?!foo)...|^.{0,2})bar(.*)/ + foobar crowbar etc + 0: rowbar etc + 1: etc + barrel + 0: barrel + 1: rel + 2barrel + 0: 2barrel + 1: rel + A barrel + 0: A barrel + 1: rel + +/^(\D*)(?=\d)(?!123)/ + abc456 + 0: abc + 1: abc +\= Expect no match + abc123 +No match + +/^1234(?# test newlines + inside)/ + 1234 + 0: 1234 + +/^1234 #comment in extended re + /x + 1234 + 0: 1234 + +/#rhubarb + abcd/x + abcd + 0: abcd + +/^abcd#rhubarb/x + abcd + 0: abcd + +/^(a)\1{2,3}(.)/ + aaab + 0: aaab + 1: a + 2: b + aaaab + 0: aaaab + 1: a + 2: b + aaaaab + 0: aaaaa + 1: a + 2: a + aaaaaab + 0: aaaaa + 1: a + 2: a + +/(?!^)abc/ + the abc + 0: abc +\= Expect no match + abc +No match + +/(?=^)abc/ + abc + 0: abc +\= Expect no match + the abc +No match + +/^[ab]{1,3}(ab*|b)/ + aabbbbb + 0: aabb + 1: b + +/^[ab]{1,3}?(ab*|b)/ + aabbbbb + 0: aabbbbb + 1: abbbbb + +/^[ab]{1,3}?(ab*?|b)/ + aabbbbb + 0: aa + 1: a + +/^[ab]{1,3}(ab*?|b)/ + aabbbbb + 0: aabb + 1: b + +/ (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* # optional leading comment +(?: (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +" (?: # opening quote... +[^\\\x80-\xff\n\015"] # Anything except backslash and quote +| # or +\\ [^\x80-\xff] # Escaped something (something != CR) +)* " # closing quote +) # initial word +(?: (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* \. (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +" (?: # opening quote... +[^\\\x80-\xff\n\015"] # Anything except backslash and quote +| # or +\\ [^\x80-\xff] # Escaped something (something != CR) +)* " # closing quote +) )* # further okay, if led by a period +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* @ (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| \[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) # initial subdomain +(?: # +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* \. # if led by a period... +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| \[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) # ...further okay +)* +# address +| # or +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +" (?: # opening quote... +[^\\\x80-\xff\n\015"] # Anything except backslash and quote +| # or +\\ [^\x80-\xff] # Escaped something (something != CR) +)* " # closing quote +) # one word, optionally followed by.... +(?: +[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] | # atom and space parts, or... +\( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) | # comments, or... + +" (?: # opening quote... +[^\\\x80-\xff\n\015"] # Anything except backslash and quote +| # or +\\ [^\x80-\xff] # Escaped something (something != CR) +)* " # closing quote +# quoted strings +)* +< (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* # leading < +(?: @ (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| \[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) # initial subdomain +(?: # +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* \. # if led by a period... +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| \[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) # ...further okay +)* + +(?: (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* , (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* @ (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| \[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) # initial subdomain +(?: # +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* \. # if led by a period... +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| \[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) # ...further okay +)* +)* # further okay, if led by comma +: # closing colon +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* )? # optional route +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +" (?: # opening quote... +[^\\\x80-\xff\n\015"] # Anything except backslash and quote +| # or +\\ [^\x80-\xff] # Escaped something (something != CR) +)* " # closing quote +) # initial word +(?: (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* \. (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +" (?: # opening quote... +[^\\\x80-\xff\n\015"] # Anything except backslash and quote +| # or +\\ [^\x80-\xff] # Escaped something (something != CR) +)* " # closing quote +) )* # further okay, if led by a period +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* @ (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| \[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) # initial subdomain +(?: # +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* \. # if led by a period... +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* (?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| \[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) # ...further okay +)* +# address spec +(?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* > # trailing > +# name and address +) (?: [\040\t] | \( +(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )* +\) )* # optional trailing comment +/x + Alan Other + 0: Alan Other + + 0: user@dom.ain + user\@dom.ain + 0: user@dom.ain + \"A. Other\" (a comment) + 0: "A. Other" (a comment) + A. Other (a comment) + 0: Other (a comment) + \"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"\@x400-re.lay + 0: "/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/"@x400-re.lay + A missing angle @,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +# Atom +| # or +" # " +[^\\\x80-\xff\n\015"] * # normal +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )* +" # " +# Quoted string +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +\. +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +# Atom +| # or +" # " +[^\\\x80-\xff\n\015"] * # normal +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )* +" # " +# Quoted string +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# additional words +)* +@ +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +\[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# optional trailing comments +(?: +\. +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +\[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# optional trailing comments +)* +# address +| # or +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +# Atom +| # or +" # " +[^\\\x80-\xff\n\015"] * # normal +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )* +" # " +# Quoted string +) +# leading word +[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] * # "normal" atoms and or spaces +(?: +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +| +" # " +[^\\\x80-\xff\n\015"] * # normal +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )* +" # " +) # "special" comment or quoted string +[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] * # more "normal" +)* +< +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# < +(?: +@ +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +\[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# optional trailing comments +(?: +\. +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +\[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# optional trailing comments +)* +(?: , +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +@ +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +\[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# optional trailing comments +(?: +\. +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +\[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# optional trailing comments +)* +)* # additional domains +: +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# optional trailing comments +)? # optional route +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +# Atom +| # or +" # " +[^\\\x80-\xff\n\015"] * # normal +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )* +" # " +# Quoted string +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +\. +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +# Atom +| # or +" # " +[^\\\x80-\xff\n\015"] * # normal +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )* +" # " +# Quoted string +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# additional words +)* +@ +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +\[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# optional trailing comments +(?: +\. +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +(?: +[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters... +(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom +| +\[ # [ +(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff +\] # ] +) +[\040\t]* # Nab whitespace. +(?: +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: # ( +(?: \\ [^\x80-\xff] | +\( # ( +[^\\\x80-\xff\n\015()] * # normal* +(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)* +\) # ) +) # special +[^\\\x80-\xff\n\015()] * # normal* +)* # )* +\) # ) +[\040\t]* )* # If comment found, allow more spaces. +# optional trailing comments +)* +# address spec +> # > +# name and address +) +/x + Alan Other + 0: Alan Other + + 0: user@dom.ain + user\@dom.ain + 0: user@dom.ain + \"A. Other\" (a comment) + 0: "A. Other" + A. Other (a comment) + 0: Other + \"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"\@x400-re.lay + 0: "/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/"@x400-re.lay + A missing angle ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f + +/P[^*]TAIRE[^*]{1,6}?LL/ + xxxxxxxxxxxPSTAIREISLLxxxxxxxxx + 0: PSTAIREISLL + +/P[^*]TAIRE[^*]{1,}?LL/ + xxxxxxxxxxxPSTAIREISLLxxxxxxxxx + 0: PSTAIREISLL + +/(\.\d\d[1-9]?)\d+/ + 1.230003938 + 0: .230003938 + 1: .23 + 1.875000282 + 0: .875000282 + 1: .875 + 1.235 + 0: .235 + 1: .23 + +/(\.\d\d((?=0)|\d(?=\d)))/ + 1.230003938 + 0: .23 + 1: .23 + 2: + 1.875000282 + 0: .875 + 1: .875 + 2: 5 +\= Expect no match + 1.235 +No match + +/\b(foo)\s+(\w+)/i + Food is on the foo table + 0: foo table + 1: foo + 2: table + +/foo(.*)bar/ + The food is under the bar in the barn. + 0: food is under the bar in the bar + 1: d is under the bar in the + +/foo(.*?)bar/ + The food is under the bar in the barn. + 0: food is under the bar + 1: d is under the + +/(.*)(\d*)/ + I have 2 numbers: 53147 + 0: I have 2 numbers: 53147 + 1: I have 2 numbers: 53147 + 2: + +/(.*)(\d+)/ + I have 2 numbers: 53147 + 0: I have 2 numbers: 53147 + 1: I have 2 numbers: 5314 + 2: 7 + +/(.*?)(\d*)/ + I have 2 numbers: 53147 + 0: + 1: + 2: + +/(.*?)(\d+)/ + I have 2 numbers: 53147 + 0: I have 2 + 1: I have + 2: 2 + +/(.*)(\d+)$/ + I have 2 numbers: 53147 + 0: I have 2 numbers: 53147 + 1: I have 2 numbers: 5314 + 2: 7 + +/(.*?)(\d+)$/ + I have 2 numbers: 53147 + 0: I have 2 numbers: 53147 + 1: I have 2 numbers: + 2: 53147 + +/(.*)\b(\d+)$/ + I have 2 numbers: 53147 + 0: I have 2 numbers: 53147 + 1: I have 2 numbers: + 2: 53147 + +/(.*\D)(\d+)$/ + I have 2 numbers: 53147 + 0: I have 2 numbers: 53147 + 1: I have 2 numbers: + 2: 53147 + +/^\D*(?!123)/ + ABC123 + 0: AB + +/^(\D*)(?=\d)(?!123)/ + ABC445 + 0: ABC + 1: ABC +\= Expect no match + ABC123 +No match + +/^[W-]46]/ + W46]789 + 0: W46] + -46]789 + 0: -46] +\= Expect no match + Wall +No match + Zebra +No match + 42 +No match + [abcd] +No match + ]abcd[ +No match + +/^[W-\]46]/ + W46]789 + 0: W + Wall + 0: W + Zebra + 0: Z + Xylophone + 0: X + 42 + 0: 4 + [abcd] + 0: [ + ]abcd[ + 0: ] + \\backslash + 0: \ +\= Expect no match + -46]789 +No match + well +No match + +/\d\d\/\d\d\/\d\d\d\d/ + 01/01/2000 + 0: 01/01/2000 + +/word (?:[a-zA-Z0-9]+ ){0,10}otherword/ + word cat dog elephant mussel cow horse canary baboon snake shark otherword + 0: word cat dog elephant mussel cow horse canary baboon snake shark otherword +\= Expect no match + word cat dog elephant mussel cow horse canary baboon snake shark +No match + +/word (?:[a-zA-Z0-9]+ ){0,300}otherword/ +\= Expect no match + word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope +No match + +/^(a){0,0}/ + bcd + 0: + abc + 0: + aab + 0: + +/^(a){0,1}/ + bcd + 0: + abc + 0: a + 1: a + aab + 0: a + 1: a + +/^(a){0,2}/ + bcd + 0: + abc + 0: a + 1: a + aab + 0: aa + 1: a + +/^(a){0,3}/ + bcd + 0: + abc + 0: a + 1: a + aab + 0: aa + 1: a + aaa + 0: aaa + 1: a + +/^(a){0,}/ + bcd + 0: + abc + 0: a + 1: a + aab + 0: aa + 1: a + aaa + 0: aaa + 1: a + aaaaaaaa + 0: aaaaaaaa + 1: a + +/^(a){1,1}/ + abc + 0: a + 1: a + aab + 0: a + 1: a +\= Expect no match + bcd +No match + +/^(a){1,2}/ + abc + 0: a + 1: a + aab + 0: aa + 1: a +\= Expect no match + bcd +No match + +/^(a){1,3}/ + abc + 0: a + 1: a + aab + 0: aa + 1: a + aaa + 0: aaa + 1: a +\= Expect no match + bcd +No match + +/^(a){1,}/ + abc + 0: a + 1: a + aab + 0: aa + 1: a + aaa + 0: aaa + 1: a + aaaaaaaa + 0: aaaaaaaa + 1: a +\= Expect no match + bcd +No match + +/.*\.gif/ + borfle\nbib.gif\nno + 0: bib.gif + +/.{0,}\.gif/ + borfle\nbib.gif\nno + 0: bib.gif + +/.*\.gif/m + borfle\nbib.gif\nno + 0: bib.gif + +/.*\.gif/s + borfle\nbib.gif\nno + 0: borfle\x0abib.gif + +/.*\.gif/ms + borfle\nbib.gif\nno + 0: borfle\x0abib.gif + +/.*$/ + borfle\nbib.gif\nno + 0: no + +/.*$/m + borfle\nbib.gif\nno + 0: borfle + +/.*$/s + borfle\nbib.gif\nno + 0: borfle\x0abib.gif\x0ano + +/.*$/ms + borfle\nbib.gif\nno + 0: borfle\x0abib.gif\x0ano + +/.*$/ + borfle\nbib.gif\nno\n + 0: no + +/.*$/m + borfle\nbib.gif\nno\n + 0: borfle + +/.*$/s + borfle\nbib.gif\nno\n + 0: borfle\x0abib.gif\x0ano\x0a + +/.*$/ms + borfle\nbib.gif\nno\n + 0: borfle\x0abib.gif\x0ano\x0a + +/(.*X|^B)/ + abcde\n1234Xyz + 0: 1234X + 1: 1234X + BarFoo + 0: B + 1: B +\= Expect no match + abcde\nBar +No match + +/(.*X|^B)/m + abcde\n1234Xyz + 0: 1234X + 1: 1234X + BarFoo + 0: B + 1: B + abcde\nBar + 0: B + 1: B + +/(.*X|^B)/s + abcde\n1234Xyz + 0: abcde\x0a1234X + 1: abcde\x0a1234X + BarFoo + 0: B + 1: B +\= Expect no match + abcde\nBar +No match + +/(.*X|^B)/ms + abcde\n1234Xyz + 0: abcde\x0a1234X + 1: abcde\x0a1234X + BarFoo + 0: B + 1: B + abcde\nBar + 0: B + 1: B + +/(?s)(.*X|^B)/ + abcde\n1234Xyz + 0: abcde\x0a1234X + 1: abcde\x0a1234X + BarFoo + 0: B + 1: B +\= Expect no match + abcde\nBar +No match + +/(?s:.*X|^B)/ + abcde\n1234Xyz + 0: abcde\x0a1234X + BarFoo + 0: B +\= Expect no match + abcde\nBar +No match + +/^.*B/ +\= Expect no match + abc\nB +No match + +/(?s)^.*B/ + abc\nB + 0: abc\x0aB + +/(?m)^.*B/ + abc\nB + 0: B + +/(?ms)^.*B/ + abc\nB + 0: abc\x0aB + +/(?ms)^B/ + abc\nB + 0: B + +/(?s)B$/ + B\n + 0: B + +/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/ + 123456654321 + 0: 123456654321 + +/^\d\d\d\d\d\d\d\d\d\d\d\d/ + 123456654321 + 0: 123456654321 + +/^[\d][\d][\d][\d][\d][\d][\d][\d][\d][\d][\d][\d]/ + 123456654321 + 0: 123456654321 + +/^[abc]{12}/ + abcabcabcabc + 0: abcabcabcabc + +/^[a-c]{12}/ + abcabcabcabc + 0: abcabcabcabc + +/^(a|b|c){12}/ + abcabcabcabc + 0: abcabcabcabc + 1: c + +/^[abcdefghijklmnopqrstuvwxy0123456789]/ + n + 0: n +\= Expect no match + z +No match + +/abcde{0,0}/ + abcd + 0: abcd +\= Expect no match + abce +No match + +/ab[cd]{0,0}e/ + abe + 0: abe +\= Expect no match + abcde +No match + +/ab(c){0,0}d/ + abd + 0: abd +\= Expect no match + abcd +No match + +/a(b*)/ + a + 0: a + 1: + ab + 0: ab + 1: b + abbbb + 0: abbbb + 1: bbbb +\= Expect no match + bbbbb +No match + +/ab\d{0}e/ + abe + 0: abe +\= Expect no match + ab1e +No match + +/"([^\\"]+|\\.)*"/ + the \"quick\" brown fox + 0: "quick" + 1: quick + \"the \\\"quick\\\" brown fox\" + 0: "the \"quick\" brown fox" + 1: brown fox + +/]{0,})>]{0,})>([\d]{0,}\.)(.*)((
([\w\W\s\d][^<>]{0,})|[\s]{0,}))<\/a><\/TD>]{0,})>([\w\W\s\d][^<>]{0,})<\/TD>]{0,})>([\w\W\s\d][^<>]{0,})<\/TD><\/TR>/is + 43.Word Processor
(N-1286)
Lega lstaff.comCA - Statewide + 0: 43.Word Processor
(N-1286)
Lega lstaff.comCA - Statewide + 1: BGCOLOR='#DBE9E9' + 2: align=left valign=top + 3: 43. + 4: Word Processor
(N-1286) + 5: + 6: + 7: + 8: align=left valign=top + 9: Lega lstaff.com +10: align=left valign=top +11: CA - Statewide + +/a[^a]b/ + acb + 0: acb + a\nb + 0: a\x0ab + +/a.b/ + acb + 0: acb +\= Expect no match + a\nb +No match + +/a[^a]b/s + acb + 0: acb + a\nb + 0: a\x0ab + +/a.b/s + acb + 0: acb + a\nb + 0: a\x0ab + +/^(b+?|a){1,2}?c/ + bac + 0: bac + 1: a + bbac + 0: bbac + 1: a + bbbac + 0: bbbac + 1: a + bbbbac + 0: bbbbac + 1: a + bbbbbac + 0: bbbbbac + 1: a + +/^(b+|a){1,2}?c/ + bac + 0: bac + 1: a + bbac + 0: bbac + 1: a + bbbac + 0: bbbac + 1: a + bbbbac + 0: bbbbac + 1: a + bbbbbac + 0: bbbbbac + 1: a + +/(?!\A)x/m + a\bx\n + 0: x + a\nx\n + 0: x +\= Expect no match + x\nb\n +No match + +/(A|B)*?CD/ + CD + 0: CD + +/(A|B)*CD/ + CD + 0: CD + +/(AB)*?\1/ + ABABAB + 0: ABAB + 1: AB + +/(AB)*\1/ + ABABAB + 0: ABABAB + 1: AB + +/(?.*/)foo" + /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/and/foo + 0: /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/and/foo +\= Expect no match + /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/it/you/see/ +No match + +/(?>(\.\d\d[1-9]?))\d+/ + 1.230003938 + 0: .230003938 + 1: .23 + 1.875000282 + 0: .875000282 + 1: .875 +\= Expect no match + 1.235 +No match + +/^((?>\w+)|(?>\s+))*$/ + now is the time for all good men to come to the aid of the party + 0: now is the time for all good men to come to the aid of the party + 1: party +\= Expect no match + this is not a line with only words and spaces! +No match + +/(\d+)(\w)/ + 12345a + 0: 12345a + 1: 12345 + 2: a + 12345+ + 0: 12345 + 1: 1234 + 2: 5 + +/((?>\d+))(\w)/ + 12345a + 0: 12345a + 1: 12345 + 2: a +\= Expect no match + 12345+ +No match + +/(?>a+)b/ + aaab + 0: aaab + +/((?>a+)b)/ + aaab + 0: aaab + 1: aaab + +/(?>(a+))b/ + aaab + 0: aaab + 1: aaa + +/(?>b)+/ + aaabbbccc + 0: bbb + +/(?>a+|b+|c+)*c/ + aaabbbbccccd + 0: aaabbbbc + +/((?>[^()]+)|\([^()]*\))+/ + ((abc(ade)ufh()()x + 0: abc(ade)ufh()()x + 1: x + +/\(((?>[^()]+)|\([^()]+\))+\)/ + (abc) + 0: (abc) + 1: abc + (abc(def)xyz) + 0: (abc(def)xyz) + 1: xyz +\= Expect no match + ((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +No match + +/a(?-i)b/i + ab + 0: ab + Ab + 0: Ab +\= Expect no match + aB +No match + AB +No match + +/(a (?x)b c)d e/ + a bcd e + 0: a bcd e + 1: a bc +\= Expect no match + a b cd e +No match + abcd e +No match + a bcde +No match + +/(a b(?x)c d (?-x)e f)/ + a bcde f + 0: a bcde f + 1: a bcde f +\= Expect no match + abcdef +No match + +/(a(?i)b)c/ + abc + 0: abc + 1: ab + aBc + 0: aBc + 1: aB +\= Expect no match + abC +No match + aBC +No match + Abc +No match + ABc +No match + ABC +No match + AbC +No match + +/a(?i:b)c/ + abc + 0: abc + aBc + 0: aBc +\= Expect no match + ABC +No match + abC +No match + aBC +No match + +/a(?i:b)*c/ + aBc + 0: aBc + aBBc + 0: aBBc +\= Expect no match + aBC +No match + aBBC +No match + +/a(?=b(?i)c)\w\wd/ + abcd + 0: abcd + abCd + 0: abCd +\= Expect no match + aBCd +No match + abcD +No match + +/(?s-i:more.*than).*million/i + more than million + 0: more than million + more than MILLION + 0: more than MILLION + more \n than Million + 0: more \x0a than Million +\= Expect no match + MORE THAN MILLION +No match + more \n than \n million +No match + +/(?:(?s-i)more.*than).*million/i + more than million + 0: more than million + more than MILLION + 0: more than MILLION + more \n than Million + 0: more \x0a than Million +\= Expect no match + MORE THAN MILLION +No match + more \n than \n million +No match + +/(?>a(?i)b+)+c/ + abc + 0: abc + aBbc + 0: aBbc + aBBc + 0: aBBc +\= Expect no match + Abc +No match + abAb +No match + abbC +No match + +/(?=a(?i)b)\w\wc/ + abc + 0: abc + aBc + 0: aBc +\= Expect no match + Ab +No match + abC +No match + aBC +No match + +/(?<=a(?i)b)(\w\w)c/ + abxxc + 0: xxc + 1: xx + aBxxc + 0: xxc + 1: xx +\= Expect no match + Abxxc +No match + ABxxc +No match + abxxC +No match + +/(?:(a)|b)(?(1)A|B)/ + aA + 0: aA + 1: a + bB + 0: bB +\= Expect no match + aB +No match + bA +No match + +/^(a)?(?(1)a|b)+$/ + aa + 0: aa + 1: a + b + 0: b + bb + 0: bb +\= Expect no match + ab +No match + +# Perl gets this next one wrong if the pattern ends with $; in that case it +# fails to match "12". + +/^(?(?=abc)\w{3}:|\d\d)/ + abc: + 0: abc: + 12 + 0: 12 + 123 + 0: 12 +\= Expect no match + xyz +No match + +/^(?(?!abc)\d\d|\w{3}:)$/ + abc: + 0: abc: + 12 + 0: 12 +\= Expect no match + 123 +No match + xyz +No match + +/(?(?<=foo)bar|cat)/ + foobar + 0: bar + cat + 0: cat + fcat + 0: cat + focat + 0: cat +\= Expect no match + foocat +No match + +/(?(?a*)*/ + a + 0: a + aa + 0: aa + aaaa + 0: aaaa + +/(abc|)+/ + abc + 0: abc + 1: + abcabc + 0: abcabc + 1: + abcabcabc + 0: abcabcabc + 1: + xyz + 0: + 1: + +/([a]*)*/ + a + 0: a + 1: + aaaaa + 0: aaaaa + 1: + +/([ab]*)*/ + a + 0: a + 1: + b + 0: b + 1: + ababab + 0: ababab + 1: + aaaabcde + 0: aaaab + 1: + bbbb + 0: bbbb + 1: + +/([^a]*)*/ + b + 0: b + 1: + bbbb + 0: bbbb + 1: + aaa + 0: + 1: + +/([^ab]*)*/ + cccc + 0: cccc + 1: + abab + 0: + 1: + +/([a]*?)*/ + a + 0: + 1: + aaaa + 0: + 1: + +/([ab]*?)*/ + a + 0: + 1: + b + 0: + 1: + abab + 0: + 1: + baba + 0: + 1: + +/([^a]*?)*/ + b + 0: + 1: + bbbb + 0: + 1: + aaa + 0: + 1: + +/([^ab]*?)*/ + c + 0: + 1: + cccc + 0: + 1: + baba + 0: + 1: + +/(?>a*)*/ + a + 0: a + aaabcde + 0: aaa + +/((?>a*))*/ + aaaaa + 0: aaaaa + 1: + aabbaa + 0: aa + 1: + +/((?>a*?))*/ + aaaaa + 0: + 1: + aabbaa + 0: + 1: + +/(?(?=[^a-z]+[a-z]) \d{2}-[a-z]{3}-\d{2} | \d{2}-\d{2}-\d{2} ) /x + 12-sep-98 + 0: 12-sep-98 + 12-09-98 + 0: 12-09-98 +\= Expect no match + sep-12-98 +No match + +/(?<=(foo))bar\1/ + foobarfoo + 0: barfoo + 1: foo + foobarfootling + 0: barfoo + 1: foo +\= Expect no match + foobar +No match + barfoo +No match + +/(?i:saturday|sunday)/ + saturday + 0: saturday + sunday + 0: sunday + Saturday + 0: Saturday + Sunday + 0: Sunday + SATURDAY + 0: SATURDAY + SUNDAY + 0: SUNDAY + SunDay + 0: SunDay + +/(a(?i)bc|BB)x/ + abcx + 0: abcx + 1: abc + aBCx + 0: aBCx + 1: aBC + bbx + 0: bbx + 1: bb + BBx + 0: BBx + 1: BB +\= Expect no match + abcX +No match + aBCX +No match + bbX +No match + BBX +No match + +/^([ab](?i)[cd]|[ef])/ + ac + 0: ac + 1: ac + aC + 0: aC + 1: aC + bD + 0: bD + 1: bD + elephant + 0: e + 1: e + Europe + 0: E + 1: E + frog + 0: f + 1: f + France + 0: F + 1: F +\= Expect no match + Africa +No match + +/^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)/ + ab + 0: ab + 1: ab + aBd + 0: aBd + 1: aBd + xy + 0: xy + 1: xy + xY + 0: xY + 1: xY + zebra + 0: z + 1: z + Zambesi + 0: Z + 1: Z +\= Expect no match + aCD +No match + XY +No match + +/(?<=foo\n)^bar/m + foo\nbar + 0: bar +\= Expect no match + bar +No match + baz\nbar +No match + +/(?<=(?]&/ + <&OUT + 0: <& + +/^(a\1?){4}$/ + aaaaaaaaaa + 0: aaaaaaaaaa + 1: aaaa +\= Expect no match + AB +No match + aaaaaaaaa +No match + aaaaaaaaaaa +No match + +/^(a(?(1)\1)){4}$/ + aaaaaaaaaa + 0: aaaaaaaaaa + 1: aaaa +\= Expect no match + aaaaaaaaa +No match + aaaaaaaaaaa +No match + +/(?:(f)(o)(o)|(b)(a)(r))*/ + foobar + 0: foobar + 1: f + 2: o + 3: o + 4: b + 5: a + 6: r + +/(?<=a)b/ + ab + 0: b +\= Expect no match + cb +No match + b +No match + +/(? + 2: abcd + xy:z:::abcd + 0: xy:z:::abcd + 1: xy:z::: + 2: abcd + +/^[^bcd]*(c+)/ + aexycd + 0: aexyc + 1: c + +/(a*)b+/ + caab + 0: aab + 1: aa + +/([\w:]+::)?(\w+)$/ + abcd + 0: abcd + 1: + 2: abcd + xy:z:::abcd + 0: xy:z:::abcd + 1: xy:z::: + 2: abcd +\= Expect no match + abcd: +No match + abcd: +No match + +/^[^bcd]*(c+)/ + aexycd + 0: aexyc + 1: c + +/(>a+)ab/ + +/(?>a+)b/ + aaab + 0: aaab + +/([[:]+)/ + a:[b]: + 0: :[ + 1: :[ + +/([[=]+)/ + a=[b]= + 0: =[ + 1: =[ + +/([[.]+)/ + a.[b]. + 0: .[ + 1: .[ + +/((?>a+)b)/ + aaab + 0: aaab + 1: aaab + +/(?>(a+))b/ + aaab + 0: aaab + 1: aaa + +/((?>[^()]+)|\([^()]*\))+/ + ((abc(ade)ufh()()x + 0: abc(ade)ufh()()x + 1: x + +/a\Z/ +\= Expect no match + aaab +No match + a\nb\n +No match + +/b\Z/ + a\nb\n + 0: b + +/b\z/ + +/b\Z/ + a\nb + 0: b + +/b\z/ + a\nb + 0: b + +/^(?>(?(1)\.|())[^\W_](?>[a-z0-9-]*[^\W_])?)+$/ + a + 0: a + 1: + abc + 0: abc + 1: + a-b + 0: a-b + 1: + 0-9 + 0: 0-9 + 1: + a.b + 0: a.b + 1: + 5.6.7 + 0: 5.6.7 + 1: + the.quick.brown.fox + 0: the.quick.brown.fox + 1: + a100.b200.300c + 0: a100.b200.300c + 1: + 12-ab.1245 + 0: 12-ab.1245 + 1: +\= Expect no match + \ +No match + .a +No match + -a +No match + a- +No match + a. +No match + a_b +No match + a.- +No match + a.. +No match + ab..bc +No match + the.quick.brown.fox- +No match + the.quick.brown.fox. +No match + the.quick.brown.fox_ +No match + the.quick.brown.fox+ +No match + +/(?>.*)(?<=(abcd|wxyz))/ + alphabetabcd + 0: alphabetabcd + 1: abcd + endingwxyz + 0: endingwxyz + 1: wxyz +\= Expect no match + a rather long string that doesn't end with one of them +No match + +/word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword/ + word cat dog elephant mussel cow horse canary baboon snake shark otherword + 0: word cat dog elephant mussel cow horse canary baboon snake shark otherword +\= Expect no match + word cat dog elephant mussel cow horse canary baboon snake shark +No match + +/word (?>[a-zA-Z0-9]+ ){0,30}otherword/ +\= Expect no match + word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope +No match + +/(?<=\d{3}(?!999))foo/ + 999foo + 0: foo + 123999foo + 0: foo +\= Expect no match + 123abcfoo +No match + +/(?<=(?!...999)\d{3})foo/ + 999foo + 0: foo + 123999foo + 0: foo +\= Expect no match + 123abcfoo +No match + +/(?<=\d{3}(?!999)...)foo/ + 123abcfoo + 0: foo + 123456foo + 0: foo +\= Expect no match + 123999foo +No match + +/(?<=\d{3}...)(? + 2: + 3: abcd +
+ 2: + 3: abcd + \s*)=(?>\s*) # find + 2: + 3: abcd + Z)+|A)*/ + ZABCDEFG + 0: ZA + 1: A + +/((?>)+|A)*/ + ZABCDEFG + 0: + 1: + +/^[\d-a]/ + abcde + 0: a + -things + 0: - + 0digit + 0: 0 +\= Expect no match + bcdef +No match + +/[\s]+/ + > \x09\x0a\x0c\x0d\x0b< + 0: \x09\x0a\x0c\x0d\x0b + +/\s+/ + > \x09\x0a\x0c\x0d\x0b< + 0: \x09\x0a\x0c\x0d\x0b + +/a b/x + ab + 0: ab + +/(?!\A)x/m + a\nxb\n + 0: x + +/(?!^)x/m +\= Expect no match + a\nxb\n +No match + +#/abc\Qabc\Eabc/ +# abcabcabc +# 0: abcabcabc + +#/abc\Q(*+|\Eabc/ +# abc(*+|abc +# 0: abc(*+|abc + +#/ abc\Q abc\Eabc/x +# abc abcabc +# 0: abc abcabc +#\= Expect no match +# abcabcabc +#No match + +#/abc#comment +# \Q#not comment +# literal\E/x +# abc#not comment\n literal +# 0: abc#not comment\x0a literal + +#/abc#comment +# \Q#not comment +# literal/x +# abc#not comment\n literal +# 0: abc#not comment\x0a literal + +#/abc#comment +# \Q#not comment +# literal\E #more comment +# /x +# abc#not comment\n literal +# 0: abc#not comment\x0a literal + +#/abc#comment +# \Q#not comment +# literal\E #more comment/x +# abc#not comment\n literal +# 0: abc#not comment\x0a literal + +#/\Qabc\$xyz\E/ +# abc\\\$xyz +# 0: abc\$xyz + +#/\Qabc\E\$\Qxyz\E/ +# abc\$xyz +# 0: abc$xyz + +/\Gabc/ + abc + 0: abc +\= Expect no match + xyzabc +No match + +/a(?x: b c )d/ + XabcdY + 0: abcd +\= Expect no match + Xa b c d Y +No match + +/((?x)x y z | a b c)/ + XabcY + 0: abc + 1: abc + AxyzB + 0: xyz + 1: xyz + +/(?i)AB(?-i)C/ + XabCY + 0: abC +\= Expect no match + XabcY +No match + +/((?i)AB(?-i)C|D)E/ + abCE + 0: abCE + 1: abC + DE + 0: DE + 1: D +\= Expect no match + abcE +No match + abCe +No match + dE +No match + De +No match + +/(.*)\d+\1/ + abc123abc + 0: abc123abc + 1: abc + abc123bc + 0: bc123bc + 1: bc + +/(.*)\d+\1/s + abc123abc + 0: abc123abc + 1: abc + abc123bc + 0: bc123bc + 1: bc + +/((.*))\d+\1/ + abc123abc + 0: abc123abc + 1: abc + 2: abc + abc123bc + 0: bc123bc + 1: bc + 2: bc + +# This tests for an IPv6 address in the form where it can have up to +# eight components, one and only one of which is empty. This must be +# an internal component. + +/^(?!:) # colon disallowed at start + (?: # start of item + (?: [0-9a-f]{1,4} | # 1-4 hex digits or + (?(1)0 | () ) ) # if null previously matched, fail; else null + : # followed by colon + ){1,7} # end item; 1-7 of them required + [0-9a-f]{1,4} $ # final hex number at end of string + (?(1)|.) # check that there was an empty component + /ix + a123::a123 + 0: a123::a123 + 1: + a123:b342::abcd + 0: a123:b342::abcd + 1: + a123:b342::324e:abcd + 0: a123:b342::324e:abcd + 1: + a123:ddde:b342::324e:abcd + 0: a123:ddde:b342::324e:abcd + 1: + a123:ddde:b342::324e:dcba:abcd + 0: a123:ddde:b342::324e:dcba:abcd + 1: + a123:ddde:9999:b342::324e:dcba:abcd + 0: a123:ddde:9999:b342::324e:dcba:abcd + 1: +\= Expect no match + 1:2:3:4:5:6:7:8 +No match + a123:bce:ddde:9999:b342::324e:dcba:abcd +No match + a123::9999:b342::324e:dcba:abcd +No match + abcde:2:3:4:5:6:7:8 +No match + ::1 +No match + abcd:fee0:123:: +No match + :1 +No match + 1: +No match + +#/[z\Qa-d]\E]/ +# z +# 0: z +# a +# 0: a +# - +# 0: - +# d +# 0: d +# ] +# 0: ] +#\= Expect no match +# b +#No match + +#TODO: PCRE has an optimization to make this workable, .NET does not +#/(a+)*b/ +#\= Expect no match +# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +#No match + +# All these had to be updated because we understand unicode +# and this looks like it's expecting single byte matches + +# .NET generates \xe4...not sure what's up, might just be different code pages +/(?i)reg(?:ul(?:[aä]|ae)r|ex)/ + REGular + 0: REGular + regulaer + 0: regulaer + Regex + 0: Regex + regulär + 0: regul\xc3\xa4r + +#/Åæåä[à-ÿÀ-ß]+/ +# Åæåäà +# 0: \xc5\xe6\xe5\xe4\xe0 +# Åæåäÿ +# 0: \xc5\xe6\xe5\xe4\xff +# ÅæåäÀ +# 0: \xc5\xe6\xe5\xe4\xc0 +# Åæåäß +# 0: \xc5\xe6\xe5\xe4\xdf + +/(?<=Z)X./ + \x84XAZXB + 0: XB + +/ab cd (?x) de fg/ + ab cd defg + 0: ab cd defg + +/ab cd(?x) de fg/ + ab cddefg + 0: ab cddefg +\= Expect no match + abcddefg +No match + +/(? + 2: + D + 0: D + 1: + 2: + +# this is really long with debug -- removing for now +#/(a|)*\d/ +# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4 +# 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4 +# 1: +#\= Expect no match +# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +#No match + +/(?>a|)*\d/ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4 + 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4 +\= Expect no match + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +No match + +/(?:a|)*\d/ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4 + 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4 +\= Expect no match + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +No match + +/^(?s)(?>.*)(? + 2: a + +/(?>(a))b|(a)c/ + ac + 0: ac + 1: + 2: a + +/(?=(a))ab|(a)c/ + ac + 0: ac + 1: + 2: a + +/((?>(a))b|(a)c)/ + ac + 0: ac + 1: ac + 2: + 3: a + +/(?=(?>(a))b|(a)c)(..)/ + ac + 0: ac + 1: + 2: a + 3: ac + +/(?>(?>(a))b|(a)c)/ + ac + 0: ac + 1: + 2: a + +/((?>(a+)b)+(aabab))/ + aaaabaaabaabab + 0: aaaabaaabaabab + 1: aaaabaaabaabab + 2: aaa + 3: aabab + +/(?>a+|ab)+?c/ +\= Expect no match + aabc +No match + +/(?>a+|ab)+c/ +\= Expect no match + aabc +No match + +/(?:a+|ab)+c/ + aabc + 0: aabc + +/^(?:a|ab)+c/ + aaaabc + 0: aaaabc + +/(?=abc){0}xyz/ + xyz + 0: xyz + +/(?=abc){1}xyz/ +\= Expect no match + xyz +No match + +/(?=(a))?./ + ab + 0: a + 1: a + bc + 0: b + +/(?=(a))??./ + ab + 0: a + bc + 0: b + +/^(?!a){0}\w+/ + aaaaa + 0: aaaaa + +/(?<=(abc))?xyz/ + abcxyz + 0: xyz + 1: abc + pqrxyz + 0: xyz + +/^[g]+/ + ggg<<>> + 0: ggg<<>> +\= Expect no match + \\ga +No match + +/^[ga]+/ + gggagagaxyz + 0: gggagaga + +/[:a]xxx[b:]/ + :xxx: + 0: :xxx: + +/(?<=a{2})b/i + xaabc + 0: b +\= Expect no match + xabc +No match + +/(? +# 4: +# 5: c +# 6: d +# 7: Y + +#/^X(?7)(a)(?|(b|(?|(r)|(t))(s))|(q))(c)(d)(Y)/ +# XYabcdY +# 0: XYabcdY +# 1: a +# 2: b +# 3: +# 4: +# 5: c +# 6: d +# 7: Y + +/(?'abc'\w+):\k{2}/ + a:aaxyz + 0: a:aa + 1: a + ab:ababxyz + 0: ab:abab + 1: ab +\= Expect no match + a:axyz +No match + ab:abxyz +No match + +/^(?a)? (?(ab)b|c) (?(ab)d|e)/x + abd + 0: abd + 1: a + ce + 0: ce + +# .NET has more consistent grouping numbers with these dupe groups for the two options +/(?:a(? (?')|(?")) |b(? (?')|(?")) ) (?(quote)[a-z]+|[0-9]+)/x,dupnames + a\"aaaaa + 0: a"aaaaa + 1: " + 2: + 3: " + b\"aaaaa + 0: b"aaaaa + 1: " + 2: + 3: " +\= Expect no match + b\"11111 +No match + +#/(?P(?P0)(?P>L1)|(?P>L2))/ +# 0 +# 0: 0 +# 1: 0 +# 00 +# 0: 00 +# 1: 00 +# 2: 0 +# 0000 +# 0: 0000 +# 1: 0000 +# 2: 0 + +#/(?P(?P0)|(?P>L2)(?P>L1))/ +# 0 +# 0: 0 +# 1: 0 +# 2: 0 +# 00 +# 0: 0 +# 1: 0 +# 2: 0 +# 0000 +# 0: 0 +# 1: 0 +# 2: 0 + +# Check the use of names for failure + +# Check opening parens in comment when seeking forward reference. + +#/(?P(?P=abn)xxx|)+/ +# xxx +# 0: +# 1: + +#Posses +/^(a)?(\w)/ + aaaaX + 0: aa + 1: a + 2: a + YZ + 0: Y + 1: + 2: Y + +#Posses +/^(?:a)?(\w)/ + aaaaX + 0: aa + 1: a + YZ + 0: Y + 1: Y + +/\A.*?(a|bc)/ + ba + 0: ba + 1: a + +/\A.*?(?:a|bc|d)/ + ba + 0: ba + +# -------------------------- + +/(another)?(\1?)test/ + hello world test + 0: test + 1: + 2: + +/(another)?(\1+)test/ +\= Expect no match + hello world test +No match + +/((?:a?)*)*c/ + aac + 0: aac + 1: + +/((?>a?)*)*c/ + aac + 0: aac + 1: + +/(?>.*?a)(?<=ba)/ + aba + 0: ba + +/(?:.*?a)(?<=ba)/ + aba + 0: aba + +/(?>.*?a)b/s + aab + 0: ab + +/(?>.*?a)b/ + aab + 0: ab + +/(?>^a)b/s +\= Expect no match + aab +No match + +/(?>.*?)(?<=(abcd)|(wxyz))/ + alphabetabcd + 0: + 1: abcd + endingwxyz + 0: + 1: + 2: wxyz + +/(?>.*)(?<=(abcd)|(wxyz))/ + alphabetabcd + 0: alphabetabcd + 1: abcd + endingwxyz + 0: endingwxyz + 1: + 2: wxyz + +"(?>.*)foo" +\= Expect no match + abcdfooxyz +No match + +"(?>.*?)foo" + abcdfooxyz + 0: foo + +# Tests that try to figure out how Perl works. My hypothesis is that the first +# verb that is backtracked onto is the one that acts. This seems to be the case +# almost all the time, but there is one exception that is perhaps a bug. + +/a(?=bc).|abd/ + abd + 0: abd + abc + 0: ab + +/a(?>bc)d|abd/ + abceabd + 0: abd + +# These tests were formerly in test 2, but changes in PCRE and Perl have +# made them compatible. + +/^(a)?(?(1)a|b)+$/ +\= Expect no match + a +No match + +# ---- + +/^\d*\w{4}/ + 1234 + 0: 1234 +\= Expect no match + 123 +No match + +/^[^b]*\w{4}/ + aaaa + 0: aaaa +\= Expect no match + aaa +No match + +/^[^b]*\w{4}/i + aaaa + 0: aaaa +\= Expect no match + aaa +No match + +/^a*\w{4}/ + aaaa + 0: aaaa +\= Expect no match + aaa +No match + +/^a*\w{4}/i + aaaa + 0: aaaa +\= Expect no match + aaa +No match + +/(?:(?foo)|(?bar))\k/dupnames + foofoo + 0: foofoo + 1: foo + barbar + 0: barbar + 1: bar + +# A notable difference between PCRE and .NET. According to +# the PCRE docs: +# If you make a subroutine call to a non-unique named +# subpattern, the one that corresponds to the first +# occurrence of the name is used. In the absence of +# duplicate numbers (see the previous section) this is +# the one with the lowest number. +# .NET takes the most recently captured number according to MSDN: +# A backreference refers to the most recent definition of +# a group (the definition most immediately to the left, +# when matching left to right). When a group makes multiple +# captures, a backreference refers to the most recent capture. + +#/(?A)(?:(?foo)|(?bar))\k/dupnames +# AfooA +# 0: AfooA +# 1: A +# 2: foo +# AbarA +# 0: AbarA +# 1: A +# 2: +# 3: bar +#\= Expect no match +# Afoofoo +#No match +# Abarbar +#No match + +/^(\d+)\s+IN\s+SOA\s+(\S+)\s+(\S+)\s*\(\s*$/ + 1 IN SOA non-sp1 non-sp2( + 0: 1 IN SOA non-sp1 non-sp2( + 1: 1 + 2: non-sp1 + 3: non-sp2 + +# TODO: .NET's group number ordering here in the second example is a bit odd +/^ (?:(?A)|(?'B'B)(?A)) (?(A)x) (?(B)y)$/x,dupnames + Ax + 0: Ax + 1: A + BAxy + 0: BAxy + 1: A + 2: B + +/ ^ a + b $ /x + aaaab + 0: aaaab + +/ ^ a + #comment + b $ /x + aaaab + 0: aaaab + +/ ^ a + #comment + #comment + b $ /x + aaaab + 0: aaaab + +/ ^ (?> a + ) b $ /x + aaaab + 0: aaaab + +/ ^ ( a + ) + \w $ /x + aaaab + 0: aaaab + 1: aaaa + +/(?:x|(?:(xx|yy)+|x|x|x|x|x)|a|a|a)bc/ +\= Expect no match + acb +No match + +#Posses +#/\A(?:[^\"]+|\"(?:[^\"]*|\"\")*\")+/ +# NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED +# 0: NON QUOTED "QUOT""ED" AFTER + +#Posses +#/\A(?:[^\"]+|\"(?:[^\"]+|\"\")*\")+/ +# NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED +# 0: NON QUOTED "QUOT""ED" AFTER + +#Posses +#/\A(?:[^\"]+|\"(?:[^\"]+|\"\")+\")+/ +# NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED +# 0: NON QUOTED "QUOT""ED" AFTER + +#Posses +#/\A([^\"1]+|[\"2]([^\"3]*|[\"4][\"5])*[\"6])+/ +# NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED +# 0: NON QUOTED "QUOT""ED" AFTER +# 1: AFTER +# 2: + +/^\w+(?>\s*)(?<=\w)/ + test test + 0: tes + +#/(?Pa)?(?Pb)?(?()c|d)*l/ +# acl +# 0: acl +# 1: a +# bdl +# 0: bdl +# 1: +# 2: b +# adl +# 0: dl +# bcl +# 0: l + +/\sabc/ + \x0babc + 0: \x0babc + +#/[\Qa]\E]+/ +# aa]] +# 0: aa]] + +#/[\Q]a\E]+/ +# aa]] +# 0: aa]] + +/A((((((((a))))))))\8B/ + AaaB + 0: AaaB + 1: a + 2: a + 3: a + 4: a + 5: a + 6: a + 7: a + 8: a + +/A(((((((((a)))))))))\9B/ + AaaB + 0: AaaB + 1: a + 2: a + 3: a + 4: a + 5: a + 6: a + 7: a + 8: a + 9: a + +/(|ab)*?d/ + abd + 0: abd + 1: ab + xyd + 0: d + +/(\2|a)(\1)/ + aaa + 0: aa + 1: a + 2: a + +/(\2)(\1)/ + +"Z*(|d*){216}" + +/((((((((((((x))))))))))))\12/ + xx + 0: xx + 1: x + 2: x + 3: x + 4: x + 5: x + 6: x + 7: x + 8: x + 9: x +10: x +11: x +12: x + +#"(?|(\k'Pm')|(?'Pm'))" +# abcd +# 0: +# 1: + +#/(?|(aaa)|(b))\g{1}/ +# aaaaaa +# 0: aaaaaa +# 1: aaa +# bb +# 0: bb +# 1: b + +#/(?|(aaa)|(b))(?1)/ +# aaaaaa +# 0: aaaaaa +# 1: aaa +# baaa +# 0: baaa +# 1: b +#\= Expect no match +# bb +#No match + +#/(?|(aaa)|(b))/ +# xaaa +# 0: aaa +# 1: aaa +# xbc +# 0: b +# 1: b + +#/(?|(?'a'aaa)|(?'a'b))\k'a'/ +# aaaaaa +# 0: aaaaaa +# 1: aaa +# bb +# 0: bb +# 1: b + +#/(?|(?'a'aaa)|(?'a'b))(?'a'cccc)\k'a'/dupnames +# aaaccccaaa +# 0: aaaccccaaa +# 1: aaa +# 2: cccc +# bccccb +# 0: bccccb +# 1: b +# 2: cccc + +# End of testinput1 diff --git a/vendor/modules.txt b/vendor/modules.txt index 318282157..2234064e4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -6,6 +6,10 @@ filippo.io/edwards25519/field ## explicit; go 1.18 github.com/BurntSushi/toml github.com/BurntSushi/toml/internal +# github.com/alecthomas/chroma/v2 v2.26.1 +## explicit; go 1.25 +github.com/alecthomas/chroma/v2 +github.com/alecthomas/chroma/v2/lexers # github.com/beorn7/perks v1.0.1 ## explicit; go 1.11 github.com/beorn7/perks/quantile @@ -34,6 +38,11 @@ github.com/cloudbase/garm-provider-common/util/websocket # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew +# github.com/dlclark/regexp2/v2 v2.1.1 +## explicit; go 1.25 +github.com/dlclark/regexp2/v2 +github.com/dlclark/regexp2/v2/helpers +github.com/dlclark/regexp2/v2/syntax # github.com/felixge/httpsnoop v1.0.4 ## explicit; go 1.13 github.com/felixge/httpsnoop