Lynxer

Documentation

Windows support

Lynxer runs on Windows through the MSYS2 MinGW-w64 and CLANGARM64 builds. This page records the port's state, the supported surface, and what is deliberately excluded because it depends on Linux-only syscalls or platform APIs.

Status

Done (host abstraction). Every POSIX/Win32 primitive the interpreter needs now lives in one place, lynxer/platform.hpp / lynxer/platform.cpp:

PrimitivePOSIXWindows
Running executable path/proc/self/exe (dyld on macOS)GetModuleFileNameW
Dynamic librarydlopen/dlsym/dlcloseLoadLibraryW/GetProcAddress/FreeLibrary
Subprocessfork/execvp/waitpidCreateProcessA in a kill-on-close Job Object
Temporary directorymkdtempunique directory under GetTempPath
Executable bitchmod 0755no-op (extension-based)
Install linksymlinkcopy

The core (config.cpp, bundle.cpp, ast.cpp, builtins.cpp, shell.cpp) calls the layer instead of the POSIX APIs directly, and the native shared library extension is .dll where the host needs it. The Linux behavior is unchanged and the Linux test suite is the regression gate.

The standard streams are put in binary mode on Windows, so \n is not translated to \r\n: Lynxer's output is byte-identical to POSIX, which the fixture comparisons rely on. The console is also switched to ANSI escape processing (platform::enableVirtualTerminal()), so tui and graphics sequences render, and both console code pages are set to UTF-8 so non-ASCII text round-trips. A redirected stream has no console mode and is unaffected.

Named pipes. winpipe (stdlib/winpipe.cpp + stdlib/winpipe.lynx) is a Windows-only blocking named-pipe module (create/connect/open/read/ write/disconnect/peek/close). It complements the low-level winAPI pipe calls and gives server-style programs a local IPC option beside sockets. See stdlib/winpipe.

Registry. winreg (stdlib/winreg.cpp + stdlib/winreg.lynx) is a Windows-only module for reading and writing HKCU/HKLM/HKCR/HKU/HKCC values. It supports string, expand-string, multi-string, DWORD and QWORD values, key/value enumeration, and recursive deletion. The winreg_basic.lynx fixture writes under HKCU and cleans up after itself. See stdlib/winreg.

Windows API surface. On Windows, selecting the Windows target (syscalls("winAPI", "amd64"), with windows and win32 as synonyms) enables the winAPI.* namespace — mostly kernel32, with shell32/ole32 for Known Folders: the system information calls (getProcessId, getCurrentDirectory, getComputerName, getTempPath, getSystemDirectory, getWindowsDirectory, getModuleFileName, getTickCount, getLastError, getDiskFreeBytes), the environment calls (getEnvironmentVariable, setEnvironmentVariable, expandEnvironmentStrings) and file/handle access (createFile, readFile, writeFile, closeHandle, fileSize, seekFile, deleteFile, copyFile, moveFile, createDirectory, removeDirectory), process control (getCurrentThreadId, openProcess, terminateProcess, getExitCodeProcess, createProcess, waitForSingleObject), the console session (getStdHandle, getConsoleMode, setConsoleMode, setConsoleTitle, getConsoleOutputCP, setConsoleOutputCP), file attributes and paths (getFileAttributes, setFileAttributes, getFullPathName, flushFileBuffers), the high-resolution timer (queryPerformanceCounter, queryPerformanceFrequency), Known Folders (getKnownFolderPath), and sleep, beep and outputDebugString. The winapi.lynx fixture runs it in the Windows job; the Win32 calls live in lynxer/winapi.cpp so nothing else includes <windows.h>. See syscalls.

--compile works unchanged: the payload is appended to the running image and read back through platform::executablePath() (GetModuleFileNameW), and the compiled-executable parity loop passes on Windows. Native modules load through the same ABI too: examples/native_signatures.cpp builds and its fixture passes (the Rust libffi engine compiles under both Windows toolchains). The shared LYNXER_MODULE_EXPORT macro in stdlib/lynxer_native_abi.h expands to __declspec(dllexport) on Windows, so the entry point and any op marked with it are exported under MSVC and clang-cl as well as MinGW. Every registered op must be exported: the bundled MinGW/CLANGARM64 module rule passes -Wl,--export-all-symbols, while an MSVC/clang-cl module marks each op with LYNXER_MODULE_EXPORT (or uses a .def file); see native-module-abi.

One consequence of the copy-instead-of-symlink install: on POSIX /proc/self/exe resolves $PREFIX/bin/lynxer to the real binary, so <exeDir>/stdlib is the stdlib. On Windows the launcher is a copy in <prefix>/bin, so stdlibDirectory() falls back to <prefix>/lib/lynxer/stdlib (and lynxer.config is looked up there too).

