keisan 0.1.0

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.
Files changed (112) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +4 -0
  6. data/MIT-LICENSE +19 -0
  7. data/README.md +188 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +10 -0
  10. data/bin/setup +8 -0
  11. data/keisan.gemspec +31 -0
  12. data/lib/keisan.rb +118 -0
  13. data/lib/keisan/ast/arithmetic_operator.rb +9 -0
  14. data/lib/keisan/ast/bitwise_and.rb +21 -0
  15. data/lib/keisan/ast/bitwise_operator.rb +9 -0
  16. data/lib/keisan/ast/bitwise_or.rb +21 -0
  17. data/lib/keisan/ast/bitwise_xor.rb +21 -0
  18. data/lib/keisan/ast/boolean.rb +15 -0
  19. data/lib/keisan/ast/builder.rb +141 -0
  20. data/lib/keisan/ast/exponent.rb +25 -0
  21. data/lib/keisan/ast/function.rb +19 -0
  22. data/lib/keisan/ast/indexing.rb +16 -0
  23. data/lib/keisan/ast/list.rb +10 -0
  24. data/lib/keisan/ast/literal.rb +6 -0
  25. data/lib/keisan/ast/logical_and.rb +21 -0
  26. data/lib/keisan/ast/logical_greater_than.rb +17 -0
  27. data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +17 -0
  28. data/lib/keisan/ast/logical_less_than.rb +17 -0
  29. data/lib/keisan/ast/logical_less_than_or_equal_to.rb +17 -0
  30. data/lib/keisan/ast/logical_operator.rb +9 -0
  31. data/lib/keisan/ast/logical_or.rb +21 -0
  32. data/lib/keisan/ast/node.rb +9 -0
  33. data/lib/keisan/ast/null.rb +12 -0
  34. data/lib/keisan/ast/number.rb +15 -0
  35. data/lib/keisan/ast/operator.rb +50 -0
  36. data/lib/keisan/ast/parent.rb +15 -0
  37. data/lib/keisan/ast/plus.rb +49 -0
  38. data/lib/keisan/ast/string.rb +15 -0
  39. data/lib/keisan/ast/times.rb +36 -0
  40. data/lib/keisan/ast/unary_bitwise_not.rb +9 -0
  41. data/lib/keisan/ast/unary_identity.rb +9 -0
  42. data/lib/keisan/ast/unary_inverse.rb +9 -0
  43. data/lib/keisan/ast/unary_logical_not.rb +9 -0
  44. data/lib/keisan/ast/unary_minus.rb +9 -0
  45. data/lib/keisan/ast/unary_operator.rb +13 -0
  46. data/lib/keisan/ast/unary_plus.rb +9 -0
  47. data/lib/keisan/ast/variable.rb +16 -0
  48. data/lib/keisan/calculator.rb +30 -0
  49. data/lib/keisan/context.rb +36 -0
  50. data/lib/keisan/exceptions.rb +19 -0
  51. data/lib/keisan/function.rb +15 -0
  52. data/lib/keisan/functions/default_registry.rb +58 -0
  53. data/lib/keisan/functions/rand.rb +22 -0
  54. data/lib/keisan/functions/registry.rb +50 -0
  55. data/lib/keisan/functions/sample.rb +20 -0
  56. data/lib/keisan/parser.rb +211 -0
  57. data/lib/keisan/parsing/argument.rb +6 -0
  58. data/lib/keisan/parsing/arithmetic_operator.rb +6 -0
  59. data/lib/keisan/parsing/bitwise_and.rb +9 -0
  60. data/lib/keisan/parsing/bitwise_not.rb +9 -0
  61. data/lib/keisan/parsing/bitwise_not_not.rb +9 -0
  62. data/lib/keisan/parsing/bitwise_operator.rb +6 -0
  63. data/lib/keisan/parsing/bitwise_or.rb +9 -0
  64. data/lib/keisan/parsing/bitwise_xor.rb +9 -0
  65. data/lib/keisan/parsing/boolean.rb +11 -0
  66. data/lib/keisan/parsing/component.rb +6 -0
  67. data/lib/keisan/parsing/divide.rb +9 -0
  68. data/lib/keisan/parsing/element.rb +6 -0
  69. data/lib/keisan/parsing/exponent.rb +9 -0
  70. data/lib/keisan/parsing/function.rb +12 -0
  71. data/lib/keisan/parsing/group.rb +11 -0
  72. data/lib/keisan/parsing/indexing.rb +14 -0
  73. data/lib/keisan/parsing/list.rb +10 -0
  74. data/lib/keisan/parsing/logical_and.rb +9 -0
  75. data/lib/keisan/parsing/logical_greater_than.rb +9 -0
  76. data/lib/keisan/parsing/logical_greater_than_or_equal_to.rb +9 -0
  77. data/lib/keisan/parsing/logical_less_than.rb +9 -0
  78. data/lib/keisan/parsing/logical_less_than_or_equal_to.rb +9 -0
  79. data/lib/keisan/parsing/logical_not.rb +9 -0
  80. data/lib/keisan/parsing/logical_not_not.rb +9 -0
  81. data/lib/keisan/parsing/logical_operator.rb +6 -0
  82. data/lib/keisan/parsing/logical_or.rb +9 -0
  83. data/lib/keisan/parsing/minus.rb +9 -0
  84. data/lib/keisan/parsing/null.rb +6 -0
  85. data/lib/keisan/parsing/number.rb +10 -0
  86. data/lib/keisan/parsing/operator.rb +13 -0
  87. data/lib/keisan/parsing/plus.rb +9 -0
  88. data/lib/keisan/parsing/round_group.rb +6 -0
  89. data/lib/keisan/parsing/square_group.rb +6 -0
  90. data/lib/keisan/parsing/string.rb +10 -0
  91. data/lib/keisan/parsing/times.rb +9 -0
  92. data/lib/keisan/parsing/unary_minus.rb +9 -0
  93. data/lib/keisan/parsing/unary_operator.rb +9 -0
  94. data/lib/keisan/parsing/unary_plus.rb +9 -0
  95. data/lib/keisan/parsing/variable.rb +11 -0
  96. data/lib/keisan/token.rb +25 -0
  97. data/lib/keisan/tokenizer.rb +41 -0
  98. data/lib/keisan/tokens/arithmetic_operator.rb +29 -0
  99. data/lib/keisan/tokens/bitwise_operator.rb +29 -0
  100. data/lib/keisan/tokens/boolean.rb +20 -0
  101. data/lib/keisan/tokens/comma.rb +11 -0
  102. data/lib/keisan/tokens/group.rb +28 -0
  103. data/lib/keisan/tokens/logical_operator.rb +38 -0
  104. data/lib/keisan/tokens/null.rb +15 -0
  105. data/lib/keisan/tokens/number.rb +24 -0
  106. data/lib/keisan/tokens/operator.rb +9 -0
  107. data/lib/keisan/tokens/string.rb +15 -0
  108. data/lib/keisan/tokens/word.rb +11 -0
  109. data/lib/keisan/variables/default_registry.rb +20 -0
  110. data/lib/keisan/variables/registry.rb +41 -0
  111. data/lib/keisan/version.rb +3 -0
  112. metadata +238 -0
