fear 0.11.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -1
  3. data/.rubocop.yml +18 -0
  4. data/.travis.yml +0 -3
  5. data/CHANGELOG.md +12 -1
  6. data/Gemfile +1 -0
  7. data/{gemfiles/dry_equalizer_0.2.1.gemfile.lock → Gemfile.lock} +21 -12
  8. data/README.md +594 -241
  9. data/Rakefile +166 -219
  10. data/benchmarks/README.md +1 -0
  11. data/benchmarks/dry_do_vs_fear_for.txt +11 -0
  12. data/benchmarks/dry_some_fmap_vs_fear_some_map.txt +11 -0
  13. data/benchmarks/factorial.txt +16 -0
  14. data/benchmarks/fear_gaurd_and1_vs_new.txt +13 -0
  15. data/benchmarks/fear_gaurd_and2_vs_and.txt +13 -0
  16. data/benchmarks/fear_gaurd_and3_vs_and_and.txt +13 -0
  17. data/benchmarks/fear_pattern_extracting_with_vs_without_cache.txt +11 -0
  18. data/benchmarks/fear_pattern_matching_construction_vs_execution.txt +13 -0
  19. data/benchmarks/pattern_matching_dry_vs_qo_vs_fear_try.txt +14 -0
  20. data/benchmarks/pattern_matching_qo_vs_fear_pattern_extraction.txt +11 -0
  21. data/benchmarks/pattern_matching_qo_vs_fear_try_execution.txt +11 -0
  22. data/examples/pattern_extracting.rb +15 -0
  23. data/examples/pattern_matching_binary_tree_set.rb +96 -0
  24. data/examples/pattern_matching_number_in_words.rb +54 -0
  25. data/fear.gemspec +4 -2
  26. data/lib/fear.rb +21 -4
  27. data/lib/fear/either.rb +77 -59
  28. data/lib/fear/either_api.rb +21 -0
  29. data/lib/fear/empty_partial_function.rb +1 -1
  30. data/lib/fear/extractor.rb +108 -0
  31. data/lib/fear/extractor/anonymous_array_splat_matcher.rb +8 -0
  32. data/lib/fear/extractor/any_matcher.rb +15 -0
  33. data/lib/fear/extractor/array_head_matcher.rb +34 -0
  34. data/lib/fear/extractor/array_matcher.rb +38 -0
  35. data/lib/fear/extractor/array_splat_matcher.rb +14 -0
  36. data/lib/fear/extractor/empty_list_matcher.rb +18 -0
  37. data/lib/fear/extractor/extractor_matcher.rb +42 -0
  38. data/lib/fear/extractor/grammar.rb +201 -0
  39. data/lib/fear/extractor/grammar.treetop +129 -0
  40. data/lib/fear/extractor/identifier_matcher.rb +16 -0
  41. data/lib/fear/extractor/matcher.rb +54 -0
  42. data/lib/fear/extractor/matcher/and.rb +36 -0
  43. data/lib/fear/extractor/named_array_splat_matcher.rb +15 -0
  44. data/lib/fear/extractor/pattern.rb +55 -0
  45. data/lib/fear/extractor/typed_identifier_matcher.rb +24 -0
  46. data/lib/fear/extractor/value_matcher.rb +17 -0
  47. data/lib/fear/extractor_api.rb +33 -0
  48. data/lib/fear/failure.rb +32 -10
  49. data/lib/fear/for.rb +14 -69
  50. data/lib/fear/for_api.rb +66 -0
  51. data/lib/fear/future.rb +414 -0
  52. data/lib/fear/future_api.rb +19 -0
  53. data/lib/fear/left.rb +8 -0
  54. data/lib/fear/none.rb +17 -8
  55. data/lib/fear/option.rb +55 -49
  56. data/lib/fear/option_api.rb +38 -0
  57. data/lib/fear/partial_function.rb +9 -12
  58. data/lib/fear/partial_function/empty.rb +1 -1
  59. data/lib/fear/partial_function/guard.rb +8 -20
  60. data/lib/fear/partial_function/lifted.rb +1 -0
  61. data/lib/fear/partial_function_class.rb +10 -0
  62. data/lib/fear/pattern_match.rb +10 -0
  63. data/lib/fear/pattern_matching_api.rb +35 -11
  64. data/lib/fear/promise.rb +87 -0
  65. data/lib/fear/right.rb +8 -0
  66. data/lib/fear/some.rb +22 -3
  67. data/lib/fear/success.rb +22 -1
  68. data/lib/fear/try.rb +82 -67
  69. data/lib/fear/try_api.rb +31 -0
  70. data/lib/fear/unit.rb +28 -0
  71. data/lib/fear/version.rb +1 -1
  72. data/spec/fear/done_spec.rb +3 -3
  73. data/spec/fear/either/mixin_spec.rb +15 -0
  74. data/spec/fear/either_pattern_match_spec.rb +10 -12
  75. data/spec/fear/extractor/array_matcher_spec.rb +228 -0
  76. data/spec/fear/extractor/extractor_matcher_spec.rb +151 -0
  77. data/spec/fear/extractor/grammar_array_spec.rb +23 -0
  78. data/spec/fear/extractor/identified_matcher_spec.rb +47 -0
  79. data/spec/fear/extractor/identifier_matcher_spec.rb +66 -0
  80. data/spec/fear/extractor/pattern_spec.rb +32 -0
  81. data/spec/fear/extractor/typed_identifier_matcher_spec.rb +62 -0
  82. data/spec/fear/extractor/value_matcher_number_spec.rb +77 -0
  83. data/spec/fear/extractor/value_matcher_string_spec.rb +86 -0
  84. data/spec/fear/extractor/value_matcher_symbol_spec.rb +69 -0
  85. data/spec/fear/extractor_api_spec.rb +113 -0
  86. data/spec/fear/extractor_spec.rb +59 -0
  87. data/spec/fear/failure_spec.rb +73 -13
  88. data/spec/fear/for_spec.rb +35 -35
  89. data/spec/fear/future_spec.rb +466 -0
  90. data/spec/fear/guard_spec.rb +4 -4
  91. data/spec/fear/left_spec.rb +40 -14
  92. data/spec/fear/none_spec.rb +28 -12
  93. data/spec/fear/option/mixin_spec.rb +37 -0
  94. data/spec/fear/option_pattern_match_spec.rb +7 -9
  95. data/spec/fear/partial_function_spec.rb +25 -3
  96. data/spec/fear/pattern_match_spec.rb +33 -1
  97. data/spec/fear/promise_spec.rb +94 -0
  98. data/spec/fear/right_spec.rb +37 -9
  99. data/spec/fear/some_spec.rb +32 -6
  100. data/spec/fear/success_spec.rb +32 -4
  101. data/spec/fear/try/mixin_spec.rb +17 -0
  102. data/spec/fear/try_pattern_match_spec.rb +8 -10
  103. data/spec/spec_helper.rb +1 -1
  104. metadata +115 -20
  105. data/Appraisals +0 -32
  106. data/gemfiles/dry_equalizer_0.1.0.gemfile +0 -8
  107. data/gemfiles/dry_equalizer_0.1.0.gemfile.lock +0 -82
  108. data/gemfiles/dry_equalizer_0.2.1.gemfile +0 -8
  109. data/lib/fear/done.rb +0 -22
  110. data/spec/fear/option_spec.rb +0 -15
