diff --git a/src/Runic.jl b/src/Runic.jl index b939dc4..1cdcb6b 100644 --- a/src/Runic.jl +++ b/src/Runic.jl @@ -5,7 +5,7 @@ module Runic using JuliaSyntax: JuliaSyntax, @K_str, @KSet_str -# compat for const fields +# Julia compat for const struct fields @eval macro $(Symbol("const"))(field) if VERSION >= v"1.8.0-DEV.1148" Expr(:const, esc(field)) @@ -14,7 +14,8 @@ using JuliaSyntax: end end -include("chisel.jl") +# JuliaSyntax extensions and other utilities +include("chisels.jl") # Return the result of expr if it doesn't evaluate to `nothing` macro return_something(expr) @@ -33,6 +34,7 @@ mutable struct Context fmt_tree::Union{JuliaSyntax.GreenNode{JuliaSyntax.SyntaxHead}, Nothing} # User settings verbose::Bool + assert::Bool debug::Bool # Current state # node::Union{JuliaSyntax.GreenNode{JuliaSyntax.SyntaxHead}, Nothing} @@ -41,14 +43,17 @@ mutable struct Context # parent::Union{JuliaSyntax.GreenNode{JuliaSyntax.SyntaxHead}, Nothing} end -function Context(src_str; debug::Bool = false, verbose::Bool = debug) +function Context(src_str; assert::Bool = true, debug::Bool = false, verbose::Bool = debug) src_io = IOBuffer(src_str) src_tree = JuliaSyntax.parseall(JuliaSyntax.GreenNode, src_str; ignore_warnings = true) fmt_io = IOBuffer() fmt_tree = nothing + # Debug mode enforces verbose and assert + verbose = debug ? true : verbose + assert = debug ? true : assert return Context( - src_str, src_tree, src_io, fmt_io, fmt_tree, verbose, debug, - nothing, nothing, + src_str, src_tree, src_io, fmt_io, fmt_tree, verbose, assert, debug, nothing, + nothing, ) end diff --git a/src/ToggleableAsserts.jl b/src/ToggleableAsserts.jl new file mode 100644 index 0000000..4928952 --- /dev/null +++ b/src/ToggleableAsserts.jl @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: MIT + +# The code in this file is derived from the ToggleableAsserts.jl package +# (https://github.com/MasonProtter/ToggleableAsserts.jl) licensed under the MIT license. +# (https://github.com/MasonProtter/ToggleableAsserts.jl/blob/master/LICENSE): + +# MIT License +# +# Copyright (c) 2021 Mason Protter +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +assert_enabled() = true + +macro assert(expr) + code = macroexpand_assert(expr) + :(assert_enabled() ? $(code) : nothing) +end + +const toggle_lock = ReentrantLock() + +function enable_assert(enable::Bool) + @lock toggle_lock begin + if assert_enabled() != enable + @eval Runic assert_enabled() = $enable + end + end +end diff --git a/src/chisel.jl b/src/chisels.jl similarity index 58% rename from src/chisel.jl rename to src/chisels.jl index c114947..1407214 100644 --- a/src/chisel.jl +++ b/src/chisels.jl @@ -1,5 +1,42 @@ # SPDX-License-Identifier: MIT +############## +# Debug info # +############## + +# @lock is defined but not exported in older Julia versions +if VERSION < v"1.7.0" + using Base: @lock +end + +# Code derived from ToggleableAsserts.jl kept in a separate file +include("ToggleableAsserts.jl") + +abstract type RunicException <: Exception end + +struct AssertionError <: RunicException + msg::String +end + +function Base.showerror(io::IO, err::AssertionError) + print( + io, + "Runic.AssertionError: `", err.msg, "`. This is unexpected, " * + "please file an issue with a reproducible example at " * + "https://github.com/fredrikekre/Runic.jl/issues/new.", + ) +end + +function macroexpand_assert(expr) + msg = string(expr) + return :($(esc(expr)) || throw(AssertionError($msg))) +end + + +########################## +# JuliaSyntax extensions # +########################## + function is_leaf(node::JuliaSyntax.GreenNode) return !JuliaSyntax.haschildren(node) end @@ -23,10 +60,13 @@ end function is_assignment(node::JuliaSyntax.GreenNode) return JuliaSyntax.is_prec_assignment(node) end + +# Just like `JuliaSyntax.is_infix_op_call`, but also check that the node is K"call" function is_infix_op_call(node::JuliaSyntax.GreenNode) return JuliaSyntax.kind(node) === K"call" && JuliaSyntax.is_infix_op_call(node) end + function infix_op_call_op(node::JuliaSyntax.GreenNode) @assert is_infix_op_call(node) children = JuliaSyntax.children(node)::AbstractVector @@ -34,9 +74,11 @@ function infix_op_call_op(node::JuliaSyntax.GreenNode) op_index = findnext(JuliaSyntax.is_operator, children, first_operand_index + 1) return children[op_index] end + function is_comparison_leaf(node::JuliaSyntax.GreenNode) return is_leaf(node) && JuliaSyntax.is_prec_comparison(node) end + function is_operator_leaf(node::JuliaSyntax.GreenNode) return is_leaf(node) && JuliaSyntax.is_operator(node) end