z3 0.0.20161010 → 0.0.20161117

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -12
  3. data/examples/crossflip +82 -0
  4. data/examples/mortal_coil_puzzle +105 -0
  5. data/examples/mortal_coil_puzzle-9.txt +6 -0
  6. data/examples/oneofus +102 -0
  7. data/examples/regexp_crossword/beginner-1.txt +5 -0
  8. data/examples/regexp_crossword/beginner-2.txt +5 -0
  9. data/examples/regexp_crossword/beginner-3.txt +5 -0
  10. data/examples/regexp_crossword/beginner-4.txt +5 -0
  11. data/examples/regexp_crossword/beginner-5.txt +5 -0
  12. data/examples/regexp_crossword/experienced-1.txt +9 -0
  13. data/examples/regexp_crossword/experienced-2.txt +11 -0
  14. data/examples/regexp_crossword/experienced-3.txt +11 -0
  15. data/examples/regexp_crossword/experienced-4.txt +11 -0
  16. data/examples/regexp_crossword/experienced-5.txt +11 -0
  17. data/examples/regexp_crossword/tutorial-1.txt +3 -0
  18. data/examples/regexp_crossword/tutorial-2.txt +3 -0
  19. data/examples/regexp_crossword/tutorial-3.txt +3 -0
  20. data/examples/regexp_crossword/tutorial-4.txt +4 -0
  21. data/examples/regexp_crossword/tutorial-5.txt +4 -0
  22. data/examples/regexp_crossword/tutorial-6.txt +4 -0
  23. data/examples/regexp_crossword/tutorial-7.txt +4 -0
  24. data/examples/regexp_crossword/tutorial-8.txt +4 -0
  25. data/examples/regexp_crossword/tutorial-9.txt +3 -0
  26. data/examples/regexp_crossword_solver +80 -0
  27. data/examples/regexp_solver +44 -0
  28. data/examples/regexp_string_matcher.rb +124 -0
  29. data/examples/simple_regexp_parser.rb +187 -0
  30. data/lib/z3/expr/bitvec_expr.rb +4 -0
  31. data/lib/z3/expr/bool_expr.rb +4 -0
  32. data/lib/z3/probe.rb +4 -0
  33. data/spec/bitvec_expr_spec.rb +2 -1
  34. data/spec/bool_expr_spec.rb +3 -1
  35. data/spec/integration/crossflip_spec.rb +8 -0
  36. data/spec/integration/mortal_coil_puzzle_spec.rb +12 -0
  37. data/spec/integration/oneofus_spec.rb +19 -0
  38. data/spec/integration/regexp_crossword_solver_spec.rb +31 -0
  39. data/spec/integration/regexp_solver_spec.rb +27 -0
  40. metadata +48 -5
  41. data/lib/z3/expr/.DS_Store +0 -0
  42. data/lib/z3/sort/.DS_Store +0 -0
  43. data/spec/.DS_Store +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6cae47eadb8b6cb435ab68a3c1d5e64e7ab7ef10
4
- data.tar.gz: 59e4479e128bad8ea7588b8de42ef9c6ad9af4e5
3
+ metadata.gz: 23036614e8d142331481794ca573d49be3d4c18f
4
+ data.tar.gz: 1d765179f046c53ae950605fbf4d431d9bc3d7b6
5
5
  SHA512:
