Skip to content

Commit

Permalink
Complete overhaul of implementation taken from Rematch2.jl (#78)
Browse files Browse the repository at this point in the history
* Code generation via an optimized decision automaton
* Require Julia 1.4
* No longer supports multidimensional arrays
  • Loading branch information
Neal Gafter authored Aug 3, 2023
1 parent 6f0c04e commit 37f4c35
Show file tree
Hide file tree
Showing 32 changed files with 5,308 additions and 1,188 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ jobs:
fail-fast: false
matrix:
version:
- '1.6'
- '1'
- '1.4'
- '1.5'
- '1.8.2'
- '1.9'
- 'nightly'
os:
- ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
*.jl.*.cov
*.jl.*.mem
*.jl.cov
*.jl.mem
Manifest.toml
/docs/build/
/docs/Manifest.toml
/tmp/*
.vscode
.DS_Store
._*
tmp
9 changes: 8 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
v2.0.0 / 2023-08-02
===================

* Complete overhaul of implementation (taken from Rematch2.jl)
* Code generation via an optimized decision automaton.
* Requires Julia 1.4
* Note incompatibility: drops support for multidimensional arrays. See `README.md`.

v0.4.0 / 2017-07-11
==================
Expand Down Expand Up @@ -97,7 +104,7 @@ v0.0.3 / 2014-03-02
* Improve code generation for testing constant values.
* Update exports, remove @fmatch, rename _fmatch -> fmatch
* Fix matrix matching, update contains->in usage

* Doc format updates
* Fixes for ReadTheDocs/sphinx

Expand Down
41 changes: 21 additions & 20 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
# Match.jl - Advanced Pattern Matching for Julia

The Match.jl package is licensed under the MIT Expat License:

> Copyright (c) 2013: Kevin Squire.
>
> 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.
Copyright (c) 2013-2023: Kevin Squire, RelationalAI, Inc, and contributors.

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.
12 changes: 9 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
name = "Match"
uuid = "7eb4fadd-790c-5f42-8a69-bfa0b872bfbf"
version = "1.2.0"
author = "Kevin Squire <[email protected]>"
version = "2.0.0"
authors = ["Neal Gafter <[email protected]>", "Kevin Squire <[email protected]>"]

[deps]
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"

[compat]
julia = "0.7, 1"
MacroTools = "0.4, 0.5"
OrderedCollections = "1"
julia = "1.4"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand Down
80 changes: 71 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@
Features:

* Matching against almost any data type with a first-match policy
* Deep matching within data types and matrices
* Deep matching within data types, tuples, and vectors
* Variable binding within matches

For alternatives to `Match`, check out

* toivoh's [`PatternDispatch.jl`](https://github.com/toivoh/PatternDispatch.jl) for a more Julia-like function dispatch on patterns.

* Produces a decision automaton to avoid repeated tests between patterns.

## Installation
Use the Julia package manager. Within Julia, do:
Expand All @@ -24,7 +20,9 @@ Pkg.add("Match")

## Usage

The package provides one macro, `@match`, which can be used as:
The package provides two macros for pattern-matching: `@match` and `@ismatch`.
It is possible to supply variables inside patterns, which will be bound
to corresponding values.

using Match

Expand All @@ -35,9 +33,73 @@ The package provides one macro, `@match`, which can be used as:
_ => default_result
end

It is possible to supply variables inside pattern, which will be bound
to corresponding values.
if @ismatch value pattern
# Code that uses variables bound in the pattern
end

See the [documentation](https://JuliaServices.github.io/Match.jl/stable/)
for examples of this and other features.

## Patterns

* `_` matches anything
* `x` (an identifier) matches anything, binds value to the variable `x`
* `T(x,y,z)` matches structs of type `T` with fields matching patterns `x,y,z`
* `T(y=1)` matches structs of type `T` whose `y` field equals `1`
* `[x,y,z]` matches `AbstractArray`s with 3 entries matching `x,y,z`
* `(x,y,z)` matches `Tuple`s with 3 entries matching `x,y,z`
* `[x,y...,z]` matches `AbstractArray`s with at least 2 entries, where `x` matches the first entry, `z` matches the last entry and `y` matches the remaining entries.
* `(x,y...,z)` matches `Tuple`s with at least 2 entries, where `x` matches the first entry, `z` matches the last entry and `y` matches the remaining entries.
* `::T` matches any subtype (`isa`) of type `T`
* `x::T` matches any subtype (`isa`) of T that also matches pattern `x`
* `x || y` matches values which match either pattern `x` or `y` (only variables which exist in both branches will be bound)
* `x && y` matches values which match both patterns `x` and `y`
* `x, if condition end` matches only if `condition` is true (`condition` may use any variables that occur earlier in the pattern eg `(x, y, z where x + y > z)`)
* `x where condition` An alternative form for `x, if condition end`
* Anything else is treated as a constant and tested for equality
* Expressions can be interpolated in as constants via standard interpolation syntax `\$(x)`. Interpolations may use previously bound variables.

Patterns can be nested arbitrarily.

Repeated variables only match if they are equal (`isequal`). For example `(x,x)` matches `(1,1)` but not `(1,2)`.

## Early exit and failure

Inside the result part of a case, you can cause the pattern to fail (as if the pattern did not match), or you can return a value early:

```julia
@match value begin
pattern1 => begin
if some_failure_condition
@match_fail
end
if some_shortcut_condition
@match_return 1
end
...
2
end
...
end
```

In this example, the result value when matching `pattern1` is a block that has two early exit conditions.
When `pattern1` matches but `some_failure_condition` is `true`, then the whole case is treated as not matching and the following cases are tried.
Otherwise, if `some_shortcut_condition` is `true`, then `1` is the result value for this case.
Otherwise `2` is the result.

## Differences from previous versions of `Match.jl`

* If no branches are matched, throws `MatchFailure` instead of returning nothing.
* Matching against a struct with the wrong number of fields produces an error instead of silently failing.
* Repeated variables require equality, ie `@match (1,2) begin (x,x) => :ok end` fails.
* We add a syntax for guards `x where x > 1` in addition to the existing `x, if x > 1 end`.
* Structs can be matched by field-names, allowing partial matches: `@match Foo(1,2) begin Foo(y=2) => :ok end` returns `:ok`.
* Patterns support interpolation, ie `let x=1; @match ($x,$(x+1)) = (1,2); end` is a match.
* We have dropped support for matching against multidimensional arrays - all array patterns use linear indexing.
* We no longer support the (undocumented) syntax `@match value pattern` which returned an array of the bindings of the pattern variables.
* Errors now identify a specific line in the user's program where the problem occurred.
* Previously bound variables may now be used in interpolations, ie `@match (x, $(x+2)) = (1, 3)` is a match.
* A pure type match (without another pattern) can be written as `::Type`.
* Types appearing in type patterns (`::Type`) and struct patterns (`Type(...)`) are bound at macro-expansion time in the context of the module containing the macro usage. As a consequence, you cannot use certain type expressions that would differ. For example, you cannot use a type parameter or a local variable containing a type. The generated code checks that the type is the same at evaluation time as it was at macro expansion time, and an error is thrown if they differ. If this rare incompatibility affects you, you can use `x where x isa Type` as a workaround. If the type is not defined at macro-expansion time, an error is issued.
* A warning is issued at macro-expansion time if a case cannot be reached because it is subsumed by prior cases.
2 changes: 1 addition & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ DocMeta.setdocmeta!(Match, :DocTestSetup, :(using Match); recursive=true)

makedocs(;
modules=[Match],
authors="Kevin Squire, Neal Gafter <neal@gafter.com> and contributors",
authors="Neal Gafter <neal.gafter@relational.ai>, Kevin Squire <kevin.squire@gmail.com>, and contributors",
repo="https://github.com/JuliaServices/Match.jl/blob/{commit}{path}#{line}",
sitename="Match.jl",
format=Documenter.HTML(;
Expand Down
Loading

0 comments on commit 37f4c35

Please sign in to comment.