keisan 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c77b933e609cf2ed878939973a183082fa01b87ac49a8bece5278c0df5e66aed
4
- data.tar.gz: e737e883ba1fadfa8c9c9ab19f6b995e4cc33ab4f5f2f7f7750bcd0091dd9cfc
3
+ metadata.gz: 11f1a2644fbb02817db69dd289564d6fdb9bfd09d294cda62b07156723aebc0e
4
+ data.tar.gz: 4b9787c0d558af15d466759e40fe4cfac35131d254d227416144588256e5ea9f
5
5
  SHA512:
6
- metadata.gz: 1cbf2ba0b83b1c74b6d70530dbdebe5a6519cff81c90a2e79216fff1ced7d7ded853d88691afbcdcb75b3995349d82c7b02b0cda915cb871c545080b37c25b7f
7
- data.tar.gz: 7877b72629fe6f90c7d508451dc580f88c8f10b9d14529917fb9235c805d84c55d972188dadd85ffb6acef6a81e728911e60a0012968a66408bb8a600cfaca45
6
+ metadata.gz: 8149fee69d598164334b96f8be59b9763168af97e3803c9abea7285da5ed1d90bb9f87710a3de94498461baec65db335168f8e721fe73c4a837c684a30bc4114
7
+ data.tar.gz: 8a4e301fb14461b164bf0331684fe7ebe3cd660d4abd38f3c44aba5f50fbed58b6b945082accdf13f9da55bcb76579890fd63db75e8b17e5c098c52d0e2d80ae
data/README.md CHANGED
@@ -280,6 +280,14 @@ calculator.evaluate("range(5,10)")
280
280
  #=> [5,6,7,8,9]
281
281
  calculator.evaluate("range(0,10,2)")
282
282
  #=> [0,2,4,6,8]
