Talk and C, Sitting in a Binary Tree
TalkTalk opens a C-facing compiler API, adds a Swift package on top, gives protocols real arguments, and lets more leading-dot variants find their type from context.
Today's Talk issue is about making the language easier to host and easier to write. The full TalkTalk compiler now has a C front door, Swift gets a package that rides the same bridge, protocols can be parameterized by the conformance you mean, leading-dot variants need less ceremony, and LSP rename covers more of the surface developers actually touch.
talk-c is the new embedding story for the full TalkTalk compiler. It is
not
just a parser shim or a syntax highlighter hook: C callers can drive the same compiler pipeline used by
the
command line, from source text through diagnostics and compiled output.
The boundary stays deliberately plain. C code deals in compiler handles, option structs, source
buffers,
result objects, and explicit destruction, while TalkTalk keeps ownership of the compiler internals. That
makes
the API useful to build systems, editors, test harnesses, and host applications without asking them to
shell
out to talk.
#include <talk-c/talk.h> TalkCompiler *c = talk_compiler_create(); TalkOptions opts = talk_options_default(); opts.module_name = "Demo"; TalkResult res = talk_compile_source( c, "Demo.tlk", source, opts ); if (!res.ok) { talk_diagnostics_write( res.diagnostics, stderr ); } talk_result_destroy(res); talk_compiler_destroy(c);
The new talk-swift Swift package builds on that C layer instead of
inventing
a second compiler interface. Swift tools can import a package, compile Talk source, inspect diagnostics,
and
keep using the same compiler behavior that C embedders and the CLI see.
That matters for editor integrations and app-hosted Talk. The Swift side can feel native while still sharing the boring correctness properties of the C ABI: stable handles at the boundary, explicit result lifetimes under the hood, and one compiler implementation behind every host language.
import TalkSwift let compiler = TalkCompiler() let result = try compiler.compile( source: source, filename: "Demo.tlk" ) for diagnostic in result.diagnostics { print(diagnostic.message) }
Protocols can now take arguments directly, rather than forcing every variation through associated types. The protocol arguments are part of the conformance identity, so one concrete type can conform to the same protocol more than once as long as the arguments are different.
That unlocks ordinary modeling patterns that associated types made awkward. A value can encode itself as JSON and as a row format, a collection can expose more than one index discipline, and a bridge type can satisfy the same protocol for several foreign representations without wrapper types whose only job is to make the conformance table unique.
struct JSON { let text: String } struct Row { let text: String } protocol Encode<Wire> { func encode() -> Wire } struct User { let name: String } extend User: Encode<JSON> { func encode() -> JSON { JSON(text: self.name) } } extend User: Encode<Row> { func encode() -> Row { Row(text: self.name) } }
Associated types say what a conformance does.
Protocol arguments say what a conformance is.- Yoda (Empire Strikes
Back)
Leading-dot enum spelling keeps spreading from explicit call sites into inferred contexts. When the expected type is already known, Talk can now infer the enum behind a bare variant in more places, including return positions, annotated lets, arrays, and nested expressions.
The result is the same value with less repeated type noise. Programmers still get a real type check -
an
ambiguous dot stays ambiguous - but ordinary code can use .home, .user(id), or
.notFound where the surrounding expression has already named Route.
enum Route { case home case user(Int) case notFound } func fallback(id: Int) -> Route { if id > 0 { .user(id) } else { .notFound } } let routes: Array<Route> = [ .home, .user(42), .notFound ] func push(route: Route) -> () { () } push(.home)
The LSP rename work continues past the easy local-variable case. The server now has more syntax-aware paths for declarations and references that show up through protocol arguments, extension conformances, enum variants, and the leading-dot shorthand that hides the enum name at the use site.
That is especially important now that more meaning lives in surface syntax instead of wrapper types and spelling conventions. Rename can update more real programs, and when the server cannot prove a rename is safe, it is expected to refuse the edit rather than guess across generated or ambiguous references.