* feat: LIDL as the interface IR — --header-to-lidl frontend + --dep backend
Make the generator pivot around LIDL so every binding flows source -> LIDL ->
C++ (and a future Rust module plugs into the same backend via Rust -> LIDL):
- --header-to-lidl <impl.h> --impl-class X --metadata m.json -o out.lidl: the
standalone C++ frontend. Runs parseImplHeader -> lidlSerialize and emits ONLY
the <name>.lidl contract (no Qt glue/dispatch), so a module can publish a
cheap `lidl` artifact without compiling its plugin.
- --dep <name>=<lidl>: the LIDL backend for concrete dependencies. Reuses the
interface-wrapper path with BindMode::Static, emitting the name-baked
modules().<dep> wrapper from the dep's published LIDL. Deduped vs each other
and vs --interface names.
- generateInterfaceWrappers gains a BindMode param (default Bound); --interface
stays Bound, --dep is Static. parseInterfaceFlags generalized to
parseSpecFlags(args, flag) for both --interface and --dep.
The umbrella already emits a `<dep>` member per metadata dependency, so no
umbrella change is needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* address review: strip leading '@' in --header-to-lidl paths; fix doc comment
- --header-to-lidl now strips a leading '@' from the header/metadata/output
path args (matches legacy_main; some build drivers pass @/abs/path).
- Remove the stale "bound wrapper" doc comment above generateInterfaceWrappers
(it now generates Static dep wrappers too via BindMode).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: dependency interfaces — runtime-bound typed wrappers
Turn a declared "interface" (a .lidl file or a pure-C++ header with a
logos_events: block) into a BOUND client wrapper: the target module name
is a constructor argument instead of a baked-in literal, so one interface
can be bound to any satisfying module at runtime.
- generator_lib: new BindMode { Static, Bound }. In Bound mode the ctor
takes (LogosAPI*, const QString& moduleName) and every invokeRemoteMethod*
/ ensureReplica routes through m_moduleName. Default Static leaves existing
name-baked output byte-for-byte unchanged.
- legacy/main.cpp: repeatable --interface <name>=<path>[=<impl_class>] flag,
consumed in --general-only. Parses .lidl via lidlParse and .h via
parseImplHeader, emits the bound <name>_api.{h,cpp}, and adds
bind_<name>(moduleName) factories (QString + std::string) to the
LogosModules umbrella. Also self-resolves local interface_dependencies
from metadata.json for non-nix builds.
- experimental/lidl_gen_client: same BindMode parity for the --lidl path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* address review: dedup and validate --interface specs
A repeated --interface <name>=... would emit duplicate #include and
bind_<name>(...) into logos_sdk.h and fail to compile; empty name/path were
silently accepted. Dedup the flag-derived specs by name and drop malformed
ones with an explanatory message on stderr. (Copilot review, PR #74.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* handle reserved words in names/parameters correctly
* parser: accept reserved words as dependency names too
Addresses review feedback on the depends list: parseMetadata() still
hard-required LidlToken::Ident for each entry, so a dependency named after a
keyword (e.g. `version`) would fail to parse even though lidlSerialize()
emits it unquoted. Use atName() there too, consistent with the
contextual-keyword rule applied to the other name positions. Adds a
KeywordAsDependencyName regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add per-event documentation + getPluginEvents introspection
Mirror the per-method documentation pipeline for events. Events
(declared in a universal module's logos_events: section) now carry a
description parsed from their /// doc comments, and are introspectable
at runtime via a new getPluginEvents framework call.
- lidl_ast: EventDecl gains a description field.
- impl_header_parser: capture the event's doc comment (previously
discarded) and an optional metadata.json events[].description.
- lidl_gen_provider: generated universal provider emits
getEvents() override, mirroring getMethods() (name/signature/
parameters/description; no returnType/isInvokable — events are void).
- logos_provider_object: default-empty virtual getEvents() so the
legacy provider path and QtProviderObject inherit empty.
- module_proxy / qt_provider_object: intercept getPluginEvents next to
the getPluginMethods special-case.
- docs: spec + README event-documentation notes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add unit tests for event documentation + getEvents generation
Address review feedback (#71): cover the event-introspection paths that
previously only had method-side tests.
- impl_header_parser test: assert metadata.json events[].description is
parsed; new documented_events fixture asserts `///` doc-comment
capture on a logos_events: block (multi-line joined with \n,
adjacent-only, plain // ignored).
- lidl_gen_provider test: assert the generated dispatch contains
getEvents() emitting each event's name/signature/parameters and an
escaped description, and that events carry no returnType/isInvokable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fold event introspection into getMethods() to keep the provider ABI stable
The previous approach added a getEvents() virtual to LogosProviderObject,
which inserted a new vtable slot and shifted every later slot — an ABI
break that would misdispatch virtual calls whenever an old and new
host/module were mixed across the in-process plugin boundary.
Instead, report events INSIDE the existing getMethods() call: it now
returns the module's whole interface, with each entry tagged
type "method" or "event" (events omit returnType/isInvokable). The
provider vtable is therefore byte-for-byte unchanged, so old/new hosts
and modules stay binary-compatible — a new host reading an old module
sees no event entries (zero events), and an old host reading a new
module just ignores the "type" field (cosmetic). An entry with no
"type" is treated as a method.
- logos_provider_object.h: remove the getEvents() virtual; document that
getMethods() carries both, and why.
- generator (lidl_gen_provider): emit events as type "event" entries
inside getMethods(); tag methods type "method"; no getEvents() output.
- module_proxy / qt_provider_object: getPluginMethods()/getPluginEvents()
are now type-filtered views of getMethods(), plus a new
getPluginInterface() returning the whole list. (These are name-
dispatched Q_INVOKABLEs, not vtable surface — adding them is safe.)
- tests: generator asserts events fold into getMethods() tagged "event";
ModuleProxy asserts the three filtered views; parser tests unchanged.
- docs: spec/project/docs/README updated, incl. an ABI rationale note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* parse adjacent method comments to populate description
* preserve line breaks in method descriptions
Join doc-comment lines with newlines instead of spaces (markers stripped,
leading/trailing blank lines dropped, interior blanks kept), and escape \n
when emitting the description into the generated getMethods(). Both codegen
paths updated; docs corrected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* don't count braces inside comment lines (impl-header parser)
A brace in a doc/line comment (e.g. `/// returns { ... }`) no longer affects
class-scope tracking, which previously could make the parser think the class
ended early and drop later declarations. Addresses review feedback on #70.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: marshal provider events onto the source thread
ModuleProxy's event listener emitted eventResponse directly on whatever thread
the module fired the event from (its worker/FFI thread). QtRemoteObjects then
serialized and sent the event from that foreign thread, racing the source
socket against a method reply being sent from the source thread, which silently
dropped the reply.
This is why a method that emits an event mid-call never returns to the caller
(e.g. delivery_module start(), which emits connectionStateChanged as the node
connects) while a method that emits nothing (createNode) returns fine.
Marshal the emission onto the ModuleProxy's own thread via a queued invocation
so events and method replies are serialized on the single thread
QtRemoteObjects expects to own the source.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: generalize the threading comment
* fix: use AutoConnection so same-thread emits stay synchronous
QueuedConnection deferred every emission, breaking same-thread callers that
emit-then-assert and crashing when a queued lambda outlived the object.
AutoConnection invokes synchronously when already on the source thread and only
queues cross-thread emissions (the actual fix); passing 'this' as context
cancels a queued call if the object is destroyed first.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
overload with method that don't use QString
overload with method that don't use QString
overload with method that don't use QString
overload with method that don't use QString
* refactor: abstract connection/transport; and clearly separate qt remote obj and qt local into separate implementations
* abstract qt remote registry
* add mock implementation; these serves to further test the abstraction but also useful for testing modules later
* use LogosObject instead of QObject
* abstract provider side
* updates to use new api
* re-add async api back
---------
Co-authored-by: Logos Workspace <logos@workspace.local>
Remove logging of method arguments. It may contain private keys,
DB passwords, auth tokens, and token values. Log only method
names and argument counts for debugging.
- https://github.com/status-im/infra-logos/issues/1
* Add LogosResult
* Add documentation for complex types
* Add get type util function
* Add more shorthand functions
* Add bool support
* Provide more shorthand functions
* Throw exception on bad access
* Fix typo in doc