Phase 3: dictionary loading pipeline + binary embedding
- engine_init.cc: loads StenoDictionaryCollection from embedded binary, validates JSC4 magic + timestamp, constructs dict list + full processor pipeline (Repeat → AllUp → JeffModifiers → Engine) - dict_embed.S: embeds compiled dict binary via .incbin directive. Falls back to null placeholder if no dict available - CMakeLists.txt: dict binary discovery (dicts/steno_dict.bin or JAVELIN_DICT_BIN cmake var), assembly embedding - tools/dict_compiler/compile_dict.py: stroke parser + skeleton compiler. Full JSC4 binary format not yet implemented — for now, use Javelin web tool (lim.au) to generate dict binary - dicts/: directory for compiled dictionary binaries Dict compiler is the remaining gap — JSC4 format requires exact XIP pointer layout matching Javelin's C++ struct casting.
This commit is contained in:
parent
d4dc836b52
commit
1751171667
6 changed files with 411 additions and 46 deletions
17
src/dict_embed.S
Normal file
17
src/dict_embed.S
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* Embed compiled steno dictionary binary into firmware.
|
||||
* JAVELIN_DICT_BIN_PATH is set by CMake to the path of the compiled
|
||||
* dictionary file. If not set, a 4-byte null placeholder is used.
|
||||
*/
|
||||
|
||||
.section .rodata.steno_dict,"a",%progbits
|
||||
.global _javelin_dict_start
|
||||
.global _javelin_dict_end
|
||||
.balign 4
|
||||
|
||||
_javelin_dict_start:
|
||||
#ifdef JAVELIN_DICT_BIN_PATH
|
||||
.incbin JAVELIN_DICT_BIN_PATH
|
||||
#else
|
||||
.byte 0x00, 0x00, 0x00, 0x00
|
||||
#endif
|
||||
_javelin_dict_end:
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
#include "zmk_javelin_steno/zmk_platform_shim.h"
|
||||
|
||||
#include "container/list.h"
|
||||
#include "dictionary/dictionary_definition.h"
|
||||
#include "dictionary/dictionary_list.h"
|
||||
#include "dictionary/invalid_dictionary.h"
|
||||
#include "dictionary/user_dictionary.h"
|
||||
#include "engine.h"
|
||||
#include "orthography.h"
|
||||
#include "processor/all_up.h"
|
||||
|
|
@ -14,45 +15,86 @@
|
|||
#include "static_allocate.h"
|
||||
#include "stroke.h"
|
||||
|
||||
// Pipeline: StenoProcessor → StenoRepeat → StenoAllUp → StenoJeffModifiers → StenoEngine
|
||||
//
|
||||
// StenoProcessor converts raw StenoKey press/release → StenoKeyState.
|
||||
// StenoRepeat handles stroke repetition on held keys.
|
||||
// StenoAllUp triggers when all keys released (standard steno behavior).
|
||||
// StenoJeffModifiers handles modifier key combos within steno.
|
||||
// StenoEngine does dictionary lookup → text output via Key::Press/Release.
|
||||
extern "C" {
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
}
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
// Linker symbols for the embedded dictionary binary.
|
||||
// Defined by the linker script or incbin directive in dict_embed.S
|
||||
extern "C" const uint8_t _javelin_dict_start[];
|
||||
extern "C" const uint8_t _javelin_dict_end[];
|
||||
|
||||
static JavelinStaticAllocate<StenoCompiledOrthography> compiledOrthography;
|
||||
static JavelinStaticAllocate<StenoDictionaryList> dictionaryList;
|
||||
static JavelinStaticAllocate<StenoJeffModifiers> jeffModifiers;
|
||||
static JavelinStaticAllocate<StenoRepeat> repeat;
|
||||
static JavelinStaticAllocate<StenoRepeat> stenoRepeat;
|
||||
static JavelinStaticAllocate<StenoAllUp> allUp;
|
||||
static StenoProcessor *processor = nullptr;
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
static bool load_dictionary_collection() {
|
||||
const auto *collection =
|
||||
reinterpret_cast<const StenoDictionaryCollection *>(_javelin_dict_start);
|
||||
|
||||
if (collection->magic != STENO_MAP_DICTIONARY_COLLECTION_MAGIC) {
|
||||
LOG_ERR("Steno dict magic mismatch: 0x%08x (expected 0x%08x)",
|
||||
collection->magic, STENO_MAP_DICTIONARY_COLLECTION_MAGIC);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!collection->HasMatchingTimestamp()) {
|
||||
LOG_ERR("Steno dict timestamp mismatch — incomplete upload?");
|
||||
return false;
|
||||
}
|
||||
|
||||
List<StenoDictionaryListEntry> entries;
|
||||
collection->AddDictionariesToList(entries);
|
||||
|
||||
if (entries.IsEmpty()) {
|
||||
LOG_ERR("Steno dict collection has no dictionaries");
|
||||
return false;
|
||||
}
|
||||
|
||||
new (dictionaryList) StenoDictionaryList(static_cast<List<StenoDictionaryListEntry>&&>(entries));
|
||||
|
||||
const StenoOrthography *ortho = &StenoOrthography::emptyOrthography;
|
||||
// TODO: load orthography from collection if present
|
||||
new (compiledOrthography) StenoCompiledOrthography(*ortho);
|
||||
|
||||
new (StenoEngine::container)
|
||||
StenoEngine(dictionaryList.value, nullptr, compiledOrthography.value);
|
||||
|
||||
LOG_INF("Steno engine initialized with %d dictionaries",
|
||||
collection->dictionaryCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void zmk_javelin_steno_init(void) {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use empty orthography until a dictionary collection is loaded (Phase 3).
|
||||
// The empty orthography has no rules — suffix folding won't work, but
|
||||
// basic dictionary lookups will function once a dict is in flash.
|
||||
new (compiledOrthography)
|
||||
StenoCompiledOrthography(StenoOrthography::emptyOrthography);
|
||||
|
||||
// TODO Phase 3: Load StenoDictionaryCollection from flash partition.
|
||||
// 1. Get pointer to steno_dict_partition start address
|
||||
// 2. Validate magic == 0x3443534a ('JSC4')
|
||||
// 3. Call collection->AddDictionariesToList() to populate dict list
|
||||
// 4. Construct engine with real dictionary
|
||||
//
|
||||
// For now, engine construction is deferred until dictionary is available.
|
||||
// The processor pipeline is NOT built yet — process_key will early-return.
|
||||
|
||||
initialized = true;
|
||||
|
||||
if (!load_dictionary_collection()) {
|
||||
LOG_WRN("Steno engine not started — no valid dictionary found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build processor pipeline: Repeat → AllUp → JeffModifiers → Engine
|
||||
new (jeffModifiers) StenoJeffModifiers(StenoEngine::container.value);
|
||||
new (allUp) StenoAllUp(jeffModifiers.value);
|
||||
new (stenoRepeat) StenoRepeat(allUp.value);
|
||||
|
||||
static StenoProcessor processorInstance(stenoRepeat.value);
|
||||
processor = &processorInstance;
|
||||
|
||||
LOG_INF("Steno processor pipeline ready");
|
||||
}
|
||||
|
||||
void zmk_javelin_steno_process_key(int steno_key_index, bool is_press) {
|
||||
|
|
@ -63,19 +105,3 @@ void zmk_javelin_steno_process_key(int steno_key_index, bool is_press) {
|
|||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
// Called once dictionary is loaded (Phase 3) to complete initialization.
|
||||
void zmk_javelin_steno_init_engine(StenoDictionary &dictionary,
|
||||
const StenoOrthography &orthography) {
|
||||
new (compiledOrthography) StenoCompiledOrthography(orthography);
|
||||
|
||||
new (StenoEngine::container)
|
||||
StenoEngine(dictionary, nullptr, compiledOrthography.value);
|
||||
|
||||
new (jeffModifiers) StenoJeffModifiers(StenoEngine::container.value);
|
||||
new (allUp) StenoAllUp(jeffModifiers.value);
|
||||
new (repeat) StenoRepeat(allUp.value);
|
||||
|
||||
static StenoProcessor processorInstance(repeat.value);
|
||||
processor = &processorInstance;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue