Skip to content

Instantly share code, notes, and snippets.

@fcard
Last active March 7, 2019 00:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fcard/8563a55c8d28e874886ea743ffe137ad to your computer and use it in GitHub Desktop.
Save fcard/8563a55c8d28e874886ea743ffe137ad to your computer and use it in GitHub Desktop.
Simple Printf constructed using the generated functions from the julia language
"""
exports fmt and @fmt, functions to build
format specifiers in an alternate
way to @fmt_str
"""
module Cons
export fmt, @fmt
import Base: @pure
import ..GenPrintf: PrintArgument, PrintValue,
PrintBoth, FormatSpecifier
"""
builds format specifiers.
examples:
@fmt("abc")
@fmt("hello, ", {})
"""
macro fmt(args...)
fmt(args...)
end
"""
builds format specifiers.
examples:
fmt("abc")
a = "hello, "
fmt(a, :{})
"""
@pure function fmt(literal::Union{Number, String})
PrintValue{Symbol(String(literal))}()
end
@pure function fmt(ex::Expr)
if ex.head == :tuple
fmt(ex.args...)
else
@assert ex == :{} "invalid formatting expression"
PrintArgument()
end
end
@pure function fmt(ex::Tuple)
fmt(args...)
end
@pure function fmt(ex::FormatSpecifier)
ex
end
@pure function fmt(args...)
PrintBoth(fmt(args[1]), fmt(args[2:end]...))
end
end
module GenPrintf
export printf, @fmt_str
abstract type FormatSpecifier end
struct PrintArgument <: FormatSpecifier end
struct PrintValue{V} <: FormatSpecifier end
struct PrintBoth{A,B} <: FormatSpecifier
a::A
b::B
end
const ARGN_ERR =
"wrong number of arguments sent to printf"
include("impl.jl")
include("meta.jl")
include("cons.jl")
include("list.jl")
"""
Macro to create formatting strings.
{} are where arguments will go.
examples:
fmt"ab cd"
fmt"hello, {}!"
"""
macro fmt_str(s)
values = split(unescape_string(s), "{}")
print_list(values)
end
"""
Prints formatted output. Use the fmt macro for it
to generate code at compile time.
examples:
printf(fmt"ab cd")
printf(fmt"hello, {}!", "world")
printf("ab{}{}", "c", "d")
"""
@generated function printf(fmt::FormatSpecifier, args...)
printf_impl(fmt, length(args))
end
function printf(fmt::String, args...)
i = 1
values = split(fmt, "{}")
@assert length(values) == length(args)+1 ARGN_ERR
for value in values
print(value)
if i != length(args) + 1
print(args[i])
i += 1
end
end
end
end
import Base: @pure
@pure function printf_impl(fmt::Type{PrintArgument}, len, i=1)
assert_argument_number(len, 1)
quote
Base.print(args[$i])
end
end
@pure assert_argument_number(len, required) =
@assert len == required ARGN_ERR
@pure function printf_impl(fmt::Type{PrintValue{V}}, len, i=1) where V
assert_argument_number(len, 0)
quote
Base.print($(String(V)))
end
end
@pure function printf_impl(fmt::Type{PrintBoth{A,B}}, len, i=1) where {A,B}
l1 = arglength(A)
l2 = arglength(B)
assert_argument_number(len, l1+l2)
quote
$(printf_impl(A, l1, i))
$(printf_impl(B, l2, i+l1))
end
end
@pure function printf_impl(fmt::Type{String}, len, i=1) where {A,B}
:?
end
@pure function arglength(fmt)
a = Nothing
b = fmt
result = 0
while b <: PrintBoth
a <: PrintArgument && (result += 1)
a = b.parameters[1]
b = b.parameters[2]
end
a <: PrintArgument && (result += 1)
b <: PrintArgument && (result += 1)
result
end
import Base: @pure
@pure function print_list(values)
argument() = PrintArgument()
to_value(x) = PrintValue{Symbol(String(x))}()
if length(values) == 1
to_value(values[1])
else
if values[1] == ""
PrintBoth(
argument(),
print_list(values[2:end])
)
else
PrintBoth(
to_value(values[1]),
print_list([""; values[2:end]])
)
end
end
end
"""
exports @printf_code and printf_code, which returns
the code generated for printf.
"""
module Meta
export @printf_code, printf_code
import Base: @pure
import ..GenPrintf: printf_impl
"""
returns the code that printf will generate.
e.g. `@printf_code(fmt"abc")` will return
```
quote
print("abc")
end
```
"""
macro printf_code(fmt, args...)
quote
flatten_body(
remove_line_nodes(
printf_impl(
typeof($(esc(fmt))),
$(length(args))
)
)
)
end
end
"""
returns the code that printf will generate.
e.g. `printf_code(fmt"abc")` will return
```
quote
print("abc")
end
```
"""
@pure function printf_code(fmt, args...)
flatten_body(
remove_line_nodes(
printf_impl(
typeof(fmt),
length(args)
)
)
)
end
@pure remove_line_nodes(x) = x
@pure function remove_line_nodes(x::Expr)
Expr(
x.head,
map(
remove_line_nodes,
filter(!x->x isa LineNumberNode, x.args)
)...
)
end
@pure flatten_body(x) = x
@pure function flatten_body(x::Expr)
result_args = []
for arg in x.args
if arg isa Expr && arg.head == :block
append!(result_args, flatten_body(arg).args)
else
push!(result_args, arg)
end
end
Expr(:block, result_args...)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment