diff --git a/goals/G-096-zig-buildsuite-piperepl.md b/goals/G-096-zig-buildsuite-piperepl.md index b38304f5..ff89b766 100644 --- a/goals/G-096-zig-buildsuite-piperepl.md +++ b/goals/G-096-zig-buildsuite-piperepl.md @@ -2,8 +2,8 @@ Status: active Scope: src/buildsuites/ (suite_tcl90 new; samplesuite1 sketch reconciliation; VCS ignore flip), src/buildsuites/_build/ (untracked build/source area via existing _build globs), .gitignore + .fossil-settings/ignore-glob, src/scriptapps/bin/ (getzig scriptset rename), bin/ (regenerated punk-getzig wrapper twins, old names retired, AGENTS.md naming policy), bin/tools/ (untracked zig toolchains, existing), TEMP_REFERENCE/2024zig (read-only recovery origin) -Goal: src/buildsuites gains its first real, VCS-tracked suite (suite_tcl90) that reproducibly builds the Tcl 9.0 windows runtime from clean sources using only project-managed tooling - a punk-getzig wrapper fetching a pinned zig into untracked bin/tools, with all transient checkouts and outputs under untracked src/buildsuites/_build - and the 2024 TCLSH_PIPEREPL tclMain patch is recovered into the suite in tracked form and rebased onto current Tcl 9 sources, so a suite-built runtime can return to a standard interactive repl after a kit-style main.tcl completes with unconsumed piped input buffered rather than eval'd, while stock behaviour is preserved whenever the env gate is unset. -Acceptance: from a clean checkout plus a punk-getzig-fetched pinned zig, the suite builds tclsh90s/tclsh90szip from a fresh core-9-0-branch checkout under src/buildsuites/_build with recipe-generated tclUuid.h and tclsh.exe.manifest (no hand-placed files) and the result reports the expected patchlevel; the recovered patch set (piperepl plus the companion tclZipfs/tclCmdIL patches and a notes doc) is tracked in the suite; a piperepl-patched build with TCLSH_PIPEREPL unset behaves identically to the unpatched build on a tty/piped/script-arg matrix, and with TCLSH_PIPEREPL=1 a script setting ::tclsh(dorepl) 1 lands in a live interactive console repl after piped-input eof with the unconsumed input available in ::tclsh(inputbuffer); src/buildsuites is tracked in both VCSes with only _build content ignored and the samplesuite1 vendorbuild-prefix residue reconciled (vendorbuild concept retired); bin/AGENTS.md documents the utility naming policy (punk- prefix for punkshell-own utilities; exceptions: external-tool wrappers such as dtplite/sdx/kettle, and getpunk as the intended future cross-platform entry point) and the getzig scriptset and its bin wrapper twins (getzig.cmd/getzig.ps1) are renamed to punk-getzig with the old names removed and references updated. +Goal: src/buildsuites gains its first real, VCS-tracked suite (suite_tcl90) that reproducibly builds the Tcl 9.0 windows runtime from clean sources using only project-managed tooling - a punk-getzig wrapper fetching a pinned zig into untracked bin/tools, with all transient checkouts and outputs under untracked src/buildsuites/_build - and the 2024 TCLSH_PIPEREPL tclMain patch is recovered into the suite in tracked form and rebased onto current Tcl 9 sources, so a suite-built runtime can return to a standard interactive repl after a kit-style main.tcl completes with unconsumed piped input buffered rather than eval'd. The gate default follows the G-103 kit-family policy (2026-07-20): the patched binary is ENABLED unless TCLSH_PIPEREPL=0 (opt-out; the unpatched plain build serves those wanting stock semantics) - and even enabled it is inert for scripts that never set ::tclsh(dorepl): script-arg runs default dorepl to 0 and unconsumed piped input is buffered, not eval'd, so plain scripts behave stock. +Acceptance: from a clean checkout plus a punk-getzig-fetched pinned zig, the suite builds tclsh90s/tclsh90szip from a fresh core-9-0-branch checkout under src/buildsuites/_build with recipe-generated tclUuid.h and tclsh.exe.manifest (no hand-placed files) and the result reports the expected patchlevel; the recovered patch set (piperepl plus the companion tclZipfs/tclCmdIL patches and a notes doc) is tracked in the suite; the rebased piperepl build gates per the G-103 punk-kit policy: with TCLSH_PIPEREPL=0 it behaves identically to the unpatched build on a tty/piped/script-arg matrix, with the gate open (unset or any other value - the default) scripts that never touch ::tclsh(dorepl) still behave stock on the same matrix (script-arg runs default dorepl to 0; unconsumed piped input is buffered in ::tclsh(inputbuffer), not eval'd), and a script setting ::tclsh(dorepl) 1 lands in a live interactive console repl after piped-input eof with the unconsumed input available in ::tclsh(inputbuffer); src/buildsuites is tracked in both VCSes with only _build content ignored and the samplesuite1 vendorbuild-prefix residue reconciled (vendorbuild concept retired); bin/AGENTS.md documents the utility naming policy (punk- prefix for punkshell-own utilities; exceptions: external-tool wrappers such as dtplite/sdx/kettle, and getpunk as the intended future cross-platform entry point) and the getzig scriptset and its bin wrapper twins (getzig.cmd/getzig.ps1) are renamed to punk-getzig with the old names removed and references updated. ## Context @@ -295,14 +295,49 @@ Remaining for acceptance: - punk-getzig per-version fetch argument, so the wrapper itself can materialize arbitrary pinned versions into versioned dirs (payload growth needs the 512-byte spacer check on re-wrap). -- Rebase the piperepl patch onto current core-9-0-branch sources; build a - piperepl-enabled variant; behaviour matrix (TCLSH_PIPEREPL unset == stock on - tty/piped/script-arg forms; =1 with a script setting ::tclsh(dorepl) 1 -> live - interactive console repl after piped eof with unconsumed input in - ::tclsh(inputbuffer)). - KIT-DEFAULT POLICY UPDATE (2026-07-20, G-103): with a kit FAMILY the env gating - flips for punk kits - the patch is applied and ENABLED BY DEFAULT there (env - opt-OUT, e.g TCLSH_PIPEREPL=0), while the PLAIN family member carries no patch - for stock semantics. The matrix above still characterizes the patch itself on - the patched binary; restate it in opt-out terms when the variant lands. +- DONE 2026-07-20 (see the Progress increment): piperepl rebase + variant builds + + behaviour matrix. (Acceptance/Goal wording was first aligned to the G-103 + kit-default policy at user request - the pre-G-103 text described the 2024 + opt-in gating.) Residual user-level check: a true interactive-typing session + on tclsh90spr (tty launch behaves as the normal repl; TCLSH_PIPEREPL=0 + likewise) - the automated matrix covers the piped/script-arg forms and the + console-reopen landing, not keystroke-driven use. - Final clean-checkout re-verification of the whole flow once the above land. + +### 2026-07-20 increment: piperepl rebased, gate flipped to G-103 policy, variants built + matrix PASS + +- Rebase was clean: the recovered 2024 patch applies to current core-9-0-branch + (9.0.5) tclMain.c with all 8 hunks at offset 4. Tracked as + patches/tclMain_piperepl_905.c (stock + patch + gate flip + provenance banner); + the 2024 era files are kept unmodified as recovered artifacts. +- Gate default flipped per the G-103 punk-kit policy: hasPipeRepl is now + (s == NULL) || strcmp(s, "0") - ENABLED unless TCLSH_PIPEREPL=0 (2024 original + was opt-in set-and-not-"0"). Goal/Acceptance wording aligned first, at user + request, before the rebase landed. +- Products: build905.zig gains tclsh90spr (same per-source construction as + tclsh90s with both tclMain compiles - unicode + TCL_ASCII_MAIN ansi - swapped + to the patched file) and tclsh90sprzip (zipfs_mkimg wrap of the pr exe with + the tcl library attached, -infile form). Stock products remain byte-for-byte + unpatched. patches/ added to the suite.tcl staging list. Full suite build PASS + (9.0.5 checkout 1a9c3b9d96, zig 0.16.0) with the new products installed. +- Behaviour matrix (tclsh90s vs tclsh90spr; env unset / =0 / =1): + - script-arg, script-consuming-stdin, and piped-commands forms: OUTPUT+EXITCODE + IDENTICAL across all 9 combinations - including the key open-gate rows (a + plain script never touching ::tclsh(dorepl) behaves stock; piped payload a + script left unread is NOT eval'd - 'puts SHOULD-NOT-EVAL' payload never + executed). + - Feature row (script sets ::tclsh(dorepl) 1, piped payload unread, gate + open): payload lines buffered VERBATIM into ::tclsh(inputbuffer) (proven via + a write trace firing when the C code publishes the buffer: BUFFER), '? reopen stdin from console' + emitted, CONIN$ reopen succeeded and the repl landed (in an uninstrumented + run the process sits live at the prompt reading the console - observed as + the harness timeout; the trace variant exits deterministically pre-prompt). + - Same feature row with TCLSH_PIPEREPL=0: identical to stock (script-ran, + rc 0, no ::tclsh machinery). + - tclsh90sprzip: identical feature behaviour to tclsh90spr - the attached-lib + kit-style form works, i.e. the G-103 punk-kit precursor. +- Known divergence documented in patches/README.md: with the gate open, + tcl_interactive is LINKED before the startup script runs (stock links at repl + entry), so scripts that WRITE ::tcl_interactive can influence prompting where + stock ignores the write until repl entry; readers see identical values. diff --git a/src/buildsuites/suite_tcl90/build905.zig b/src/buildsuites/suite_tcl90/build905.zig index d7d1ddad..383aebb7 100644 --- a/src/buildsuites/suite_tcl90/build905.zig +++ b/src/buildsuites/suite_tcl90/build905.zig @@ -1798,6 +1798,119 @@ pub fn build(b: *std.Build) !void { std.debug.print("prefix {s}\n", .{b.install_path}); b.getInstallStep().dependOn(&install_tclsh.step); + //--------------------------------------------------------------------- + //tclsh90spr: the TCLSH_PIPEREPL-patched shell (G-096). Identical to + //tclsh90s except tclMain.c is replaced by patches/tclMain_piperepl_905.c + //(rebased 2024 patch; gate default flipped per the G-103 punk-kit policy: + //enabled unless TCLSH_PIPEREPL=0 - see patches/README.md). Kept as a + //SEPARATE product so the stock shells stay byte-for-byte unpatched. + const tclsh_pr_exe = b.addExecutable(.{ + .name = tclsh_static ++ "pr", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + tclsh_pr_exe.mingw_unicode_entry_point = true; + tclsh_pr_exe.step.dependOn(&finalstublib.step); + tclsh_pr_exe.step.dependOn(&tcl_objs_static.step); + tclsh_pr_exe.root_module.addIncludePath(b.path(tcl_source_folder ++ "/generic")); + if (target.result.os.tag == .windows) { + tclsh_pr_exe.root_module.addIncludePath(b.path(tcl_source_folder ++ "/win")); + tclsh_pr_exe.root_module.addIncludePath(b.path(tcl_source_folder ++ "/compat/zlib")); + tclsh_pr_exe.root_module.addIncludePath(b.path(tcl_source_folder ++ "/compat/zlib/contrib/minizip")); + } else { + tclsh_pr_exe.root_module.addIncludePath(b.path(tcl_source_folder ++ "/unix")); + tclsh_pr_exe.root_module.addIncludePath(b.path(zlib_sourcedir)); + tclsh_pr_exe.root_module.addIncludePath(b.path(zlib_sourcedir ++ "/contrib/minizip")); + } + tclsh_pr_exe.root_module.addIncludePath(b.path(tommath_sourcedir)); + if (target.result.os.tag == .windows) { + //ansi copy of the patched main (see the stock shell's mainansi note above) + var pr_mainansi_flags = std.array_list.Managed([]const u8).init(b.allocator); + try pr_mainansi_flags.appendSlice(tclobjs_flags.items); + try pr_mainansi_flags.appendSlice(&.{ + "-UUNICODE", + "-U_UNICODE", + "-DTCL_ASCII_MAIN", + }); + tclsh_pr_exe.root_module.addCSourceFile(.{ + .file = b.path("patches/tclMain_piperepl_905.c"), + .flags = pr_mainansi_flags.items, + }); + } + tclsh_pr_exe.root_module.addCSourceFile(.{ + .file = b.path(appinit_path), + .flags = tclsh_flags.items, + }); + for (tcl_objs_static_sources.items) |src| { + if (build_specials.get(std.fs.path.stem(src))) |val| { + current_flagset = val.flags; + } else { + current_flagset = tclobjs_flags; + } + if (std.mem.eql(u8, std.fs.path.stem(src), "tclMain")) { + //the unicode copy: patched file in place of stock, same special flags + tclsh_pr_exe.root_module.addCSourceFile(.{ + .file = b.path("patches/tclMain_piperepl_905.c"), + .flags = current_flagset.items, + }); + } else { + tclsh_pr_exe.root_module.addCSourceFile(.{ + .file = b.path(src), + .flags = current_flagset.items, + }); + } + } + tclsh_pr_exe.root_module.addCSourceFile(.{ + .file = b.path(tcl_source_folder ++ "/generic/tclStubLib.c"), + .flags = stublib_flags.items, + }); + tclsh_pr_exe.root_module.addCSourceFile(.{ + .file = b.path(tcl_source_folder ++ "/generic/tclStubCall.c"), + .flags = stublib_call_flags.items, + }); + tclsh_pr_exe.root_module.addCSourceFile(.{ + .file = b.path(tcl_source_folder ++ "/generic/tclStubLibTbl.c"), + .flags = stublib_tbl_flags.items, + }); + tclsh_pr_exe.root_module.addCSourceFile(.{ + .file = b.path(tcl_source_folder ++ "/generic/tclTomMathStubLib.c"), + .flags = stublib_tom_flags.items, + }); + tclsh_pr_exe.root_module.addCSourceFile(.{ + .file = b.path(tcl_source_folder ++ "/generic/tclOOStubLib.c"), + .flags = stublib_oo_flags.items, + }); + if (target.result.os.tag == .windows) { + tclsh_pr_exe.root_module.addCSourceFile(.{ + .file = b.path(tcl_source_folder ++ "/win/tclWinPanic.c"), + .flags = winpaniclib_flags.items, + }); + tclsh_pr_exe.root_module.linkSystemLibrary("netapi32", .{}); + tclsh_pr_exe.root_module.linkSystemLibrary("kernel32", .{}); + tclsh_pr_exe.root_module.linkSystemLibrary("user32", .{}); + tclsh_pr_exe.root_module.linkSystemLibrary("advapi32", .{}); + tclsh_pr_exe.root_module.linkSystemLibrary("userenv", .{}); + tclsh_pr_exe.root_module.linkSystemLibrary("ws2_32", .{}); + tclsh_pr_exe.root_module.addObjectFile(b.path(tcl_source_folder ++ "/libtommath/win64/libtommath.dll.a")); + } + tclsh_pr_exe.step.dependOn(&zlib_lib_compile.step); + tclsh_pr_exe.root_module.linkLibrary(zlib_lib_compile); + tclsh_pr_exe.step.dependOn(&tclvfs_compile.step); // review (parity with stock shell) + tclsh_pr_exe.root_module.link_libc = true; + tclsh_pr_exe.root_module.addWin32ResourceFile(.{ + .file = b.path(tcl_source_folder ++ "/win/tclsh.rc"), + .flags = &.{ + "/I", + tcl_source_folder ++ "/generic", + }, + }); + const install_tclsh_pr = b.addInstallArtifact(tclsh_pr_exe, .{}); + b.getInstallStep().dependOn(&install_tclsh_pr.step); + //--------------------------------------------------------------------- + //OPTIONAL steps const install_tzdata_step = b.step("install-tzdata", "install tcl timezone data"); @@ -2045,6 +2158,41 @@ pub fn build(b: *std.Build) !void { make_zipfs.dependOn(&install_mkimg.step); // ** *** *** + // ** *** *** mkimg for the piperepl shell -> tclsh90sprzip(.exe) + //Same attached-library wrap as tclsh90szip, but the prefix executable is the + //patched shell, named explicitly via -infile (the stock wrap uses the calling + //interp as its own prefix on windows). + var mkimg_pr_run: *std.Build.Step.Run = undefined; + if (target.result.os.tag == builtin.os.tag) { + mkimg_pr_run = b.addRunArtifact(tclsh_exe); + } else { + //require an existing tclsh90 - with zipfs support on the path for x-builds + mkimg_pr_run = b.addSystemCommand(&.{"tclsh90"}); + } + mkimg_pr_run.addFileArg(b.path("tools/zipfs_mkimg.tcl")); + mkimg_pr_run.addArg("-outfile"); + var out_mkimg_pr: std.Build.LazyPath = undefined; + if (target.result.os.tag == .windows) { + out_mkimg_pr = mkimg_pr_run.addOutputFileArg("tclsh90sprzip.exe"); + } else { + out_mkimg_pr = mkimg_pr_run.addOutputFileArg("tclsh90sprzip"); + } + mkimg_pr_run.addArg("-indir"); + mkimg_pr_run.addDirectoryArg(tcl_library_path.dirname()); + mkimg_pr_run.addArg("-strip"); + mkimg_pr_run.addDirectoryArg(tcl_library_path.dirname()); + mkimg_pr_run.addArg("-infile"); + mkimg_pr_run.addArtifactArg(tclsh_pr_exe); + var install_mkimg_pr: *std.Build.Step.InstallFile = undefined; + if (target.result.os.tag == .windows) { + install_mkimg_pr = b.addInstallFileWithDir(out_mkimg_pr, .prefix, "bin/tclsh90sprzip.exe"); + } else { + install_mkimg_pr = b.addInstallFileWithDir(out_mkimg_pr, .prefix, "bin/tclsh90sprzip"); + } + install_mkimg_pr.step.dependOn(&mkimg_pr_run.step); + make_zipfs.dependOn(&install_mkimg_pr.step); + // ** *** *** + //**************************** const thread_lib_compile = try build_tclthread(tcl_source_folder, "../tclthread", b, target, optimize, finalstublib); diff --git a/src/buildsuites/suite_tcl90/patches/README.md b/src/buildsuites/suite_tcl90/patches/README.md index dcef94e3..7d9400b7 100644 --- a/src/buildsuites/suite_tcl90/patches/README.md +++ b/src/buildsuites/suite_tcl90/patches/README.md @@ -5,8 +5,10 @@ Origin: `c:/buildtcl/2024zig/build_tcl90/patch/` (reference snapshot (Aug 2024; tclZipfs/tclCmdIL Nov 2024). Recovered into tracked form 2026-07-20 - these files previously existed only in those two VCS-ignored on-disk copies. -NONE of these are applied by the suite's default build: `suite.tcl build` compiles -stock upstream sources. Rebasing/applying is per-patch opt-in work. +The tclZipfs/tclCmdIL patches are NOT applied by any build. The piperepl capability +IS built by default since 2026-07-20 - as SEPARATE products (`tclsh90spr`, +`tclsh90sprzip`) compiled from the rebased `tclMain_piperepl_905.c`; the stock +`tclsh90s`/`tclsh90szip` products continue to compile upstream sources unpatched. ## tclMain.c.piperepl.patch / tclMain_piperepl.c (the TCLSH_PIPEREPL capability) @@ -29,9 +31,21 @@ enabled: windows, `/dev/tty` elsewhere) and installed as the std channel, yielding a live standard repl after a pipeline ends. -Status: NOT yet rebased onto current Tcl 9 sources. Note the punkshell-level analogue -for the punk repl is G-038 (piped-to-interactive session continuity) - this patch is -the plain-tclsh/kit-runtime counterpart. +Status: REBASED 2026-07-20 as `tclMain_piperepl_905.c` (stock 9.0.5 tclMain.c + this +patch, which applied cleanly at offset 4, + the gate-default flip). GATE SEMANTICS +CHANGED from the 2024 original per the G-103 punk-kit policy: the patched binary is +ENABLED unless `TCLSH_PIPEREPL=0` (2024 was opt-in via set-and-not-"0"). Even +enabled it is inert for scripts that never set `::tclsh(dorepl)` - script-arg runs +default dorepl to 0, and unconsumed piped input is buffered rather than eval'd. The +stock-behaviour escape hatches are `TCLSH_PIPEREPL=0` and the unpatched plain +products. Era files (`tclMain_piperepl.c`, the `.patch`) are kept unmodified as the +recovered 2024 artifacts. Known divergence when the gate is OPEN: `tcl_interactive` +is linked to the internal tty flag BEFORE the startup script runs (stock links it +only when entering the repl loop), so a script that WRITES ::tcl_interactive +influences prompting behaviour in the patched shell where stock would ignore the +write until repl entry - scripts that merely read it see identical values. Note the +punkshell-level analogue for the punk repl is G-038 (piped-to-interactive session +continuity) - this patch is the plain-tclsh/kit-runtime counterpart. ## tclZipfs.patch, tclCmdIL.patch diff --git a/src/buildsuites/suite_tcl90/patches/tclMain_piperepl_905.c b/src/buildsuites/suite_tcl90/patches/tclMain_piperepl_905.c new file mode 100644 index 00000000..9d4ee3b7 --- /dev/null +++ b/src/buildsuites/suite_tcl90/patches/tclMain_piperepl_905.c @@ -0,0 +1,1112 @@ +/* + * tclMain_piperepl_905.c -- + * + * PUNKSHELL PATCHED COPY (G-096 TCLSH_PIPEREPL capability). Generated + * 2026-07-20 from the stock core-9-0-branch (9.0.5) generic/tclMain.c by + * applying patches/tclMain.c.piperepl.patch (recovered 2024 patch, applied + * cleanly at offset 4) and flipping the env gate default to the G-103 + * punk-kit policy: enabled unless TCLSH_PIPEREPL=0. See patches/README.md. + * Compiled ONLY into the tclsh90spr / tclsh90sprzip build products - the + * stock tclsh90s / tclsh90szip continue to compile upstream tclMain.c. + * + * (original header follows) + * + * tclMain.c -- + * + * Main program for Tcl shells and other Tcl-based applications. + * This file contains a generic main program for Tcl shells and other + * Tcl-based applications. It can be used as-is for many applications, + * just by supplying a different appInitProc function for each specific + * application. Or, it can be used as a template for creating new main + * programs for Tcl applications. + * + * Copyright © 1988-1994 The Regents of the University of California. + * Copyright © 1994-1997 Sun Microsystems, Inc. + * Copyright © 2000 Ajuba Solutions. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +/* + * On Windows, this file needs to be compiled twice, once with UNICODE and + * _UNICODE defined. This way both Tcl_Main and Tcl_MainExW can be + * implemented, sharing the same source code. + */ + +#include "tclInt.h" + +/* + * The default prompt used when the user has not overridden it. + */ + +static const char DEFAULT_PRIMARY_PROMPT[] = "% "; +static const char ENCODING_ERROR[] = "\n\t(encoding error in stderr)"; + +/* + * This file can be compiled on Windows in UNICODE mode, as well as on all + * other platforms using the native encoding. This is done by using the normal + * Windows functions like _tcscmp, but on platforms which don't have + * we have to translate that to strcmp here. + */ + +#ifdef _WIN32 +# if defined (__clang__) && (__clang_major__ > 20) +# pragma clang diagnostic ignored "-Wc++-keyword" +# endif +#else +# define TCHAR char +# define TEXT(arg) arg +# define _tcscmp strcmp +#endif + +static inline Tcl_Obj * +NewNativeObj( + TCHAR *string) +{ + Tcl_DString ds; + +#ifdef UNICODE + Tcl_DStringInit(&ds); + Tcl_WCharToUtfDString(string, -1, &ds); +#else + (void)Tcl_ExternalToUtfDString(NULL, (char *)string, -1, &ds); +#endif + return Tcl_DStringToObj(&ds); +} + +/* + * Declarations for various library functions and variables (don't want to + * include tclPort.h here, because people might copy this file out of the Tcl + * source directory to make their own modified versions). + */ + +/* + * The thread-local variables for this file's functions. + */ + +typedef struct { + Tcl_Obj *path; /* The filename of the script for *_Main() + * routines to [source] as a startup script, + * or NULL for none set, meaning enter + * interactive mode. */ + Tcl_Obj *encoding; /* The encoding of the startup script file. */ + Tcl_MainLoopProc *mainLoopProc; + /* Any installed main loop handler. The main + * extension that installs these is Tk. */ +} ThreadSpecificData; + +/* + * Structure definition for information used to keep the state of an + * interactive command processor that reads lines from standard input and + * writes prompts and results to standard output. + */ + +typedef enum { + PROMPT_NONE, /* Print no prompt */ + PROMPT_START, /* Print prompt for command start */ + PROMPT_CONTINUE /* Print prompt for command continuation */ +} PromptType; + +typedef struct { + Tcl_Channel input; /* The standard input channel from which lines + * are read. */ + int tty; /* Non-zero means standard input is a + * terminal-like device. Zero means it's a + * file. */ + Tcl_Obj *commandPtr; /* Used to assemble lines of input into Tcl + * commands. */ + PromptType prompt; /* Next prompt to print */ + Tcl_Interp *interp; /* Interpreter that evaluates interactive + * commands. */ +} InteractiveState; + +typedef struct { + int istty; /* Non-zero means standard input is a + * terminal-like device + * Unlike InteractiveState.tty this is + * unaffected by whether a script path + * was specified at argv0 */ + int dorepl; /* whether to run the default repl + * linked to ::tclsh(dorepl) + * so script file or stdin script + * can influence */ + int reopened; /* if 1, we are processing from console - not piped input*/ + int evalinput; /* default 0 - whether to evaluate piped input as commands + * This is input the script (if any) didn't consume + */ + Tcl_Obj *inputbuffer; +} PipeRepl; +/* + * Forward declarations for functions defined later in this file. + */ + +MODULE_SCOPE Tcl_MainLoopProc *TclGetMainLoop(void); +static void Prompt(Tcl_Interp *interp, InteractiveState *isPtr); +static void StdinProc(void *clientData, int mask); +static void FreeMainInterp(void *clientData); + +#if !defined(_WIN32) || defined(UNICODE) && !defined(TCL_ASCII_MAIN) +static Tcl_ThreadDataKey dataKey; + +/* + *---------------------------------------------------------------------- + * + * Tcl_SetStartupScript -- + * + * Sets the path and encoding of the startup script to be evaluated by + * Tcl_Main, used to override the command line processing. + * + * Results: + * None. + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +void +Tcl_SetStartupScript( + Tcl_Obj *path, /* Filesystem path of startup script file */ + const char *encodingName) /* Encoding of the data in that file */ +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + Tcl_Obj *encodingObj = NULL; + + if (encodingName != NULL) { + encodingObj = Tcl_NewStringObj(encodingName, -1); + Tcl_IncrRefCount(encodingObj); + } + + if (path != NULL) { + Tcl_IncrRefCount(path); + } + if (tsdPtr->path != NULL) { + Tcl_DecrRefCount(tsdPtr->path); + } + tsdPtr->path = path; + + if (tsdPtr->encoding != NULL) { + Tcl_DecrRefCount(tsdPtr->encoding); + } + tsdPtr->encoding = encodingObj; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_GetStartupScript -- + * + * Gets the path and encoding of the startup script to be evaluated by + * Tcl_Main. + * + * Results: + * The path of the startup script; NULL if none has been set. + * + * Side effects: + * If encodingPtr is not NULL, stores a (const char *) in it pointing to + * the encoding name registered for the startup script. Tcl retains + * ownership of the string, and may free it. Caller should make a copy + * for long-term use. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +Tcl_GetStartupScript( + const char **encodingPtr) /* When not NULL, points to storage for the + * (const char *) that points to the + * registered encoding name for the startup + * script. */ +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (encodingPtr != NULL) { + if (tsdPtr->encoding != NULL) { + *encodingPtr = Tcl_GetString(tsdPtr->encoding); + } else { + *encodingPtr = NULL; + } + } + return tsdPtr->path; +} + +/*---------------------------------------------------------------------- + * + * Tcl_SourceRCFile -- + * + * This function is typically invoked by Tcl_Main of Tk_Main function to + * source an application specific rc file into the interpreter at startup + * time. If the filename cannot be translated (e.g. it referred to a bogus + * user or there was no HOME environment variable). Just do nothing. + * + * Results: + * None. + * + * Side effects: + * Depends on what's in the rc script. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_SourceRCFile( + Tcl_Interp *interp) /* Interpreter to source rc file into. */ +{ + Tcl_DString temp; + const char *fileName; + Tcl_Channel chan; + + fileName = Tcl_GetVar2(interp, "tcl_rcFileName", NULL, TCL_GLOBAL_ONLY); + if (fileName != NULL) { + Tcl_Channel c; + const char *fullName; + + Tcl_DStringInit(&temp); + fullName = Tcl_TranslateFileName(interp, fileName, &temp); + if (fullName != NULL) { + /* + * Test for the existence of the rc file before trying to read it. + */ + + c = Tcl_OpenFileChannel(NULL, fullName, "r", 0); + if (c != NULL) { + Tcl_CloseEx(NULL, c, 0); + if (Tcl_EvalFile(interp, fullName) != TCL_OK) { + chan = Tcl_GetStdChannel(TCL_STDERR); + if (chan) { + if (Tcl_WriteObj(chan, Tcl_GetObjResult(interp)) < 0) { + Tcl_WriteChars(chan, ENCODING_ERROR, -1); + } + Tcl_WriteChars(chan, "\n", 1); + } + } + } + } + Tcl_DStringFree(&temp); + } +} +#endif /* !UNICODE */ + +/*---------------------------------------------------------------------- + * + * Tcl_MainEx -- + * + * Main program for tclsh and most other Tcl-based applications. + * + * Results: + * None. This function never returns (it exits the process when it's + * done). + * + * Side effects: + * This function initializes the Tcl world and then starts interpreting + * commands; almost anything could happen, depending on the script being + * interpreted. + * + *---------------------------------------------------------------------- + */ + +TCL_NORETURN void +Tcl_MainEx( + Tcl_Size argc, /* Number of arguments. */ + TCHAR **argv, /* Array of argument strings. */ + Tcl_AppInitProc *appInitProc, + /* Application-specific initialization + * function to call after most initialization + * but before starting to execute commands. */ + Tcl_Interp *interp) +{ + Tcl_Size i=0; /* argv[i] index */ + Tcl_Obj *path, *resultPtr, *argvPtr, *appName; + const char *encodingName = NULL; + int code, exitCode = 0; + Tcl_MainLoopProc *mainLoopProc; + Tcl_Channel chan; + InteractiveState is; + PipeRepl piperepl; + Tcl_Obj *tclInteractive; //JMN + int ttyAtStart; // Whether we initially had piped data or not + int inputReopen; // flag for reopening stdin + + TclpSetInitialEncodings(); + if (argc > 0) { + --argc; /* consume argv[0] */ + ++i; + } + TclpFindExecutable((const char *)argv[0]); /* nb: this could be NULL + * w/ (eg) an empty argv supplied to execve() */ + + Tcl_InitMemory(interp); + + is.interp = interp; + is.prompt = PROMPT_START; + TclNewObj(is.commandPtr); + + /* + * If the application has not already set a startup script, parse the + * first few command line arguments to determine the script path and + * encoding. + */ + + if (NULL == Tcl_GetStartupScript(NULL)) { + /* + * Check whether first 3 args (argv[1] - argv[3]) look like + * -encoding ENCODING FILENAME + * or like + * FILENAME + */ + + /* mind argc is being adjusted as we proceed */ + if ((argc >= 3) && (0 == _tcscmp(TEXT("-encoding"), argv[1])) + && ('-' != argv[3][0])) { + Tcl_Obj *value = NewNativeObj(argv[2]); + Tcl_SetStartupScript(NewNativeObj(argv[3]), + Tcl_GetString(value)); + Tcl_DecrRefCount(value); + argc -= 3; + i += 3; + } else if ((argc >= 1) && ('-' != argv[1][0])) { + Tcl_SetStartupScript(NewNativeObj(argv[1]), NULL); + argc--; + i++; + } + } + + path = Tcl_GetStartupScript(&encodingName); + if (path != NULL) { + appName = path; + } else if (argv[0]) { + appName = NewNativeObj(argv[0]); + } else { + appName = Tcl_NewStringObj("tclsh", -1); + } + Tcl_SetVar2Ex(interp, "argv0", NULL, appName, TCL_GLOBAL_ONLY); + + Tcl_SetVar2Ex(interp, "argc", NULL, Tcl_NewWideIntObj(argc), TCL_GLOBAL_ONLY); + + argvPtr = Tcl_NewListObj(0, NULL); + while (argc--) { + Tcl_ListObjAppendElement(NULL, argvPtr, NewNativeObj(argv[i++])); + } + Tcl_SetVar2Ex(interp, "argv", NULL, argvPtr, TCL_GLOBAL_ONLY); + + /* + * Set the "tcl_interactive" variable. + */ + + is.tty = isatty(0); + ttyAtStart = is.tty; //JMN + int hasPipeRepl; //flag for whether piperepl and ::tclsh() behaviour enabled + { + const char *s; + Tcl_DString ds; + s = TclGetEnv("TCLSH_PIPEREPL", &ds); + /* G-103 punk-kit gate policy (2026-07-20): ENABLED unless explicitly + * TCLSH_PIPEREPL=0 (the 2024 original was opt-in: set-and-not-"0"). + * The unpatched plain build serves stock semantics; even enabled, this + * is inert for scripts that never set ::tclsh(dorepl) - script-arg runs + * default dorepl to 0 and unconsumed piped input is buffered, not + * eval'd. */ + hasPipeRepl = ((s == NULL) || strcmp(s, "0")); + if (s != NULL) { + Tcl_DStringFree(&ds); + } + } + piperepl.reopened = 0; + piperepl.dorepl = 0; + piperepl.evalinput = 0; + if (hasPipeRepl) { + TclNewObj(piperepl.inputbuffer); + piperepl.istty = ttyAtStart; + piperepl.dorepl = ttyAtStart; + //unlinked - setting from script should have no effect + Tcl_SetVar2Ex(interp, "tclsh", "istty", + Tcl_NewBooleanObj(ttyAtStart), TCL_GLOBAL_ONLY); + //will be linked so script/stdin can influence + // if path (script) supplied, but no stdin + // - will be set 0 to match default 'tclsh