What is excluded on Windows (for now)

These features need work that is being done slowly and are not part of the Windows build yet:

LYNXER_WINDOWS_SKIP_MODULES in the Makefile names the C++ module a Windows build skips (sys); watch now builds on Windows.

Shared-library embedding

The MinGW-w64 Windows builds now produce lynxer.dll and lynxer.dll.a. The DLL exports only the lynxer_embed_* C ABI. make sdk stages the public headers under build/sdk/include, the import library under build/sdk/lib, and the runtime under build/sdk/bin. Windows lynxer --emit-library app.lynx -o app.dll emits app.dll, app.dll.a, and app.h; the generated DLL links to lynxer.dll.

At run time, place lynxer.dll next to the host executable or add the runtime directory to PATH. The installed SDK runtime lives under <prefix>/lib/lynxer; --emit-library locates it from an installed Lynxer launcher. Emitted libraries are currently supported by the MSYS2 MinGW-w64 GNU ABI used in CI; an MSVC/clang-cl runtime is still unavailable.

Still to do

FixtureWhy it is skipped
stdlib_cliDrives printf through the shell
stdlib_jsNeeds Node.js on PATH; the Windows runner does not install it
stdlib_multiprocessingUses printf commands
stdlib_os, stdlib_pathAssert POSIX separators, /tmp, and POSIX path semantics
stdlib_sysThe sys module is not built on Windows
stdlib_watchReplaced by the Windows-only winwatch fixture
stdlibTestAllRuns the aggregate suite, including POSIX-only modules
native_stdlibsImports sys and checks for sh
program_argsLaunched with POSIX shell quoting in the gate
stdlib_game_api, stdlib_graphics_raster, stdlib_graphics_raster_shapes, stdlib_turtleNeed an OpenGL context; the Windows runner has none

C runtime and minimum Windows version

Lynxer targets Windows 10 or newer on both x64 and ARM64. The AMD64 build uses the MSYS2 MINGW64 toolchain and therefore msvcrt.dll plus the MSYS2 GCC runtime DLLs (libgcc_s_seh-1.dll, libstdc++-6.dll, libwinpthread-1.dll). libffi is built from the vendored crate and linked into the interpreter, so no separate libffi DLL is shipped. The ARM64 build uses CLANGARM64 and the UCRT plus the CLANGARM64 runtime DLLs. The Windows CI workflows run ldd on lynxer.exe and copy the non-system DLLs into lynxer/runtime; release zips must ship those DLLs next to lynxer.exe. packaging/windows/stage-payload.sh copies them into the installer payload, and the installer puts them in <prefix>/lib/lynxer and adds that directory to the user PATH (so both lynxer.exe and emitted DLLs find them). No Visual C++ Redistributable is required.

The Windows ecosystem

Making the interpreter run is only half of "Lynxer on Windows": it also has to install, update and tool the way Windows users expect.

PieceStatePlan
Prebuilt binariesdonelynxer-windows-amd64.zip / lynxer-windows-arm64.zip on every release (CI)
--install / --uninstallpartiallays out <prefix>/lib/lynxer and a bin launcher (a copy), but does not touch PATH or register an uninstaller
Package managerspartialrender-package-manifests.py renders winget, Scoop and Chocolatey manifests; registry submission remains
Installerpartialpackaging/windows/lynxer.iss builds an Inno Setup installer on the --install layout, adds bin and lib/lynxer to the user PATH, and registers an uninstall entry; CI compiles it on the AMD64 job; signing remains
Editor and shelldoneeditors/vscode (syntax highlighting, --lint/--format) plus PowerShell and Clink completion
Background taskspartialpackaging/windows/lynxer-task.ps1 registers a program with Task Scheduler; a SCM service host is not shipped
Code signingtodoAuthenticode signatures so a download does not trip SmartScreen
C runtimedoneWindows 10+; AMD64 MSYS2 MINGW64 (msvcrt.dll + GCC runtime DLLs), ARM64 CLANGARM64 (UCRT + CLANGARM64 runtime DLLs); CI collects them with ldd

Runtime breadth

Several host capabilities still ride the POSIX implementation and need a Win32 backend behind lynxer/platform.*:

Both Windows jobs are required (no continue-on-error): .github/workflows/build-lynxer-windows-amd64.yml (MSYS2 MINGW64) and .github/workflows/build-lynxer-windows-arm64.yml (MSYS2 CLANGARM64).

See the Windows support section of ../todo for the tracked items.