Skip to content

Commit

Permalink
juliac: use a minimal IO implementation on top of raw file descriptors
Browse files Browse the repository at this point in the history
This seems to behave a bit better compared to `Core.stdout` and
`Core.stderr`.
  • Loading branch information
fredrikekre committed Sep 3, 2024
1 parent 8d1dde8 commit 38a0486
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 60 deletions.
79 changes: 46 additions & 33 deletions src/juliac.jl
Original file line number Diff line number Diff line change
@@ -1,48 +1,61 @@
# SPDX-License-Identifier: MIT

# juliac-compatible replacement for `read(stdin)`
function read_juliac()
# Minimal juliac-compatible IO implementation for stdin/stdout/stderr
struct RawIO <: Base.IO
fd::RawFD
end

function Base.unsafe_write(io::RawIO, buf::Ptr{UInt8}, count::UInt)
n = @ccall write(io.fd::Cint, buf::Ptr{Cvoid}, count::Csize_t)::Cssize_t
return n % Int
end

function Base.write(io::RawIO, byte::UInt8)
n = @ccall write(io.fd::Cint, Ref(byte)::Ptr{Cvoid}, 1::Csize_t)::Cssize_t
return n % Int
end

# TODO: This could potentially hook into `Base.readbytes!` instead to make this more
# generally useful but for the usecase here we just need to read all the bytes until EOF.
function Base.read(io::RawIO)
@assert io === stdin
bytes = UInt8[]
size = 1
nmemb = 1024
buf = zeros(UInt8, nmemb)
file = Libc.FILE(RawFD(0), "r") # FILE constructor calls `fdopen`
local fread, feof, ferror # Silence of the Langs(erver)
bufsize = 1024
buf = Vector{UInt8}(undef, bufsize)
while true
nread = @ccall fread(buf::Ptr{UInt8}, size::Csize_t, nmemb::Cint, file::Ptr{Libc.FILE})::Csize_t
nread = @ccall read(io.fd::Cint, buf::Ptr{Cvoid}, bufsize::Csize_t)::Cssize_t
nread == -1 && systemerror("read")
nread == 0 && break # eof
append!(bytes, @view(buf[1:nread]))
if nread < nmemb
if (@ccall feof(file::Ptr{Libc.FILE})::Cint) != 0
close(file)
break
else
@assert (@ccall ferror(file::Ptr{Libc.FILE})::Cint) != 0
close(file)
error("ferror: fread failed")
end
end
end
return bytes
end

# juliac-compatible `Base.printstyled` that simply forces color
# TODO: detect color support (and maybe support `--color=(yes|no)`?), right now color is
# forced. For juliac we can detect whether stdout/stderr is a tty with
# `(@ccall isatty(RawFD(1)::Cint)::Cint) == 1`.
function printstyled_juliac(io::IO, str::String; bold = false, color::Symbol = :normal)
@assert io === Core.stdout || io === Core.stderr
# juliac-compatible `Base.printstyled`
function printstyled_juliac(io::RawIO, str::String; bold = false, color::Symbol = :normal)
# TODO: Base.printstyled splits on \n and prints each line separately
@assert !occursin('\n', str)
color === :red && write(io, "\e[31m")
color === :green && write(io, "\e[32m")
color === :blue && write(io, "\e[34m")
color === :normal && write(io, "\e[0m")
bold && write(io, "\e[1m")
use_color = isatty(io)
if use_color
color === :red && write(io, "\e[31m")
color === :green && write(io, "\e[32m")
color === :blue && write(io, "\e[34m")
color === :normal && write(io, "\e[0m")
bold && write(io, "\e[1m")
end
print(io, str)
bold && write(io, "\e[22m")
color in (:red, :green, :blue) && write(io, "\e[39m")
if use_color
bold && write(io, "\e[22m")
color in (:red, :green, :blue) && write(io, "\e[39m")
end
return
end

function isatty(io::RawIO)
return (@ccall isatty(io.fd::Cint)::Cint) == 1
end
supports_color(io::RawIO) = isatty(io)

# juliac-compatible `Base.showerror`
function sprint_showerror_juliac(err::Exception)
if err isa SystemError
Expand Down Expand Up @@ -121,10 +134,10 @@ end
function run_juliac(cmd::Base.CmdRedirect)
# Unpack the redirection layers
@assert cmd.stream_no == 2
@assert cmd.handle::Core.CoreSTDERR === Core.stderr
@assert cmd.handle::RawIO === stderr
cmd′ = cmd.cmd::Base.CmdRedirect
@assert cmd′.stream_no == 1
@assert cmd′.handle::Core.CoreSTDERR === Core.stderr
@assert cmd′.handle::RawIO === stderr
cmd′′ = cmd′.cmd::Cmd
@assert cmd′′.ignorestatus
argv = cmd′′.exec
Expand Down
56 changes: 29 additions & 27 deletions src/main.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,26 @@ using Preferences: @load_preference
const juliac = @load_preference("juliac", false)

