keisan 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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