6
- metadata.gz: 9e3a0cf9fff6457c318ad086e987029fad72a292080572ea0ddc204cf5c054881c95b515977456aed9a07bb57a86718d85c8ac03e4144c5379c7417a1c667696
7
- data.tar.gz: 3bf40c2ff99cb57ef7b623415ddf50605bbdde75d524c05c40223ca38d81e3cd58d122d39a1b51dc490bc2575dde884823e24cd2fc06a8568d5ee42f4a4cb216
6
+ metadata.gz: f49fb4df1ff7df3b283aa2f2eada3b263d13af54ddf0b9e7640d315efcae9fe5cdcaa00b5f4693e5ead379d9babf45923832c269de5dedef84b516a9c1245f78
7
+ data.tar.gz: c936f488e71e3b8fe99f86ec8602bfde64848d1c008407a73316739dec194e0f99a0567487f3d6371ba775265431bfb44fa64ea52c1a7042941f4b5289a71fcb
data/README.md CHANGED
@@ -1,18 +1,16 @@
1
1
  This is Ruby interface for Z3 [ https://github.com/Z3Prover/z3 ].
2
2
 
3
- It's in extremely early stages of development. Pull requests always welcome.
3
+ It's in very early stages of development. Pull requests always welcome.
4
4
 
5
5
  ### Interface
6
6
 
7
- `Z3::VeryLowLever` / `Z3::LowLevel` are low level FFI interface, and they shouldn't be used directly.
7
+ The public interface is various methods in `Z3` module, and on objects created by it. `examples/` directory is probably the best place to start.
8
8
 
9
- The rest of `Z3` is high level API, but the interface is extremely unstable at this point, and it's pretty much guaranteed to change many times. Check specs or `examples/` directory for usage.
9
+ You can use most Ruby operators to construct Z3 expressions, but use `| &` instead of `|| &&` for boolean operators. They unfortunately have wrong operator precedence so you'll need to use some extra parentheses.
10
10
 
11
- You can use most Ruby operators to construct Z3 expressions, but use `~ | &` instead of `! || &&` for boolean operators. They unfortunately have wrong operator precedence so you'll need to use some extra parentheses.
11
+ The interface is potentially unstable, and can change in the future.
12
12
 
13
- As for API internals, attributes starting with `_` are FFI internals you shouldn't touch, other attributes are generally legitimate Ruby objects.
14
-
15
- Bit vectors are treated as signed by default. [well, mostly, more systematic treatment of this is on TODO list]
13
+ `Z3::VeryLowLever` and `Z3::LowLevel` are FFI interface for internal use, and they shouldn't be used directly. Also don't use any method starting with `_`. Doing this is likely to lead to segmentation faults unless extreme care is taken.
16
14
 
17
15
  ### Requirements
18
16
 
@@ -22,12 +20,12 @@ To use it, you'll need to install `z3`. On OSX that would be:
22
20
 
23
21
  On other systems use appropriate package manager.
24
22
 
25
- ### Known Bugs
23
+ ### Known Issues
26
24
 
27
- Ruby API tries to catch the most common mistakes, but if you use API in a weird way you can get C crash instead of nice Ruby exception.
25
+ As Z3 is a C library, doing anything weird with it will segfault your process. Ruby API tries its best to prevent such problems and turn them into exceptions instead, but if you do anything weird (especially touch any method prefixed with `_` or `Z3::LowLevel` interface), crashes are possible. If you have reproducible crash on reasonable looking code, definitely submit it as a bug, and I'll try to come up with a workaround.
28
26
 
29
- Memory will leak a good deal. Generally avoid in long running processes.
27
+ As Z3 mixes aggressively interning ASTs and reference counting, it's not very compatible with Ruby style memory management, so memory will leak a good deal. It's usually not much worse than the usual Symbol memory leak, but you might want to avoid Z3 in a long running processes exposed to public input.
30
28
 
31
- ### Python versions
29
+ ### Python examples
32
30
 
33
- Most of example solvers have Python versions available from https://github.com/taw/puzzle-solvers
31
+ Some of example solvers also have Python versions available from https://github.com/taw/puzzle-solvers
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "z3"
4
+
5
+ class CrossFlipSolver
6
+ def initialize(board)
7
+ # 0 - light
8
+ # 1 - dark
9
+ # 2 - wall
10
+ @board = board.split(",").map{|line| line.chars.map(&:to_i)}
11
+ @ysize = @board.size
12
+ @xsize = @board[0].size
13
+ end
14
+
15
+ def board(x,y)
16
+ @board[y][x]
17
+ end
18
+
19
+ def wall?(x,y)
20
+ board(x,y) == 2
21
+ end
22
+
23
+ def click(x,y)
24
+ Z3.Bool("click[#{x},#{y}]")
25
+ end
26
+
27
+ def cells_visible_from(x0,y0)
28
+ result = []
29
+ result << click(x0,y0)
30
+ (x0+1...@xsize).each do |x|
31
+ break if wall?(x,y0)
32
+ result << click(x,y0)
33
+ end
34
+ (0..x0-1).reverse_each do |x|
35
+ break if wall?(x,y0)
36
+ result << click(x,y0)
37
+ end
38
+ (y0+1...@ysize).each do |y|
39
+ break if wall?(x0,y)
40
+ result << click(x0,y)
41
+ end
42
+ (0..y0-1).reverse_each do |y|
43
+ break if wall?(x0,y)
44
+ result << click(x0,y)
45
+ end
46
+ result
47
+ end
48
+
49
+ def solve!
50
+ @solver = Z3::Solver.new
51
+
52
+ @ysize.times do |y|
53
+ @xsize.times do |x|
54
+ if wall?(x,y)
55
+ @solver.assert click(x,y) == false
56
+ else
57
+ starts_light = (board(x,y) == 0)
58
+ ends_light = Z3.Xor(*cells_visible_from(x,y)) ^ starts_light
59
+ @solver.assert ends_light
60
+ end
61
+ end
62
+ end
63
+
64
+ unless @solver.satisfiable?
65
+ raise "This can't be solved, go away!"
66
+ end
67
+ model = @solver.model
68
+
69
+ @ysize.times do |y|
70
+ @xsize.times do |x|
71
+ clicked = (model[click(x,y)].to_s == "true")
72
+ print clicked ? "[x] " : "[ ] "
73
+ end
74
+ print "\n"
75
+ end
76
+ end
77
+ end
78
+
79
+
80
+
81
+ board = "1021,1000"
82
+ CrossFlipSolver.new(board).solve!
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/z3"
4
+ require "pathname"
5
+
6
+ # From hacker.org
7
+ class MortalCoilSolver
8
+ def initialize(path)
9
+ data = Pathname(path).read
10
+ data = data.strip.split("\n").map do |line|
11
+ line.chars.map{|c| c == "x" ? true : false}
12
+ end
13
+ @data = data
14
+ @solver = Z3::Solver.new
15
+ @size_y = @data.size
16
+ @size_x = @data[0].size
17
+ @empty_count = @data.flatten.count{|x| !x}
18
+ end
19
+
20
+ def setup_vars!
21
+ empty_cell_vars = []
22
+ @vars = {}
23
+ (0...@size_x).each do |x|
24
+ (0...@size_y).each do |y|
25
+ v = Z3.Int("c#{x},#{y}")
26
+ if @data[y][x]
27
+ @solver.assert v == -1
28
+ else
29
+ @solver.assert v >= 1
30
+ @solver.assert v <= @empty_count
31
+ empty_cell_vars << v
32
+ end
33
+ @vars[[x,y]] = v
34
+ end
35
+ end
36
+ @solver.assert Z3.Distinct(*empty_cell_vars)
37
+ end
38
+
39
+ def line_continuity!
40
+ (0...@size_y).each do |y|
41
+ (0...@size_x).each do |x|
42
+ v = @vars[[x,y]]
43
+ neighbours = [[x-1,y], [x+1,y], [x,y-1], [x,y+1]].map{|a,b| @vars[[a,b]]}.compact
44
+ @solver.assert Z3.Or(v == -1, v == @empty_count, *neighbours.map{|n| n == v+1})
45
+ @solver.assert Z3.Or(v == -1, v == 1, *neighbours.map{|n| n == v-1})
46
+ end
47
+ end
48
+ end
49
+
50
+ def keep_going!(a,b,c)
51
+ # If line goes from a to b (b == a+1), then it must:
52
+ # * hit a wall (c == nil)
53
+ # * hit a filled block (c == -1)
54
+ # * continue (c == b+1)
55
+ # * hit earlier line (c < b)
56
+ return unless a and c
57
+ @solver.assert (b == a+1).implies(Z3.Or(
58
+ c == -1,
59
+ c == b+1,
60
+ c < b,
61
+ )
62
+ )
63
+ end
64
+
65
+ def line_goes_until_it_hits_something!
66
+ (0...@size_y).each do |y|
67
+ (0...@size_x).each do |x|
68
+ v = @vars[[x,y]]
69
+ left = @vars[[x-1,y]]
70
+ right = @vars[[x+1,y]]
71
+ up = @vars[[x,y-1]]
72
+ down = @vars[[x,y+1]]
73
+ keep_going! left, v, right
74
+ keep_going! right, v, left
75
+ keep_going! up, v, down
76
+ keep_going! down, v, up
77
+ end
78
+ end
79
+ end
80
+
81
+ def solve!
82
+ setup_vars!
83
+ line_continuity!
84
+ line_goes_until_it_hits_something!
85
+
86
+ if @solver.satisfiable?
87
+ model = @solver.model
88
+ (0...@size_y).each do |y|
89
+ puts (0...@size_x).map{|x|
90
+ v = model[@vars[[x,y]]].to_i
91
+ if v == -1
92
+ "###"
93
+ else
94
+ "% 3d" % v
95
+ end
96
+ }.join(" ")
97
+ end
98
+ else
99
+ puts "Puzzle has no solution"
100
+ end
101
+ end
102
+ end
103
+
104
+ path = ARGV[0] || Pathname(__dir__) + "mortal_coil_puzzle-9.txt"
105
+ MortalCoilSolver.new(path).solve!
@@ -0,0 +1,6 @@
1
+ ---x--
2
+ -x-x--
3
+ ------
4
+ ----x-
5
+ -x--x-
6
+ ------
data/examples/oneofus ADDED
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "z3"
4
+ require "pry"
5
+
6
+ class OneofusSolver
7
+ def initialize
8
+ @x_size = 3
9
+ @y_size = 3
10
+ @board_str = "CbBbCbAaCbCaCbAbCb"
11
+ @solver = Z3::Solver.new
12
+
13
+ @board = (0...@x_size).map{|x|
14
+ (0...@y_size).map{|y| @board_str[(y * @x_size+x) * 2, 2] }
15
+ }.transpose
16
+ end
17
+
18
+ def print_board
19
+ 3.times do |y|
20
+ puts 3.times.map{|x|
21
+ cell = @board[y][x]
22
+ shape = {"C" => "Triangle", "B" => "Circle", "A" => "Square"}[cell[0]]
23
+ color = {"b" => "grey", "a"=> "purple"}[cell[1]]
24
+ "#{color} #{shape}"
25
+ }.join(" ")
26
+ end
27
+ end
28
+
29
+ def shape_number(x,y)
30
+ cell = @board[y][x]
31
+ {"C" => 2, "B" => 1, "A" => 0}[cell[0]]
32
+ end
33
+
34
+ def color_number(x,y)
35
+ cell = @board[y][x]
36
+ {"b" => 1, "a"=> 0}[cell[1]]
37
+ end
38
+
39
+ def run!
40
+ print_board
41
+ puts ""
42
+
43
+ 9.times do |i|
44
+ cx = Z3.Int("click_#{i}_x")
45
+ cy = Z3.Int("click_#{i}_y")
46
+ cc = Z3.Int("click_#{i}_color")
47
+ cs = Z3.Int("click_#{i}_shape")
48
+ @solver.assert (cx >= 0) & (cx <= 2)
49
+ @solver.assert (cy >= 0) & (cy <= 2)
50
+ cn = Z3.Int("click_#{i}_n")
51
+ @solver.assert cn == (3 * cx + cy)
52
+ end
53
+ @solver.assert Z3.Distinct(
54
+ *9.times.map{|i|
55
+ Z3.Int("click_#{i}_n")
56
+ }
57
+ )
58
+ 8.times do |i|
59
+ cx = Z3.Int("click_#{i}_x")
60
+ cy = Z3.Int("click_#{i}_y")
61
+ cc = Z3.Int("click_#{i}_color")
62
+ cs = Z3.Int("click_#{i}_shape")
63
+ cx_next = Z3.Int("click_#{i+1}_x")
64
+ cy_next = Z3.Int("click_#{i+1}_y")
65
+ cc_next = Z3.Int("click_#{i+1}_color")
66
+ cs_next = Z3.Int("click_#{i+1}_shape")
67
+ @solver.assert (cx == cx_next) | (cy == cy_next)
68
+ @solver.assert (cc == cc_next) | (cs == cs_next)
69
+ end
70
+ 9.times do |i|
71
+ 3.times do |y|
72
+ 3.times do |x|
73
+ cx = Z3.Int("click_#{i}_x")
74
+ cy = Z3.Int("click_#{i}_y")
75
+ cc = Z3.Int("click_#{i}_color")
76
+ cs = Z3.Int("click_#{i}_shape")
77
+ @solver.assert (
78
+ (cx == x) & (cy == y)
79
+ ).implies(
80
+ (cs == shape_number(x,y)) & (cc == color_number(x,y))
81
+ )
82
+ end
83
+ end
84
+ end
85
+
86
+ print_solution
87
+ end
88
+
89
+ def print_solution
90
+ raise "FAIL" unless @solver.satisfiable?
91
+ model = @solver.model
92
+ 9.times do |i|
93
+ cx = model[Z3.Int("click_#{i}_x")].to_i
94
+ cy = model[Z3.Int("click_#{i}_y")].to_i
95
+ cc = ["purple", "grey"][model[Z3.Int("click_#{i}_color")].to_i]
96
+ cs = ["Square", "Circle", "Triangle"][model[Z3.Int("click_#{i}_shape")].to_i]
97
+ puts "Click #{i}: #{cx},#{cy} - #{cc} #{cs}"
98
+ end
99
+ end
100
+ end
101
+
102
+ OneofusSolver.new.run!
@@ -0,0 +1,5 @@
1
+ [^SPEAK]+
2
+ EP|IP|EF
3
+
4
+ HE|LL|O+
5
+ [PLEASE]+
@@ -0,0 +1,5 @@
1
+ (A|B|C)\1
2
+ (AB|OE|SK)
3
+
4
+ .*M?O.*
5
+ (AN|FE|BE)
@@ -0,0 +1,5 @@
1
+ [COBRA]+
2
+ (AB|O|OR)+
3
+
4
+ (.)+\1
5
+ [^ABRC]+
@@ -0,0 +1,5 @@
1
+ .?.+
2
+ .+
3
+
4
+ [*]+
5
+ /+
@@ -0,0 +1,5 @@
1
+ \d[2480]
2
+ 56|94|73
3
+
4
+ 18|19|20
5
+ [6789]\d
@@ -0,0 +1,9 @@
1
+ (FI|A)+
2
+ (YE|OT)K
3
+ (.)[IF]+
4
+ [NODE]+
5
+ (FY|F|RG)+
6
+
7
+ (Y|F)(.)\2[DAF]\1
8
+ (U|O|I)*T[FRO]+
9
+ [KANE]*[GIN]*
@@ -0,0 +1,11 @@
1
+ [ARK]*O.*
2
+ (.).*\1N\1
3
+ (SOD|DO|GE)*
4
+ [FAXUS]*
5
+ [LOPITY]*
6
+
7
+ [UGLER]*
8
+ [CAST]*REX[PEA]*
9
+ [SIRES]*
10
+ (L|OFT|ON)*
11
+ H*(AY|ED)*
@@ -0,0 +1,11 @@
1
+ [ABC]*(.)\1(ME|UO)
2
+ (.)T*E*\1
3
+ [HAS]*(SN|PA)
4
+ (WE|GA|AL)T*O+
5
+ (EG|BEEE)[WIQ]*
6
+
7
+ [QA].[WEST]*
8
+ (HE|RT|TK)*.
9
+ (RE|QR)[QUART]*
10
+ [EUW]*S[RITE]*
11
+ (.)(.)\2\1[WE]
@@ -0,0 +1,11 @@
1
+ (!!|\?!|;\?)*\?
2
+ [<!?*#>]*
3
+ [?*!].\?[!<*]*
4
+ (.)[?#]\1\?\1
5
+ [<?>][\[!]*[?\|#]
6
+
7
+ [#?]!*[:?;]
8
+ (.)([;?])\1\2\1
9
+ ["!"]*\?!*#?
10
+ .[;?][>!?]*
11
+ (.)[;!"]*!\1
@@ -0,0 +1,11 @@
1
+ [RUTH]*(OE|EO)[RB]*
2
+ (BG|ON|KK)+[RIF]+
3
+ (MN|BO|FI)[EU]{2,}
4
+ (KT|AL|ET)+G
5
+ [OH](PR|AX|TR)+
6
+
7
+ [IT](O)*(BE|AD)*\1
8
+ [NORMAL]+T{2}
9
+ .*(XA|BE).*
10
+ (EG|UL){2}[ALF]*
11
+ [REQ]*(G|P)(.)+
@@ -0,0 +1,3 @@
1
+ A|B
2
+
3
+ A|Z
@@ -0,0 +1,3 @@
1
+ [ABC]
2
+
3
+ [BDF]
@@ -0,0 +1,3 @@
1
+ [^AB]
2
+
3
+ [ABC]
@@ -0,0 +1,4 @@
1
+ A*
2
+
3
+ A
4
+ AB*
@@ -0,0 +1,4 @@
1
+ A?B?
2
+
3
+ A|C
4
+ B
@@ -0,0 +1,4 @@
1
+ A+
2
+
3
+ A|B
4
+ A|Z
@@ -0,0 +1,4 @@
1
+ (A)\1
2
+
3
+ A|B
4
+ A|B
@@ -0,0 +1,4 @@
1
+ A{2,}
2
+
3
+ A{1}
4
+ B|A
@@ -0,0 +1,3 @@
1
+ A|\s
2
+
3
+ \s
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/z3"
4
+ require "pathname"
5
+ require_relative "simple_regexp_parser"
6
+ require_relative "regexp_string_matcher"
7
+
8
+ class RegexpCrosswordSolver
9
+ def initialize(path)
10
+ data = Pathname(path).read
11
+ data = data.strip.split("\n")
12
+ col_number = data.index("")
13
+ row_number = data.size - col_number - 1
14
+ # left to right
15
+ @cols = data.first(col_number)
16
+ # top to bottom
17
+ @rows = data.last(row_number)
18
+ @solver = Z3::Solver.new
19
+ @xsize = @cols.size
20
+ @ysize = @rows.size
21
+ end
22
+
23
+ def setup_cell(x,y)
24
+ v = Z3.Int("cell#{x},#{y}")
25
+ @solver.assert v >= 0
26
+ @solver.assert v <= 127
27
+ v
28
+ end
29
+
30
+ def col_str(x)
31
+ (0...@ysize).map{|y| @crossword[[x,y]]}
32
+ end
33
+
34
+ def row_str(y)
35
+ (0...@xsize).map{|x| @crossword[[x,y]]}
36
+ end
37
+
38
+ def col_rx(x)
39
+ SimpleRegexpParser.new(@cols[x], "col-#{x}").parse
40
+ end
41
+
42
+ def row_rx(y)
43
+ SimpleRegexpParser.new(@rows[y], "row-#{y}").parse
44
+ end
45
+
46
+ def solve!
47
+ @crossword = {}
48
+ @xsize.times do |x|
49
+ @ysize.times do |y|
50
+ @crossword[[x,y]] = setup_cell(x,y)
51
+ end
52
+ end
53
+
54
+ @xsize.times do |x|
55
+ RegexpStringMatcher.new(@solver, col_rx(x), col_str(x), "col-#{x}").run!
56
+ end
57
+ @ysize.times do |y|
58
+ RegexpStringMatcher.new(@solver, row_rx(y), row_str(y), "row-#{y}").run!
59
+ end
60
+
61
+ print_answer
62
+ end
63
+
64
+ def print_answer
65
+ if @solver.satisfiable?
66
+ @model = @solver.model
67
+ @ysize.times do |y|
68
+ puts @xsize.times.map{|x|
69
+ @model[@crossword[[x,y]]].to_i.chr
70
+ }.join.inspect[1...-1]
71
+ end
72
+ else
73
+ raise
74
+ print "Crossword has no solution"
75
+ end
76
+ end
77
+ end
78
+
79
+ path = ARGV[0] || Pathname(__dir__) + "regexp_crossword/tutorial-1.txt"
80
+ RegexpCrosswordSolver.new(path).solve!
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/z3"
4
+ require_relative "simple_regexp_parser"
5
+ require_relative "regexp_string_matcher"
6
+
7
+ class RegexpSolver
8
+ def initialize(length, rx)
9
+ @length = length
10
+ @rx = SimpleRegexpParser.new(rx, "").parse
11
+ @solver = Z3::Solver.new
12
+ end
13
+
14
+ def solve!
15
+ @str = (0...@length).map do |i|
16
+ v = Z3.Int("char[#{i}]")
17
+ @solver.assert v >= 0
18
+ @solver.assert v <= 127
19
+ v
20
+ end
21
+
22
+ RegexpStringMatcher.new(@solver, @rx, @str, "").run!
23
+ print_answer
24
+ end
25
+
26
+ def print_answer
27
+ if @solver.satisfiable?
28
+ @model = @solver.model
29
+ puts @str.map{|c| @model[c].to_i.chr }.join.inspect[1...-1]
30
+ else
31
+ raise
32
+ print "Crossword has no solution"
33
+ end
34
+ end
35
+ end
36
+
37
+ unless ARGV.size == 2
38
+ STDERR.puts "Usage: #{$0} <length> <regexp>"
39
+ exit 1
40
+ end
41
+
42
+ length = ARGV[0].to_i
43
+ regexp = ARGV[1]
44
+ RegexpSolver.new(length, regexp).solve!
@@ -0,0 +1,124 @@
1
+ require "set"
2
+
3
+ class RegexpStringMatcher
4
+ # rx comes from SimpleRegexpParser
5
+ # str is a bunch of Z3.Ints with proper constraints (0..127)
6
+ def initialize(solver, rx, str, context)
7
+ @solver = solver
8
+ @rx = rx
9
+ @str = str
10
+ @context = context
11
+ @subexprs = Set[]
12
+ end
13
+
14
+ def group_equal(group_identifier, start_idx, end_idx)
15
+ str = @str[start_idx...end_idx]
16
+ Z3.And(
17
+ Z3.Int("group-#{group_identifier}-size") == str.size,
18
+ *str.each_with_index.map{|s,i| Z3.Int("group-#{group_identifier}-char[#{i}]") == s }
19
+ )
20
+ end
21
+
22
+ def anchor_match(anchor, start_idx)
23
+ case anchor[0]
24
+ when :bos
25
+ start_idx == 0
26
+ when :eos
27
+ start_idx == @str.size
28
+ when :bol
29
+ if start_idx == 0
30
+ true
31
+ else
32
+ @str[start_idx-1] == 10
33
+ end
34
+ when :eol
35
+ # This is actually tricky
36
+ # $ matches both before and after \n
37
+ if start_idx == @str.size
38
+ true
39
+ elsif start_idx == 0
40
+ (@str[start_idx] == 10)
41
+ else
42
+ (@str[start_idx] == 10) | (@str[start_idx-1] == 10)
43
+ end
44
+ when :lookahead
45
+ regexp_match([:seq, anchor[1], [:any_star]], start_idx, @str.size)
46
+ when :negative_lookahead
47
+ ~regexp_match([:seq, anchor[1], [:any_star]], start_idx, @str.size)
48
+ when :lookbehind
49
+ regexp_match([:seq, [:any_star], anchor[1]], 0, start_idx)
50
+ when :lookbehind
51
+ ~regexp_match([:seq, [:any_star], anchor[1]], 0, start_idx)
52
+ else
53
+ raise "Unknown anchor type #{anchor[0]}"
54
+ end
55
+ end
56
+
57
+ # @subexprs just prevents loops for self-referential regexps
58
+ def regexp_match(rx, start_idx, end_idx)
59
+ str = @str[start_idx...end_idx]
60
+ var = Z3.Bool("#{rx} =~ #{@context}[#{start_idx}...#{end_idx}]")
61
+ return var if @subexprs.include?(var)
62
+ @subexprs << var
63
+
64
+ expr = case rx[0]
65
+ when :seq
66
+ rx_a, rx_b = rx[1], rx[2]
67
+ possibilities = (start_idx..end_idx).map do |i|
68
+ a_ok = regexp_match(rx_a, start_idx, i)
69
+ b_ok = regexp_match(rx_b, i, end_idx)
70
+ a_ok & b_ok
71
+ end
72
+ Z3.Or(*possibilities)
73
+ when :set
74
+ if str.size == 1
75
+ Z3.Or(*rx[1].map{|c| str[0] == c })
76
+ else
77
+ false
78
+ end
79
+ when :neg_set
80
+ if str.size == 1
81
+ ~Z3.Or(*rx[1].map{|c| str[0] == c })
82
+ else
83
+ false
84
+ end
85
+ when :alt
86
+ rx_a, rx_b = rx[1], rx[2]
87
+ regexp_match(rx_a, start_idx, end_idx) | regexp_match(rx_b, start_idx, end_idx)
88
+ when :star
89
+ if str.size == 0
90
+ true
91
+ else
92
+ r = rx[1]
93
+ possibilities = (start_idx+1..end_idx).map do |i|
94
+ regexp_match(r, start_idx, i) & regexp_match(rx, i, end_idx)
95
+ end
96
+ Z3.Or(*possibilities)
97
+ end
98
+ when :group
99
+ n = rx[1]
100
+ rx_a = rx[2]
101
+ regexp_match(rx_a, start_idx, end_idx) & group_equal(n, start_idx, end_idx)
102
+ when :backref
103
+ group_equal(rx[1], start_idx, end_idx)
104
+ when :empty
105
+ (str.size == 0)
106
+ when :anchor
107
+ if str.size == 0
108
+ anchor_match(rx[1..-1], start_idx)
109
+ else
110
+ false
111
+ end
112
+ when :any_star # like /.*/m or /.*/s depending on language
113
+ true
114
+ else
115
+ raise "Unknown regexp type #{rx[0]}"
116
+ end
117
+ @solver.assert (var == expr).simplify
118
+ var
119
+ end
120
+
121
+ def run!
122
+ @solver.assert regexp_match(@rx, 0, @str.length)
123
+ end
124
+ end
@@ -0,0 +1,187 @@
1
+ require "regexp_parser"
2
+
3
+ # Format generated by Regexp::Parser is not convenient at all,
4
+ # let's translate it into something more useful
5
+ class SimpleRegexpParser
6
+ def initialize(str, context)
7
+ @tree = Regexp::Parser.parse(str)
8
+ @context = context
9
+ @group_number = 0
10
+ end
11
+
12
+ def new_group
13
+ @group_number += 1
14
+ "#{@context}-#{@group_number}"
15
+ end
16
+
17
+ def sequence(*parts)
18
+ parts = parts.select{|x| x[0] != :empty}
19
+ case parts.size
20
+ when 0
21
+ [:empty]
22
+ when 1
23
+ parts[0]
24
+ else
25
+ while parts.size > 1
26
+ parts.unshift [:seq, parts.shift, parts.shift]
27
+ end
28
+ parts[0]
29
+ end
30
+ end
31
+
32
+ def alternative(*parts)
33
+ case parts.size
34
+ when 0
35
+ raise "Can't have empty alternative"
36
+ when 1
37
+ parts[0]
38
+ else
39
+ while parts.size > 1
40
+ parts.unshift [:alt, parts.shift, parts.shift]
41
+ end
42
+ parts[0]
43
+ end
44
+ end
45
+
46
+ # Saves us time to reuse ruby regexp engine for 1 character case
47
+ def character_type(char_rx)
48
+ char_rx = Regexp.new(char_rx)
49
+ codes = (0..127).select{|c| c.chr =~ char_rx}
50
+ # This is mostly here to make debugging easier
51
+ if codes.size > 127-codes.size
52
+ [:neg_set, (0..127).to_a - codes]
53
+ else
54
+ [:set, codes]
55
+ end
56
+ end
57
+
58
+ def character_set(negated, members)
59
+ if negated
60
+ character_type "[^#{members.join}]"
61
+ else
62
+ character_type "[#{members.join}]"
63
+ end
64
+ end
65
+
66
+ def literal(chars)
67
+ sequence(*chars.map{|c| character_type(c)})
68
+ end
69
+
70
+ def star(part)
71
+ [:star, part]
72
+ end
73
+
74
+ def backref(num)
75
+ [:backref, "#{@context}-#{num}"]
76
+ end
77
+
78
+ def group(number, part)
79
+ [:group, number, part]
80
+ end
81
+
82
+ def empty
83
+ [:empty]
84
+ end
85
+
86
+ def repeat(part, min, max)
87
+ if max == -1
88
+ sequence(star(part), *([part]*min))
89
+ else
90
+ maybe_part = alternative([:empty], part)
91
+ sequence(*([part]*min), *([maybe_part] * (max-min)))
92
+ end
93
+ end
94
+
95
+ # Groups and qualifiers interact in weird ways, (a){3} is actually aa(a)
96
+ # We need to do extensive rewriting to make it work
97
+ def repeat_group(part, min, max)
98
+ base = part[2]
99
+ if max == -1
100
+ if min == 0 # (a)* -> |a*(a)
101
+ alternative(
102
+ empty,
103
+ sequence(repeat(base, min, max), part)
104
+ )
105
+ else # (a){2,} -> a{1,}(a)
106
+ sequence(repeat(base, min-1, max), part)
107
+ end
108
+ elsif max == 0 # a{0} -> empty, not really a thing
109
+ :empty
110
+ else
111
+ if min == 0
112
+ # (a){2,3} -> |a{1,2}(a)
113
+ alternative(
114
+ empty,
115
+ sequence(repeat(base, min, max-1), part)
116
+ )
117
+ else # (a){2,3} -> a{1,2}(a)
118
+ sequence(repeat(base, min-1, max-1), part)
119
+ end
120
+ end
121
+ end
122
+
123
+ # Try to express regexps with minimum number of primitives:
124
+ # * seq - ab
125
+ # * alt - a|b
126
+ # * star - a*
127
+ # * set - a [a-z] [^a-z]
128
+ # * empty
129
+ # * backref - \1
130
+ # * group - (a)
131
+ def parse(node=@tree)
132
+ result = case node
133
+ when Regexp::Expression::Group::Capture
134
+ # Assumes it's going to be parsed in right order
135
+ group(new_group, sequence(*node.expressions.map{|n| parse(n)}))
136
+ when Regexp::Expression::Alternation
137
+ alternative(*node.expressions.map{|n| parse(n)})
138
+ when Regexp::Expression::Assertion::Lookahead
139
+ [:anchor, :lookahead, sequence(*node.expressions.map{|n| parse(n)})]
140
+ when Regexp::Expression::Assertion::NegativeLookahead
141
+ [:anchor, :negative_lookahead, sequence(*node.expressions.map{|n| parse(n)})]
142
+ when Regexp::Expression::Assertion::Lookbehind
143
+ [:anchor, :lookbehind, sequence(*node.expressions.map{|n| parse(n)})]
144
+ when Regexp::Expression::Assertion::NegativeLookbehind
145
+ [:anchor, :negative_lookbehind, sequence(*node.expressions.map{|n| parse(n)})]
146
+ when Regexp::Expression::Subexpression
147
+ # It's annoyingly subtypes a lot
148
+ raise unless node.class == Regexp::Expression::Subexpression or
149
+ node.class == Regexp::Expression::Group::Passive or
150
+ node.class == Regexp::Expression::Root or
151
+ node.class == Regexp::Expression::Alternative
152
+ sequence(*node.expressions.map{|n| parse(n)})
153
+ when Regexp::Expression::CharacterSet
154
+ character_set(node.negative?, node.members)
155
+ when Regexp::Expression::Literal
156
+ literal(node.text.chars)
157
+ when Regexp::Expression::CharacterType::Base
158
+ character_type(node.text)
159
+ when Regexp::Expression::EscapeSequence::Base
160
+ character_type(node.text)
161
+ when Regexp::Expression::Backreference::Number
162
+ num = node.text[%r[\A\\(\d+)\z], 1] or raise "Parse error"
163
+ backref(num.to_i)
164
+ when Regexp::Expression::Anchor::BeginningOfString
165
+ [:anchor, :bos]
166
+ when Regexp::Expression::Anchor::EndOfString
167
+ [:anchor, :eos]
168
+ when Regexp::Expression::Anchor::BeginningOfLine
169
+ [:anchor, :bol]
170
+ when Regexp::Expression::Anchor::EndOfLine
171
+ [:anchor, :eol]
172
+ else
173
+ raise "Unknown expression"
174
+ end
175
+ if node.quantified?
176
+ min = node.quantifier.min
177
+ max = node.quantifier.max
178
+ result = if result[0] == :group
179
+ repeat_group(result, min, max)
180
+ else
181
+ repeat(result, min, max)
182
+ end
183
+ end
184
+
185
+ result
186
+ end
187
+ end
@@ -4,6 +4,10 @@ module Z3
4
4
  sort.new(LowLevel.mk_bvnot(self))
5
5
  end
6
6
 
7
+ def !
8
+ sort.new(LowLevel.mk_bvnot(self))
9
+ end
10
+
7
11
  def -@
8
12
  sort.new(LowLevel.mk_bvneg(self))
9
13
  end
@@ -4,6 +4,10 @@ module Z3
4
4
  sort.new(LowLevel.mk_not(self))
5
5
  end
6
6
 
7
+ def !
8
+ sort.new(LowLevel.mk_not(self))
9
+ end
10
+
7
11
  def &(other)
8
12
  Expr.And(self, other)
9
13
  end
data/lib/z3/probe.rb CHANGED
@@ -20,6 +20,10 @@ module Z3
20
20
  Probe.new LowLevel.probe_not(self)
21
21
  end
22
22
 
23
+ def !
24
+ Probe.new LowLevel.probe_not(self)
25
+ end
26
+
23
27
  def ==(other)
24
28
  raise Z3::Exception, "Probe required" unless other.is_a?(Probe)
25
29
  Probe.new LowLevel.probe_eq(self, other)
@@ -63,8 +63,9 @@ module Z3
63
63
  expect([a == 50, b == -a]).to have_solution(b => 206)
64
64
  end
65
65
 
66
- it "~" do
66
+ it "~ and !" do
67
67
  expect([a == 50, b == ~a]).to have_solution(b => 205)
68
+ expect([a == 50, b == !a]).to have_solution(b => 205)
68
69
  end
69
70
 
70
71
  it ">> (sign-dependent)" do
@@ -54,9 +54,11 @@ module Z3
54
54
  expect([a == false, b == false, c == (a == b)]).to have_solution(c => true)
55
55
  end
56
56
 
57
- it "~" do
57
+ it "~ and !" do
58
58
  expect([a == true, b == ~a]).to have_solution(b => false)
59
59
  expect([a == false, b == ~a]).to have_solution(b => true)
60
+ expect([a == true, b == !a]).to have_solution(b => false)
61
+ expect([a == false, b == !a]).to have_solution(b => true)
60
62
  end
61
63
 
62
64
  it "if then else" do
@@ -0,0 +1,8 @@
1
+ describe "CrossFlip" do
2
+ it do
3
+ expect("crossflip").to have_output <<EOF
4
+ [x] [ ] [ ] [ ]
5
+ [ ] [x] [ ] [x]
6
+ EOF
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ describe "Mortal Coil Puzzle" do
2
+ it do
3
+ expect("mortal_coil_puzzle").to have_output <<EOF
4
+ 22 21 20 ### 14 13
5
+ 23 ### 19 ### 15 12
6
+ 24 1 18 17 16 11
7
+ 25 2 3 4 ### 10
8
+ 26 ### 30 5 ### 9
9
+ 27 28 29 6 7 8
10
+ EOF
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ describe "OneOfUs" do
2
+ it do
3
+ expect("oneofus").to have_output <<EOF
4
+ grey Triangle grey Circle grey Triangle
5
+ purple Square grey Triangle purple Triangle
6
+ grey Triangle grey Square grey Triangle
7
+
8
+ Click 0: 0,1 - purple Square
9
+ Click 1: 2,1 - purple Triangle
10
+ Click 2: 2,2 - grey Triangle
11
+ Click 3: 1,2 - grey Square
12
+ Click 4: 1,1 - grey Triangle
13
+ Click 5: 1,0 - grey Circle
14
+ Click 6: 2,0 - grey Triangle
15
+ Click 7: 0,0 - grey Triangle
16
+ Click 8: 0,2 - grey Triangle
17
+ EOF
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ describe "Regexp Crossword Solver" do
2
+ let(:binary) { Pathname(__dir__) + "../../examples/regexp_crossword_solver" }
3
+
4
+ examples = (Pathname(__dir__) + "../../examples/regexp_crossword")
5
+ .children
6
+ .select{|x| x.extname == ".txt"}
7
+ examples.each do |example_path|
8
+ describe example_path.basename.to_s do
9
+ let(:example) { example_path.readlines.map(&:chomp) }
10
+ let(:col_number) { example.index("") }
11
+ let(:row_number) { example.size - col_number - 1 }
12
+ # left to right
13
+ let(:cols) { example.first(col_number) }
14
+ # top to bottom
15
+ let(:rows) { example.last(row_number) }
16
+
17
+ it "matches output" do
18
+ output_rows = `#{binary} #{example_path}`.split("\n").map(&:chomp)
19
+ output_cols = output_rows.map(&:chars).transpose.map(&:join)
20
+ expect(output_rows.size).to eq(rows.size)
21
+ expect(output_cols.size).to eq(cols.size)
22
+ output_rows.zip(rows).each do |s, rx|
23
+ expect(s).to match Regexp.new(/\A(?:#{rx})\z/)
24
+ end
25
+ output_cols.zip(cols).each do |s, rx|
26
+ expect(s).to match Regexp.new(/\A(?:#{rx})\z/)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ require "yaml"
2
+
3
+ describe "Regexp Solver" do
4
+ let(:binary) { Pathname(__dir__) + "../../examples/regexp_solver" }
5
+
6
+ examples = [
7
+ [3 ,'lol'],
8
+ [3, '\d\d\d'],
9
+ [4, 'a*\Ab*\zc*'],
10
+ [2, '^\n^\n'],
11
+ [1, '(?=[ab])(?=[bc])[bd]'],
12
+ [1, '(?=[ab])(?![bc])[ac]'],
13
+ [2, '(?=a$)a\n'],
14
+ [2, '(?=a\s$)a\n'],
15
+ ]
16
+
17
+ examples.each do |length, rx|
18
+ describe rx do
19
+ it "matches output" do
20
+ actual_esc = `#{binary} #{length} '#{rx}'`.chomp
21
+ actual = YAML.load(%Q(---\n"#{actual_esc}"\n)) # avoid eval
22
+ expect(actual.size).to eq(length)
23
+ expect(actual).to match Regexp.new(/\A(?:#{rx})\z/)
24
+ end
25
+ end
26
+ end
27
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: z3
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.20161010
4
+ version: 0.0.20161117
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomasz Wegrzanowski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-10 00:00:00.000000000 Z
11
+ date: 2016-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: regexp_parser
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: ffi
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -97,6 +111,7 @@ files:
97
111
  - examples/bridges-1.txt
98
112
  - examples/circuit_problems
99
113
  - examples/clogic_puzzle
114
+ - examples/crossflip
100
115
  - examples/four_hackers_puzzle
101
116
  - examples/geometry_problem
102
117
  - examples/kakuro
@@ -109,8 +124,34 @@ files:
109
124
  - examples/light_up-1.txt
110
125
  - examples/minisudoku
111
126
  - examples/minisudoku-1.txt
127
+ - examples/mortal_coil_puzzle
128
+ - examples/mortal_coil_puzzle-9.txt
112
129
  - examples/nonogram
130
+ - examples/oneofus
131
+ - examples/regexp_crossword/beginner-1.txt
132
+ - examples/regexp_crossword/beginner-2.txt
133
+ - examples/regexp_crossword/beginner-3.txt
134
+ - examples/regexp_crossword/beginner-4.txt
135
+ - examples/regexp_crossword/beginner-5.txt
136
+ - examples/regexp_crossword/experienced-1.txt
137
+ - examples/regexp_crossword/experienced-2.txt
138
+ - examples/regexp_crossword/experienced-3.txt
139
+ - examples/regexp_crossword/experienced-4.txt
140
+ - examples/regexp_crossword/experienced-5.txt
141
+ - examples/regexp_crossword/tutorial-1.txt
142
+ - examples/regexp_crossword/tutorial-2.txt
143
+ - examples/regexp_crossword/tutorial-3.txt
144
+ - examples/regexp_crossword/tutorial-4.txt
145
+ - examples/regexp_crossword/tutorial-5.txt
146
+ - examples/regexp_crossword/tutorial-6.txt
147
+ - examples/regexp_crossword/tutorial-7.txt
148
+ - examples/regexp_crossword/tutorial-8.txt
149
+ - examples/regexp_crossword/tutorial-9.txt
150
+ - examples/regexp_crossword_solver
151
+ - examples/regexp_solver
152
+ - examples/regexp_string_matcher.rb
113
153
  - examples/selfref
154
+ - examples/simple_regexp_parser.rb
114
155
  - examples/sudoku
115
156
  - examples/sudoku-1.txt
116
157
  - examples/verbal_arithmetic
@@ -118,7 +159,6 @@ files:
118
159
  - lib/z3/ast.rb
119
160
  - lib/z3/context.rb
120
161
  - lib/z3/exception.rb
121
- - lib/z3/expr/.DS_Store
122
162
  - lib/z3/expr/arith_expr.rb
123
163
  - lib/z3/expr/array_expr.rb
124
164
  - lib/z3/expr/bitvec_expr.rb
@@ -138,7 +178,6 @@ files:
138
178
  - lib/z3/printer.rb
139
179
  - lib/z3/probe.rb
140
180
  - lib/z3/solver.rb
141
- - lib/z3/sort/.DS_Store
142
181
  - lib/z3/sort/array_sort.rb
143
182
  - lib/z3/sort/bitvec_sort.rb
144
183
  - lib/z3/sort/bool_sort.rb
@@ -151,7 +190,6 @@ files:
151
190
  - lib/z3/tactic.rb
152
191
  - lib/z3/very_low_level.rb
153
192
  - lib/z3/very_low_level_auto.rb
154
- - spec/.DS_Store
155
193
  - spec/array_expr_spec.rb
156
194
  - spec/array_sort_spec.rb
157
195
  - spec/bitvec_expr_spec.rb
@@ -171,6 +209,7 @@ files:
171
209
  - spec/integration/bit_tricks_spec.rb
172
210
  - spec/integration/bridges_spec.rb
173
211
  - spec/integration/cicruit_problem_spec.rb
212
+ - spec/integration/crossflip_spec.rb
174
213
  - spec/integration/four_hackers_puzzle_spec.rb
175
214
  - spec/integration/geometry_problem_spec.rb
176
215
  - spec/integration/kakuro_spec.rb
@@ -179,7 +218,11 @@ files:
179
218
  - spec/integration/letter_connections_spec.rb
180
219
  - spec/integration/light_up_spec.rb
181
220
  - spec/integration/minisudoku_spec.rb
221
+ - spec/integration/mortal_coil_puzzle_spec.rb
182
222
  - spec/integration/nonogram_spec.rb
223
+ - spec/integration/oneofus_spec.rb
224
+ - spec/integration/regexp_crossword_solver_spec.rb
225
+ - spec/integration/regexp_solver_spec.rb
183
226
  - spec/integration/selfref_spec.rb
184
227
  - spec/integration/sudoku_spec.rb
185
228
  - spec/integration/verbal_arithmetic_spec.rb
Binary file
Binary file
data/spec/.DS_Store DELETED
Binary file