@@ -0,0 +1,129 @@
1
+ # PEG grammar for pattern matching and extracting
2
+
3
+ module Fear::Extractor
4
+ grammar Grammar
5
+ rule matcher
6
+ identified_matcher / anonymous_matcher
7
+ end
8
+
9
+ rule identified_matcher
10
+ identifier space '@' space anonymous_matcher <IdentifiedMatcher>
11
+ end
12
+
13
+ rule anonymous_matcher
14
+ array / literal / identifier / extractor / type
15
+ end
16
+
17
+ rule array
18
+ '[' (non_empty_array / array_splat / empty_array) ']' <ArrayLiteral>
19
+ end
20
+
21
+ rule empty_array
22
+ '' <EmptyArray>
23
+ end
24
+
25
+ rule non_empty_array
26
+ array_head (array_tail / empty_array) <NonEmptyArray>
27
+ end
28
+
29
+ rule array_tail
30
+ ',' (non_empty_array / array_splat) <ArrayTail>
31
+ end
32
+
33
+ rule array_splat
34
+ space? (named_array_splat / anonymous_array_splat) space? <ArraySplat>
35
+ end
36
+
37
+ rule array_splat_identifier
38
+ space? anonymous_array_splat space? <ArraySplat>
39
+ end
40
+
41
+ rule anonymous_array_splat
42
+ '*' '_'? <AnonymousArraySplat>
43
+ end
44
+
45
+ rule named_array_splat
46
+ '*' [a-z] [a-z0-9_]* <NamedArraySplat>
47
+ end
48
+
49
+ rule array_head
50
+ space? anonymous_matcher space? <ArrayHead>
51
+ end
52
+
53
+ rule space
54
+ [\s]+
55
+ end
56
+
57
+ rule any
58
+ '_' <AnyIdentifier>
59
+ end
60
+
61
+ rule variable_identifier
62
+ [a-z_] [a-z0-9_]* <Identifier>
63
+ end
64
+
65
+ rule identifier
66
+ typed_identifier / any / variable_identifier
67
+ end
68
+
69
+ rule typed_identifier
70
+ (any / variable_identifier) ' : ' type <TypedIdentifier>
71
+ end
72
+
73
+ rule literal
74
+ nil / true / false / number / string / symbol
75
+ end
76
+
77
+ rule number
78
+ float / integer
79
+ end
80
+
81
+ rule integer
82
+ ('+' / '-')? [0-9]+ <IntegerLiteral>
83
+ end
84
+
85
+ rule float
86
+ ('+' / '-')? [0-9]+ '.' [0-9]+ <FloatLiteral>
87
+ end
88
+
89
+ rule symbol
90
+ ':' (string / any / variable_identifier) <SymbolLiteral>
91
+ end
92
+
93
+ rule string
94
+ double_quoted_string / single_quoted_string
95
+ end
96
+
97
+ rule double_quoted_string
98
+ '"' ([^"\\] / '\\' . )* '"' <DoubleQuotedStringLiteral>
99
+ end
100
+
101
+ rule single_quoted_string
102
+ "'" ([^'\\] / '\\' . )* "'" <StringLiteral>
103
+ end
104
+
105
+ rule true
106
+ 'true' <TrueLiteral>
107
+ end
108
+
109
+ rule false
110
+ 'false' <FalseLiteral>
111
+ end
112
+
113
+ rule nil
114
+ 'nil' <NilLiteral>
115
+ end
116
+
117
+ rule extractor
118
+ type '(' non_empty_array? ')' <ExtractorLiteral>
119
+ end
120
+
121
+ rule type
122
+ constant ('::' constant)* <TypeLiteral>
123
+ end
124
+
125
+ rule constant
126
+ [A-Z] [a-zA-Z0-9_]*
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,16 @@
1
+ module Fear
2
+ module Extractor
3
+ class IdentifierMatcher < Matcher
4
+ # @!attribute name
5
+ # @return [Types::Strict::Symbol]
6
+
7
+ def defined_at?(_)
8
+ true
9
+ end
10
+
11
+ def bindings(other)
12
+ { name => other }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,54 @@
1
+ require 'ostruct'
2
+
3
+ module Fear
4
+ module Extractor
5
+ # @abstract abstract matcher to inherit from.
6
+ class Matcher < OpenStruct
7
+ autoload :And, 'fear/extractor/matcher/and'
8
+
9
+ EMPTY_HASH = {}.freeze
10
+ EMPTY_ARRAY = [].freeze
11
+
12
+ # @param node [Fear::Extractor::Grammar::Node]
13
+ def initialize(node:, **attributes)
14
+ @input = node.input
15
+ @input_position = node.interval.first
16
+ super(attributes)
17
+ end
18
+ attr_reader :input_position, :input
19
+ private :input
20
+ private :input_position
21
+
22
+ def call(arg)
23
+ call_or_else(arg, &PartialFunction::EMPTY)
24
+ end
25
+
26
+ def and(other)
27
+ And.new(self, other)
28
+ end
29
+
30
+ # @param arg [any]
31
+ # @yield [arg] if function not defined
32
+ def call_or_else(arg)
33
+ if defined_at?(arg)
34
+ bindings(arg)
35
+ else
36
+ yield arg
37
+ end
38
+ end
39
+
40
+ # Shows why matcher has failed. Use it for debugging.
41
+ # @example
42
+ # Fear['[1, 2, _]'].failure_reason([1, 3, 4])
43
+ # # it will show that the second element hasn't match
44
+ #
45
+ def failure_reason(other)
46
+ if defined_at?(other)
47
+ Fear.none
48
+ else
49
+ Fear.some("Expected `#{other.inspect}` to match:\n#{input}\n#{'~' * input_position}^")
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,36 @@
1
+ require 'ostruct'
2
+
3
+ module Fear
4
+ module Extractor
5
+ class Matcher < OpenStruct
6
+ # Combine two matchers, so both should pass
7
+ class And < Matcher
8
+ def initialize(matcher1, matcher2)
9
+ @matcher1 = matcher1
10
+ @matcher2 = matcher2
11
+ end
12
+ attr_reader :matcher1, :matcher2
13
+
14
+ def defined_at?(arg)
15
+ matcher1.defined_at?(arg) && matcher2.defined_at?(arg)
16
+ end
17
+
18
+ def bindings(arg)
19
+ matcher1.bindings(arg).merge(matcher2.bindings(arg))
20
+ end
21
+
22
+ def failure_reason(arg)
23
+ if matcher1.defined_at?(arg)
24
+ if matcher2.defined_at?(arg)
25
+ Fear.none
26
+ else
27
+ matcher2.failure_reason(arg)
28
+ end
29
+ else
30
+ matcher1.failure_reason(arg)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ module Fear
2
+ module Extractor
3
+ # Match against array splat, and capture rest of an array
4
+ # E.g. +[1, 2, *tail]+
5
+ #
6
+ class NamedArraySplatMatcher < ArraySplatMatcher
7
+ # @!attribute name
8
+ # @return [Types::Strict::Symbol]
9
+
10
+ def bindings(other)
11
+ { name => other }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ require 'lru_redux'
2
+
3
+ module Fear
4
+ module Extractor
5
+ # Parse pattern. Used within +Fear[]+
6
+ class Pattern
7
+ DEFAULT_PATTERN_CACHE_SIZE = 10_000
8
+ @pattern_cache = LruRedux::Cache.new(ENV.fetch('FEAR_PATTERNS_CACHE_SIZE', DEFAULT_PATTERN_CACHE_SIZE))
9
+
10
+ class << self
11
+ attr_reader :pattern_cache
12
+ end
13
+
14
+ def initialize(pattern)
15
+ @matcher = compile_pattern(pattern)
16
+ end
17
+ attr_reader :matcher
18
+ private :matcher
19
+
20
+ private def compile_pattern(pattern)
21
+ self.class.pattern_cache.getset(pattern) do
22
+ compile_pattern_without_cache(pattern)
23
+ end
24
+ end
25
+
26
+ private def compile_pattern_without_cache(pattern)
27
+ parser = Extractor::GrammarParser.new
28
+ if (result = parser.parse(pattern))
29
+ result.to_matcher
30
+ else
31
+ raise PatternSyntaxError, syntax_error_message(parser, pattern)
32
+ end
33
+ end
34
+
35
+ def ===(other)
36
+ matcher.defined_at?(other)
37
+ end
38
+
39
+ def and_then(other)
40
+ Fear::PartialFunction::Combined.new(matcher, other)
41
+ end
42
+
43
+ def failure_reason(other)
44
+ matcher.failure_reason(other).get_or_else { 'It matches' }
45
+ end
46
+
47
+ private def syntax_error_message(parser, pattern)
48
+ parser.failure_reason =~ /^(Expected .+) after/m
49
+ "#{Regexp.last_match(1).gsub("\n", '$NEWLINE')}:\n" +
50
+ pattern.split("\n")[parser.failure_line - 1] + "\n" \
51
+ "#{'~' * (parser.failure_column - 1)}^\n"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,24 @@
1
+ module Fear
2
+ module Extractor
3
+ # Match and capture identifier with specific type. E.g. +foo : Integer+
4
+ #
5
+ class TypedIdentifierMatcher < Matcher
6
+ # @!attribute identifier
7
+ # @return [IdentifierMatcher]
8
+ # @!attribute type
9
+ # @return [ValueMatcher]
10
+
11
+ def defined_at?(other)
12
+ type.defined_at?(other)
13
+ end
14
+
15
+ def bindings(other)
16
+ { identifier.name => other }
17
+ end
18
+
19
+ def failure_reason(other)
20
+ type.failure_reason(other)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Fear
2
+ module Extractor
3
+ # Match against values -- true, false, 1, "foo" etc.
4
+ class ValueMatcher < Matcher
5
+ # @!attribute value
6
+ # @return [Any]
7
+
8
+ def defined_at?(arg)
9
+ value === arg
10
+ end
11
+
12
+ def bindings(_)
13
+ EMPTY_HASH
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module Fear
2
+ module ExtractorApi
3
+ # Allows to pattern match and extract matcher variables
4
+ #
5
+ # @param pattern [String]
6
+ # @return [Extractor::Pattern]
7
+ # @note it is not intended to be used by itself, rather then with partial functions
8
+ def [](pattern)
9
+ Extractor::Pattern.new(pattern)
10
+ end
11
+
12
+ # Register extractor for given class
13
+ # @!method register_extractor(*names, extractor)
14
+ # @param names [<Class, String>, Class, String] name of a class. You can also pass alias for the name
15
+ # @param extractor [Proc<any => Fear::Option>] proc taking any argument and returned Option
16
+ # of extracted value('s)
17
+ #
18
+ # @example
19
+ # register_extractor(Fear::Some, Fear.case(Fear::Some) { |some| some.get }.lift)
20
+ #
21
+ # register_extractor(User, Fear.case(User) { |user|} [user.id, user.email] , )
22
+ #
23
+ # @example registering an alias. Alias should be CamelCased string
24
+ # register_extractor(Fear::Some, 'Some', Fear.case(Fear::Some) { |some| some.get }.lift)
25
+ #
26
+ # # no you can extract Fear::Some's using Some alias
27
+ # m.case(Fear['Some(value : Integer)']) { |value:| value * 2 }
28
+ #
29
+ def register_extractor(*args)
30
+ Extractor.register_extractor(*args)
31
+ end
32
+ end
33
+ end
@@ -1,9 +1,15 @@
1
1
  module Fear
2
2
  class Failure
3
3
  include Try
4
- include Dry::Equalizer(:exception)
5
4
  include RightBiased::Left
6
5
  include FailurePatternMatch.mixin
6
+ EXTRACTOR = proc do |try|
7
+ if Fear::Failure === try
8
+ Fear.some([try.exception])
9
+ else
10
+ Fear.none
11
+ end
12
+ end
7
13
 
8
14
  # @param [StandardError]
9
15
  def initialize(exception)
@@ -44,22 +50,24 @@ module Fear
44
50
  self
45
51
  end
46
52
 
47
- # @yieldparam [Exception]
48
- # @yieldreturn [Try]
49
- # @return [Try]
53
+ # @yieldparam [Fear::PatternMatch]
54
+ # @yieldreturn [Fear::Try]
55
+ # @return [Fear::Try]
50
56
  def recover_with
51
- yield(exception).tap do |result|
52
- Utils.assert_type!(result, Success, Failure)
53
- end
57
+ Fear.matcher { |m| yield(m) }
58
+ .and_then { |result| result.tap { Utils.assert_type!(result, Success, Failure) } }
59
+ .call_or_else(exception) { self }
54
60
  rescue StandardError => error
55
61
  Failure.new(error)
56
62
  end
57
63
 
58
- # @yieldparam [Exception]
64
+ # @yieldparam [Fear::PatternMatch]
59
65
  # @yieldreturn [any]
60
- # @return [Try]
66
+ # @return [Fear::Try]
61
67
  def recover
62
- Success.new(yield(exception))
68
+ Fear.matcher { |m| yield(m) }
69
+ .and_then { |v| Success.new(v) }
70
+ .call_or_else(exception) { self }
63
71
  rescue StandardError => error
64
72
  Failure.new(error)
65
73
  end
@@ -69,6 +77,12 @@ module Fear
69
77
  Left.new(exception)
70
78
  end
71
79
 
80
+ # @param other [Any]
81
+ # @return [Boolean]
82
+ def ==(other)
83
+ other.is_a?(Failure) && exception == other.exception
84
+ end
85
+
72
86
  # Used in case statement
73
87
  # @param other [any]
74
88
  # @return [Boolean]
@@ -79,5 +93,13 @@ module Fear
79
93
  super
80
94
  end
81
95
  end
96
+
97
+ # @return [String]
98
+ def inspect
99
+ "#<Fear::Failure exception=#{exception.inspect}>"
100
+ end
101
+
102
+ # @return [String]
103
+ alias to_s inspect
82
104
  end
83
105
  end