-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
315 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ authors = ["Morten Piibeleht <[email protected]> and contributors"] | |
version = "0.0.1" | ||
|
||
[deps] | ||
#Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" | ||
IOCapture = "b5f81e59-6552-4d32-b1f0-c071b021bf89" | ||
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,125 @@ | ||
struct CodeBlock | ||
input::Bool | ||
code::String | ||
end | ||
|
||
""" | ||
struct REPLResult | ||
""" | ||
struct REPLResult1 end | ||
struct REPLResult | ||
sandbox::Sandbox | ||
blocks::Vector{CodeBlock} | ||
_code::AbstractString | ||
_source_exprs::Vector{Any} | ||
end | ||
|
||
function join_to_string(result::REPLResult) | ||
out = IOBuffer() | ||
for block in result.blocks | ||
println(out, block.code) | ||
end | ||
return String(take!(out)) | ||
end | ||
|
||
""" | ||
CodeEvaluation.repl!(sandbox::Sandbox, code::AbstractString) -> AbstractValue | ||
CodeEvaluation.replblock!(sandbox::Sandbox, code::AbstractString) -> REPLResult | ||
Evaluates the code in a special REPL-mode, where `code` gets split up into expressions, | ||
each of which gets evaluated one by one. The output is a string representing what this | ||
would look like if each expression had been evaluated in the REPL as separate commands. | ||
""" | ||
function repl!(sandbox::Sandbox, code::AbstractString) | ||
function replblock!( | ||
sandbox::Sandbox, code::AbstractString; | ||
color::Bool=true, | ||
post_process_inputs = identity, | ||
) | ||
exprs = parseblock( | ||
code; | ||
keywords = false, | ||
# line unused, set to 0 | ||
linenumbernode = LineNumberNode(0, "REPL"), | ||
) | ||
codeblocks = CodeBlock[] | ||
source_exprs = map(exprs) do pex | ||
input = post_process_inputs(pex.code) | ||
result = evaluate!(sandbox, pex.expr; color, softscope=true, setans = true) | ||
# Add the input and output to the codeblocks, if appropriate. | ||
if !isempty(input) | ||
push!(codeblocks, CodeBlock(true, _prepend_prompt(input))) | ||
end | ||
# Determine the output string and add to codeblocks | ||
object_repl_repr = let buffer = IOContext(IOBuffer(), :color=>color) | ||
if !result.error | ||
hide = REPL.ends_with_semicolon(input) | ||
_result_to_string(buffer, hide ? nothing : result.value) | ||
else | ||
_error_to_string(buffer, result.value, result.backtrace) | ||
end | ||
end | ||
# Construct the full output. We have to prepend the stdout/-err to the | ||
# output first, and then finally render the returned object. | ||
out = IOBuffer() | ||
print(out, result.output) # stdout and stderr from the evaluation | ||
if !isempty(input) && !isempty(object_repl_repr) | ||
print(out, object_repl_repr, "\n") | ||
end | ||
outstr = _remove_sandbox_from_output(sandbox, String(take!(out))) | ||
push!(codeblocks, CodeBlock(false, outstr)) | ||
return (; | ||
expr = pex, | ||
result, | ||
input, | ||
outstr, | ||
) | ||
end | ||
return REPLResult(sandbox, codeblocks, code, source_exprs) | ||
end | ||
|
||
# Replace references to gensym'd module with Main | ||
function _remove_sandbox_from_output(sandbox::Sandbox, str::AbstractString) | ||
replace(str, Regex(("(Main\\.)?$(nameof(sandbox))")) => "Main") | ||
end | ||
|
||
function _prepend_prompt(input::AbstractString) | ||
prompt = "julia> " | ||
padding = " "^length(prompt) | ||
out = IOBuffer() | ||
for (n, line) in enumerate(split(input, '\n')) | ||
line = rstrip(line) | ||
println(out, n == 1 ? prompt : padding, line) | ||
end | ||
rstrip(String(take!(out))) | ||
end | ||
|
||
function _result_to_string(buffer::IO, value::Any) | ||
if value !== nothing | ||
Base.invokelatest( | ||
show, | ||
IOContext(buffer, :limit => true), | ||
MIME"text/plain"(), | ||
value | ||
) | ||
end | ||
return _sanitise(buffer) | ||
end | ||
|
||
function _error_to_string(buffer::IO, e::Any, bt) | ||
# Remove unimportant backtrace info. | ||
bt = _remove_common_backtrace(bt, backtrace()) | ||
# Remove everything below the last eval call (which should be the one in IOCapture.capture) | ||
index = findlast(ptr -> Base.ip_matches_func(ptr, :eval), bt) | ||
bt = (index === nothing) ? bt : bt[1:(index - 1)] | ||
# Print a REPL-like error message. | ||
print(buffer, "ERROR: ") | ||
Base.invokelatest(showerror, buffer, e, bt) | ||
return _sanitise(buffer) | ||
end | ||
|
||
# Strip trailing whitespace from each line and return resulting string | ||
function _sanitise(buffer::IO) | ||
out = IOBuffer() | ||
for line in eachline(seekstart(Base.unwrapcontext(buffer)[1])) | ||
println(out, rstrip(line)) | ||
end | ||
return rstrip(String(take!(out)), '\n') | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
@testset "replblock! - basic" begin | ||
sb = CodeEvaluation.Sandbox() | ||
let r = CodeEvaluation.replblock!(sb, "nothing") | ||
@test r.sandbox === sb | ||
@test length(r.blocks) == 2 | ||
let b = r.blocks[1] | ||
@test b.input | ||
@test b.code == "julia> nothing" | ||
end | ||
let b = r.blocks[2] | ||
@test !b.input | ||
@test b.code == "" | ||
end | ||
|
||
@test CodeEvaluation.join_to_string(r) == """ | ||
julia> nothing | ||
""" | ||
end | ||
let r = CodeEvaluation.replblock!(sb, "40 + 2") | ||
@test r.sandbox === sb | ||
@test length(r.blocks) == 2 | ||
let b = r.blocks[1] | ||
@test b.input | ||
@test b.code == "julia> 40 + 2" | ||
end | ||
let b = r.blocks[2] | ||
@test !b.input | ||
@test b.code == "42\n" | ||
end | ||
@test CodeEvaluation.join_to_string(r) == """ | ||
julia> 40 + 2 | ||
42 | ||
""" | ||
end | ||
let r = CodeEvaluation.replblock!(sb, "println(\"...\")") | ||
@test r.sandbox === sb | ||
@test length(r.blocks) == 2 | ||
let b = r.blocks[1] | ||
@test b.input | ||
@test b.code == "julia> println(\"...\")" | ||
end | ||
let b = r.blocks[2] | ||
@test !b.input | ||
@test b.code == "...\n" | ||
end | ||
@test CodeEvaluation.join_to_string(r) == """ | ||
julia> println("...") | ||
... | ||
""" | ||
end | ||
end | ||
|
||
@testset "replblock! - multiple expressions" begin | ||
sb = CodeEvaluation.Sandbox() | ||
r = CodeEvaluation.replblock!(sb, """ | ||
x = 2 | ||
x += 2 | ||
x ^ 2 | ||
""") | ||
@test length(r.blocks) == 6 | ||
let b = r.blocks[1] | ||
@test b.input | ||
@test b.code == "julia> x = 2" | ||
end | ||
let b = r.blocks[2] | ||
@test !b.input | ||
@test b.code == "2\n" | ||
end | ||
let b = r.blocks[3] | ||
@test b.input | ||
@test b.code == "julia> x += 2" | ||
end | ||
let b = r.blocks[4] | ||
@test !b.input | ||
@test b.code == "4\n" | ||
end | ||
let b = r.blocks[5] | ||
@test b.input | ||
@test b.code == "julia> x ^ 2" | ||
end | ||
let b = r.blocks[6] | ||
@test !b.input | ||
@test b.code == "16\n" | ||
end | ||
|
||
@test CodeEvaluation.join_to_string(r) == """ | ||
julia> x = 2 | ||
2 | ||
julia> x += 2 | ||
4 | ||
julia> x ^ 2 | ||
16 | ||
""" | ||
end | ||
|
||
@testset "replblock! - output & results" begin | ||
sb = CodeEvaluation.Sandbox() | ||
r = CodeEvaluation.replblock!(sb, """ | ||
print(stdout, "out"); print(stderr, "err"); 42 | ||
""") | ||
@test length(r.blocks) == 2 | ||
let b = r.blocks[1] | ||
@test b.input | ||
@test b.code == "julia> print(stdout, \"out\"); print(stderr, \"err\"); 42" | ||
end | ||
let b = r.blocks[2] | ||
@test !b.input | ||
@test b.code == "outerr42\n" | ||
end | ||
@test CodeEvaluation.join_to_string(r) == """ | ||
julia> print(stdout, "out"); print(stderr, "err"); 42 | ||
outerr42 | ||
""" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters