A code formatter for Julia with rules set in stone.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

220 lines
6.8 KiB

# SPDX-License-Identifier: MIT
# 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[]
bufsize = 1024
buf = Vector{UInt8}(undef, bufsize)
while true
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]))
end
return bytes
end
# 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)
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)
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
return "SystemError: " * err.prefix * ": " * Libc.strerror(err.errnum)
elseif err isa AssertionError
# sprint uses dynamic dispatch
io = IOBuffer()
showerror(io, err)
return String(take!(io))
else
return string(typeof(err))
end
end
# juliac-compatible `Base.tempdir` and `Base.mktempdir` without logging and deferred cleanup
function tempdir_juliac()
buf = Base.StringVector(Base.Filesystem.AVG_PATH - 1)
sz = Base.RefValue{Csize_t}(length(buf) + 1)
while true
rc = ccall(:uv_os_tmpdir, Cint, (Ptr{UInt8}, Ptr{Csize_t}), buf, sz)
if rc == 0
resize!(buf, sz[])
break
elseif rc == Base.UV_ENOBUFS
resize!(buf, sz[] - 1)
else
Base.uv_error("tempdir()", rc)
end
end
tempdir = String(buf)
return tempdir
end
function mktempdir_juliac()
parent = tempdir_juliac()
prefix = Base.Filesystem.temp_prefix
if isempty(parent) || occursin(Base.Filesystem.path_separator_re, parent[end:end])
tpath = "$(parent)$(prefix)XXXXXX"
else
tpath = "$(parent)$(Base.Filesystem.path_separator)$(prefix)XXXXXX"
end
req = Libc.malloc(Base._sizeof_uv_fs)
try
ret = ccall(
:uv_fs_mkdtemp, Cint,
(Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}),
C_NULL, req, tpath, C_NULL
)
if ret < 0
Base.Filesystem.uv_fs_req_cleanup(req)
Base.uv_error("mktempdir($(repr(parent)))", ret)
end
path = unsafe_string(ccall(:jl_uv_fs_t_path, Cstring, (Ptr{Cvoid},), req))
Base.Filesystem.uv_fs_req_cleanup(req)
return path
finally
Libc.free(req)
end
end
function mktempdir_juliac(f::F) where {F}
tmpdir = mktempdir_juliac()
try
f(tmpdir)
finally
try
rm(tmpdir; force = true, recursive = true)
catch
end
end
return
end
# juliac-compatible `run(::Base.CmdRedirect)` where both stdout and stderr are redirected
# and read.
function run_juliac(cmd::Base.CmdRedirect)
# Unpack the redirection layers
@assert cmd.stream_no == 2
@assert cmd.handle::RawIO === stderr
cmd′ = cmd.cmd::Base.CmdRedirect
@assert cmd′.stream_no == 1
@assert cmd′.handle::RawIO === stderr
cmd′′ = cmd′.cmd::Cmd
@assert cmd′′.ignorestatus
argv = cmd′′.exec
dir = cmd′′.dir
# Run the command
pipe_fork_exec(argv, dir)
return
end
function WIFEXITED(status)
return (status[] & 0x7f) == 0
end
function WEXITSTATUS(status)
return (status[] & 0xff00) >> 8
end
function pipe_fork_exec(argv::Vector{String}, dir::String)
local pipe, fork, dup2, chdir, execv, waitpid # Silence of the Langs(erver)
STDOUT_FILENO, STDERR_FILENO = 1, 2
# Set up the pipe
fds = Vector{Cint}(undef, 2)
READ_END, WRITE_END = 1, 2
err = @ccall pipe(fds::Ref{Cint})::Cint
err == -1 && systemerror("pipe")
# Fork
cpid = @ccall fork()::Cint
cpid == -1 && systemerror("fork")
# Handle the child process
if cpid == 0
# Close read end of the pipe
err = @ccall close(fds[READ_END]::Cint)::Cint
err == -1 && systemerror("close")
# Duplicate write end of the pipe to stdout and stderr
err = @ccall dup2(fds[WRITE_END]::Cint, STDOUT_FILENO::Cint)::Cint
err == -1 && systemerror("dup2")
err = @ccall dup2(fds[WRITE_END]::Cint, STDERR_FILENO::Cint)::Cint
err = @ccall close(fds[WRITE_END]::Cint)::Cint # No longer needed
err == -1 && systemerror("close")
# Change directory
err = @ccall chdir(dir::Cstring)::Cint
err == 0 || systemerror("chdir")
# Execute the command
@ccall execv(argv[1]::Cstring, argv::Ref{Cstring})::Cint
systemerror("execv")
end
# Continuing the parent process
# Close write end of the pipe
err = @ccall close(fds[WRITE_END]::Cint)::Cint
err == -1 && systemerror("close")
# Shuffle bytes from the pipe to stderr
bufsize = 1024
buf = Vector{UInt8}(undef, bufsize)
while true
# Read bytes from the pipe
nread = @ccall read(fds[READ_END]::Cint, buf::Ptr{Cvoid}, bufsize::Csize_t)::Cssize_t
nread == -1 && systemerror("read")
nread == 0 && break # eof
# Write bytes to stderr
nwrite = @ccall write(STDERR_FILENO::Cint, buf::Ptr{Cvoid}, nread::Csize_t)::Cssize_t
nread == -1 && systemerror("write")
@assert nwrite == nread
end
# Close the read end of the pipe
err = @ccall close(fds[READ_END]::Cint)::Cint
err == -1 && systemerror("close")
# Wait for, and check exit status of, the child
status = Ref{Cint}()
wpid = @ccall waitpid(cpid::Cint, status::Ref{Cint}, 0::Cint)::Cint
wpid == -1 && systemerror("waitpid")
if !WIFEXITED(status)
error("child process did not exit normally")
end
# crc = WEXITSTATUS(status) # ignore this like `ignorestatus(cmd)`
return
end