Ellipses
A pattern followed by an ellipsis means that the pattern repeats zero or more times.
julia> exprs = @pattern {x}...
Pattern:
[~rep]
x:::expr :: ~var
julia> using JuliaSyntax: parseall
julia> syntax_match(exprs, parseall(SyntaxNode, """
a = 1
b = 2
a + b
"""))
BindingSet @ 0:0 with 1 entry:
:x => Binding:
Name: :x
Bound sources: [(= a 1) @ 1:1, (= b 2) @ 2:1, (call-i a + b) @ 3:1]
Ellipsis depth: 1
Sub-bindings:
[
BindingSet @ 0:0 with 0 entries,
BindingSet @ 0:0 with 0 entries,
BindingSet @ 0:0 with 0 entries
]This is useful in many situations. For example, when defining a pattern that matches a function definition:
julia> function_def = @pattern begin
function {f}({args}...)
{body}...
end
end
Pattern:
[function]
[call]
f:::expr :: ~var
[~rep]
args:::expr :: ~var
[block]
[~rep]
body:::expr :: ~var
julia> syntax_match(function_def, parsestmt(SyntaxNode, """
function my_fun()
println("My fun!")
return nothing
end
"""))
BindingSet @ 0:0 with 3 entries:
:f => Binding:
Name: :f
Bound source: my_fun @ 1:10
Ellipsis depth: 0
Sub-bindings:
BindingSet @ 0:0 with 0 entries
:args => Binding:
Name: :args
Bound sources: []
Ellipsis depth: 1
Sub-bindings:
[]
:body => Binding:
Name: :body
Bound sources: [(call println (string "My fun!")) @ 2:5, (return nothing) @ 3:5]
Ellipsis depth: 1
Sub-bindings:
[
BindingSet @ 0:0 with 0 entries,
BindingSet @ 0:0 with 0 entries
]args and body both bind to sequences of expressions. You can see this by looking at their ellipsis depth, which is 1, or by noting that they are bound to vectors of source nodes instead of just source nodes, like f is.
Ellipses can be nested to match sequences of any depth.
julia> vec_of_vecs = @pattern [[{el}...]...]
Pattern:
[vect]
[~rep]
[vect]
[~rep]
el:::expr :: ~var
julia> syntax_match(vec_of_vecs, parsestmt(SyntaxNode, "[[1, 2], [[3]]]"))
BindingSet @ 0:0 with 1 entry:
:el => Binding:
Name: :el
Bound sources: [[1 @ 1:3, 2 @ 1:6], [(vect 3) @ 1:11]]
Ellipsis depth: 2
Sub-bindings:
[
[
BindingSet @ 0:0 with 0 entries,
BindingSet @ 0:0 with 0 entries
],
[
BindingSet @ 0:0 with 0 entries
]
]el has ellipsis depth 2, so it binds to a sequence of sequences of expressions. In the example above, it binds to a sequence containing two sequences: the sequence with 1 and 2 and the sequence with the vector [3].
You can read the pattern vec_of_vecs more intuitively as: A vector that contains any number of elements. Its elements need to be vectors that have any number of elements of any kind.
By default, the matching algorithm for ellipses is "greedy".
julia> ones_vec = @pattern [1, {ones1}..., 1, {ones2}...];
julia> syntax_match(ones_vec, parsestmt(SyntaxNode, "[1, 1, 1, 1]"))
BindingSet @ 0:0 with 2 entries:
:ones2 => Binding:
Name: :ones2
Bound sources: []
Ellipsis depth: 1
Sub-bindings:
[]
:ones1 => Binding:
Name: :ones1
Bound sources: [1 @ 1:5, 1 @ 1:8]
Ellipsis depth: 1
Sub-bindings:
[
BindingSet @ 0:0 with 0 entries,
BindingSet @ 0:0 with 0 entries
]ones1 consumes all the expressions it can, leaving none for ones2. We can choose to match with a non-"greedy" algorithm:
julia> syntax_match(ones_vec, parsestmt(SyntaxNode, "[1, 1, 1, 1]"); greedy=false)
BindingSet @ 0:0 with 2 entries:
:ones2 => Binding:
Name: :ones2
Bound sources: [1 @ 1:8, 1 @ 1:11]
Ellipsis depth: 1
Sub-bindings:
[
BindingSet @ 0:0 with 0 entries,
BindingSet @ 0:0 with 0 entries
]
:ones1 => Binding:
Name: :ones1
Bound sources: []
Ellipsis depth: 1
Sub-bindings:
[]This is useful if we expect a non-greedy approach to be more efficient in a particular case.
Argus hijacks Julia's splat operator. If we want to use ... with the regular splat meaning, we need to escape it:
julia> syntax_match((@pattern v...), parsestmt(SyntaxNode, "v..."))
MatchFail: no match @ :1:1
julia> syntax_match((@pattern @esc(v...)), parsestmt(SyntaxNode, "v..."))
BindingSet @ 0:0 with 0 entriesWe can also escape an expression only up to a certain depth.
julia> syntax_match((@pattern @esc([{elems}...]..., 1)), parsestmt(SyntaxNode, "[1, 2, 3]..."))
BindingSet @ 0:0 with 1 entry:
:elems => Binding:
Name: :elems
Bound sources: [1 @ 1:2, 2 @ 1:5, 3 @ 1:8]
Ellipsis depth: 1
Sub-bindings:
[
BindingSet @ 0:0 with 0 entries,
BindingSet @ 0:0 with 0 entries,
BindingSet @ 0:0 with 0 entries
]Without specifying an escape depth, @esc would have escaped the entire expression:
julia> @pattern @esc([{elems}...])
Pattern:
[vect]
[...]
[braces]
elems :: Identifier