keisan 0.8.4 → 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +28 -0
- data/README.md +16 -0
- data/lib/keisan/ast/block.rb +2 -2
- data/lib/keisan/ast/cell.rb +2 -2
- data/lib/keisan/ast/hash.rb +10 -2
- data/lib/keisan/ast/node.rb +16 -2
- data/lib/keisan/ast/number.rb +5 -0
- data/lib/keisan/ast/parent.rb +8 -2
- data/lib/keisan/calculator.rb +7 -2
- data/lib/keisan/context.rb +11 -2
- data/lib/keisan/functions/enumerable_function.rb +7 -4
- data/lib/keisan/string_and_group_parser.rb +12 -6
- data/lib/keisan/tokenizer.rb +1 -1
- data/lib/keisan/version.rb +1 -1
- metadata +4 -4
- data/.travis.yml +0 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0817ad5c7f153f93b34fd0c3861a1b4d9afcb49ad325310f2258f6ea020e444d'
|
|
4
|
+
data.tar.gz: 2d1601371bf02fdacdcbd6f29cf72ce9ec15d46a402579dc89418825fde97a5c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4faa11222178814693fb58d9db1ddafa7097e49a15e5c59aa8132c26a56e8fc8f69487057ad11e2a37d6dd84d1bb7afa9af4b536ed898d9cd082f357e226098d
|
|
7
|
+
data.tar.gz: ba57a649fbaf1fec6dc29495f86921141c627ea1e652cccd884f30edf4f9e70096803f1ed36acab4b010930abb75dc01cb09cdd5f399c2bec0b03c6c27d1d449
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Ruby
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ master ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
ruby-version: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v2
|
|
20
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
21
|
+
uses: ruby/setup-ruby@v1
|
|
22
|
+
with:
|
|
23
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
24
|
+
bundler-cache: true
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: bundle install
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: bundle exec rspec
|
data/README.md
CHANGED
|
@@ -178,6 +178,22 @@ calculator.evaluate("x = 11; {let x = 12}; x")
|
|
|
178
178
|
#=> 11
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
+
##### Comments
|
|
182
|
+
|
|
183
|
+
When working with multi-line blocks of code, sometimes comments are useful to include.
|
|
184
|
+
Comments are parts of a string from the `#` character to the end of a line (indicated by a newline character `"\n"`).
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
calculator = Keisan::Calculator.new
|
|
188
|
+
calculator.evaluate("""
|
|
189
|
+
# This is a comment
|
|
190
|
+
x = 'foo'
|
|
191
|
+
x += '#bar' # Notice that `#` inside strings is not part of the comment
|
|
192
|
+
x # Should print 'foo#bar'
|
|
193
|
+
""")
|
|
194
|
+
#=> "foo#bar"
|
|
195
|
+
```
|
|
196
|
+
|
|
181
197
|
##### Lists
|
|
182
198
|
|
|
183
199
|
Just like in Ruby, lists can be defined using square brackets, and indexed using square brackets
|
data/lib/keisan/ast/block.rb
CHANGED
data/lib/keisan/ast/cell.rb
CHANGED
data/lib/keisan/ast/hash.rb
CHANGED
|
@@ -21,8 +21,16 @@ module Keisan
|
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
|
|
24
|
+
def traverse(&block)
|
|
25
|
+
value = super(&block)
|
|
26
|
+
return value if value
|
|
27
|
+
@hash.each do |k, v|
|
|
28
|
+
value = k.to_node.traverse(&block)
|
|
29
|
+
return value if value
|
|
30
|
+
value = v.traverse(&block)
|
|
31
|
+
return value if value
|
|
32
|
+
end
|
|
33
|
+
false
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
def evaluate(context = nil)
|
data/lib/keisan/ast/node.rb
CHANGED
|
@@ -37,12 +37,26 @@ module Keisan
|
|
|
37
37
|
value(context)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Takes a block, and does a DFS down the AST, evaluating the received block
|
|
41
|
+
# at each node, passing in the node as the single argument. If the block
|
|
42
|
+
# returns a truthy value at any point, the DFS ends and the return value is
|
|
43
|
+
# percolated up the tree.
|
|
44
|
+
def traverse(&block)
|
|
45
|
+
block.call(self)
|
|
46
|
+
end
|
|
47
|
+
|
|
40
48
|
def contains_a?(klass)
|
|
41
49
|
case klass
|
|
42
50
|
when Array
|
|
43
|
-
klass.any?
|
|
51
|
+
klass.any? do |k|
|
|
52
|
+
traverse do |node|
|
|
53
|
+
node.is_a?(k)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
44
56
|
else
|
|
45
|
-
|
|
57
|
+
traverse do |node|
|
|
58
|
+
node.is_a?(klass)
|
|
59
|
+
end
|
|
46
60
|
end
|
|
47
61
|
end
|
|
48
62
|
|
data/lib/keisan/ast/number.rb
CHANGED
data/lib/keisan/ast/parent.rb
CHANGED
|
@@ -25,8 +25,14 @@ module Keisan
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def
|
|
29
|
-
|
|
28
|
+
def traverse(&block)
|
|
29
|
+
value = super(&block)
|
|
30
|
+
return value if value
|
|
31
|
+
children.each do |child|
|
|
32
|
+
value = child.traverse(&block)
|
|
33
|
+
return value if value
|
|
34
|
+
end
|
|
35
|
+
false
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
def freeze
|
data/lib/keisan/calculator.rb
CHANGED
|
@@ -4,11 +4,12 @@ module Keisan
|
|
|
4
4
|
|
|
5
5
|
# Note, allow_recursive would be more appropriately named:
|
|
6
6
|
# allow_unbound_functions_in_function_definitions, but it is too late for that.
|
|
7
|
-
def initialize(context: nil, allow_recursive: false, allow_blocks: true, allow_multiline: true)
|
|
7
|
+
def initialize(context: nil, allow_recursive: false, allow_blocks: true, allow_multiline: true, allow_random: true)
|
|
8
8
|
@context = context || Context.new(
|
|
9
9
|
allow_recursive: allow_recursive,
|
|
10
10
|
allow_blocks: allow_blocks,
|
|
11
|
-
allow_multiline: allow_multiline
|
|
11
|
+
allow_multiline: allow_multiline,
|
|
12
|
+
allow_random: allow_random
|
|
12
13
|
)
|
|
13
14
|
end
|
|
14
15
|
|
|
@@ -28,6 +29,10 @@ module Keisan
|
|
|
28
29
|
context.allow_multiline
|
|
29
30
|
end
|
|
30
31
|
|
|
32
|
+
def allow_random
|
|
33
|
+
context.allow_random
|
|
34
|
+
end
|
|
35
|
+
|
|
31
36
|
def evaluate(expression, definitions = {})
|
|
32
37
|
Evaluator.new(self).evaluate(expression, definitions)
|
|
33
38
|
end
|
data/lib/keisan/context.rb
CHANGED
|
@@ -4,13 +4,15 @@ module Keisan
|
|
|
4
4
|
:variable_registry,
|
|
5
5
|
:allow_recursive,
|
|
6
6
|
:allow_multiline,
|
|
7
|
-
:allow_blocks
|
|
7
|
+
:allow_blocks,
|
|
8
|
+
:allow_random
|
|
8
9
|
|
|
9
10
|
def initialize(parent: nil,
|
|
10
11
|
random: nil,
|
|
11
12
|
allow_recursive: false,
|
|
12
13
|
allow_multiline: true,
|
|
13
14
|
allow_blocks: true,
|
|
15
|
+
allow_random: true,
|
|
14
16
|
shadowed: [])
|
|
15
17
|
@parent = parent
|
|
16
18
|
@function_registry = Functions::Registry.new(parent: @parent&.function_registry)
|
|
@@ -19,6 +21,7 @@ module Keisan
|
|
|
19
21
|
@allow_recursive = allow_recursive
|
|
20
22
|
@allow_multiline = allow_multiline
|
|
21
23
|
@allow_blocks = allow_blocks
|
|
24
|
+
@allow_random = allow_random
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
def allow_recursive!
|
|
@@ -111,7 +114,13 @@ module Keisan
|
|
|
111
114
|
end
|
|
112
115
|
|
|
113
116
|
def random
|
|
114
|
-
|
|
117
|
+
raise Keisan::Exceptions::InvalidExpression.new("Context does not permit expressions with randomness") unless allow_random
|
|
118
|
+
@random ||= @parent&.random || Random.new
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def set_random(random)
|
|
122
|
+
raise Keisan::Exceptions::InvalidExpression.new("Context does not permit expressions with randomness") unless allow_random
|
|
123
|
+
@random = random
|
|
115
124
|
end
|
|
116
125
|
|
|
117
126
|
protected
|
|
@@ -21,14 +21,17 @@ module Keisan
|
|
|
21
21
|
context ||= Context.new
|
|
22
22
|
|
|
23
23
|
operand, arguments, expression = operand_arguments_expression_for(ast_function, context)
|
|
24
|
+
|
|
25
|
+
# Extract underlying operand for cells
|
|
26
|
+
real_operand = operand.is_a?(AST::Cell) ? operand.node : operand
|
|
24
27
|
|
|
25
|
-
case
|
|
28
|
+
case real_operand
|
|
26
29
|
when AST::List
|
|
27
|
-
evaluate_list(
|
|
30
|
+
evaluate_list(real_operand, arguments, expression, context).evaluate(context)
|
|
28
31
|
when AST::Hash
|
|
29
|
-
evaluate_hash(
|
|
32
|
+
evaluate_hash(real_operand, arguments, expression, context).evaluate(context)
|
|
30
33
|
else
|
|
31
|
-
raise Exceptions::InvalidFunctionError.new("Unhandled first argument to #{name}: #{
|
|
34
|
+
raise Exceptions::InvalidFunctionError.new("Unhandled first argument to #{name}: #{real_operand}")
|
|
32
35
|
end
|
|
33
36
|
end
|
|
34
37
|
|
|
@@ -9,24 +9,26 @@ module Keisan
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
class StringPortion < Portion
|
|
12
|
-
attr_reader :string
|
|
12
|
+
attr_reader :string, :escaped_string
|
|
13
13
|
|
|
14
14
|
def initialize(expression, start_index)
|
|
15
15
|
super(start_index)
|
|
16
16
|
|
|
17
17
|
@string = expression[start_index]
|
|
18
|
+
@escaped_string = expression[start_index]
|
|
18
19
|
@end_index = start_index + 1
|
|
19
20
|
|
|
20
21
|
while @end_index < expression.size
|
|
21
22
|
if expression[@end_index] == quote_type
|
|
22
23
|
@string << quote_type
|
|
24
|
+
@escaped_string << quote_type
|
|
23
25
|
@end_index += 1
|
|
24
26
|
# Successfully parsed the string
|
|
25
27
|
return
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
n, c =
|
|
29
|
-
@
|
|
30
|
+
n, c = get_potentially_escaped_next_character(expression, @end_index)
|
|
31
|
+
@escaped_string << c
|
|
30
32
|
@end_index += n
|
|
31
33
|
end
|
|
32
34
|
|
|
@@ -44,9 +46,13 @@ module Keisan
|
|
|
44
46
|
private
|
|
45
47
|
|
|
46
48
|
# Returns number of processed input characters, and the output character
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
# If a sequence like '\"' is encountered, the first backslash escapes the
|
|
50
|
+
# second double-quote, and the two characters will act as a one double-quote
|
|
51
|
+
# character.
|
|
52
|
+
def get_potentially_escaped_next_character(expression, index)
|
|
53
|
+
@string << expression[index]
|
|
54
|
+
if expression[index] == "\\" && index + 1 < expression.size
|
|
55
|
+
@string << expression[index + 1]
|
|
50
56
|
return [2, escaped_character(expression[index + 1])]
|
|
51
57
|
else
|
|
52
58
|
return [1, expression[index]]
|
data/lib/keisan/tokenizer.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Keisan
|
|
|
33
33
|
@tokens = portions.inject([]) do |tokens, portion|
|
|
34
34
|
case portion
|
|
35
35
|
when StringAndGroupParser::StringPortion
|
|
36
|
-
tokens << Tokens::String.new(portion.
|
|
36
|
+
tokens << Tokens::String.new(portion.escaped_string)
|
|
37
37
|
when StringAndGroupParser::GroupPortion
|
|
38
38
|
tokens << Tokens::Group.new(portion.to_s)
|
|
39
39
|
when StringAndGroupParser::OtherPortion
|
data/lib/keisan/version.rb
CHANGED
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.8.
|
|
4
|
+
version: 0.8.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Christopher Locke
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-05-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: cmath
|
|
@@ -115,9 +115,9 @@ executables: []
|
|
|
115
115
|
extensions: []
|
|
116
116
|
extra_rdoc_files: []
|
|
117
117
|
files:
|
|
118
|
+
- ".github/workflows/ruby.yml"
|
|
118
119
|
- ".gitignore"
|
|
119
120
|
- ".rspec"
|
|
120
|
-
- ".travis.yml"
|
|
121
121
|
- Gemfile
|
|
122
122
|
- MIT-LICENSE
|
|
123
123
|
- README.md
|
|
@@ -328,7 +328,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
328
328
|
- !ruby/object:Gem::Version
|
|
329
329
|
version: '0'
|
|
330
330
|
requirements: []
|
|
331
|
-
rubygems_version: 3.
|
|
331
|
+
rubygems_version: 3.2.15
|
|
332
332
|
signing_key:
|
|
333
333
|
specification_version: 4
|
|
334
334
|
summary: An equation parser and evaluator
|