283
+ calculator.evaluate("[1, 2, 2, 3].uniq")
284
+ #=> [1,2,3]
285
+ calculator.evaluate("[1, 2, 3].difference([2, 3, 4])")
286
+ #=> [1]
287
+ calculator.evaluate("[1, 2, 3].intersection([2, 3, 4])")
288
+ #=> [2, 3]
289
+ calculator.evaluate("[1, 2, 3].union([2, 3, 4])")
290
+ #=> [1, 2, 3, 4]
283
291
  ```
284
292
 
285
293
  ##### Hashes
@@ -8,6 +8,19 @@ module Keisan
8
8
  @indexes = indexes
9
9
  end
10
10
 
11
+ def deep_dup
12
+ dupped = super
13
+ dupped.instance_variable_set(
14
+ :@indexes, indexes.map(&:deep_dup)
15
+ )
16
+ dupped
17
+ end
18
+
19
+ def freeze
20
+ indexes.each(&:freeze)
21
+ super
22
+ end
23
+
11
24
  def value(context = nil)
12
25
  return child.value(context).send(:[], *indexes.map {|index| index.value(context)})
13
26
  end
@@ -47,10 +47,18 @@ module Keisan
47
47
  Evaluator.new(self, cache: @cache).evaluate(expression, definitions)
48
48
  end
49
49
 
50
+ def evaluate_ast(ast, definitions = {})
51
+ Evaluator.new(self, cache: @cache).evaluate_ast(ast, definitions: definitions)
52
+ end
53
+
50
54
  def simplify(expression, definitions = {})
51
55
  Evaluator.new(self, cache: @cache).simplify(expression, definitions)
52
56
  end
53
57
 
58
+ def simplify_ast(ast, definitions = {})
59
+ Evaluator.new(self, cache: @cache).simplify_ast(ast, definitions: definitions)
60
+ end
61
+
54
62
  def ast(expression)
55
63
  Evaluator.new(self, cache: @cache).parse_ast(expression)
56
64
  end
@@ -9,7 +9,11 @@ module Keisan
9
9
 
10
10
  def evaluate(expression, definitions = {})
11
11
  context = calculator.context.spawn_child(definitions: definitions, transient: true)
12
- ast = parse_ast(expression)
12
+ evaluate_ast(parse_ast(expression), context: context)
13
+ end
14
+
15
+ def evaluate_ast(ast, definitions: {}, context: nil)
16
+ context ||= calculator.context.spawn_child(definitions: definitions, transient: true)
13
17
  last_line = last_line(ast)
14
18
 
15
19
  evaluation = ast.evaluated(context)
@@ -25,7 +29,11 @@ module Keisan
25
29
 
26
30
  def simplify(expression, definitions = {})
27
31
  context = calculator.context.spawn_child(definitions: definitions, transient: true)
28
- ast = parse_ast(expression)
32
+ simplify_ast(parse_ast(expression), context: context)
33
+ end
34
+
35
+ def simplify_ast(ast, definitions: {}, context: nil)
36
+ context ||= calculator.context.spawn_child(definitions: definitions, transient: true)
29
37
  ast.simplified(context)
30
38
  end
31
39
 
@@ -118,10 +118,13 @@ module Keisan
118
118
  end
119
119
 
120
120
  def self.register_array_methods!(registry)
121
- %i(min max size flatten reverse).each do |method|
121
+ %i(min max size flatten reverse uniq).each do |method|
122
122
  registry.register!(method, Proc.new {|a| a.send(method)}, force: true)
123
123
  end
124
124
 
125
+ registry.register!(:difference, Proc.new {|a, b| a - b}, force: true)
126
+ registry.register!(:intersection, Proc.new {|a, b| a & b}, force: true)
127
+ registry.register!(:union, Proc.new {|a, b| a | b}, force: true)
125
128
  registry.register!("range", Functions::Range.new, force: true)
126
129
  end
127
130
 
@@ -54,7 +54,7 @@ module Keisan
54
54
  private
55
55
 
56
56
  def operand_arguments_expression_for(ast_function, context)
57
- operand = ast_function.children[0].simplify(context)
57
+ operand = ast_function.children[0].evaluate(context)
58
58
  arguments = ast_function.children[1...-1]
59
59
  expression = ast_function.children[-1]
60
60
 
@@ -14,6 +14,12 @@ module Keisan
14
14
  @transient_definitions = transient_definitions
15
15
  end
16
16
 
17
+ def freeze
18
+ @arguments.freeze
19
+ @expression.freeze
20
+ super
21
+ end
22
+
17
23
  def call(context, *args)
18
24
  validate_arguments!(args.count)
19
25
 
@@ -96,7 +102,7 @@ module Keisan
96
102
  private
97
103
 
98
104
  def argument_variables
99
- @argument_variables ||= arguments.map {|argument| AST::Variable.new(argument)}
105
+ arguments.map {|argument| AST::Variable.new(argument)}
100
106
  end
101
107
 
102
108
  def calculate_partial_derivatives(context)
@@ -44,6 +44,9 @@ module Keisan
44
44
  when Proc
45
45
  self[name] = ProcFunction.new(name, function)
46
46
  when Function
47
+ # The expression AST which represents the function should be constant,
48
+ # so we freeze it so it will always have the same behavior.
49
+ function.freeze if function.is_a?(ExpressionFunction)
47
50
  self[name] = function
48
51
  else
49
52
  raise Exceptions::InvalidFunctionError.new
data/lib/keisan/parser.rb CHANGED
@@ -211,7 +211,8 @@ module Keisan
211
211
  when :square
212
212
  @components << Parsing::List.new(arguments_from_group(token))
213
213
  when :curly
214
- if token.sub_tokens.any? {|token| token.is_a?(Tokens::Colon)}
214
+ # A hash either has a colon, or is empty
215
+ if token.sub_tokens.any? {|token| token.is_a?(Tokens::Colon)} || token.sub_tokens.empty?
215
216
  @components << Parsing::Hash.new(Util.array_split(token.sub_tokens) {|token| token.is_a?(Tokens::Comma)})
216
217
  else
217
218
  @components << Parsing::CurlyGroup.new(token.sub_tokens)
@@ -4,15 +4,25 @@ module Keisan
4
4
  attr_reader :key_value_pairs
5
5
 
6
6
  def initialize(key_value_pairs)
7
- @key_value_pairs = Array(key_value_pairs).map {|key_value_pair|
8
- validate_and_extract_key_value_pair(key_value_pair)
9
- }
7
+ key_value_pairs = Array(key_value_pairs)
8
+ if key_value_pairs.size == 1 && key_value_pairs.first.empty?
9
+ @key_value_pairs = []
10
+ else
11
+ @key_value_pairs = key_value_pairs.map {|key_value_pair|
12
+ validate_and_extract_key_value_pair(key_value_pair)
13
+ }
14
+ end
10
15
  end
11
16
 
12
17
  private
13
18
 
14
19
  def validate_and_extract_key_value_pair(key_value_pair)
15
- key, value = Util.array_split(key_value_pair) {|token| token.is_a?(Tokens::Colon)}
20
+ filtered_key_value_pair = key_value_pair.select {|token|
21
+ !token.is_a?(Tokens::LineSeparator)
22
+ }
23
+ key, value = Util.array_split(filtered_key_value_pair) {|token|
24
+ token.is_a?(Tokens::Colon)
25
+ }
16
26
  raise Exceptions::ParseError.new("Invalid hash") unless key.size == 1 && value.size >= 1
17
27
 
18
28
  key = key.first
@@ -1,3 +1,3 @@
1
1
  module Keisan
2
- VERSION = "0.9.0"
2
+ VERSION = "0.9.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keisan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Locke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-26 00:00:00.000000000 Z
11
+ date: 2021-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmath