-
-
Notifications
You must be signed in to change notification settings - Fork 2
Package documentation for parser and server #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
|
Comment on lines
+90
to
+92
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand the last sentence. Maybe just say: "The error recovery never generates synthetic tokens for the purpose of a valid AST"? |
||
| // - [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. | ||
|
Comment on lines
+105
to
+116
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Too in depth? I'm not sure this is interesting for adopters. They should never need to touch this or understand it beyond the basic workings of the parser (described above). |
||
| // | ||
| // [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 | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -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. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sentence sounds a bit odd, e.g.
|
||||||
| // | ||||||
| // # 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: | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| // | ||||||
| // func main() { | ||||||
| // sc := service.NewContainer() | ||||||
| // mylang.SetupServices(sc) // language, textdoc, linking, workspace services | ||||||
| // mylang.SetupGeneratedServerServices(sc) | ||||||
| // server.SetupDefaultServices(sc) | ||||||
| // server.SetupStdioServices(sc) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, we have it everywhere like this. However, intuitively I would expect the following pattern starting with basic/default definitions followed by the specific ones, esp. since there is a function What do you think?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At that I noticed that Would you complete those gaps? |
||||||
| // 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 | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick, here and below:
Suggested change
|
||||||
| func NewDefaultDocumentHighlightProvider(sc *service.Container) DocumentHighlightProvider { | ||||||
| return &DefaultDocumentHighlightProvider{sc: sc} | ||||||
| } | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Remove the whole last sentence here. It's not part of the generated parser infrastructure and I would like to change this soon, because I'm not happy with how this currently works. Having this documentation here would lead to me having more work in the future.