keisan 0.8.0 → 0.8.1
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/.travis.yml +6 -4
- data/README.md +2 -1
- data/keisan.gemspec +5 -3
- data/lib/keisan.rb +6 -0
- data/lib/keisan/ast/bitwise_left_shift.rb +17 -0
- data/lib/keisan/ast/bitwise_right_shift.rb +17 -0
- data/lib/keisan/ast/node.rb +16 -0
- data/lib/keisan/ast/number.rb +20 -0
- data/lib/keisan/ast/operator.rb +2 -0
- data/lib/keisan/ast/unary_inverse.rb +1 -1
- data/lib/keisan/ast/variable.rb +8 -4
- data/lib/keisan/functions/cmath_function.rb +3 -1
- data/lib/keisan/functions/expression_function.rb +16 -9
- data/lib/keisan/functions/proc_function.rb +2 -2
- data/lib/keisan/functions/replace.rb +6 -6
- data/lib/keisan/parser.rb +2 -0
- data/lib/keisan/parsing/bitwise_left_shift.rb +9 -0
- data/lib/keisan/parsing/bitwise_right_shift.rb +9 -0
- data/lib/keisan/string_and_group_parser.rb +193 -0
- data/lib/keisan/tokenizer.rb +19 -6
- data/lib/keisan/tokens/assignment.rb +3 -1
- data/lib/keisan/tokens/bitwise_shift.rb +23 -0
- data/lib/keisan/tokens/group.rb +1 -7
- data/lib/keisan/tokens/string.rb +2 -4
- data/lib/keisan/version.rb +1 -1
- metadata +33 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25f0cd7f7a5bd124a62c244a270bb4db0632fc0ce295d7f55fa25409eae66473
|
4
|
+
data.tar.gz: 5cdeae27276969608966b56891abe91fe8b2bddaef9f7ef3fdeb6a3fe6696176
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32d761e97663ad54bc37d2e3894875068daa8567ae521dd877879a94f66417c752cc338eba7f1a06bbe23f2a1455fce7751690323fcf4948f243d8254c69f1b4
|
7
|
+
data.tar.gz: 6c62bfc43752fe4918022e0217227c4a31d1fae6225bf70d65bc61afb025dbef5af74597055b1216a4ef9d261252e2f2d9ac26467319e33c4c842a2764711053
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -340,7 +340,7 @@ calculator.evaluate("[2, 3, 7, 11].has_element(11)")
|
|
340
340
|
|
341
341
|
##### Bitwise operations
|
342
342
|
|
343
|
-
The basic bitwise operations, NOT `~`, OR `|`, XOR `^`, and
|
343
|
+
The basic bitwise operations, NOT `~`, OR `|`, XOR `^`, AND `&`, and left/right bitwise shifts (`<<` and `>>`) are also available for use
|
344
344
|
|
345
345
|
```ruby
|
346
346
|
calculator = Keisan::Calculator.new
|
@@ -485,6 +485,7 @@ calculator.evaluate("puts x**2") # prints "25\n" to STDOUT
|
|
485
485
|
|
486
486
|
#### Bitwise operators
|
487
487
|
- `&`, `|`, `^`: bitwise **and**, **or**, **xor** operators
|
488
|
+
- `<<`, `>>` bitwise shift operators
|
488
489
|
- `~`: unary bitwise not
|
489
490
|
|
490
491
|
#### Indexing of arrays/hashes
|
data/keisan.gemspec
CHANGED
@@ -21,10 +21,12 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.required_ruby_version = ">= 2.3.0"
|
23
23
|
|
24
|
-
spec.
|
25
|
-
|
26
|
-
spec.add_development_dependency "
|
24
|
+
spec.add_dependency "cmath", "~> 1.0"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
27
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
27
28
|
spec.add_development_dependency "rspec", "~> 3.0"
|
28
29
|
spec.add_development_dependency "pry"
|
29
30
|
spec.add_development_dependency "pry-stack_explorer"
|
31
|
+
spec.add_development_dependency "simplecov"
|
30
32
|
end
|
data/lib/keisan.rb
CHANGED
@@ -37,6 +37,8 @@ require "keisan/ast/bitwise_operator"
|
|
37
37
|
require "keisan/ast/bitwise_and"
|
38
38
|
require "keisan/ast/bitwise_or"
|
39
39
|
require "keisan/ast/bitwise_xor"
|
40
|
+
require "keisan/ast/bitwise_left_shift"
|
41
|
+
require "keisan/ast/bitwise_right_shift"
|
40
42
|
require "keisan/ast/logical_operator"
|
41
43
|
require "keisan/ast/logical_and"
|
42
44
|
require "keisan/ast/logical_or"
|
@@ -78,10 +80,12 @@ require "keisan/tokens/assignment"
|
|
78
80
|
require "keisan/tokens/arithmetic_operator"
|
79
81
|
require "keisan/tokens/logical_operator"
|
80
82
|
require "keisan/tokens/bitwise_operator"
|
83
|
+
require "keisan/tokens/bitwise_shift"
|
81
84
|
require "keisan/tokens/word"
|
82
85
|
require "keisan/tokens/line_separator"
|
83
86
|
require "keisan/tokens/unknown"
|
84
87
|
|
88
|
+
require "keisan/string_and_group_parser"
|
85
89
|
require "keisan/tokenizer"
|
86
90
|
|
87
91
|
require "keisan/parsing/component"
|
@@ -128,6 +132,8 @@ require "keisan/parsing/bitwise_or"
|
|
128
132
|
require "keisan/parsing/bitwise_xor"
|
129
133
|
require "keisan/parsing/bitwise_not"
|
130
134
|
require "keisan/parsing/bitwise_not_not"
|
135
|
+
require "keisan/parsing/bitwise_left_shift"
|
136
|
+
require "keisan/parsing/bitwise_right_shift"
|
131
137
|
require "keisan/parsing/logical_operator"
|
132
138
|
require "keisan/parsing/logical_less_than"
|
133
139
|
require "keisan/parsing/logical_greater_than"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class BitwiseLeftShift < BitwiseOperator
|
4
|
+
def self.symbol
|
5
|
+
:<<
|
6
|
+
end
|
7
|
+
|
8
|
+
def blank_value
|
9
|
+
0
|
10
|
+
end
|
11
|
+
|
12
|
+
def evaluate(context = nil)
|
13
|
+
children[1..-1].inject(children.first.evaluate(context)) {|total, child| total << child.evaluate(context)}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class BitwiseRightShift < BitwiseOperator
|
4
|
+
def self.symbol
|
5
|
+
:>>
|
6
|
+
end
|
7
|
+
|
8
|
+
def blank_value
|
9
|
+
0
|
10
|
+
end
|
11
|
+
|
12
|
+
def evaluate(context = nil)
|
13
|
+
children[1..-1].inject(children.first.evaluate(context)) {|total, child| total >> child.evaluate(context)}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/keisan/ast/node.rb
CHANGED
@@ -45,10 +45,18 @@ module Keisan
|
|
45
45
|
raise Exceptions::NonDifferentiableError.new
|
46
46
|
end
|
47
47
|
|
48
|
+
def differentiated(variable, context = nil)
|
49
|
+
deep_dup.differentiate(variable, context)
|
50
|
+
end
|
51
|
+
|
48
52
|
def replace(variable, replacement)
|
49
53
|
self
|
50
54
|
end
|
51
55
|
|
56
|
+
def replaced(variable, replacement)
|
57
|
+
deep_dup.replace(variable, replacement)
|
58
|
+
end
|
59
|
+
|
52
60
|
def coerce(other)
|
53
61
|
[other.to_node, self]
|
54
62
|
end
|
@@ -132,6 +140,14 @@ module Keisan
|
|
132
140
|
BitwiseOr.new([self, other.to_node])
|
133
141
|
end
|
134
142
|
|
143
|
+
def <<(other)
|
144
|
+
BitwiseLeftShift.new([self, other.to_node])
|
145
|
+
end
|
146
|
+
|
147
|
+
def >>(other)
|
148
|
+
BitwiseRightShift.new([self, other.to_node])
|
149
|
+
end
|
150
|
+
|
135
151
|
def >(other)
|
136
152
|
LogicalGreaterThan.new([self, other.to_node])
|
137
153
|
end
|
data/lib/keisan/ast/number.rb
CHANGED
@@ -111,6 +111,26 @@ module Keisan
|
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
|
+
def <<(other)
|
115
|
+
other = other.to_node
|
116
|
+
case other
|
117
|
+
when Number
|
118
|
+
Number.new(value << other.value)
|
119
|
+
else
|
120
|
+
super
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def >>(other)
|
125
|
+
other = other.to_node
|
126
|
+
case other
|
127
|
+
when Number
|
128
|
+
Number.new(value >> other.value)
|
129
|
+
else
|
130
|
+
super
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
114
134
|
def >(other)
|
115
135
|
other = other.to_node
|
116
136
|
case other
|
data/lib/keisan/ast/operator.rb
CHANGED
@@ -13,6 +13,8 @@ module Keisan
|
|
13
13
|
"%": [2, 85, :left], # Modulo
|
14
14
|
"+": [2, 80, :left], # Plus
|
15
15
|
# "-": [2, 80, :left], # Minus
|
16
|
+
"<<": [2, 75, :left], # Bitwise left shift
|
17
|
+
">>": [2, 75, :left], # Bitwise right shift
|
16
18
|
"&": [2, 70, :left], # Bitwise and
|
17
19
|
"^": [2, 65, :left], # Bitwise xor
|
18
20
|
"|": [2, 65, :left], # Bitwise or
|
data/lib/keisan/ast/variable.rb
CHANGED
@@ -13,7 +13,13 @@ module Keisan
|
|
13
13
|
|
14
14
|
def value(context = nil)
|
15
15
|
context ||= Context.new
|
16
|
-
variable_node_from_context(context)
|
16
|
+
node = variable_node_from_context(context)
|
17
|
+
case node
|
18
|
+
when Variable
|
19
|
+
node
|
20
|
+
else
|
21
|
+
node.value(context)
|
22
|
+
end
|
17
23
|
end
|
18
24
|
|
19
25
|
def unbound_variables(context = nil)
|
@@ -78,9 +84,7 @@ module Keisan
|
|
78
84
|
|
79
85
|
def variable_node_from_context(context)
|
80
86
|
variable = context.variable(name)
|
81
|
-
if variable.is_a?(Cell)
|
82
|
-
variable = variable.node
|
83
|
-
end
|
87
|
+
variable = variable.node if variable.is_a?(Cell)
|
84
88
|
variable
|
85
89
|
end
|
86
90
|
end
|
@@ -4,7 +4,9 @@ module Keisan
|
|
4
4
|
module Functions
|
5
5
|
class CMathFunction < MathFunction
|
6
6
|
def initialize(name, proc_function = nil)
|
7
|
-
super(name, proc_function || Proc.new {|arg|
|
7
|
+
super(name, proc_function || Proc.new {|arg|
|
8
|
+
CMath.send(name, arg)
|
9
|
+
})
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
@@ -5,7 +5,11 @@ module Keisan
|
|
5
5
|
|
6
6
|
def initialize(name, arguments, expression, transient_definitions)
|
7
7
|
super(name, arguments.count)
|
8
|
-
|
8
|
+
if expression.is_a?(::String)
|
9
|
+
@expression = AST::parse(expression)
|
10
|
+
else
|
11
|
+
@expression = expression.deep_dup
|
12
|
+
end
|
9
13
|
@arguments = arguments
|
10
14
|
@transient_definitions = transient_definitions
|
11
15
|
end
|
@@ -70,17 +74,20 @@ module Keisan
|
|
70
74
|
|
71
75
|
local = local_context_for(context)
|
72
76
|
|
73
|
-
|
74
|
-
|
75
|
-
argument_values = ast_function.children.map {|child| child.evaluate(local)}
|
77
|
+
argument_values = ast_function.children.map {|child| child.evaluated(local)}
|
76
78
|
|
77
79
|
argument_derivatives = ast_function.children.map do |child|
|
78
|
-
child.
|
80
|
+
child.differentiated(variable, context)
|
79
81
|
end
|
80
82
|
|
83
|
+
partial_derivatives = calculate_partial_derivatives(context)
|
84
|
+
|
81
85
|
AST::Plus.new(
|
82
86
|
argument_derivatives.map.with_index {|argument_derivative, i|
|
83
|
-
partial_derivative = partial_derivatives[i]
|
87
|
+
partial_derivative = partial_derivatives[i]
|
88
|
+
argument_variables.each.with_index {|argument_variable, j|
|
89
|
+
partial_derivative = partial_derivative.replaced(argument_variable, argument_values[j])
|
90
|
+
}
|
84
91
|
AST::Times.new([argument_derivative, partial_derivative])
|
85
92
|
}
|
86
93
|
)
|
@@ -92,9 +99,9 @@ module Keisan
|
|
92
99
|
@argument_variables ||= arguments.map {|argument| AST::Variable.new(argument)}
|
93
100
|
end
|
94
101
|
|
95
|
-
def
|
96
|
-
|
97
|
-
partial_derivative = expression.
|
102
|
+
def calculate_partial_derivatives(context)
|
103
|
+
argument_variables.map.with_index do |variable, i|
|
104
|
+
partial_derivative = expression.differentiated(variable, context)
|
98
105
|
end
|
99
106
|
end
|
100
107
|
|
@@ -28,7 +28,7 @@ module Keisan
|
|
28
28
|
|
29
29
|
ast_function.instance_variable_set(
|
30
30
|
:@children,
|
31
|
-
ast_function.children.map {|child| child.
|
31
|
+
ast_function.children.map {|child| child.simplify(context).to_node}
|
32
32
|
)
|
33
33
|
|
34
34
|
if ast_function.children.all? {|child| child.well_defined?(context)}
|
@@ -44,7 +44,7 @@ module Keisan
|
|
44
44
|
|
45
45
|
ast_function.instance_variable_set(
|
46
46
|
:@children,
|
47
|
-
ast_function.children.map {|child| child.
|
47
|
+
ast_function.children.map {|child| child.simplify(context)}
|
48
48
|
)
|
49
49
|
|
50
50
|
if ast_function.children.all? {|child| child.is_a?(AST::ConstantLiteral)}
|
@@ -10,19 +10,19 @@ module Keisan
|
|
10
10
|
evaluate(ast_function, context).value(context)
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def simplify(ast_function, context = nil)
|
14
14
|
context ||= Context.new
|
15
15
|
expression, variable, replacement = expression_variable_replacement(ast_function)
|
16
16
|
|
17
|
-
expression = expression.
|
18
|
-
replacement = replacement.
|
17
|
+
expression = expression.simplify(context)
|
18
|
+
replacement = replacement.simplify(context)
|
19
19
|
|
20
|
-
expression.replace(variable, replacement).
|
20
|
+
expression.replace(variable, replacement).simplify(context)
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def evaluate(ast_function, context = nil)
|
24
24
|
context ||= Context.new
|
25
|
-
|
25
|
+
simplify(ast_function, context).evaluate(context)
|
26
26
|
end
|
27
27
|
|
28
28
|
private
|
data/lib/keisan/parser.rb
CHANGED
@@ -241,6 +241,8 @@ module Keisan
|
|
241
241
|
:"&" => Parsing::BitwiseAnd,
|
242
242
|
:"|" => Parsing::BitwiseOr,
|
243
243
|
:"^" => Parsing::BitwiseXor,
|
244
|
+
:<< => Parsing::BitwiseLeftShift,
|
245
|
+
:>> => Parsing::BitwiseRightShift,
|
244
246
|
:"==" => Parsing::LogicalEqual,
|
245
247
|
:"!=" => Parsing::LogicalNotEqual,
|
246
248
|
:"&&" => Parsing::LogicalAnd,
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module Keisan
|
2
|
+
class StringAndGroupParser
|
3
|
+
class Portion
|
4
|
+
attr_reader :start_index, :end_index
|
5
|
+
|
6
|
+
def initialize(start_index)
|
7
|
+
@start_index = start_index
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class StringPortion < Portion
|
12
|
+
attr_reader :string
|
13
|
+
|
14
|
+
def initialize(expression, start_index)
|
15
|
+
super(start_index)
|
16
|
+
|
17
|
+
@string = expression[start_index]
|
18
|
+
@end_index = start_index + 1
|
19
|
+
|
20
|
+
while @end_index < expression.size
|
21
|
+
if expression[@end_index] == quote_type
|
22
|
+
@string << quote_type
|
23
|
+
@end_index += 1
|
24
|
+
# Successfully parsed the string
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
n, c = process_next_character(expression, @end_index)
|
29
|
+
@string << c
|
30
|
+
@end_index += n
|
31
|
+
end
|
32
|
+
|
33
|
+
raise Keisan::Exceptions::TokenizingError.new("Tokenizing error, no closing quote #{quote_type}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def size
|
37
|
+
string.size
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
string
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Returns number of processed input characters, and the output character
|
47
|
+
def process_next_character(expression, index)
|
48
|
+
# escape character
|
49
|
+
if expression[index] == "\\"
|
50
|
+
return [2, escaped_character(expression[index + 1])]
|
51
|
+
else
|
52
|
+
return [1, expression[index]]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def quote_type
|
57
|
+
@string[0]
|
58
|
+
end
|
59
|
+
|
60
|
+
def escaped_character(character)
|
61
|
+
case character
|
62
|
+
when "\\", '"', "'"
|
63
|
+
character
|
64
|
+
when "a"
|
65
|
+
"\a"
|
66
|
+
when "b"
|
67
|
+
"\b"
|
68
|
+
when "r"
|
69
|
+
"\r"
|
70
|
+
when "n"
|
71
|
+
"\n"
|
72
|
+
when "s"
|
73
|
+
"\s"
|
74
|
+
when "t"
|
75
|
+
"\t"
|
76
|
+
else
|
77
|
+
raise Keisan::Exceptions::TokenizingError.new("Tokenizing error, unknown escape character: \"\\#{character}\"")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class GroupPortion < Portion
|
83
|
+
attr_reader :opening_brace, :closing_brace ,:portions, :size
|
84
|
+
|
85
|
+
OPENING_TO_CLOSING_BRACE = {
|
86
|
+
"(" => ")",
|
87
|
+
"{" => "}",
|
88
|
+
"[" => "]",
|
89
|
+
}
|
90
|
+
|
91
|
+
def initialize(expression, start_index)
|
92
|
+
super(start_index)
|
93
|
+
|
94
|
+
case expression[start_index]
|
95
|
+
when OPEN_GROUP_REGEX
|
96
|
+
@opening_brace = expression[start_index]
|
97
|
+
else
|
98
|
+
raise Keisan::Exceptions::TokenizingError.new("Internal error, GroupPortion did not start with brace")
|
99
|
+
end
|
100
|
+
|
101
|
+
@closing_brace = OPENING_TO_CLOSING_BRACE[opening_brace]
|
102
|
+
|
103
|
+
parser = StringAndGroupParser.new(expression, start_index: start_index + 1, ending_character: closing_brace)
|
104
|
+
@portions = parser.portions
|
105
|
+
@size = parser.size + 2
|
106
|
+
|
107
|
+
if start_index + size > expression.size || expression[start_index + size - 1] != closing_brace
|
108
|
+
raise Keisan::Exceptions::TokenizingError.new("Tokenizing error, group with opening brace #{opening_brace} did not have closing brace")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_s
|
113
|
+
opening_brace + portions.map(&:to_s).join + closing_brace
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class OtherPortion < Portion
|
118
|
+
attr_reader :string
|
119
|
+
|
120
|
+
def initialize(expression, start_index)
|
121
|
+
super(start_index)
|
122
|
+
|
123
|
+
case expression[start_index]
|
124
|
+
when STRING_CHARACTER_REGEX, OPEN_GROUP_REGEX, CLOSED_GROUP_REGEX
|
125
|
+
raise Keisan::Exceptions::TokenizingError.new("Internal error, OtherPortion should not have string/braces at start")
|
126
|
+
else
|
127
|
+
index = start_index + 1
|
128
|
+
end
|
129
|
+
|
130
|
+
while index < expression.size
|
131
|
+
case expression[index]
|
132
|
+
when STRING_CHARACTER_REGEX, OPEN_GROUP_REGEX, CLOSED_GROUP_REGEX
|
133
|
+
break
|
134
|
+
else
|
135
|
+
index += 1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
@end_index = index
|
140
|
+
@string = expression[start_index...end_index]
|
141
|
+
end
|
142
|
+
|
143
|
+
def size
|
144
|
+
string.size
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_s
|
148
|
+
string
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# An ordered array of "portions", which
|
153
|
+
attr_reader :portions, :size
|
154
|
+
|
155
|
+
STRING_CHARACTER_REGEX = /["']/
|
156
|
+
OPEN_GROUP_REGEX = /[\(\{\[]/
|
157
|
+
CLOSED_GROUP_REGEX = /[\)\}\]]/
|
158
|
+
|
159
|
+
# Ending character is used as a second ending condition besides expression size
|
160
|
+
def initialize(expression, start_index: 0, ending_character: nil)
|
161
|
+
index = start_index
|
162
|
+
@portions = []
|
163
|
+
|
164
|
+
while index < expression.size && (ending_character.nil? || expression[index] != ending_character)
|
165
|
+
case expression[index]
|
166
|
+
when STRING_CHARACTER_REGEX
|
167
|
+
portion = StringPortion.new(expression, index)
|
168
|
+
index = portion.end_index
|
169
|
+
@portions << portion
|
170
|
+
|
171
|
+
when OPEN_GROUP_REGEX
|
172
|
+
portion = GroupPortion.new(expression, index)
|
173
|
+
index += portion.size
|
174
|
+
@portions << portion
|
175
|
+
|
176
|
+
when CLOSED_GROUP_REGEX
|
177
|
+
raise Keisan::Exceptions::TokenizingError.new("Tokenizing error, unexpected closing brace #{expression[start_index]}")
|
178
|
+
|
179
|
+
else
|
180
|
+
portion = OtherPortion.new(expression, index)
|
181
|
+
index += portion.size
|
182
|
+
@portions << portion
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
@size = index - start_index
|
187
|
+
end
|
188
|
+
|
189
|
+
def to_s
|
190
|
+
portions.map(&:to_s).join
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
data/lib/keisan/tokenizer.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
module Keisan
|
2
2
|
class Tokenizer
|
3
3
|
TOKEN_CLASSES = [
|
4
|
-
Tokens::Group,
|
5
|
-
Tokens::String,
|
6
4
|
Tokens::Null,
|
7
5
|
Tokens::Boolean,
|
8
6
|
Tokens::Word,
|
9
7
|
Tokens::Number,
|
10
8
|
Tokens::Assignment,
|
9
|
+
Tokens::BitwiseShift,
|
11
10
|
Tokens::LogicalOperator,
|
12
11
|
Tokens::ArithmeticOperator,
|
13
12
|
Tokens::BitwiseOperator,
|
@@ -26,8 +25,22 @@ module Keisan
|
|
26
25
|
|
27
26
|
def initialize(expression)
|
28
27
|
@expression = self.class.normalize_expression(expression)
|
29
|
-
|
30
|
-
|
28
|
+
|
29
|
+
portions = StringAndGroupParser.new(@expression).portions
|
30
|
+
|
31
|
+
@tokens = portions.inject([]) do |tokens, portion|
|
32
|
+
case portion
|
33
|
+
when StringAndGroupParser::StringPortion
|
34
|
+
tokens << Tokens::String.new(portion.to_s)
|
35
|
+
when StringAndGroupParser::GroupPortion
|
36
|
+
tokens << Tokens::Group.new(portion.to_s)
|
37
|
+
when StringAndGroupParser::OtherPortion
|
38
|
+
scan = portion.to_s.scan(TOKEN_REGEX)
|
39
|
+
tokens += tokenize!(scan)
|
40
|
+
end
|
41
|
+
|
42
|
+
tokens
|
43
|
+
end
|
31
44
|
end
|
32
45
|
|
33
46
|
def self.normalize_expression(expression)
|
@@ -45,8 +58,8 @@ module Keisan
|
|
45
58
|
expression.gsub(/#[^;]*/, "")
|
46
59
|
end
|
47
60
|
|
48
|
-
def tokenize!
|
49
|
-
|
61
|
+
def tokenize!(scan)
|
62
|
+
scan.map do |scan_result|
|
50
63
|
i = scan_result.find_index {|token| !token.nil?}
|
51
64
|
token_string = scan_result[i]
|
52
65
|
token = TOKEN_CLASSES[i].new(token_string)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Tokens
|
3
|
+
class BitwiseShift < Operator
|
4
|
+
LEFT_SHIFT = /(?:<<)/
|
5
|
+
RIGHT_SHIFT = /(?:>>)/
|
6
|
+
|
7
|
+
REGEX = /(#{LEFT_SHIFT}|#{RIGHT_SHIFT})/
|
8
|
+
|
9
|
+
def self.regex
|
10
|
+
REGEX
|
11
|
+
end
|
12
|
+
|
13
|
+
def operator_type
|
14
|
+
case string
|
15
|
+
when LEFT_SHIFT
|
16
|
+
:<<
|
17
|
+
when RIGHT_SHIFT
|
18
|
+
:>>
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/keisan/tokens/group.rb
CHANGED
@@ -1,19 +1,13 @@
|
|
1
1
|
module Keisan
|
2
2
|
module Tokens
|
3
3
|
class Group < Token
|
4
|
-
REGEX = /(\((?:[^\[\]\(\)\{\}]*+\g<1>*+)*+\)|\[(?:[^\[\]\(\)\{\}]*+\g<1>*+)*+\]|\{(?:[^\[\]\(\)\{\}]*+\g<1>*+)*+\})/
|
5
|
-
|
6
4
|
attr_reader :sub_tokens
|
7
5
|
|
8
6
|
def initialize(string)
|
9
|
-
|
7
|
+
@string = string
|
10
8
|
@sub_tokens = Tokenizer.new(string[1...-1]).tokens
|
11
9
|
end
|
12
10
|
|
13
|
-
def self.regex
|
14
|
-
REGEX
|
15
|
-
end
|
16
|
-
|
17
11
|
# Either :round, :square
|
18
12
|
def group_type
|
19
13
|
case string[0]
|
data/lib/keisan/tokens/string.rb
CHANGED
data/lib/keisan/version.rb
CHANGED
metadata
CHANGED
@@ -1,57 +1,57 @@
|
|
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.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:
|
11
|
+
date: 2020-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: cmath
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
26
|
+
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '13.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '13.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
97
111
|
description: A library for parsing equations into an abstract syntax tree for evaluation
|
98
112
|
email:
|
99
113
|
- project.eutopia@gmail.com
|
@@ -117,8 +131,10 @@ files:
|
|
117
131
|
- lib/keisan/ast/arithmetic_operator.rb
|
118
132
|
- lib/keisan/ast/assignment.rb
|
119
133
|
- lib/keisan/ast/bitwise_and.rb
|
134
|
+
- lib/keisan/ast/bitwise_left_shift.rb
|
120
135
|
- lib/keisan/ast/bitwise_operator.rb
|
121
136
|
- lib/keisan/ast/bitwise_or.rb
|
137
|
+
- lib/keisan/ast/bitwise_right_shift.rb
|
122
138
|
- lib/keisan/ast/bitwise_xor.rb
|
123
139
|
- lib/keisan/ast/block.rb
|
124
140
|
- lib/keisan/ast/boolean.rb
|
@@ -219,10 +235,12 @@ files:
|
|
219
235
|
- lib/keisan/parsing/arithmetic_operator.rb
|
220
236
|
- lib/keisan/parsing/assignment.rb
|
221
237
|
- lib/keisan/parsing/bitwise_and.rb
|
238
|
+
- lib/keisan/parsing/bitwise_left_shift.rb
|
222
239
|
- lib/keisan/parsing/bitwise_not.rb
|
223
240
|
- lib/keisan/parsing/bitwise_not_not.rb
|
224
241
|
- lib/keisan/parsing/bitwise_operator.rb
|
225
242
|
- lib/keisan/parsing/bitwise_or.rb
|
243
|
+
- lib/keisan/parsing/bitwise_right_shift.rb
|
226
244
|
- lib/keisan/parsing/bitwise_xor.rb
|
227
245
|
- lib/keisan/parsing/boolean.rb
|
228
246
|
- lib/keisan/parsing/component.rb
|
@@ -266,11 +284,13 @@ files:
|
|
266
284
|
- lib/keisan/parsing/unary_plus.rb
|
267
285
|
- lib/keisan/parsing/variable.rb
|
268
286
|
- lib/keisan/repl.rb
|
287
|
+
- lib/keisan/string_and_group_parser.rb
|
269
288
|
- lib/keisan/token.rb
|
270
289
|
- lib/keisan/tokenizer.rb
|
271
290
|
- lib/keisan/tokens/arithmetic_operator.rb
|
272
291
|
- lib/keisan/tokens/assignment.rb
|
273
292
|
- lib/keisan/tokens/bitwise_operator.rb
|
293
|
+
- lib/keisan/tokens/bitwise_shift.rb
|
274
294
|
- lib/keisan/tokens/boolean.rb
|
275
295
|
- lib/keisan/tokens/colon.rb
|
276
296
|
- lib/keisan/tokens/comma.rb
|
@@ -308,8 +328,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
308
328
|
- !ruby/object:Gem::Version
|
309
329
|
version: '0'
|
310
330
|
requirements: []
|
311
|
-
|
312
|
-
rubygems_version: 2.7.7
|
331
|
+
rubygems_version: 3.0.3
|
313
332
|
signing_key:
|
314
333
|
specification_version: 4
|
315
334
|
summary: An equation parser and evaluator
|