From cfdc227fa5b1079fcd216252ff6ea61401674616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Spo=CC=88nemann?= Date: Tue, 21 Jul 2026 10:30:32 +0200 Subject: [PATCH] Package documentation for parser and server --- AGENTS.md | 4 + parser/doc.go | 121 ++++++++++++++++++++++++++ server/definition_provider.go | 1 + server/diagnostics_publisher.go | 14 +-- server/doc.go | 84 ++++++++++++++++++ server/document_highlight_provider.go | 5 ++ server/document_sync.go | 1 + server/folding_range_provider.go | 7 ++ server/hover_provider.go | 1 + server/name_finder.go | 10 +++ server/references_finder.go | 13 +++ server/references_provider.go | 1 + server/rename_provider.go | 5 ++ server/server.go | 19 +++- server/services.go | 3 +- server/slog_handler.go | 7 +- server/transport_stdio.go | 6 +- server/transport_wasm.go | 4 + util/parallel/doc.go | 16 ++++ 19 files changed, 309 insertions(+), 13 deletions(-) create mode 100644 parser/doc.go create mode 100644 server/doc.go diff --git a/AGENTS.md b/AGENTS.md index b415ab79..a09d9444 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,15 +7,19 @@ For project documentation, read the package documentation written in Go doc form - `doc.go`: General overview of the Fastbelt framework - `grammar/doc.go`: Reference documentation for the grammar language - `lexer/doc.go`: Shared lexer runtime used by Fastbelt-generated languages +- `parser/doc.go`: Shared parser runtime used by Fastbelt-generated languages - `textdoc/doc.go`: Text documents, overlays, and LSP position mapping - `linking/doc.go`: Cross-reference resolution, symbol tables, and scopes - `workspace/doc.go`: Document lifecycle, loading, edits, and the build pipeline +- `server/doc.go`: Language Server Protocol (LSP) implementation +- `test/doc.go`: Utilities for testing Fastbelt language implementations Utility packages: - `util/codegen/doc.go`: Building indented multi-line source for code generators - `util/collections/doc.go`: Generic collection data structures (e.g. MultiMap) - `util/extiter/doc.go`: Utilities for `iter.Seq` sequences +- `util/parallel/doc.go`: Utilities for parallel iteration over slices - `util/service/doc.go`: Typed dependency injection container ## VS Code extensions diff --git a/parser/doc.go b/parser/doc.go new file mode 100644 index 00000000..ccb1665c --- /dev/null +++ b/parser/doc.go @@ -0,0 +1,121 @@ +// Copyright 2026 TypeFox GmbH +// This program and the accompanying materials are made available under the +// terms of the MIT License, which is available in the project root. + +// Package parser provides the shared parser runtime used by Fastbelt-generated +// languages. It does not implement parsing for any particular language; each +// grammar gets its own generated recursive-descent parser that drives the +// parser state, adaptive prediction, error recovery, and completion machinery +// of this package. +// +// Run [typefox.dev/fastbelt/cmd/fastbelt] generate on a .fb grammar file to +// emit the files described below. See [typefox.dev/fastbelt] for the overall +// toolchain and [typefox.dev/fastbelt/grammar] for how parser rules are +// declared in the grammar language. +// +// # Generated parsers +// +// Code generation turns the parser rules of a grammar into four cooperating +// files: +// +// - `parser_gen.go` — a Parser implementing [Parser], with one ParseX method +// per parser rule. Each method allocates the rule's AST node, matches +// tokens through [ParserState], assigns them to node fields, and records +// the node's token segment for source ranges. +// - `parser_lookahead_gen.go` — the lookahead service, with one method per +// decision point in the grammar: which alternative to take, whether to +// enter an optional group, whether to iterate a loop once more. It is an +// interface with a default implementation, so individual decisions can be +// overridden without touching the parser. +// - `atn_gen.go` — the [RuntimeATN], an augmented transition network that +// encodes the grammar as a graph of states with token, epsilon, and rule +// transitions. It backs adaptive prediction, error recovery, and +// completion. +// - `completion_parser_gen.go` — a stripped-down twin of the parser that +// builds no AST and instead records the parse progress snapshots that +// code completion needs. +// +// The generated `SetupGeneratedServices` registers the Parser, the lookahead +// service, and the default [ErrorRecoveryStrategy] and [ErrorMessageProvider] +// in the service container ([typefox.dev/fastbelt/util/service]), each unless +// a custom implementation was registered first. During document processing, +// [typefox.dev/fastbelt/workspace.DefaultDocumentParser] obtains the [Parser], +// calls Parse on the tokens produced by [typefox.dev/fastbelt/lexer], and +// stores the resulting root [core.AstNode] and syntax errors ([ParseResult]) +// on the document. +// +// # Parsing model +// +// A generated parser is a recursive-descent parser: one method per rule, +// descending into sub-rules and consuming tokens left to right. [ParserState] +// carries the per-parse state: token slice, cursor, error mode, and rule stack. +// +// Wherever the grammar offers a choice — between alternatives, at an optional +// element, at a loop continuation — the parser asks its lookahead service. +// Depending on the grammar, the generated decision method uses one of three +// strategies, cheapest first: +// +// 1. A direct check of the next token's type, when a single expected token +// decides. +// 2. An [LL1Lookahead] table resolved by [ParserState.Lookahead] in O(1), +// when one token of lookahead decides. +// 3. Adaptive ALL(*) prediction via [ParserState.AdaptivePredict], when +// arbitrary lookahead is required. The predictor simulates the +// [RuntimeATN] ahead of the current position and caches its decisions in +// per-decision DFAs, so hot decisions approach table-lookup cost over +// time. The cache is internally synchronized and shared across +// concurrent parses. +// +// Adaptive prediction runs in [PredictionModeSLL] by default; +// [ParserLookahead.SetPredictionMode] enables full-context [PredictionModeLL] +// for grammars that need it. +// +// Cross-reference assignments in the grammar do not resolve anything at parse +// time: the parser stores an unresolved reference created by the generated +// references constructor, and the [typefox.dev/fastbelt/linking] phase +// resolves it later. +// +// # Error recovery +// +// Parse errors do not stop the parse. [Parser] implementations always return +// an AST — with nil tokens or unset fields where input was missing — plus the +// collected [core.ParserError] values, so language tooling keeps working on +// broken input. While the parser is recovering, follow-up errors are +// suppressed so one underlying mistake produces one diagnostic. The pluggable +// [ErrorRecoveryStrategy] controls resynchronization: +// +// - [DefaultErrorRecovery] attempts single-token deletion when Consume hits +// an unexpected token, discards tokens before optional/loop guards until +// one fits the decision or an enclosing follow set (Sync), and skips to +// the follow set after unwinding from a failed rule (Recover). Because +// generated AST nodes tolerate nil tokens, missing tokens are reported +// but never fabricated. +// - [BailErrorRecovery] stops at the first mismatch and unwinds, for +// two-stage parsing or when a broken parse result would be discarded +// anyway. +// +// Message texts come from the pluggable [ErrorMessageProvider]; replace it in +// the service container to change wording or to localize. +// +// # Code completion +// +// The package also contains the parser side of code completion, driven by +// [typefox.dev/fastbelt/server.DefaultCompletionProvider]: +// +// 1. The generated CompletionParser reparses the document's tokens up to the +// cursor. It mirrors the main parser's control flow but mutates no AST; +// via [CompletionParserState] it records an [ATNSnapshot] — token index, +// ATN state, rule stack — at every rule entry and sync point. +// 2. [CompletionParseResult.SimulateAt] picks a suitable snapshot and +// advances an NFA-style live set over the ATN ([RuntimeATN.Simulate]) +// through the remaining tokens. Unlike prediction, the simulation keeps +// every alternative alive: the user has not committed to a branch yet. +// 3. [RuntimeATN.NextCompletionsFromSet] reports what may come next as a +// [CompletionInfo]: valid token types, plus [CompletionHint] entries that +// mark cross-reference positions so the provider can offer resolvable +// symbols instead of a bare identifier token. +// +// [LanguageCompletionAdapter] is the contract between the framework's +// completion provider and these generated artifacts; the code generator emits +// its implementation together with the parsers. +package parser diff --git a/server/definition_provider.go b/server/definition_provider.go index dfd63365..4a6aaccc 100644 --- a/server/definition_provider.go +++ b/server/definition_provider.go @@ -25,6 +25,7 @@ type DefaultDefinitionProvider struct { sc *service.Container } +// NewDefaultDefinitionProvider creates a new default definition provider. func NewDefaultDefinitionProvider(sc *service.Container) DefinitionProvider { return &DefaultDefinitionProvider{sc: sc} } diff --git a/server/diagnostics_publisher.go b/server/diagnostics_publisher.go index 6c28b190..dc81fcd0 100644 --- a/server/diagnostics_publisher.go +++ b/server/diagnostics_publisher.go @@ -16,12 +16,14 @@ import ( ) // DiagnosticsPublisher is responsible for publishing diagnostics to the LSP client. -// The default implementation publishes diagnostics in the following scenarios: -// Document is opened (reuses diagnostics from build). -// Document is closed (clears diagnostics). -// Document is validated by the builder. +// It publishes diagnostics in the following scenarios: // -// When registered to a service container, it automatically hooks into the document lifecycle events. +// - A document is opened (reuses diagnostics from build). +// - A document is closed (clears diagnostics). +// - A document is validated by the builder. +// +// When registered to a service container, it automatically hooks into the +// document lifecycle events during server initialization. type DiagnosticsPublisher struct { sc *service.Container } @@ -31,6 +33,8 @@ func NewDiagnosticsPublisher(sc *service.Container) *DiagnosticsPublisher { return &DiagnosticsPublisher{sc: sc} } +// OnServerInitialize implements [InitializeParticipant] by subscribing to the +// document lifecycle events that trigger publishing diagnostics. func (d *DiagnosticsPublisher) OnServerInitialize(_ *lsp.ParamInitialize) { store, err := service.Get[textdoc.Store](d.sc) if err != nil { diff --git a/server/doc.go b/server/doc.go new file mode 100644 index 00000000..920c48d2 --- /dev/null +++ b/server/doc.go @@ -0,0 +1,84 @@ +// Copyright 2026 TypeFox GmbH +// This program and the accompanying materials are made available under the +// terms of the MIT License, which is available in the project root. + +// Package server implements the Language Server Protocol (LSP) side of +// Fastbelt. It exposes the language services built by +// [typefox.dev/fastbelt/workspace] — parsed, linked, and validated documents — +// to editors and IDEs: text documents are synchronized through LSP +// notifications, and language features such as completion, go to definition, +// find references, hover, and rename are answered from the workspace. +// +// # The language server and its method services +// +// [DefaultLanguageServer] implements the [typefox.dev/lsp.Server] interface, +// but contains no feature logic of its own. Each supported LSP method is +// delegated to a dedicated service interface (for example +// [CompletionProvider] or [DocumentSyncher]) that is looked up in the +// [typefox.dev/fastbelt/util/service.Container] when the request arrives. +// Request-handling methods first acquire the +// [typefox.dev/fastbelt/workspace.Lock] for reading, so a request never +// observes a half-built workspace. The capabilities announced to the client +// in the initialize response are derived from which services are registered, +// so the set of advertised features always matches the set of available +// services. +// +// [SetupDefaultServices] registers the default implementation of every LSP +// method service that has not been registered yet. To customize a feature, +// register your own implementation of the corresponding interface. Services +// that need to inspect the client's initialize request implement +// [InitializeParticipant]. +// +// # Shared name and reference lookup +// +// Most position-based LSP features start by answering the same question: +// which named symbol does the cursor point at? Two services encapsulate this +// so that all features resolve names consistently: +// +// - [NameFinder] turns the token(s) at a cursor position into a +// [FoundName], pairing the unit under the cursor with the unit that +// holds the name of the referenced symbol — following cross-references +// to their target, or staying in place when the cursor is already on a +// declaration's name. +// - [ReferencesFinder] yields every reference to a given AST node, drawing +// on the reference descriptions indexed per document. [FindReferencesOptions] +// controls whether the search is restricted to a single document and +// whether the declaration itself is included. +// +// The default definition, references, document highlight, hover, and rename +// providers are thin compositions of these two services plus conversion to +// LSP result types. Registering a custom [NameFinder] or [ReferencesFinder] +// therefore adjusts all of these features at once. +// +// # Starting a server +// +// [StartLanguageServer] is the main entry point. It expects a fully +// configured service container: the language-specific and workspace +// services, the server services from [SetupDefaultServices], and a transport +// registered with [SetupStdioServices] (communication over stdin/stdout) or +// SetupWasmServices (communication with a JavaScript host, available in +// js/wasm builds). It dials a JSON-RPC connection using the registered +// [golang.org/x/exp/jsonrpc2.Dialer] and [golang.org/x/exp/jsonrpc2.Binder] +// — by default [DefaultBinder], which attaches the [typefox.dev/lsp.Server] +// from the container as the message handler — and blocks until the +// connection is closed, which the default server triggers on the LSP exit +// notification. +// +// A typical main function for a generated language looks like this: +// +// func main() { +// sc := service.NewContainer() +// mylang.SetupServices(sc) // language, textdoc, linking, workspace services +// mylang.SetupGeneratedServerServices(sc) +// server.SetupDefaultServices(sc) +// server.SetupStdioServices(sc) +// sc.Seal() +// if err := server.StartLanguageServer(context.Background(), sc); err != nil { +// log.Fatal(err) +// } +// } +// +// While the server runs, [DiagnosticsPublisher] pushes parser, linker, and +// validation diagnostics to the client whenever an open document is rebuilt, +// and [SlogHandler] forwards log output to the client as LSP log messages. +package server diff --git a/server/document_highlight_provider.go b/server/document_highlight_provider.go index 14686dd1..ed3a196e 100644 --- a/server/document_highlight_provider.go +++ b/server/document_highlight_provider.go @@ -13,14 +13,19 @@ import ( "typefox.dev/lsp" ) +// DocumentHighlightProvider is a service for handling LSP document highlight requests. type DocumentHighlightProvider interface { HandleDocumentHighlightRequest(ctx context.Context, params *lsp.DocumentHighlightParams) ([]lsp.DocumentHighlight, error) } +// DefaultDocumentHighlightProvider is the default implementation of [DocumentHighlightProvider]. +// It highlights all references to the symbol at the cursor position that are +// located in the same document, using [NameFinder] and [ReferencesFinder]. type DefaultDocumentHighlightProvider struct { sc *service.Container } +// NewDefaultDocumentHighlightProvider creates a new default document highlight provider. func NewDefaultDocumentHighlightProvider(sc *service.Container) DocumentHighlightProvider { return &DefaultDocumentHighlightProvider{sc: sc} } diff --git a/server/document_sync.go b/server/document_sync.go index c47dcf49..c84fc06b 100644 --- a/server/document_sync.go +++ b/server/document_sync.go @@ -72,6 +72,7 @@ type DefaultDocumentSyncher struct { mu sync.RWMutex } +// NewDefaultDocumentSyncher creates a new default document syncher. func NewDefaultDocumentSyncher(sc *service.Container) DocumentSyncher { return &DefaultDocumentSyncher{sc: sc} } diff --git a/server/folding_range_provider.go b/server/folding_range_provider.go index 583640d7..3f3afcf0 100644 --- a/server/folding_range_provider.go +++ b/server/folding_range_provider.go @@ -60,11 +60,16 @@ func (f *DefaultFoldingRangeFilter) IncludeLastFoldingLineForComment() bool { return true } +// DefaultFoldingRangeProvider is the default implementation of [FoldingRangeProvider]. +// It derives folding ranges from multi-line AST nodes and comments, with the +// details controlled by a [FoldingRangeFilter]. type DefaultFoldingRangeProvider struct { sc *service.Container filter FoldingRangeFilter } +// NewDefaultFoldingRangeProvider creates a folding range provider with the +// [DefaultFoldingRangeFilter]. func NewDefaultFoldingRangeProvider(sc *service.Container) FoldingRangeProvider { return &DefaultFoldingRangeProvider{ sc: sc, @@ -72,6 +77,8 @@ func NewDefaultFoldingRangeProvider(sc *service.Container) FoldingRangeProvider } } +// NewFoldingRangeProviderWithFilter creates a folding range provider with a +// custom [FoldingRangeFilter]. func NewFoldingRangeProviderWithFilter(sc *service.Container, filter FoldingRangeFilter) FoldingRangeProvider { return &DefaultFoldingRangeProvider{ sc: sc, diff --git a/server/hover_provider.go b/server/hover_provider.go index 70787f14..f0c99d0b 100644 --- a/server/hover_provider.go +++ b/server/hover_provider.go @@ -23,6 +23,7 @@ type DefaultHoverProvider struct { sc *service.Container } +// NewDefaultHoverProvider creates a new default hover provider. func NewDefaultHoverProvider(sc *service.Container) HoverProvider { return &DefaultHoverProvider{sc: sc} } diff --git a/server/name_finder.go b/server/name_finder.go index 04c37dbb..9ddde4fe 100644 --- a/server/name_finder.go +++ b/server/name_finder.go @@ -12,6 +12,10 @@ import ( "typefox.dev/fastbelt/util/service" ) +// FoundName is the result of a [NameFinder.Find] call. Either field may be +// nil if the search could not determine the corresponding unit. Source and +// Target are the same unit when the search started on a symbol's declaration +// name rather than on a reference to it. type FoundName struct { // The unit that contains the token that was used to start the search. Source core.StringUnit @@ -31,10 +35,16 @@ type NameFinder interface { Find(ctx context.Context, first, second *core.Token) FoundName } +// DefaultNameFinder is the default implementation of [NameFinder]. +// If a given token belongs to a cross-reference, it resolves the reference +// and returns the name unit of the target symbol; if the token lies within +// the name of a declaration, it returns that name unit as both source and +// target. type DefaultNameFinder struct { sc *service.Container } +// NewDefaultNameFinder creates a new default name finder. func NewDefaultNameFinder(sc *service.Container) NameFinder { return &DefaultNameFinder{sc: sc} } diff --git a/server/references_finder.go b/server/references_finder.go index 776edfb9..c1cc8f35 100644 --- a/server/references_finder.go +++ b/server/references_finder.go @@ -15,6 +15,7 @@ import ( "typefox.dev/fastbelt/workspace" ) +// FindReferencesOptions controls the scope of a [ReferencesFinder.Find] search. type FindReferencesOptions struct { // Whether to include the declaration of the symbol as a dedicated [core.ReferenceDescription] in the results. // If true, will contain the declaration as a reference with the same source and target node, and the range of the name as the first element. @@ -25,14 +26,26 @@ type FindReferencesOptions struct { TargetURI core.URI } +// ReferencesFinder finds all references to a given symbol. It is shared by +// the LSP services that need the full set of references to a symbol, such as +// the default references, document highlight, and rename providers. +// Adopters should customize this service if they want to change how +// references are collected; downstream LSP services will automatically use +// the new implementation. type ReferencesFinder interface { + // Find returns the references pointing at the target node, + // with the search scope controlled by options. Find(ctx context.Context, target core.AstNode, options FindReferencesOptions) iter.Seq[*core.ReferenceDescription] } +// DefaultReferencesFinder is the default implementation of [ReferencesFinder]. +// It draws on the reference descriptions indexed per document, iterating all +// documents of the workspace unless a target URI is given. type DefaultReferencesFinder struct { sc *service.Container } +// NewDefaultReferencesFinder creates a new default references finder. func NewDefaultReferencesFinder(sc *service.Container) ReferencesFinder { return &DefaultReferencesFinder{sc: sc} } diff --git a/server/references_provider.go b/server/references_provider.go index f276950d..213b7a5b 100644 --- a/server/references_provider.go +++ b/server/references_provider.go @@ -23,6 +23,7 @@ type DefaultReferencesProvider struct { sc *service.Container } +// NewDefaultReferencesProvider creates a new default references provider. func NewDefaultReferencesProvider(sc *service.Container) ReferencesProvider { return &DefaultReferencesProvider{sc: sc} } diff --git a/server/rename_provider.go b/server/rename_provider.go index ea89ab1f..4d56ff98 100644 --- a/server/rename_provider.go +++ b/server/rename_provider.go @@ -13,15 +13,20 @@ import ( "typefox.dev/lsp" ) +// RenameProvider is a service for handling LSP rename and prepare rename requests. type RenameProvider interface { HandleRenameRequest(ctx context.Context, params *lsp.RenameParams) (*lsp.WorkspaceEdit, error) PrepareRenameRequest(ctx context.Context, params *lsp.PrepareRenameParams) (*lsp.PrepareRenameResult, error) } +// DefaultRenameProvider is the default implementation of [RenameProvider]. +// It renames a symbol by generating a text edit for its declaration name and +// for every reference found by [ReferencesFinder] across the workspace. type DefaultRenameProvider struct { sc *service.Container } +// NewDefaultRenameProvider creates a new default rename provider. func NewDefaultRenameProvider(sc *service.Container) RenameProvider { return &DefaultRenameProvider{sc: sc} } diff --git a/server/server.go b/server/server.go index fa38a4e9..c212d94c 100644 --- a/server/server.go +++ b/server/server.go @@ -26,7 +26,14 @@ type InitializeParticipant interface { OnServerInitialize(params *lsp.ParamInitialize) } -// DefaultLanguageServer implements the [lsp.Server] interface +// DefaultLanguageServer is the default implementation of the [lsp.Server] +// interface. It contains no feature logic itself: each supported LSP method +// is delegated to a dedicated service (such as [CompletionProvider] or +// [DocumentSyncher]) looked up in the service container, and request-handling +// methods acquire the [workspace.Lock] for reading before consulting the +// service. The capabilities announced in the initialize response reflect +// which services are registered in the container. LSP methods without a +// corresponding service are implemented as no-ops. type DefaultLanguageServer struct { sc *service.Container } @@ -498,7 +505,10 @@ func (s *DefaultLanguageServer) WorkDoneProgressCancel(ctx context.Context, para } // StartLanguageServer starts a language server using the service container. -// It sets up JSON-RPC communication over stdio and handles the essential LSP messages. +// It dials a JSON-RPC connection through the [jsonrpc2.Dialer] and +// [jsonrpc2.Binder] registered in the container (see [SetupStdioServices] +// for the stdio transport) and blocks until the connection is closed, which +// [DefaultLanguageServer] triggers on the LSP exit notification. func StartLanguageServer(ctx context.Context, sc *service.Container) error { dialer, err := service.Get[jsonrpc2.Dialer](sc) if err != nil { @@ -565,7 +575,10 @@ func toLspDiagnostic(d core.Diagnostic) lsp.Diagnostic { return result } -// DefaultBinder implements the jsonrpc2.Binder interface +// DefaultBinder is the default implementation of the [jsonrpc2.Binder] +// interface. When the connection is established, it stores it in the +// [Connection] service and attaches the [lsp.Server] from the service +// container as the message handler. type DefaultBinder struct { sc *service.Container } diff --git a/server/services.go b/server/services.go index 4a5c9cbb..2670908e 100644 --- a/server/services.go +++ b/server/services.go @@ -17,7 +17,8 @@ type WorkspaceFolders struct { Value []lsp.WorkspaceFolder } -// Connection is assigned by ConnectionBinder when the language server is started. +// Connection holds the active JSON-RPC connection to the LSP client. +// It is assigned by [DefaultBinder.Bind] when the language server is started. type Connection struct { Value *jsonrpc2.Connection } diff --git a/server/slog_handler.go b/server/slog_handler.go index 147a1521..fd63b02f 100644 --- a/server/slog_handler.go +++ b/server/slog_handler.go @@ -25,7 +25,8 @@ func NewSlogHandler(sc *service.Container) slog.Handler { return &SlogHandler{sc: sc} } -// Initializes the default slog handler to send log messages to the LSP client. +// OnServerInitialize implements [InitializeParticipant] by installing this +// handler as the default slog handler, so log messages are sent to the LSP client. func (h *SlogHandler) OnServerInitialize(_ *lsp.ParamInitialize) { slog.SetDefault(slog.New(h)) } @@ -50,12 +51,12 @@ func (h *SlogHandler) Handle(ctx context.Context, record slog.Record) error { return h.write(ctx, lspLevel, record.Message) } -// No-op implementation. [SlogHandler] does not support attributes. +// WithAttrs returns the handler unchanged. [SlogHandler] does not support attributes. func (h *SlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return h } -// No-op implementation. [SlogHandler] does not support groups. +// WithGroup returns the handler unchanged. [SlogHandler] does not support groups. func (h *SlogHandler) WithGroup(name string) slog.Handler { return h } diff --git a/server/transport_stdio.go b/server/transport_stdio.go index a40a6c03..92ce81af 100644 --- a/server/transport_stdio.go +++ b/server/transport_stdio.go @@ -13,6 +13,9 @@ import ( "typefox.dev/fastbelt/util/service" ) +// SetupStdioServices registers the transport services for JSON-RPC +// communication over stdin/stdout. It should be called together with +// [SetupDefaultServices]. If any service is already set, it's not overwritten. func SetupStdioServices(sc *service.Container) { if !service.Has[jsonrpc2.Binder](sc) { service.Put[jsonrpc2.Binder](sc, NewDefaultBinder(sc)) @@ -22,9 +25,10 @@ func SetupStdioServices(sc *service.Container) { } } -// StdioDialer implements jsonrpc2.Dialer for stdio communication +// StdioDialer implements [jsonrpc2.Dialer] for communication over stdin/stdout. type StdioDialer struct{} +// NewStdioDialer creates a dialer that communicates over stdin/stdout. func NewStdioDialer() *StdioDialer { return &StdioDialer{} } diff --git a/server/transport_wasm.go b/server/transport_wasm.go index 44283653..b21476f0 100644 --- a/server/transport_wasm.go +++ b/server/transport_wasm.go @@ -15,6 +15,10 @@ import ( "typefox.dev/fastbelt/util/service" ) +// SetupWasmServices registers the transport services for JSON-RPC +// communication with a JavaScript host in a js/wasm build. It should be +// called together with [SetupDefaultServices]. If any service is already +// set, it's not overwritten. func SetupWasmServices(sc *service.Container) { if !service.Has[jsonrpc2.Binder](sc) { service.Put[jsonrpc2.Binder](sc, NewWasmBinder(sc)) diff --git a/util/parallel/doc.go b/util/parallel/doc.go index 67898202..b572d8a4 100644 --- a/util/parallel/doc.go +++ b/util/parallel/doc.go @@ -4,4 +4,20 @@ // Package parallel provides utilities for parallel iteration over slices and // other collections. +// +// The naive way to parallelize work in Go is to spawn one goroutine per +// element. Goroutines are cheap, but not free: each one costs stack +// allocation and scheduler bookkeeping, and running far more goroutines +// than there are CPU cores adds overhead without adding throughput. +// For workloads that iterate over large collections — such as the +// document build pipeline in [typefox.dev/fastbelt/workspace], which +// parses, links, and validates every document in a workspace — this +// overhead is measurable. +// +// This package therefore bounds concurrency instead of spawning +// unboundedly: [ForEach] and [ForEachIter] distribute the elements across +// at most [runtime.GOMAXPROCS](0) worker goroutines, which pull elements +// from a shared index counter. This caps the goroutine count at the +// number of usable CPU cores while still balancing the load when +// individual elements take different amounts of time to process. package parallel