@@ -0,0 +1,22 @@
1
+ module Keisan
2
+ module Functions
3
+ class Rand < Keisan::Function
4
+ def initialize
5
+ @name = "rand"
6
+ end
7
+
8
+ # Single argument: integer in range [0, max)
9
+ # Double argument: integer in range [min, max)
10
+ def call(context, *args)
11
+ case args.size
12
+ when 1
13
+ context.random.rand(args.first)
14
+ when 2
15
+ context.random.rand(args.first...args.last)
16
+ else
17
+ raise Keisan::Exceptions::InvalidFunctionError.new
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ module Keisan
2
+ module Functions
3
+ class Registry
4
+ def initialize(functions: {}, parent: nil, use_defaults: true)
5
+ @hash = {}
6
+ @parent = parent
7
+ @use_defaults = use_defaults
8
+
9
+ functions.each do |name, function|
10
+ register!(name, function)
11
+ end
12
+ end
13
+
14
+ def [](name)
15
+ return @hash[name] if @hash.has_key?(name)
16
+ return @parent[name] if @parent.present? && @parent.has_name?(name)
17
+ return default_registry[name] if @use_defaults && default_registry.has_name?(name)
18
+ raise Keisan::Exceptions::UndefinedFunctionError.new name
19
+ end
20
+
21
+ def has_name?(name)
22
+ @hash.has_key?(name)
23
+ end
24
+
25
+ def register!(name, function)
26
+ raise Keisan::Exceptions::UnmodifiableError.new("Cannot modify frozen functions registry") if frozen?
27
+ name = name.to_s
28
+
29
+ case function
30
+ when Proc
31
+ self[name] = Keisan::Function.new(name, function)
32
+ when Keisan::Function
33
+ self[function.name] = function
34
+ else
35
+ raise Keisan::Exceptions::InvalidFunctionError.new
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def []=(name, function)
42
+ @hash[name] = function
43
+ end
44
+
45
+ def default_registry
46
+ DefaultRegistry.registry
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,20 @@
1
+ module Keisan
2
+ module Functions
3
+ class Sample < Keisan::Function
4
+ def initialize
5
+ @name = "sample"
6
+ end
7
+
8
+ # Single argument: integer in range [0, max)
9
+ # Double argument: integer in range [min, max)
10
+ def call(context, *args)
11
+ case args.size
12
+ when 1
13
+ args.first.sample(random: context.random)
14
+ else
15
+ raise Keisan::Exceptions::InvalidFunctionError.new
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,211 @@
1
+ module Keisan
2
+ class Parser
3
+ attr_reader :tokens, :components
4
+
5
+ def initialize(string: nil, tokens: nil)
6
+ if string.nil? && tokens.nil?
7
+ raise Keisan::Exceptions::InternalError.new("Invalid arguments")
8
+ end
9
+
10
+ if string.present?
11
+ @tokens = Tokenizer.new(string).tokens
12
+ else
13
+ raise Keisan::Exceptions::InternalError.new("Invalid argument: tokens = #{tokens}") if tokens.nil? || !tokens.is_a?(Array)
14
+ @tokens = tokens
15
+ end
16
+
17
+ @components = []
18
+
19
+ parse_components!
20
+ end
21
+
22
+ def ast
23
+ @ast ||= Keisan::AST::Builder.new(parser: self).ast
24
+ end
25
+
26
+ private
27
+
28
+ def parse_components!
29
+ @unparsed_tokens = tokens.dup
30
+
31
+ # Components will store the elements (numbers, variables, bracket components, function calls)
32
+ # and the operators in between
33
+ while @unparsed_tokens.count > 0
34
+ token = @unparsed_tokens.shift
35
+ add_token_to_components!(token)
36
+ end
37
+ end
38
+
39
+ # Elements are groups of tokens separated by (non-unary) operators
40
+ # The following are basic elements:
41
+ # number
42
+ # variable
43
+ # function (word + round group)
44
+ # list (square group)
45
+ #
46
+ # Additionally these can be modified by having any number of unary operators in front,
47
+ # and any number of indexing groups (square groups) at the back
48
+ #
49
+ def add_token_to_components!(token)
50
+ if @components.empty? || @components[-1].is_a?(Parsing::Operator)
51
+ # Expect an element or a unary operator
52
+ if token.type == :operator
53
+ # Here it must be a unary operator
54
+ add_unary_operator_to_components!(token)
55
+ else
56
+ # Here it must be an element
57
+ add_element_to_components!(token)
58
+ end
59
+
60
+ elsif @components[-1].is_a?(Parsing::UnaryOperator)
61
+ # Expect an element
62
+ case token.type
63
+ when :number, :string, :word, :group, :null, :boolean
64
+ add_element_to_components!(token)
65
+ else
66
+ raise Keisan::Exceptions::ParseError.new("Expected an element, received #{token.string}")
67
+ end
68
+
69
+ elsif @components[-1].is_a?(Parsing::Element)
70
+ if @components[-1].is_a?(Parsing::Variable) && token.type == :group && token.group_type == :round
71
+ add_function_to_components!(token)
72
+ elsif token.type == :group && token.group_type == :square
73
+ add_indexing_to_components!(token)
74
+ else
75
+ # Expect an operator
76
+ raise Keisan::Exceptions::ParseError.new("Expected an operator, received #{token.string}") unless token.type == :operator
77
+ add_operator_to_components!(token)
78
+ end
79
+
80
+ else
81
+ raise Keisan::Exceptions::InternalError.new("Invalid parsing!")
82
+ end
83
+ end
84
+
85
+ def add_unary_operator_to_components!(token)
86
+ case token.operator_type
87
+ when :+
88
+ @components << Keisan::Parsing::UnaryPlus.new
89
+ when :-
90
+ @components << Keisan::Parsing::UnaryMinus.new
91
+ when :"~"
92
+ @components << Keisan::Parsing::BitwiseNot.new
93
+ when :"~~"
94
+ @components << Keisan::Parsing::BitwiseNotNot.new
95
+ when :"!"
96
+ @components << Keisan::Parsing::LogicalNot.new
97
+ when :"!!"
98
+ @components << Keisan::Parsing::LogicalNotNot.new
99
+ else
100
+ raise Keisan::Exceptions::ParseError.new("Unhandled unary operator type #{token.operator_type}")
101
+ end
102
+ end
103
+
104
+ def add_element_to_components!(token)
105
+ case token
106
+ when Keisan::Tokens::Number
107
+ @components << Keisan::Parsing::Number.new(token.value)
108
+ when Keisan::Tokens::String
109
+ @components << Keisan::Parsing::String.new(token.value)
110
+ when Keisan::Tokens::Null
111
+ @components << Keisan::Parsing::Null.new
112
+ when Keisan::Tokens::Word
113
+ @components << Keisan::Parsing::Variable.new(token.string)
114
+ when Keisan::Tokens::Boolean
115
+ @components << Keisan::Parsing::Boolean.new(token.value)
116
+ when Keisan::Tokens::Group
117
+ case token.group_type
118
+ when :round
119
+ @components << Keisan::Parsing::RoundGroup.new(token.sub_tokens)
120
+ when :square
121
+ @components << if token.sub_tokens.empty?
122
+ Parsing::List.new([])
123
+ else
124
+ Parsing::List.new(
125
+ token.sub_tokens.split {|sub_token| sub_token.is_a?(Keisan::Tokens::Comma)}.map do |sub_tokens|
126
+ Parsing::Argument.new(sub_tokens)
127
+ end
128
+ )
129
+ end
130
+ else
131
+ raise Keisan::Exceptions::ParseError.new("Unhandled group type #{token.group_type}")
132
+ end
133
+ else
134
+ raise Keisan::Exceptions::ParseError.new("Unhandled operator type #{token.operator_type}")
135
+ end
136
+ end
137
+
138
+ def add_operator_to_components!(token)
139
+ case token.operator_type
140
+ # Arithmetic
141
+ when :+
142
+ @components << Keisan::Parsing::Plus.new
143
+ when :-
144
+ @components << Keisan::Parsing::Minus.new
145
+ when :*
146
+ @components << Keisan::Parsing::Times.new
147
+ when :/
148
+ @components << Keisan::Parsing::Divide.new
149
+ when :**
150
+ @components << Keisan::Parsing::Exponent.new
151
+ # Bitwise
152
+ when :"&"
153
+ @components << Keisan::Parsing::BitwiseAnd.new
154
+ when :"|"
155
+ @components << Keisan::Parsing::BitwiseOr.new
156
+ when :"^"
157
+ @components << Keisan::Parsing::BitwiseXor.new
158
+ when :"~"
159
+ @components << Keisan::Parsing::BitwiseNot.new
160
+ when :"~~"
161
+ @components << Keisan::Parsing::BitwiseNotNot.new
162
+ # Logical
163
+ when :"&&"
164
+ @components << Keisan::Parsing::LogicalAnd.new
165
+ when :"||"
166
+ @components << Keisan::Parsing::LogicalOr.new
167
+ when :"!"
168
+ @components << Keisan::Parsing::LogicalNot.new
169
+ when :"!!"
170
+ @components << Keisan::Parsing::LogicalNotNot.new
171
+ when :">"
172
+ @components << Keisan::Parsing::LogicalGreaterThan.new
173
+ when :"<"
174
+ @components << Keisan::Parsing::LogicalLessThan.new
175
+ when :">="
176
+ @components << Keisan::Parsing::LogicalGreaterThanOrEqualTo.new
177
+ when :"<="
178
+ @components << Keisan::Parsing::LogicalLessThanOrEqualTo.new
179
+ else
180
+ raise Keisan::Exceptions::ParseError.new("Unhandled operator type #{token.operator_type}")
181
+ end
182
+ end
183
+
184
+ def add_function_to_components!(token)
185
+ # Have a function actually, not a variable
186
+ if token.sub_tokens.empty?
187
+ @components[-1] = Parsing::Function.new(@components[-1].name, [])
188
+ else
189
+ @components[-1] = Parsing::Function.new(
190
+ @components[-1].name,
191
+ token.sub_tokens.split {|sub_token| sub_token.is_a?(Keisan::Tokens::Comma)}.map do |sub_tokens|
192
+ Parsing::Argument.new(sub_tokens)
193
+ end
194
+ )
195
+ end
196
+ end
197
+
198
+ def add_indexing_to_components!(token)
199
+ # Have an indexing
200
+ @components << if token.sub_tokens.empty?
201
+ Parsing::Indexing.new([])
202
+ else
203
+ Parsing::Indexing.new(
204
+ token.sub_tokens.split {|sub_token| sub_token.is_a?(Keisan::Tokens::Comma)}.map do |sub_tokens|
205
+ Parsing::Argument.new(sub_tokens)
206
+ end
207
+ )
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,6 @@
1
+ module Keisan
2
+ module Parsing
3
+ class Argument < Group
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Keisan
2
+ module Parsing
3
+ class ArithmeticOperator < Operator
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Keisan
2
+ module Parsing
3
+ class BitwiseAnd < BitwiseOperator
4
+ def node_class
5
+ Keisan::AST::BitwiseAnd
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Keisan
2
+ module Parsing
3
+ class BitwiseNot < UnaryOperator
4
+ def node_class
5
+ Keisan::AST::UnaryBitwiseNot
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Keisan
2
+ module Parsing
3
+ class BitwiseNotNot < UnaryOperator
4
+ def node_class
5
+ Keisan::AST::UnaryIdentity
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Keisan
2
+ module Parsing
3
+ class BitwiseOperator < Operator
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Keisan
2
+ module Parsing
3
+ class BitwiseOr < BitwiseOperator
4
+ def node_class
5
+ Keisan::AST::BitwiseOr
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Keisan
2
+ module Parsing
3
+ class BitwiseXor < BitwiseOperator
4
+ def node_class
5
+ Keisan::AST::BitwiseXor
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module Keisan
2
+ module Parsing
3
+ class Boolean < Element
4
+ attr_reader :value
5
+
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module Keisan
2
+ module Parsing
3
+ class Component
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Keisan
2
+ module Parsing
3
+ class Divide < ArithmeticOperator
4
+ def node_class
5
+ Keisan::AST::Times
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Keisan
2
+ module Parsing
3
+ class Element < Component
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Keisan
2
+ module Parsing
3
+ class Exponent < ArithmeticOperator
4
+ def node_class
5
+ Keisan::AST::Exponent
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Keisan
2
+ module Parsing
3
+ class Function < Element
4
+ attr_reader :name, :arguments
5
+
6
+ def initialize(name, arguments)
7
+ @name = name
8
+ @arguments = arguments
9
+ end
10
+ end
11
+ end
12
+ end