@static if juliac
stderr() = Core.stderr
stdout() = Core.stdout
include("juliac.jl")
const stdin = RawIO(RawFD(0))
const stdout = RawIO(RawFD(1))
const stderr = RawIO(RawFD(2))
const run_cmd = run_juliac
read_stdin(::Type{String}) = String(read_juliac())
const printstyled = printstyled_juliac
const mktempdir = mktempdir_juliac
const sprint_showerror = sprint_showerror_juliac
else
stderr() = Base.stderr
stdout() = Base.stdout
# const stdin = Base.stdin
# const stdout = Base.stdout
# const stderr = Base.stderr
const run_cmd = Base.run
read_stdin(::Type{String}) = read(stdin, String)
const printstyled = Base.printstyled
const mktempdir = Base.mktempdir
sprint_showerror(err::Exception) = sprint(showerror, err)
end

supports_color(io) = get(io, :color, false)

# juliac-compatible `Base.walkdir` but since we are collecting the files eagerly anyway we
# might as well use the same method even when not compiling with juliac.
function tryf(f::F, arg, default) where {F}
Expand Down Expand Up @@ -67,39 +69,38 @@ function panic(
msg::String, err::Union{Exception, Nothing} = nothing,
bt::Union{Vector{Base.StackFrame}, Nothing} = nothing
)
io = stderr()
printstyled(io, "ERROR: "; color = :red, bold = true)
print(io, msg)
printstyled(stderr, "ERROR: "; color = :red, bold = true)
print(stderr, msg)
if err !== nothing
print(io, sprint_showerror(err))
print(stderr, sprint_showerror(err))
end
@static if juliac
@assert bt === nothing
else
if bt !== nothing
Base.show_backtrace(io, bt)
Base.show_backtrace(stderr, bt)
end
end
println(io)
println(stderr)
global errno = 1
return errno
end

function okln()
printstyled(stderr(), ""; color = :green, bold = true)
println(stderr())
printstyled(stderr, ""; color = :green, bold = true)
println(stderr)
return
end
function errln()
printstyled(stderr(), ""; color = :red, bold = true)
println(stderr())
printstyled(stderr, ""; color = :red, bold = true)
println(stderr)
return
end


# Print a typical cli program help message
function print_help()
io = stdout()
io = stdout
printstyled(io, "NAME", bold = true)
println(io)
println(io, " Runic.main - format Julia source code")
Expand Down Expand Up @@ -278,7 +279,7 @@ function main(argv)
if input_is_stdin
@assert length(inputfiles) == 1
sourcetext = try
read_stdin(String)
read(stdin, String)
catch err
return panic("could not read input from stdin: ", err)
end
Expand All @@ -300,18 +301,18 @@ function main(argv)
@assert outputfile == ""
@assert isfile(inputfile)
@assert !input_is_stdin
output = Output(:file, inputfile, stdout(), true, true)
output = Output(:file, inputfile, stdout, true, true)
elseif check
@assert outputfile == ""
output = Output(:devnull, "", stdout(), false, false)
output = Output(:devnull, "", stdout, false, false)
else
@assert length(inputfiles) == 1
if outputfile == "" || outputfile == "-"
output = Output(:stdout, "", stdout(), false, false)
output = Output(:stdout, "", stdout, false, false)
elseif isfile(outputfile) && !input_is_stdin && samefile(outputfile, inputfile)
return panic("can not use same file for input and output, use `-i` to modify a file in place")
else
output = Output(:file, outputfile, stdout(), true, false)
output = Output(:file, outputfile, stdout, true, false)
end
end

Expand All @@ -326,13 +327,13 @@ function main(argv)
str = "Checking `$(input_pretty)` "
ndots = 80 - textwidth(str) - 1 - 1
dots = ndots > 0 ? "."^ndots : ""
printstyled(stderr(), str * dots * " "; color = :blue)
printstyled(stderr, str * dots * " "; color = :blue)
else
to = output.output_is_samefile ? " " : " -> `$(relpath(output.file))` "
str = "Formatting `$(inputfile)`$(to)"
ndots = 80 - textwidth(str) - 1 - 1
dots = ndots > 0 ? "."^ndots : ""
printstyled(stderr(), str * dots * " "; color = :blue)
printstyled(stderr, str * dots * " "; color = :blue)
end
end

Expand Down Expand Up @@ -411,19 +412,20 @@ function main(argv)
close(io)
end
end
color = supports_color(stderr) ? "always" : "never"
# juliac: Cmd string parsing uses dynamic dispatch
# cmd = ```
# $(git) --no-pager diff --color=always --no-index --no-prefix
# $(git) --no-pager diff --color=$(color) --no-index --no-prefix
# $(relpath(A, dir)) $(relpath(B, dir))
# ```
git_argv = String[
Sys.which("git"), "--no-pager", "diff", "--color=always", "--no-index", "--no-prefix",
Sys.which("git"), "--no-pager", "diff", "--color=$(color)", "--no-index", "--no-prefix",
relpath(A, dir), relpath(B, dir),
]
cmd = Cmd(git_argv)
# `ignorestatus` because --no-index implies --exit-code
cmd = setenv(ignorestatus(cmd); dir = dir)
cmd = pipeline(cmd, stdout = stderr(), stderr = stderr())
cmd = pipeline(cmd, stdout = stderr, stderr = stderr)
run_cmd(cmd)
end
end
Expand Down

0 comments on commit 38a0486

Please sign in to comment.