Module: io-internals Synopsis: *standard-input*, *standard-output*, *standard-error* Author: Gary Palter Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND // From WINBASE.H define constant $STD_INPUT_HANDLE = -10; define constant $STD_OUTPUT_HANDLE = -11; define constant $STD_ERROR_HANDLE = -12; // FormatMessage define constant FORMAT_MESSAGE_FLAGS = #x00001100; define constant FORMAT_MESSAGE_LANGUAGE = #x00000400; // NOTE -- Should probably have one of these per thread, possibly per stream ... define variable actual-count-ptr = primitive-wrap-machine-word (primitive-cast-pointer-as-raw (%call-c-function ("LocalAlloc", c-modifiers: "__stdcall") (flags :: , bytes :: ) => (pointer :: ) (integer-as-raw(0), integer-as-raw(4)) end)); /* define function force-object-black (buffer :: ) => () // To force an object to turn black, read it's wrapper and write it back. let zero = integer-as-raw(0); primitive-element(buffer, zero, zero) := primitive-element(buffer, zero, zero) end function; define function win32-read (handle :: , data :: , offset :: , count :: ) => (nread :: false-or()) // If the OS call fails, it might be because of a GC read/write barrier. // Try the operation again a couple of times after triggering the GC // to blacken the object, just in case. // THIS IS A TEMPORARY HACK! force-object-black(data); win32-read-internal(handle, data, offset, count) end function; */ define function win32-read // -internal (handle :: , data :: , offset :: , count :: ) => (nread :: false-or()) let success? = primitive-raw-as-boolean (%call-c-function ("ReadFile", c-modifiers: "__stdcall") (handle :: , buffer-ptr :: , count :: , actual-count :: , lpOverlapped :: ) => (success? :: ) (primitive-cast-raw-as-pointer(primitive-unwrap-machine-word(handle)), primitive-cast-raw-as-pointer (primitive-machine-word-add (primitive-cast-pointer-as-raw (primitive-repeated-slot-as-raw(data, primitive-repeated-slot-offset(data))), primitive-cast-pointer-as-raw(integer-as-raw(offset)))), integer-as-raw(count), primitive-cast-raw-as-pointer (primitive-unwrap-machine-word(actual-count-ptr)), primitive-cast-raw-as-pointer(integer-as-raw(0))) end); success? & raw-as-integer (primitive-c-unsigned-long-at (primitive-unwrap-machine-word(actual-count-ptr), integer-as-raw(0), integer-as-raw(0))) end function win32-read; // -internal; /* define function win32-write (handle :: , data :: , offset :: , count :: ) => (nwritten :: false-or()) // If the OS call fails, it might be because of a GC read/write barrier. // Try the operation again a couple of times after triggering the GC // to blacken the object, just in case. // THIS IS A TEMPORARY HACK! force-object-black(data); win32-write-internal(handle, data, offset, count); end function; */ define function win32-write // -internal (handle :: , data :: , offset :: , count :: ) => (nwritten :: false-or()) let success? = primitive-raw-as-boolean (%call-c-function ("WriteFile", c-modifiers: "__stdcall") (handle :: , buffer-ptr :: , count :: , actual-count :: , lpOverlapped :: ) => (success? :: ) (primitive-cast-raw-as-pointer(primitive-unwrap-machine-word(handle)), primitive-cast-raw-as-pointer (primitive-machine-word-add (primitive-cast-pointer-as-raw (primitive-repeated-slot-as-raw(data, primitive-repeated-slot-offset(data))), primitive-cast-pointer-as-raw(integer-as-raw(offset)))), integer-as-raw(count), primitive-cast-raw-as-pointer (primitive-unwrap-machine-word(actual-count-ptr)), primitive-cast-raw-as-pointer(integer-as-raw(0))) end); success? & raw-as-integer (primitive-c-unsigned-long-at (primitive-unwrap-machine-word(actual-count-ptr), integer-as-raw(0), integer-as-raw(0))) end function win32-write; // -internal; define function win32-force-output (handle :: ) => (success? :: ) primitive-raw-as-boolean (%call-c-function ("FlushFileBuffers", c-modifiers: "__stdcall") (handle :: ) => (success? :: ) (primitive-cast-raw-as-pointer(primitive-unwrap-machine-word(handle))) end) end function win32-force-output; // NOTE -- Should probably have one of these per thread ... define variable message-buffer-ptr = primitive-wrap-machine-word (primitive-cast-pointer-as-raw (%call-c-function ("LocalAlloc", c-modifiers: "__stdcall") (flags :: , bytes :: ) => (pointer :: ) (integer-as-raw(0), integer-as-raw(4)) end)); define function win32-last-error-message () => (message :: ) let status = primitive-wrap-machine-word (%call-c-function ("GetLastError", c-modifiers: "__stdcall") () => (status :: ) () end); %call-c-function ("FormatMessageA", c-modifiers: "__stdcall") (flags :: , lpSource :: , message-id :: , language-id :: , lpBuffer :: , bytes :: , lpArguments :: ) => (count :: ) (integer-as-raw(FORMAT_MESSAGE_FLAGS), primitive-cast-raw-as-pointer(integer-as-raw(0)), primitive-unwrap-machine-word(status), integer-as-raw(FORMAT_MESSAGE_LANGUAGE), primitive-cast-raw-as-pointer(primitive-unwrap-machine-word(message-buffer-ptr)), integer-as-raw(0), primitive-cast-raw-as-pointer(integer-as-raw(0))) end; let message = primitive-raw-as-string (primitive-c-pointer-at (primitive-unwrap-machine-word(message-buffer-ptr), integer-as-raw(0), integer-as-raw(0))); %call-c-function ("LocalFree", c-modifiers: "__stdcall") (pointer :: ) => (null-pointer :: ) (primitive-c-pointer-at(primitive-unwrap-machine-word(message-buffer-ptr), integer-as-raw(0), integer-as-raw(0))) end; message end function win32-last-error-message; // Should really signal a distinct error class ... define function win32-console-error (operation :: ) let status-message = win32-last-error-message(); error("%s: Can't %s", status-message, operation); #f end function win32-console-error; /// Here's the actual console implementation ... define class () end class ; define class (, , ) inherited slot stream-element-type = ; constant slot handle :: , required-init-keyword: handle:; slot console :: false-or() = #f; slot accessor :: false-or() = #f, init-keyword: accessor:; // inherited from end class ; define method initialize (stream :: , #rest initargs, #key) => () next-method(); stream.accessor := make(); if (stream-direction(stream) = #"input") stream-input-buffer(stream) := make(, size: 128) else stream-output-buffer(stream) := make(, size: 1024); // Force first attempt to get an output buffer to find the console stream-output-buffer(stream).buffer-next := stream-output-buffer(stream).buffer-size end end method initialize; define variable *standard-input* = make(, handle: $STD_INPUT_HANDLE, direction: #"input"); define variable *standard-output* = make(, handle: $STD_OUTPUT_HANDLE, direction: #"output"); define variable *standard-error* = make(, handle: $STD_ERROR_HANDLE, direction: #"output"); define function ensure-console (stream :: ) => () local method call-succeeded? (result :: ) => (success :: ) primitive-machine-word-not-equal? (primitive-unwrap-machine-word(result), integer-as-raw(-1)) & primitive-machine-word-not-equal? (primitive-unwrap-machine-word(result), integer-as-raw(0)) end method; local method get-handle () => (handle :: ) primitive-wrap-machine-word (primitive-cast-pointer-as-raw (%call-c-function ("GetStdHandle", c-modifiers: "__stdcall") (nStdHandle :: ) => (handle :: ) (integer-as-raw(stream.handle)) end)) end method; unless (stream.console) let handle = get-handle(); if (call-succeeded?(handle)) stream.console := handle elseif (primitive-raw-as-boolean (%call-c-function ("AllocConsole", c-modifiers: "__stdcall") () => (success? :: ) () end)) let handle = get-handle(); stream.console := call-succeeded?(handle) & handle else win32-console-error("create a console window") end; unless (stream.console) win32-console-error("locate the console window") end end end function ensure-console; define function flush-stdout () => () let ob = stream-output-buffer(*standard-output*); if (ob & ~(ob.buffer-start = ob.buffer-end)) force-output(*standard-output*) end end function flush-stdout; // Doesn't really close the stream cause you can't really close // console streams for Windows. Just flushes standard output. This // is only expected to be called on application exit. define method close (stream :: , #key abort? = #f) => () unless (abort?) if (stream.stream-direction == #"output") let ob = stream-output-buffer(stream); if (ob & ~(ob.buffer-start = ob.buffer-end)) force-output(stream); end if; end if; end; // DON'T CALL NEXT-METHOD()!! Lower level close methods shouldn't // be called on console streams. end method close; /// Input protocol ... define method do-next-input-buffer (stream :: , #key wait? = #t, bytes = 1) => (buffer :: false-or()) ignore(wait?, bytes); ensure-console(stream); next-method() end method do-next-input-buffer; define method stream-input-available? (stream :: ) => (input-available? :: ) stream-direction(stream) = #"input" & (stream-input-buffer(stream).buffer-next < stream-input-buffer(stream).buffer-end | do-input-available-at-source?(stream)) end method stream-input-available?; define method do-input-available-at-source? (stream :: ) => (input-available? :: ) // Need something here (PeekConsole)? #t end method do-input-available-at-source?; define method read-element (stream :: , #key on-end-of-stream) => (element :: ) flush-stdout(); next-method() end method read-element; define method peek (stream :: , #key on-end-of-stream) => (element :: ) flush-stdout(); next-method() end method peek; define method read (stream :: , n :: , #key on-end-of-stream) => (elements :: ) flush-stdout(); next-method() end method read; define method read-into! (stream :: , n :: , sequence :: , #key start = 0, on-end-of-stream) => (count-or-eof :: ) flush-stdout(); next-method() end method read-into!; define method read-line (stream :: , #key on-end-of-stream) => (string-or-eof :: , newline? :: ) flush-stdout(); next-method() end method read-line; define method accessor-read-into! (accessor :: , stream :: , offset :: , count :: , #key buffer) => (nread :: ) ignore(accessor); let bufv = as(, buffer | stream-input-buffer(stream)); // N.B. No checking for sufficient length, e.g. // if (offset + count > buffer.size) error "Argh!!" end; let nread = win32-read(stream.console, bufv, offset, count); unless (nread) win32-console-error("read from console window") end; nread end method accessor-read-into!; /// Output protocol ... define method do-next-output-buffer (stream :: , #key bytes = 1) => (buffer :: false-or()) ignore(bytes); ensure-console(stream); next-method() end method do-next-output-buffer; define method write-element (stream :: , element :: ) => () next-method(); if (element == '\n' | element == '\r') force-output(stream) end end method write-element; define method write-line (stream :: , elements :: , #key start: _start = 0, end: _end = #f) => () next-method(); force-output(stream); end method write-line; define method new-line (stream :: ) => () next-method(); force-output(stream) end method new-line; define method accessor-write-from (accessor :: , stream :: , offset :: , count :: , #key buffer, return-fresh-buffer? = #f) => (number-of-bytes-written :: , new-buffer :: ) ignore(accessor); let buffer = buffer | stream-output-buffer(stream); let bufv = as(, buffer); // N.B. No checking for sufficient length, e.g. // if (offset + count > buffer.size) error "Argh!!" end; let nwritten = win32-write(stream.console, bufv, offset, count); if (~nwritten) win32-console-error("write to console window") elseif (nwritten = count) nwritten else // Should use win32-console-error or variant here ... error("write: didn't write sufficient characters (%d instead of %d)", nwritten, count) end; values(nwritten, buffer) end method accessor-write-from; define method accessor-force-output (accessor :: , stream :: ) => () ignore(accessor); win32-force-output(stream.console); end method accessor-force-output; define method do-force-output-buffers (stream :: ) => () next-method(); let sb :: = stream-output-buffer(stream); sb.buffer-next := 0; sb.buffer-end := 0; values() end method do-force-output-buffers; define method accessor-newline-sequence (accessor :: ) => (newline-sequence :: ); "\r\n" end method accessor-newline-sequence; define method accessor-close (accessor :: , #key abort? = #f, wait? = #t) => (closed? :: ) #f end method;