Parsing things that should be parsable without eval
- Synopsis
- Installation
- Usage
- Notable difference to the REPL experience
- Command line argument parsing
- FAQ
When working interactively or interacting with other sources of data, one can find oneself in a situation, where one needs to construct Julia objects from a given string. While this is in general a hard problem, in practice it is more managable, as the types are restricted to a small set, including numbers, arrays or tuples. For number types Base.tryparse provides this functionality, as long as the string is a literal interger.
Here we provide a function Tryparse.tryparse, which does the same as Base.tryparse, but allows (for number types) a wider range of basic types as well expressions. Here is a short example, which illustrates the functionality:
julia> Base.tryparse(Int, "2 + 3^2 - 2 * 3") === nothing # this means, parsing could not be done
true
julia> Tryparse.tryparse(Int, "2 + 3^2 - 2 * 3")
5The package has a seamless integration with ArgParse.jl, ArgMacros.jl and Comonicon.jl to allow for ergonomic command line argument parsing. See here for details.
Since Tryparse.jl is a registered package, it can be simply installed as follows:
julia> using Pkg; Pkg.install("Tryparse")The package provides two functions:
Tryparse.tryparse(T::Type, x::String)Try to parse
xas an object of typeT. If this is not possible, the function returnsnothing.
Tryparse.parse(T::Type, x::String)Try to parse
xas an object of typeT. If this is not possible, an error of typeTryparse.ParseErroris thrown.
Table of supported types as well as allowed syntax.
| Type | Snytax | Example |
|---|---|---|
<: Number |
Any arithmetic expression (may contain ^) |
Tryparse.tryparse(BigInt, "10^100") == big(10)^100 |
<: Tuple |
Expressions of the form (...) |
Tryparse.tryparse(Tuple{Int, Float64}, "(1, 1.2)" == (1, 1.2) |
<: Vector |
Expressions of the form [...] |
Tryparse.tryparse(Vector{Int}, "[2,1+1,3]") == [2,2,3] |
<: Matrix |
Expressions of the form [...;...] |
Tryparse.tryparse(Matrix{Int}, "[1 2; 3 4]") == [1 2; 3 4] |
<: UnitRange |
Expressions of the form ...:... |
Tryparse.tryparse(UnitRange{Int}, "1:10") == 1:10 |
<: StepRange |
Expressions of the form ...:...:... |
Tryparse.tryparse(StepRange{Int}, "2:-1:-10") == 2:-1:-10 |
The types can be nested arbitrarily:
julia> Tryparse.tryparse(Vector{Matrix{Int}}, "[[2 2; 3 1], [1 2; 3 4]]")
2-element Vector{Matrix{Int64}}:
[2 2; 3 1]
[1 2; 3 4]In almost all cases, executing Tryparse.tryparse(T, x) will yield the same result as entering x in the REPL and hitting enter. One situation, where this is not the case, is related to arithmetic expressions and overflow. The function Tryparse.tryparse will always first parse all literal numbers and then evaluate the expression. For example:
julia> BigInt(2^64)
0
julia> Tryparse.tryparse(BigInt, "2^64")
18446744073709551616This choice of behavior is on purpose, to make working with command line arguments less painful.
The reason this package exists is parsing of command line arguments for julia scripts. Command line scripts are invokved in the form
bla@home> julia script.jl arg1 arg2In this situation, inside script.jl, the arguments are available only as strings and thus need to be processed. Here is where Tryparse.tryparse comes into play.
Similarly, if one uses ArgParse.jl, ArgMacros.jl or Comonicon.jl (those packages are highly recommended!), this looks like
bla@home> julia script.jl --opt1=arg1 --opt2=arg2and one can specifiy what the types of arg1 and arg2. But this will work only for types and strings, for which Base.tryparse respectively Base.parse works. For example, out of the box
bla@home> julia script.jl --opt1="10^10" --opt2="[2, 3]"would not work, whereas this is possible with Tryparse. We illustrate how to use Tryparse together with ArgParse.jl, ArgMacros.jl and Comonicon.jl.
Enabling parsing of command line arguments is straight forward for ArgParse.jl. Just add (one of) the following lines to your script:
using ArgParse
using Tryparse
Tryparse.@override Int Float64 # will intercept command line argument parsing of Int and Float64
Tryparse.@override # will intercept all command line argument parsing
Example `script_tryparse.jl`:
using ArgParse, TryParse
Tryparse.@override Int Matrix{Int}
function parse_commandline()
s = ArgParseSettings()
@add_arg_table! s begin
"--opt1"
help = "an option with an argument"
arg_type = Int
default = 0
"--opt2", "-o"
help = "another option with an argument"
arg_type = Matrix{Int}
default = [0 0; 0 0]
end
return parse_args(s)
end
function main()
parsed_args = parse_commandline()
println("Parsed args:")
for (arg,val) in parsed_args
println(" $arg => $val, $(typeof(val))")
end
end
main()Executing this yields:
bla@home> julia script_tryparse.jl --opt1="1+2^10" --opt2="[1 2; 3 4]"
Parsed args:
opt1 => 1025, Int64
opt2 => [1 2; 3 4], Matrix{Int64}Enabling parsing of command line arguments is straight forward for ArgMacros.jl. Just add (one of) the following lines to your script:
using ArgParse
using Tryparse
Tryparse.@override Int Float64 # will intercept command line argument parsing of Int and Float64
Tryparse.@override # will intercept all command line argument parsing
Example `script_argmacros.jl`:
using ArgMacros, Tryparse
Tryparse.@override Int Float64
function main()
@inlinearguments begin
@positionalrequired Int x
@positionaloptional Float64 z
end
println(x, " ", typeof(x))
println(z, " ", typeof(z))
end
main()Executing this yields:
bla@home> julia script_argmacros.jl "1+2^10" "1.1^2"
1025 Int64
1.2100000000000002 Float64Enabling parsing of command line arguments for Comonicon is a bit more brittle. To do this, we invoke Tryparse.@override_base. Note that this overrides the behavior of Base.parse and does not work for Float64.
using Comonicon
using Tryparse
Tryparse.@override_base_base Matrix{Int} BigInt # will intercept command line parsing of Matrix{Int} and BigInt
Example `script_comonicon.jl`:
using Comonicon, Tryparse
Tryparse.@override_base Matrix{Int} BigInt
@main function main(; opt1::Matrix{Int}=[0 0; 0 0],
opt2::BigInt=big(2))
println("Parsed args:")
println("opt1=>", opt1)
println("opt2=>", opt2)
endExecuting this yields:
bla@home> julia script_comonicon.jl --opt1="[1 2; 3 4]" --opt2="2^100"
Parsed args:
opt1=>[1 2; 3 4]
opt2=>1267650600228229401496703205376No. This is certified to be a eval-free.