calculo 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 647451e4c1e42739a7571ef0deb0db5bd4af3555
4
+ data.tar.gz: e27e02406f3fa7446749996881a986f81694dc49
5
+ SHA512:
6
+ metadata.gz: ca50356dc05252792acf85230babbbd64fb34e11bd1d6e486133722d46f9a0c8d6c50554155164e2081464ceff7e0db62ed9984b4217b0d871245d10b067a76a
7
+ data.tar.gz: 4e45b33979518f68c2a67769999299e8741a67cfa1ba9294ee96761b2c75b6eb8c25b8563f018e2c1aa1c0370745385822564080691d0831445d9664e6809cde
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Calculo
2
+ A calculator specifically designed to support any kind of mathematical syntax
3
+ you throw at it.
4
+
5
+ Consider it The AD to [BC](https://www.gnu.org/software/bc/), the AC to [DC](https://packages.debian.org/jessie/dc)
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rake/testtask'
2
+ require 'rake/dmg'
3
+
4
+ Rake::DmgTask.new 'calculo', '1.0.0' do |dmg|
5
+ dmg.source_files.include('bin/*')
6
+ dmg.source_files.include('calculo/*')
7
+ dmg.source_files.include('test/*')
8
+ dmg.source_files.include('README.md')
9
+ dmg.strip = 'build'
10
+ end
11
+
12
+ Rake::TestTask.new do |t|
13
+ t.libs << 'test'
14
+ end
15
+
16
+ desc "Run tests"
17
+ task :default => :test
data/bin/calculo ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'calculo'
4
+
5
+ options = {}
6
+ OptionParser.new do |opts|
7
+ opts.banner = "Usage: calculo.rb [options]"
8
+ opts.on("-t","--type TYPE",String,"The type of expression to be computed") do |type|
9
+ options[:type] = type
10
+ end
11
+
12
+ opts.on("-e","--expr EXPR","The expression to be computed") do |expr|
13
+ options[:expr] = expr
14
+ end
15
+ opts.on("-f","--file FILE",String,"Reads expressions from a file") do |file|
16
+ options[:file] = file
17
+ end
18
+ opts.on("-h","--help","Prints this help") do
19
+ puts opts
20
+ end
21
+ end.parse!
22
+
23
+ def cli_eval(string,type)
24
+ if type == 'lisp' or type == 'reverse-lisp' or type == 'reverse'
25
+ cli = Lisp.new(string,type)
26
+ output = "#{cli.string} : #{cli.result}"
27
+ return output
28
+ elsif type == 'rpn' or type == 'postfix' or type == 'pn' or type == 'prefix' or type == 'infix'
29
+ cli = MathNotation.new(string,type)
30
+ output = "#{cli.string} : #{cli.result}"
31
+ return output
32
+ end
33
+ end
34
+
35
+ if not options.empty?
36
+ if options.has_key?(:file)
37
+ File.open(options[:file]).each_line do |line|
38
+ puts(cli_eval(line,options[:type]))
39
+ end
40
+
41
+ elsif options.has_key?(:expr)
42
+ puts(cli_eval(options[:expr],options[:type]))
43
+
44
+ elsif options.has_key?(:type)
45
+ Repl.new("=>",options[:type])
46
+ end
47
+ else
48
+ Repl.new("=>","infix")
49
+ end
@@ -0,0 +1,58 @@
1
+ require 'parse'
2
+ class Lisp
3
+ attr_accessor :string
4
+ attr_accessor :type
5
+ attr_accessor :array
6
+ attr_accessor :result
7
+
8
+ def initialize(string, type)
9
+ @string = string
10
+ @type = type
11
+ @array = parse(@string)
12
+ @result = eval_lisp(@array)
13
+ end
14
+
15
+ @private
16
+ def execute(array,type)
17
+ case type
18
+ when 'lisp'
19
+ array[2..-2].reduce(array[1].to_sym)
20
+ when 'reverse-lisp' || 'reverse'
21
+ array[1..-3].reverse.reduce(array[-2].to_sym)
22
+ end
23
+ end
24
+
25
+ def innermost_pair(array)
26
+ l_parens = []
27
+ r_parens = []
28
+
29
+ array.each_with_index do |item, index|
30
+
31
+ if item == "("
32
+ l_parens.push(index)
33
+
34
+ elsif item == ")"
35
+ r_parens.push(index)
36
+ end
37
+ end
38
+
39
+ total_length = l_parens.length + r_parens.length
40
+ return [total_length, l_parens[-1], r_parens[0]]
41
+ end
42
+
43
+ def eval_lisp(array)
44
+ length, l, r = innermost_pair(array)
45
+ result = execute(array[l..r],type)
46
+
47
+ if length > 2
48
+ # Inserts the result in between the portions
49
+ # of the array not including the portion just
50
+ # used to calculate the result
51
+ new_array = array[0..(l-1)] + [result] + array[(r+1)..-1]
52
+ eval_lisp(new_array)
53
+
54
+ elsif length == 2
55
+ return result
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,56 @@
1
+ require 'parse'
2
+ require 'shunting_yard'
3
+ class MathNotation
4
+ attr_accessor :type
5
+ attr_accessor :string
6
+ attr_accessor :array
7
+ attr_accessor :result
8
+
9
+ def initialize(string,type)
10
+ @string = string
11
+ if type == 'infix'
12
+ @type = 'rpn'
13
+ @array = shunting_yard(parse(@string))
14
+ @result = eval_rpn(@array)
15
+ else
16
+ @array = parse(@string)
17
+ @type = type
18
+ if @type == 'prefix' or @type == 'pn'
19
+ @result = eval_pn(@array)
20
+ elsif @type == 'postfix' or @type == 'rpn'
21
+ @result = eval_rpn(@array)
22
+ end
23
+ end
24
+ end
25
+
26
+ @private
27
+ def eval_pn(array)
28
+ operators = ['+','-','^','%','/','**','*']
29
+ stack = []
30
+ array.reverse.each{|item|
31
+ if operators.include?(item)
32
+ num2,num1 = stack.pop(2)
33
+ result = num1.method(item).call(num2)
34
+ stack.push(result)
35
+ else
36
+ stack.push(item)
37
+ end
38
+ }
39
+ return stack.pop
40
+ end
41
+
42
+ def eval_rpn(array)
43
+ operators = ['+','-','^','%','/','**','*']
44
+ stack = []
45
+ array.each{|item|
46
+ if operators.include?(item)
47
+ num1,num2 = stack.pop(2)
48
+ result = num1.method(item).call(num2)
49
+ stack.push(result)
50
+ else
51
+ stack.push(item)
52
+ end
53
+ }
54
+ return stack.pop
55
+ end
56
+ end
@@ -0,0 +1,20 @@
1
+ def parse(string)
2
+ # First regex puts a space after a parenthesis/math operation,
3
+ # Second regex a space between a digit and a parenthesis/math
4
+ # operation(s)
5
+ # Third regex converts any double spaces into single space
6
+ # Fourth regex converts to the exponent operator used in ruby
7
+ # split seperates the characters based on a space
8
+
9
+ op_re = Regexp.new(/([^\d\w\s\-])/)
10
+ dig_op_re = Regexp.new(/\d[^\d\w\s]+/)
11
+ dbl_space_re = Regexp.new(/\s{2,}/)
12
+ exp_re = Regexp.new(/\^/)
13
+ dig_re = Regexp.new(/\d+/)
14
+ array = string.gsub(op_re,'\1 ').gsub(dig_op_re){ |s| s.chars.join(' ')}.gsub(dbl_space_re,' ').gsub(exp_re,'**').split(' ')
15
+
16
+ # Regex finds if array element matches a digit, and converts it
17
+ # to a float.
18
+ array = array.map{|x| dig_re.match(x) != nil ? x.to_f : x}
19
+ return array
20
+ end
@@ -0,0 +1,110 @@
1
+ require 'IO/console'
2
+ require 'os'
3
+ require 'pry'
4
+
5
+ class Repl
6
+ attr_accessor :history
7
+ attr_accessor :prompt
8
+ attr_accessor :type
9
+ def initialize(prompt,type)
10
+ @history = []
11
+ @og_prompt = prompt
12
+ @prompt = "[#{@history.length}]" + @og_prompt + " "
13
+ @type = type
14
+ clear_screen()
15
+ calculo_loop()
16
+ end
17
+
18
+ def clear_screen
19
+ if OS.linux? or OS.mac? or OS.bsd?
20
+ system('clear')
21
+ elsif OS.windows?
22
+ system('cls')
23
+ end
24
+ end
25
+
26
+ def calculo_print(history)
27
+ history.each_with_index.map{|x,i| puts("#{i} : #{x}\n")}
28
+ end
29
+
30
+ def repl_eval(input)
31
+ # syntax for changing prompt/type is:
32
+ # set prompt/type = 'foo'
33
+ # or set prompt/type 'foo'
34
+ if input.start_with?("set","change")
35
+ @history.push(input)
36
+ input_array = input.split(' ')
37
+
38
+ if input_array[1] == "prompt" and input_array.last != "prompt"
39
+ @og_prompt = input_array.last
40
+ @prompt = "[#{@history.length}]" + @og_prompt + " "
41
+ return "prompt is now #{@prompt}"
42
+
43
+ elsif input_array[1] == "type" and input_array.last != "type"
44
+ @type = input_array.last
45
+ return "type is now #{@type}"
46
+ else
47
+ return "Error: variable can not be changed"
48
+ end
49
+
50
+ # If input is simply history, print all of the history
51
+ # can also use ruby range syntax, STARTING AT 0
52
+ # for example: history 0..-2 will print all but the
53
+ # last entry of history
54
+ elsif input.start_with?("history")
55
+ @history.push(input)
56
+ if input == "history"
57
+ return calculo_print(@history)
58
+ else
59
+ input_array = input.split(' ')
60
+ input_range = input_array[-1]
61
+ if input_range.length == 1
62
+ return @history[input_range.to_i]
63
+ elsif input_range.length == 4
64
+ return calculo_print(@history[(input_range.chars.first.to_i)..(input_range.chars.last.to_i)])
65
+ elsif input_range.length == 5
66
+ return calculo_print(@history[(input_range.chars.first.to_i)..(input_range.chars.drop(2).to_i)])
67
+ end
68
+ end
69
+
70
+ elsif input.start_with?("clear")
71
+ @history.push(input)
72
+ if input == "clear"
73
+ return clear_screen()
74
+ elsif input == "clear history"
75
+ @history = []
76
+ return "History is now blank"
77
+ end
78
+
79
+ elsif input == "exit" or input == "quit"
80
+ return "Exiting calculo!"
81
+
82
+ else
83
+ if @type == 'lisp' or @type == 'reverse-lisp' or @type == 'reverse'
84
+ repl_class = Lisp.new(input,@type)
85
+ calculo_output = "#{repl_class.string} : #{repl_class.result}"
86
+ @history.push(calculo_output)
87
+ return calculo_output
88
+
89
+ elsif @type == 'postfix' or @type == 'infix' or @type == 'prefix' or @type == 'pn' or @type == 'rpn'
90
+ repl_class = MathNotation.new(input,@type)
91
+ calculo_output = "#{repl_class.string} : #{repl_class.result}"
92
+ @history.push(calculo_output)
93
+ return calculo_output
94
+ end
95
+ end
96
+ end
97
+
98
+ def calculo_loop(output="Welcome to calculo!")
99
+ input = nil
100
+ while output != "Exiting calculo!"
101
+ puts(output)
102
+ @prompt = "[#{@history.length}]" + @og_prompt + " "
103
+ print(@prompt)
104
+ input = gets.chomp
105
+ output = repl_eval(input)
106
+ end
107
+ puts(output)
108
+ exit(1)
109
+ end
110
+ end
@@ -0,0 +1,32 @@
1
+ def shunting_yard(infix_array)
2
+ operators = {'+' => 2, '-' => 2, '*' => 3, '/' => 3, '>' => 4, '<' => 4, '=' => 4, '%' => 4, '**' => 4}
3
+ rpn_expr = []
4
+ op_stack = []
5
+
6
+ infix_array.each do |item|
7
+ if operators.has_key?(item)
8
+ op2 = op_stack.last
9
+ if operators.has_key?(op2) and ((op2 == "**" and operators[item] < operators[op2]) or (op2 != "**" and operators[item] <= operators[op2]))
10
+ rpn_expr.push(op_stack.pop)
11
+ end
12
+ op_stack.push(item)
13
+
14
+ elsif item == "("
15
+ op_stack.push(item)
16
+
17
+ elsif item == ")"
18
+ until op_stack.last == "("
19
+ rpn_expr.push(op_stack.pop)
20
+ end
21
+ op_stack.pop
22
+ else
23
+ rpn_expr.push(item)
24
+ end
25
+ end
26
+
27
+ until op_stack.empty?
28
+ rpn_expr.push(op_stack.pop)
29
+ end
30
+
31
+ return rpn_expr
32
+ end
data/lib/calculo.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'calculo/math'
2
+ require 'calculo/lisp'
3
+ require 'calculo/repl'
data/test/test.rb ADDED
@@ -0,0 +1,129 @@
1
+ require_relative "../lib/calculo/lisp"
2
+ require_relative "../lib/calculo/math"
3
+ require "minitest/autorun"
4
+
5
+ class TestLisp < MiniTest::Test
6
+ def setup
7
+ @type = "lisp"
8
+ @string = "( - ( + ( / 18 ( ^ 3 2 ) ) 12 ) 13 )"
9
+ @array = ["(", "-", "(", "+", "(", "/", 18.0, "(", "**", 3.0, 2.0, ")", ")", 12.0, ")", 13.0, ")"]
10
+ @result = 1
11
+ @Lisp = Lisp.new(@string, @type)
12
+ end
13
+
14
+ def test_type_access
15
+ assert_equal(@type, @Lisp.type)
16
+ end
17
+
18
+ def test_string_access
19
+ assert_equal(@string, @Lisp.string)
20
+ end
21
+
22
+ def test_array_access
23
+ assert_equal(@array, @Lisp.array)
24
+ end
25
+
26
+ def test_results
27
+ assert_equal(@result, @Lisp.result)
28
+ end
29
+ end
30
+
31
+ class TestReverseLisp < MiniTest::Test
32
+ def setup
33
+ @type = "reverse-lisp"
34
+ @string = "( 13 ( 12 ( ( 2 3 ^ ) 18 / ) + ) - )"
35
+ @array = ["(", 13.0, "(", 12.0, "(", "(", 2.0, 3.0, "**", ")", 18.0, "/", ")", "+", ")", "-", ")"]
36
+ @result = 1
37
+ @ReverseLisp = Lisp.new(@string, @type)
38
+ end
39
+
40
+ def test_type_access
41
+ assert_equal(@type, @ReverseLisp.type)
42
+ end
43
+
44
+ def test_string_access
45
+ assert_equal(@string, @ReverseLisp.string)
46
+ end
47
+
48
+ def test_array_access
49
+ assert_equal(@array, @ReverseLisp.array)
50
+ end
51
+
52
+ def test_results
53
+ assert_equal(@result, @ReverseLisp.result)
54
+ end
55
+ end
56
+
57
+ class TestPN < MiniTest::Test
58
+ def setup
59
+ @type = "pn"
60
+ @string = "- + / 18 ^ 3 2 12 13"
61
+ @array = ['-', '+', '/', 18.0, '**', 3.0, 2.0, 12.0, 13.0]
62
+ @result = 1
63
+ @PN = MathNotation.new(@string, @type)
64
+ end
65
+
66
+ def test_type_access
67
+ assert_equal(@type, @PN.type)
68
+ end
69
+
70
+ def test_string_access
71
+ assert_equal(@string, @PN.string)
72
+ end
73
+
74
+ def test_array_access
75
+ assert_equal(@array, @PN.array)
76
+ end
77
+
78
+ def test_result_access
79
+ assert_equal(@result, @PN.result)
80
+ end
81
+ end
82
+
83
+ class TestRPN < MiniTest::Test
84
+ def setup
85
+ @type = "rpn"
86
+ @string = "12 18 3 2 ^ / 13 - +"
87
+ @array = [12.0, 18.0, 3.0, 2.0, "**", "/", 13.0, "-", "+"]
88
+ @result = 1
89
+ @RPN = MathNotation.new(@string, @type)
90
+ end
91
+
92
+ def test_string_access
93
+ assert_equal(@string, @RPN.string)
94
+ end
95
+
96
+ def test_array_access
97
+ assert_equal(@array, @RPN.array)
98
+ end
99
+
100
+ def test_result_access
101
+ assert_equal(@result, @RPN.result)
102
+ end
103
+ end
104
+
105
+ class TestInfix < MiniTest::Test
106
+ def setup
107
+ @type = "rpn"
108
+ @string = "12 + 18 / ( 3 ^ 2 ) - 13"
109
+ @rpn_array = [12.0, 18.0, 3.0, 2.0, "**", "/", 13.0, "-", "+"]
110
+ @result = 1
111
+ @Infix = MathNotation.new(@string, "infix")
112
+ end
113
+
114
+ def test_type
115
+ assert_equal(@type, @Infix.type)
116
+ end
117
+
118
+ def test_string_access
119
+ assert_equal(@string, @Infix.string)
120
+ end
121
+
122
+ def test_shunting_yard
123
+ assert_equal(@rpn_array, @Infix.array)
124
+ end
125
+
126
+ def test_result_access
127
+ assert_equal(@result, @Infix.result)
128
+ end
129
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: calculo
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Noah Holt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: os
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake_dmg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ocra
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A calculator for the command line supporting postfix, prefix, infix,
56
+ and lisp notation (plus a REPL)
57
+ email: noahryanholt@gmail.com
58
+ executables:
59
+ - calculo
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - Rakefile
64
+ - README.md
65
+ - test/test.rb
66
+ - lib/calculo.rb
67
+ - lib/calculo/lisp.rb
68
+ - lib/calculo/parse.rb
69
+ - lib/calculo/math.rb
70
+ - lib/calculo/shunting_yard.rb
71
+ - lib/calculo/repl.rb
72
+ - bin/calculo
73
+ homepage: https://github.com/noaoh/calculo
74
+ licenses:
75
+ - GPLv3
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ - bin
82
+ - test
83
+ - lib/calculo
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 2.0.0
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.0.14.1
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: A calculator for the command line
100
+ test_files: []