calc_jzapat80 0.0.2
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 +7 -0
- data/bin/calc +22 -0
- data/lib/ast.rb +87 -0
- data/lib/calcex.rb +7 -0
- data/lib/calculator.rb +19 -0
- data/lib/parser.rb +146 -0
- data/lib/scanner.rb +127 -0
- data/lib/token.rb +20 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7bf6c1ffe5810b1f0c2c62456548ff651c63b90e
|
4
|
+
data.tar.gz: eb9dca67ac1460e3190d166e2e01bc4f7a245d86
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: be096b0fe55e4e67538c7b041a6b677af3347aebb5f25efa6587cc78fedddc7d536386f0f5f82045aa15d1aefa6867770c830e3fd487339b2956d7b9dc7be5c5
|
7
|
+
data.tar.gz: 142c94b1bbd680ed43db5c50f20b94ca94b2574ef5c852f25d91bf8c5978d4ef13c9a45cd56b639b0778c6fbf7fb841741b7904e674c52d2eaee016683a4191e
|
data/bin/calc
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'calculator'
|
5
|
+
require 'calcex'
|
6
|
+
|
7
|
+
$stdout.print "> "
|
8
|
+
$stdout.flush
|
9
|
+
|
10
|
+
text = gets
|
11
|
+
|
12
|
+
$calc = Calculator.new()
|
13
|
+
|
14
|
+
begin
|
15
|
+
puts "= " + $calc.eval(text).to_s
|
16
|
+
rescue ParseError
|
17
|
+
puts "Parse Error"
|
18
|
+
rescue UnrecognizedTokenException
|
19
|
+
puts "UnrecognizedTokenException"
|
20
|
+
rescue
|
21
|
+
puts "Unkown exception"
|
22
|
+
end
|
data/lib/ast.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
class BinaryNode
|
5
|
+
attr_reader :left, :right
|
6
|
+
|
7
|
+
def initialize(left,right)
|
8
|
+
@left = left
|
9
|
+
@right = right
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class UnaryNode
|
14
|
+
attr_reader :subTree
|
15
|
+
|
16
|
+
def initialize(subTree)
|
17
|
+
@subTree = subTree
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class AddNode < BinaryNode
|
22
|
+
def initialize(left, right)
|
23
|
+
super(left,right)
|
24
|
+
end
|
25
|
+
|
26
|
+
def evaluate()
|
27
|
+
return @left.evaluate() + @right.evaluate() #Como ya sabemos, los paréntesis no son obligatorios.
|
28
|
+
#El return no hace falta.
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class SubNode < BinaryNode
|
33
|
+
def initialize(left, right)
|
34
|
+
super(left,right)
|
35
|
+
end
|
36
|
+
|
37
|
+
def evaluate()
|
38
|
+
return @left.evaluate() - @right.evaluate()
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class TimesNode < BinaryNode
|
43
|
+
def initialize(left, right)
|
44
|
+
super(left, right)
|
45
|
+
end
|
46
|
+
|
47
|
+
def evaluate()
|
48
|
+
return @left.evaluate() * @right.evaluate()
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class DivideNode < BinaryNode
|
53
|
+
def initialize(left, right)
|
54
|
+
super(left, right)
|
55
|
+
end
|
56
|
+
|
57
|
+
def evaluate()
|
58
|
+
return @left.evaluate() / @right.evaluate()
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class StoreNode < UnaryNode
|
63
|
+
def initialize(sub)
|
64
|
+
super(sub)
|
65
|
+
end
|
66
|
+
|
67
|
+
def evaluate
|
68
|
+
$calc.memory = subTree.evaluate #El $calc llama a la variable global calc que se llama en el irb>require 'calculator'
|
69
|
+
#irb>$calc = Calculator.new
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class RecallNode
|
74
|
+
def evaluate
|
75
|
+
return $calc.memory
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class NumNode
|
80
|
+
def initialize(num)
|
81
|
+
@num = num
|
82
|
+
end
|
83
|
+
|
84
|
+
def evaluate()
|
85
|
+
return @num
|
86
|
+
end
|
87
|
+
end
|
data/lib/calcex.rb
ADDED
data/lib/calculator.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'parser'
|
2
|
+
require 'ast'
|
3
|
+
|
4
|
+
class Calculator
|
5
|
+
#attr_reader :memory
|
6
|
+
#attr_writer :memory
|
7
|
+
|
8
|
+
attr_accessor :memory
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
@memory = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def eval(expr)
|
15
|
+
parser = Parser.new(StringIO.new(expr))
|
16
|
+
ast = parser.parse()
|
17
|
+
return ast.evaluate()
|
18
|
+
end
|
19
|
+
end
|
data/lib/parser.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'ast'
|
3
|
+
require 'scanner'
|
4
|
+
require 'token'
|
5
|
+
require 'calcex'
|
6
|
+
|
7
|
+
class Parser
|
8
|
+
def initialize(istream)
|
9
|
+
@scan = Scanner.new(istream)
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse()
|
13
|
+
return Prog()
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def Prog()
|
18
|
+
result = Expr()
|
19
|
+
t = @scan.getToken()
|
20
|
+
|
21
|
+
if t.type != :eof then
|
22
|
+
print "Expected EOF. Found ", t.type, ".\n"
|
23
|
+
raise ParseError.new
|
24
|
+
end
|
25
|
+
|
26
|
+
return result
|
27
|
+
end
|
28
|
+
|
29
|
+
def Expr()
|
30
|
+
RestExpr(Term())
|
31
|
+
end
|
32
|
+
|
33
|
+
def RestExpr(e)
|
34
|
+
t = @scan.getToken()
|
35
|
+
|
36
|
+
if t.type == :add then
|
37
|
+
return RestExpr(AddNode.new(e,Term()))
|
38
|
+
end
|
39
|
+
|
40
|
+
if t.type == :sub then
|
41
|
+
return RestExpr(SubNode.new(e,Term()))
|
42
|
+
end
|
43
|
+
|
44
|
+
@scan.putBackToken()
|
45
|
+
|
46
|
+
return e
|
47
|
+
end
|
48
|
+
|
49
|
+
def Term()
|
50
|
+
# # Write your Term() code here. This code is just temporary
|
51
|
+
# # so you can try the calculator out before finishing it.
|
52
|
+
|
53
|
+
# t = @scan.getToken()
|
54
|
+
|
55
|
+
# if t.type == :number then
|
56
|
+
# val = t.lex.to_i
|
57
|
+
# return NumNode.new(val)
|
58
|
+
# end
|
59
|
+
|
60
|
+
# puts "Term not implemented\n"
|
61
|
+
|
62
|
+
# raise ParseError.new
|
63
|
+
|
64
|
+
RestTerm(Storable()) #Look at this. You dont have to return
|
65
|
+
end
|
66
|
+
|
67
|
+
def RestTerm(e)
|
68
|
+
|
69
|
+
#puts "RestTerm not implemented"
|
70
|
+
#raise ParseError.new # "Parse Error"
|
71
|
+
|
72
|
+
t=@scan.getToken
|
73
|
+
|
74
|
+
if t.type == :times
|
75
|
+
return RestTerm(TimesNode.new(e,Storable()))
|
76
|
+
end
|
77
|
+
|
78
|
+
if t.type == :divide
|
79
|
+
return RestTerm(DivideNode.new(e,Storable()))
|
80
|
+
end
|
81
|
+
|
82
|
+
@scan.putBackToken
|
83
|
+
|
84
|
+
e #No hay que hacer return. La ventaja de poner return es por documentación.
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
def Storable()
|
91
|
+
|
92
|
+
#puts "Storable not implemented"
|
93
|
+
#raise ParseError.new # "Parse Error"
|
94
|
+
|
95
|
+
result = Factor()
|
96
|
+
|
97
|
+
t=@scan.getToken
|
98
|
+
|
99
|
+
if t.type == :keyword then
|
100
|
+
if t.lex == "S" then
|
101
|
+
return StoreNode.new(result)
|
102
|
+
end
|
103
|
+
puts "Expected S, found: ", t.lex
|
104
|
+
raise ParseError.new
|
105
|
+
end
|
106
|
+
|
107
|
+
@scan.putBackToken
|
108
|
+
return result
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
def Factor()
|
113
|
+
|
114
|
+
#puts "Factor not implemented"
|
115
|
+
#raise ParseError.new # "Parse Error"
|
116
|
+
|
117
|
+
t = @scan.getToken
|
118
|
+
if t.type == :number then
|
119
|
+
return NumNode.new(t.lex.to_i)
|
120
|
+
end
|
121
|
+
|
122
|
+
if t.type == :keyword then
|
123
|
+
if t.lex == "R" then
|
124
|
+
return RecallNode.new
|
125
|
+
end
|
126
|
+
puts "Expected R, found: " + t.lex
|
127
|
+
raise ParseError.new
|
128
|
+
end
|
129
|
+
|
130
|
+
if t.type == :lparen then
|
131
|
+
result = Expr()
|
132
|
+
|
133
|
+
t = @scan.getToken
|
134
|
+
if t.type == :rparen then
|
135
|
+
return result
|
136
|
+
end
|
137
|
+
|
138
|
+
puts "Expected ')' found: " + t.type.to_s
|
139
|
+
raise ParseError.new
|
140
|
+
end
|
141
|
+
|
142
|
+
puts "Expected number, R, ( found: " + t.type
|
143
|
+
raise ParseError.new #"ParseError"
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
data/lib/scanner.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'stringio' #No se pone *.rb
|
2
|
+
require 'calcex'
|
3
|
+
|
4
|
+
class Scanner
|
5
|
+
def initialize(inStream)
|
6
|
+
@istream = inStream
|
7
|
+
@keywords = Set.new(["S","R"])
|
8
|
+
@lineCount = 1
|
9
|
+
@colCount = -1
|
10
|
+
@needToken = true
|
11
|
+
@lastToken = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def putBackToken()
|
15
|
+
@needToken = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def getToken()
|
19
|
+
unless @needToken
|
20
|
+
@needToken = true
|
21
|
+
return @lastToken
|
22
|
+
end
|
23
|
+
|
24
|
+
state = 0
|
25
|
+
foundOne = false
|
26
|
+
c = @istream.getc()
|
27
|
+
|
28
|
+
if @istream.eof() then
|
29
|
+
@lastToken = Token.new(:eof,@lineCount,@colCount)
|
30
|
+
return @lastToken
|
31
|
+
end
|
32
|
+
|
33
|
+
until foundOne
|
34
|
+
@colCount = @colCount + 1
|
35
|
+
case state
|
36
|
+
when 0 #La manera de preguntar por los estados: when
|
37
|
+
lex = ""
|
38
|
+
column = @colCount
|
39
|
+
line = @lineCount
|
40
|
+
if isLetter(c) then state=1
|
41
|
+
elsif isDigit(c) then state=2
|
42
|
+
elsif c == ?+ then state = 3
|
43
|
+
elsif c == ?- then state = 4
|
44
|
+
elsif c == ?* then state = 5
|
45
|
+
elsif c == ?/ then state = 6
|
46
|
+
elsif c == ?( then state = 7
|
47
|
+
elsif c == ?) then state = 8
|
48
|
+
elsif c == ?\n then
|
49
|
+
@colCount = -1
|
50
|
+
@lineCount = @lineCount+1
|
51
|
+
elsif isWhiteSpace(c) then state = state #ignore whitespace
|
52
|
+
elsif @istream.eof() then
|
53
|
+
@foundOne = true
|
54
|
+
type = :eof
|
55
|
+
else
|
56
|
+
puts "Unrecognized Token found at line ",line," and column ",column,"\n"
|
57
|
+
raise UnrecognizedTokenException # "Unrecognized Token"
|
58
|
+
end
|
59
|
+
when 1
|
60
|
+
if isLetter(c) or isDigit(c) then state = 1
|
61
|
+
else
|
62
|
+
if @keywords.include?(lex) then
|
63
|
+
foundOne = true
|
64
|
+
type = :keyword
|
65
|
+
else
|
66
|
+
foundOne = true
|
67
|
+
type = :identifier
|
68
|
+
end
|
69
|
+
end
|
70
|
+
when 2
|
71
|
+
if isDigit(c) then state = 2
|
72
|
+
else
|
73
|
+
type = :number
|
74
|
+
foundOne = true
|
75
|
+
end
|
76
|
+
when 3
|
77
|
+
type = :add
|
78
|
+
foundOne = true
|
79
|
+
when 4
|
80
|
+
type = :sub
|
81
|
+
foundOne = true
|
82
|
+
when 5
|
83
|
+
type = :times
|
84
|
+
foundOne = true
|
85
|
+
when 6
|
86
|
+
type = :divide
|
87
|
+
foundOne = true
|
88
|
+
when 7
|
89
|
+
type = :lparen
|
90
|
+
foundOne = true
|
91
|
+
when 8
|
92
|
+
type = :rparen
|
93
|
+
foundOne = true
|
94
|
+
end
|
95
|
+
|
96
|
+
if !foundOne then
|
97
|
+
lex.concat(c)
|
98
|
+
c = @istream.getc()
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
@istream.ungetc(c)
|
104
|
+
@colCount = @colCount - 1
|
105
|
+
if type == :number or type == :identifier or type == :keyword then
|
106
|
+
t = LexicalToken.new(type,lex,line,column)
|
107
|
+
else
|
108
|
+
t = Token.new(type,line,column)
|
109
|
+
end
|
110
|
+
|
111
|
+
@lastToken = t
|
112
|
+
return t
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
def isLetter(c)
|
117
|
+
return ((?a <= c and c <= ?z) or (?A <= c and c <= ?Z))
|
118
|
+
end
|
119
|
+
|
120
|
+
def isDigit(c)
|
121
|
+
return (?0 <= c and c <= ?9)
|
122
|
+
end
|
123
|
+
|
124
|
+
def isWhiteSpace(c)
|
125
|
+
return (c == ?\ or c == ?\n or c == ?\t)
|
126
|
+
end
|
127
|
+
end
|
data/lib/token.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
class Token
|
3
|
+
attr_reader :type, :line, :col
|
4
|
+
|
5
|
+
def initialize(type,lineNum,colNum)
|
6
|
+
@type = type
|
7
|
+
@line = lineNum
|
8
|
+
@col = colNum
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class LexicalToken < Token #Herencia simple, no se puede multiple como en c++, no se pone public tampoco
|
13
|
+
attr_reader :lex
|
14
|
+
|
15
|
+
def initialize(type,lex,lineNum,colNum) #constructor que dentro de sí, llama al constructor de la clase padre mediante el super.
|
16
|
+
super(type,lineNum,colNum)
|
17
|
+
|
18
|
+
@lex = lex
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: calc_jzapat80
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kent D. Lee - Juan Francisco Cardona Mc - Jonathan Stiven Zapata
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-12 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: An calculator implementation on ruby
|
14
|
+
email: fcardona@eafit.edu.co - jzapat80@eafit.edu.co
|
15
|
+
executables:
|
16
|
+
- calc
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- bin/calc
|
21
|
+
- lib/ast.rb
|
22
|
+
- lib/calcex.rb
|
23
|
+
- lib/calculator.rb
|
24
|
+
- lib/parser.rb
|
25
|
+
- lib/scanner.rb
|
26
|
+
- lib/token.rb
|
27
|
+
homepage: http://www1.eafit.edu.co/fcardona/cursos/st0244/rubycal
|
28
|
+
licenses:
|
29
|
+
- ARTISTIC
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 2.4.7
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: Another calculator in ruby
|
51
|
+
test_files: []
|