Skip to content

Implement multi-language support - #130

Open
msujew wants to merge 7 commits into
mainfrom
msujew/multi-language-support
Open

msujew wants to merge 7 commits into
mainfrom
msujew/multi-language-support

Conversation

@msujew

@msujew msujew commented Jul 23, 2026

Copy link
Copy Markdown
Member

Closes #96

Does as the title says, similar to how it is outlined in the issue:

  • Adds a fastbelt/cmd package adopters can use to write their own CLIs to generate Fastbelt code.
  • Adds a LanguageSelector service that is used to determine during runtime what language a URI/document belongs to.
  • Implements a switch-case based selector in the parser and lexer to circumvent the need for a separate service instance per language.
  • Adds a multilang example that shows that this is working as expected (including parser, lexer and completion tests).

FYI, while this has a Plugins field in the API, plugins aren't actually supported in this PR. This will need a follow-up.

@cdietrich

Copy link
Copy Markdown

wondering about the common lexical scope and an option to have a lexer per entry rule
#131

@msujew
msujew force-pushed the msujew/multi-language-support branch from 78988e1 to 9f6d652 Compare July 27, 2026 14:14

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.

Benchmark suite Current: 40d792c Previous: d803417 Ratio
BenchmarkWorkspaceCycle (typefox.dev/fastbelt/examples/statemachine) - MB/s 13.16 MB/s 5.21 MB/s 2.53

This comment was automatically generated by workflow using github-action-benchmark.

@msujew

msujew commented Jul 27, 2026

Copy link
Copy Markdown
Member Author

@cdietrich Thanks for the input. I plan to finish implementing this once #127 lands, since it refactors a lot of the lexer infrastructure.

@msujew
msujew marked this pull request as ready for review August 5, 2026 14:00

@ssmifi ssmifi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks solid, thanks. I have a couple of questions to get a better understanding before merging and some documentation seems to be stale.

Comment thread cmd/fastbelt/generate.go
@@ -55,7 +55,7 @@ func runGenerateCLI(opts generateOptions) error {
}

sc := grammar.CreateServices()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice separation.
Question: The following lines are conceptionally doing for one file what parseAndMerge and reportDiagnostic do generically now. I was wondering if we can de-duplicate some code here and use come common helper?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Comment thread cmd/fastbelt/generate.go
Comment thread cmd/build.go
Comment thread cmd/build.go
Comment thread cmd/build.go
Comment thread workspace/builder.go
Comment thread workspace/initializer.go
Comment thread document.go
Comment thread language_selector.go Outdated

type DocumentMatcher func(uri URI) bool

// DocumentFilter matches a document by LSP language id and/or a glob over the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DocumentSelector?

Comment thread language_selector.go
"typefox.dev/fastbelt/util/service"
)

type DocumentMatcher func(uri URI) bool

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is an exported symbol, it should probably get a doc, even if self-explanatory.

Comment thread examples/arithmetics/services.go
Comment thread cmd/build.go
// documents.
type Language struct {
Entry string
LanguageID string

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a lexer mode, after #127 is merged.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or combine it within the syntax of the entry rule:

entry XRule on mode YMode: ...

Omitting on mode YMode falls back to the default mode.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @Lotes.

However, this idea mixes concepts that don't belong together:

entry XRule on mode YMode: ...

I don't support this proposal.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I would keep the entry lexer mode local to the Language struct value.

@@ -0,0 +1,15 @@
grammar MultilangModel

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this PR is also about multi file support.
But yes, this might be a smaller step towards this direction.

Comment thread document.go
DocStateLocalSymbols // 0x0010
// DocStateLinked marks that cross-references were linked.
DocStateLinked // 0x0010
DocStateLinked // 0x0020

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the comment "//0x0001" ...? Who needs it?
The only reason in my eyes: to not forget the flag nature of this constant group.

Alternative: Rename the constants to reflect the flag nature.

  • DocStateFlagLinked
  • DocStateBitMaskLinked

@sailingKieler sailingKieler left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @msujew for this effort! Great step forward.

Actually, I doubt the necessity and benefit for the additional language build customization for the multi language aspect. Other concerns might justify that.
I left some comments inline.

The other aspect is the documentation of impact of multiple entry rules on the lexer.
That is something I didn't expect in the first place, and others (newbies) probably also don't. See also below.

Comment thread cmd/build.go
Comment thread language_selector.go

// LanguageSelector resolves a document URI to the index of the owning language
// (into the configured languages), or -1 if none match.
type LanguageSelector interface {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the first impression this felt a bit over engineered to me.
After thinking about it for a moment or tow, I came to the conclusion that this mainly bridges the lack of multi-bindings in our dependency container, here, multiple DocumentSelectors, correct?

I would appreciate a corresponding hint on that here in the docs.

}
if err := ctx.Build(); err != nil {
log.Fatalf("multilang generation failed: %v", err)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this route really needed for the multi lange setup?

Why not having it configured manually in the scaffold provided services.go, as you did in this language impl? Coding agents will be able to do that shortly.

Hence, I'm favor of skipping the generation in service_generator.go.

Comment thread cmd/fastbelt/generate.go
@@ -55,7 +55,7 @@ func runGenerateCLI(opts generateOptions) error {
}

sc := grammar.CreateServices()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

n.AppendLine(")")
})
n.AppendLine("}")
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the need/benefit of this, see also my remark in languages/multilang/gen/main.go.

Having it codified in services.go based on an initial template should be fine IMHO.
The single rule validation in cmd/fastbelt/generator.go could be suppressed by a cmd line switch that is to be added in the // go:generate ... config.

Comment thread cmd/build.go
// documents.
type Language struct {
Entry string
LanguageID string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @Lotes.

However, this idea mixes concepts that don't belong together:

entry XRule on mode YMode: ...

I don't support this proposal.

}
}
})
nn.AppendLine("},")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uuuhhh this is sophisticated.

This should be documented very prominently, specifically the fact parser (entry) rule names/ids are used in the lexer setup.

Comment thread lexer/doc.go
// Grammars with multiple configured languages use [NewMultiLanguageLexer]
// instead, passing one token type list per language; at lex time the document
// is routed to its language's token set via [core.LanguageSelector], mirroring
// the generated parser's entry rule dispatch.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I lexer_gen.go I asked for prominent documentation of this fact.
Okay, it is already stated here but I didn't get the consequences of this fairly compact statement.

I strongly advocate to extend it, explicit state that reachable token sets from each entry rules are determined.
That this way ambiguities among the entire token definitions set are tolerated as long the sets reachable from the entry rules a unambiguous (enough).
And that this is also beneficial performance wise (less dispatches)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's adhere the common pattern of naming this file doc.go.

//go:generate go run ./gen

can go to services.go, like in the other languages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Improvement] Support multi-language setups

5 participants