From bf7f1a33257d5a640278317f4be428ea594eb834 Mon Sep 17 00:00:00 2001 From: Ademar Gonzalez Date: Sat, 22 Aug 2026 14:56:26 -0400 Subject: [PATCH] show renders to a string instead of printing (print (show x)) -- the natural idiom -- failed with "Expected 1 args, found values #inert": show wrote showVal to the console itself and returned inert, so print then rejected show's return value. A printing primitive wearing a renderer's name, and redundant with write besides. show now returns the printed representation as a string and writes nothing. Rendering grants no host authority, so the HostIO gate is gone: show exists under every profile (the CLR fault sweep's four (show value) cases move from capability errors to values). It carries a certified contract, (show any) -> string, pure. The five in-repo call sites that used show as a printer -- zipper, contracts, and vau-dotnet examples, IronKernel.Test's show-line, and the website's trace sample -- become (print (show ...)), preserving their output byte for byte. The operators reference now documents the rendering semantics. --- Examples/contracts.ikr | 2 +- Examples/vau-dotnet.ikr | 2 +- Examples/zipper.ikr | 4 ++-- IronKernel.Runtime/Interop.fs | 13 +++++++------ IronKernel.Runtime/Runtime.fs | 4 +++- IronKernel.Tests/CliTests.fs | 16 ++++++++++++++++ IronKernel.Tests/InteropTests.fs | 23 +++++++++++++++++++++++ lib/IronKernel.Test/src/test.ikr | 2 +- website/docs/guide.html | 2 +- website/docs/operators.html | 2 +- 10 files changed, 56 insertions(+), 14 deletions(-) diff --git a/Examples/contracts.ikr b/Examples/contracts.ikr index 325ec37..a56a890 100644 --- a/Examples/contracts.ikr +++ b/Examples/contracts.ikr @@ -7,4 +7,4 @@ (contract raw operative (any) any pure #t) (printf "double={0}\n" (double 21)) -(show (raw (+ 1 2))) +(print (show (raw (+ 1 2)))) diff --git a/Examples/vau-dotnet.ikr b/Examples/vau-dotnet.ikr index 2b73e9f..b980452 100644 --- a/Examples/vau-dotnet.ikr +++ b/Examples/vau-dotnet.ikr @@ -30,7 +30,7 @@ (vau (exp) env (begin (write " >> ") - (show exp) + (print (show exp)) (writeln "") (eval exp env)))) diff --git a/Examples/zipper.ikr b/Examples/zipper.ikr index 8beb3ea..c1f3c68 100644 --- a/Examples/zipper.ikr +++ b/Examples/zipper.ikr @@ -21,7 +21,7 @@ (define tree1 '(a (b) (c (d 1 2)) e)) (define tree2 '(z (u) (v (w 10 12)) y)) -(depth-first (lambda (node) (begin (show node) (print "\n") #f)) tree1) +(depth-first (lambda (node) (begin (print (show node)) (print "\n") #f)) tree1) (provide! (zipper zipper? z-curr-node z-k) @@ -50,7 +50,7 @@ (if (zipper? x) (begin - (show (z-curr-node x)) + (print (show (z-curr-node x))) (print "\n") (loop ((z-k x) #f))) #f)))) diff --git a/IronKernel.Runtime/Interop.fs b/IronKernel.Runtime/Interop.fs index ff6453e..258cc3c 100644 --- a/IronKernel.Runtime/Interop.fs +++ b/IronKernel.Runtime/Interop.fs @@ -328,11 +328,12 @@ bounceContinue env cont Inert | _ -> signal cont (NumArgs(2,prms)) + /// Render a value's printed representation as a string. Pure -- + /// rendering grants no host authority, so `show` exists under every + /// profile; writing is `print`, `write`, and the ports' job. It used + /// to write to the console itself and return inert, which made the + /// natural `(print (show x))` an arity error on print. let show env cont (prms : LispVal list) = match prms with - | _ when not (has HostIO env) -> - signal cont (CapabilityDenied "host output requires HostIO") - | h :: _ -> - System.Console.Write(showVal h) - bounceContinue env cont Inert - | [] -> signal cont (NumArgs(1, [])) + | [value] -> bounceContinue env cont (Obj(showVal value :> obj)) + | _ -> signal cont (NumArgs(1, prms)) diff --git a/IronKernel.Runtime/Runtime.fs b/IronKernel.Runtime/Runtime.fs index 945d8b9..d3caf53 100644 --- a/IronKernel.Runtime/Runtime.fs +++ b/IronKernel.Runtime/Runtime.fs @@ -2754,6 +2754,8 @@ Some(certifiedVariadicApplicative name [NumberShape; NumberShape] 2 NumberShape) | "<" | "<=" | ">" -> Some(certifiedApplicative name [NumberShape; NumberShape] BooleanShape) + | "show" -> + Some(certifiedApplicative name [AnyShape] StringShape) | _ -> None let value = match contract with @@ -2785,7 +2787,7 @@ (name <> "load" || Set.contains SourceLoading capabilities) && (not (Set.contains name (Set.ofList - [ "print"; "printf"; "show"; "read"; "write" + [ "print"; "printf"; "read"; "write" "close-input-file"; "close-output-file" "get-current-input-port"; "get-current-output-port" "with-input-from-file"; "with-output-to-file" diff --git a/IronKernel.Tests/CliTests.fs b/IronKernel.Tests/CliTests.fs index 7cb1302..2acae74 100644 --- a/IronKernel.Tests/CliTests.fs +++ b/IronKernel.Tests/CliTests.fs @@ -669,3 +669,19 @@ let ``repl completion extends the symbol at the cursor`` () = // Control: a bare tab must not dump the whole environment. let _, none = completionCandidates env "(" 1 Assert.Empty none + +[] +let ``print of show renders and writes -- the print-show regression`` () = + match bootstrapEnv () with + | Choice1Of2 error -> failwith (showError error) + | Choice2Of2 env -> + let original = Console.Out + use writer = new StringWriter() + Console.SetOut writer + try + match runSource env "print-show.ikr" "(print (show (list 1 2)))\n(print (show (list 3 4)))\n" with + | Choice1Of2 error -> failwith (showError error) + | Choice2Of2 _ -> () + finally + Console.SetOut original + Assert.Equal("(1 2)(3 4)", writer.ToString()) diff --git a/IronKernel.Tests/InteropTests.fs b/IronKernel.Tests/InteropTests.fs index 283223a..6bc1879 100644 --- a/IronKernel.Tests/InteropTests.fs +++ b/IronKernel.Tests/InteropTests.fs @@ -228,3 +228,26 @@ let ``CLR boolean results are Kernel booleans`` () = """(. System.Char IsDigit (. "x" get_Chars 0))""", Bool false """(.get System.Environment Is64BitProcess)""", Bool System.Environment.Is64BitProcess ] + +[] +let ``show renders a value to a string`` () = + evalSessionKernel [ + "(show 5)", Obj "5" + "(show (list 1 2))", Obj "(1 2)" + // The idiom that used to fail: show wrote to the console itself and + // returned inert, so print then rejected #inert with an arity error. + "(print (show (list 1 2)))", Inert + ] + +[] +let ``show requires no authority`` () = + IronKernel.RuntimeSourceServices.configure () + IronKernel.Compiler.installBodyCompiler () + let env = makePrimitiveBindingsForProfile Minimal + match evalIn env "(show 42)" with + | Obj value -> Assert.Equal("42", string value) + | other -> failwithf "unexpected show result: %s" (showVal other) + // Control: writing is still authority the minimal profile lacks. + match evalIn env "(print \"x\")" with + | Status message -> Assert.Contains("print", message) + | other -> failwithf "print unexpectedly available: %s" (showVal other) diff --git a/lib/IronKernel.Test/src/test.ikr b/lib/IronKernel.Test/src/test.ikr index c950563..7d320b0 100644 --- a/lib/IronKernel.Test/src/test.ikr +++ b/lib/IronKernel.Test/src/test.ikr @@ -76,7 +76,7 @@ (define show-line (lambda (label value) (printf " {0} " label) - (show value) + (print (show value)) (printf "\n"))) ; --- checks ------------------------------------------------------------- diff --git a/website/docs/guide.html b/website/docs/guide.html index 2725d4f..ee9be5a 100644 --- a/website/docs/guide.html +++ b/website/docs/guide.html @@ -314,7 +314,7 @@

14. Invent your own syntax

(define trace
   (vau (exp) env
     (begin
-      (show exp)
+      (print (show exp))
       (. System.Console WriteLine "")
       (eval exp env))))
 
diff --git a/website/docs/operators.html b/website/docs/operators.html
index d733f0a..c382e91 100644
--- a/website/docs/operators.html
+++ b/website/docs/operators.html
@@ -470,7 +470,7 @@ 

I/O & display

print / printf / show

applicative

(print obj) (printf format arg…) (show value)

-

printf uses .NET String.Format placeholders ({0}, {1}, …). show writes the Kernel printed representation.

+

printf uses .NET String.Format placeholders ({0}, {1}, …). show renders the Kernel printed representation as a string — pure, available under every profile; write it with (print (show value)).

open-*-file / read / write / …

applicative