math_engine 0.1.3 → 0.2.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.
- data/README.md +9 -2
- data/lib/errors.rb +41 -0
- data/lib/lexer.rb +20 -0
- data/lib/math_engine.rb +32 -15
- data/lib/nodes.rb +85 -0
- data/lib/parser.rb +128 -0
- metadata +10 -8
- data/lib/math_lexer.rb +0 -12
- data/lib/math_parser.rb +0 -158
data/README.md
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
# MathEngine
|
2
2
|
|
3
|
-
MathEngine is a lightweight mathematical expression parser and evaluator. It currently handles addition, subtraction, multiplication, division and the use of variables.
|
3
|
+
MathEngine is a lightweight mathematical expression parser and evaluator. It currently handles addition, subtraction, multiplication, division, exponent, modulus and the use of variables and pre-defined functions.
|
4
4
|
|
5
5
|
Install with
|
6
6
|
|
7
7
|
gem install math_engine
|
8
8
|
|
9
|
+
The only dependency is on my other gem, lexr[http://github.com/michaelbaldry/lexr], which is really lightweight and has no external dependencies.
|
10
|
+
|
9
11
|
## An example: Expressions
|
10
12
|
|
11
13
|
require 'rubygems'
|
@@ -25,4 +27,9 @@ if you missed a closing parenthesis, had an operator where it wasn't meant to be
|
|
25
27
|
|
26
28
|
Unexpected multiplication(*), expected: number, variable name or open_parenthesis
|
27
29
|
|
28
|
-
and that is pretty much every feature so far. Please let me know of any bugs or additions that you'd like to see!
|
30
|
+
and that is pretty much every feature so far. Please let me know of any bugs or additions that you'd like to see!
|
31
|
+
|
32
|
+
## License
|
33
|
+
|
34
|
+
See the LICENSE file included with the distribution for licensing and
|
35
|
+
copyright details.
|
data/lib/errors.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
class MathEngine
|
2
|
+
class ParseError < StandardError
|
3
|
+
def initialize(message)
|
4
|
+
@message = message
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
@message
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class UnknownVariableError < StandardError
|
13
|
+
def initialize(variable_name)
|
14
|
+
@variable_name = variable_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"Variable '#{@variable_name}' was referenced but does not exist"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class UnknownFunctionError < StandardError
|
23
|
+
def initialize(function_name)
|
24
|
+
@function_name = function_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"Function '#{@function_name}' was referenced but does not exist"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class UnableToModifyConstant < StandardError
|
33
|
+
def initialize(constant_name)
|
34
|
+
@constant_name = constant_name
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"Unable to modify value of constant '#{@constant_name}'"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/lexer.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'lexr'
|
3
|
+
|
4
|
+
class MathEngine
|
5
|
+
Lexer = Lexr.that {
|
6
|
+
ignores /\s/ => :whitespace
|
7
|
+
matches /[a-z][a-z0-9_]*/ => :identifier, :convert_with => lambda { |v| v.to_sym }
|
8
|
+
matches /[-+]?[0-9]*\.?[0-9]+/ => :number, :convert_with => lambda { |v| Float(v) }
|
9
|
+
matches ',' => :comma
|
10
|
+
matches '=' => :assignment
|
11
|
+
matches '+' => :addition
|
12
|
+
matches '-' => :subtraction
|
13
|
+
matches '*' => :multiplication
|
14
|
+
matches '/' => :division
|
15
|
+
matches '^' => :exponent
|
16
|
+
matches '%' => :modulus
|
17
|
+
matches '(' => :open_parenthesis
|
18
|
+
matches ')' => :close_parenthesis
|
19
|
+
}
|
20
|
+
end
|
data/lib/math_engine.rb
CHANGED
@@ -1,19 +1,20 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
|
4
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'math_lexer'))
|
5
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'math_parser'))
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'errors'))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'lexer'))
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'parser'))
|
6
4
|
|
7
5
|
class MathEngine
|
8
|
-
def initialize
|
6
|
+
def initialize()
|
9
7
|
@variables = {}
|
8
|
+
@dyn_library = Class.new.new
|
9
|
+
@libraries = [@dyn_library, Math]
|
10
10
|
end
|
11
11
|
|
12
12
|
def evaluate(expression)
|
13
|
-
|
13
|
+
Parser.new(Lexer.new(expression)).parse.evaluate(self)
|
14
14
|
end
|
15
15
|
|
16
16
|
def set(variable_name, value)
|
17
|
+
raise UnableToModifyConstant.new(variable_name) if @variables.keys.include? variable_name and variable_name.to_s == variable_name.to_s.upcase
|
17
18
|
@variables[variable_name] = value
|
18
19
|
end
|
19
20
|
|
@@ -23,16 +24,32 @@ class MathEngine
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def variables
|
26
|
-
@variables.keys
|
27
|
+
@variables.keys.collect { |k| k.to_s }.reject { |v| v.downcase != v }.sort.collect { |k| k.to_sym }
|
27
28
|
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
def constants
|
31
|
+
@variables.keys.collect { |k| k.to_s }.reject { |v| v.upcase != v }.sort.collect { |k| k.to_sym }
|
32
|
+
end
|
33
|
+
|
34
|
+
def call(name, *parameters)
|
35
|
+
cls = class_for_function(name)
|
36
|
+
raise UnknownFunctionError.new(name) unless cls
|
37
|
+
cls.send name, *parameters
|
38
|
+
end
|
39
|
+
|
40
|
+
def define(name, func = nil, &block)
|
41
|
+
@dyn_library.class.send :define_method, name.to_sym do |*args|
|
42
|
+
func ? func.call(*args) : block.call(*args)
|
36
43
|
end
|
37
44
|
end
|
45
|
+
|
46
|
+
def include_library(library)
|
47
|
+
@libraries << library
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def class_for_function(name)
|
53
|
+
@libraries.detect { |l| l.methods.include? name.to_s }
|
54
|
+
end
|
38
55
|
end
|
data/lib/nodes.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
class MathEngine
|
2
|
+
class Node
|
3
|
+
attr_reader :left, :right
|
4
|
+
alias :value :left
|
5
|
+
|
6
|
+
def initialize(left, right = nil)
|
7
|
+
@left, @right = left, right
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class LiteralNumberNode < Node
|
12
|
+
def evaluate(engine)
|
13
|
+
value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ExpressionNode < Node
|
18
|
+
def evaluate(engine)
|
19
|
+
left.evaluate(engine)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class IdentifierNode < Node
|
24
|
+
def evaluate(engine)
|
25
|
+
engine.get value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class AssignmentNode < Node
|
30
|
+
def evaluate(engine)
|
31
|
+
result = right.evaluate(engine)
|
32
|
+
engine.set(left.value, result)
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class AdditionNode < Node
|
38
|
+
def evaluate(engine)
|
39
|
+
left.evaluate(engine) + right.evaluate(engine)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class SubtractionNode < Node
|
44
|
+
def evaluate(engine)
|
45
|
+
left.evaluate(engine) - right.evaluate(engine)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
class MultiplicationNode < Node
|
49
|
+
def evaluate(engine)
|
50
|
+
left.evaluate(engine) * right.evaluate(engine)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
class DivisionNode < Node
|
54
|
+
def evaluate(engine)
|
55
|
+
left.evaluate(engine) / right.evaluate(engine)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
class ExponentNode < Node
|
59
|
+
def evaluate(engine)
|
60
|
+
left.evaluate(engine) ** right.evaluate(engine)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
class ModulusNode < Node
|
64
|
+
def evaluate(engine)
|
65
|
+
left.evaluate(engine) % right.evaluate(engine)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class FunctionCallNode < Node
|
70
|
+
def evaluate(engine)
|
71
|
+
parameters = right ? right.to_a.collect { |p| p.evaluate(engine) } : []
|
72
|
+
engine.call(left, *parameters)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class ParametersNode < Node
|
77
|
+
def evaluate(engine)
|
78
|
+
left.evaluate(engine)
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_a
|
82
|
+
[left] + (right ? right.to_a : [])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/parser.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'nodes'))
|
2
|
+
|
3
|
+
class MathEngine
|
4
|
+
class Parser
|
5
|
+
def initialize(lexer)
|
6
|
+
@lexer = lexer
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
#statement = { <identifier> <assignment> } expression <end>
|
11
|
+
#expression = term { ( <addition> | <subtraction> ) term }
|
12
|
+
#term = exp { ( <multiplication> | <division> ) exp }
|
13
|
+
#exp = factor { ( <exponent> | <modulus> ) factor }
|
14
|
+
#factor = <call> | <identifier> | <number> | ( <open_parenthesis> expression <close_parenthesis> )
|
15
|
+
#call = <identifier> <open_parenthesis> { call_parameter } <close_parenthesis>
|
16
|
+
#call_parameter = <expression> { <comma> call_parameter }
|
17
|
+
statement
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def statement
|
23
|
+
next!
|
24
|
+
if current.type == :identifier && peek.type == :assignment
|
25
|
+
variable_name = current.value
|
26
|
+
next!
|
27
|
+
expect_current :assignment
|
28
|
+
next!
|
29
|
+
result = MathEngine::AssignmentNode.new(MathEngine::IdentifierNode.new(variable_name), expression)
|
30
|
+
else
|
31
|
+
result = expression
|
32
|
+
end
|
33
|
+
next!
|
34
|
+
expect_current :end
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def expression
|
39
|
+
left = term
|
40
|
+
result = nil
|
41
|
+
while [:addition, :subtraction].include? current.type
|
42
|
+
node_type = current.type == :addition ? MathEngine::AdditionNode : MathEngine::SubtractionNode
|
43
|
+
next!
|
44
|
+
left = node_type.new(left, term)
|
45
|
+
end
|
46
|
+
result = MathEngine::ExpressionNode.new(result || left)
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def term
|
51
|
+
left = exp
|
52
|
+
result = nil
|
53
|
+
while [:multiplication, :division].include? current.type
|
54
|
+
node_type = current.type == :multiplication ? MathEngine::MultiplicationNode : MathEngine::DivisionNode
|
55
|
+
next!
|
56
|
+
left = node_type.new(left, exp)
|
57
|
+
end
|
58
|
+
result || left
|
59
|
+
end
|
60
|
+
|
61
|
+
def exp
|
62
|
+
left = factor
|
63
|
+
result = nil
|
64
|
+
while [:exponent, :modulus].include? current.type
|
65
|
+
node_type = current.type == :exponent ? MathEngine::ExponentNode : MathEngine::ModulusNode
|
66
|
+
next!
|
67
|
+
left = node_type.new(left, factor)
|
68
|
+
end
|
69
|
+
result || left
|
70
|
+
end
|
71
|
+
|
72
|
+
def factor
|
73
|
+
if current.type == :number
|
74
|
+
result = MathEngine::LiteralNumberNode.new(current.value)
|
75
|
+
next!
|
76
|
+
return result
|
77
|
+
elsif current.type == :identifier
|
78
|
+
result = peek.type == :open_parenthesis ? call : MathEngine::IdentifierNode.new(current.value)
|
79
|
+
next!
|
80
|
+
return result
|
81
|
+
end
|
82
|
+
|
83
|
+
expect_current :open_parenthesis, "number, variable or open_parenthesis"
|
84
|
+
next!
|
85
|
+
result = expression
|
86
|
+
expect_current :close_parenthesis
|
87
|
+
next!
|
88
|
+
result
|
89
|
+
end
|
90
|
+
|
91
|
+
def call
|
92
|
+
expect_current :identifier
|
93
|
+
function_name = current.value
|
94
|
+
next!
|
95
|
+
expect_current :open_parenthesis
|
96
|
+
next!
|
97
|
+
result = MathEngine::FunctionCallNode.new(function_name, current.type == :close_parenthesis ? nil : call_parameter)
|
98
|
+
expect_current :close_parenthesis
|
99
|
+
result
|
100
|
+
end
|
101
|
+
|
102
|
+
def call_parameter
|
103
|
+
left = expression
|
104
|
+
right = nil
|
105
|
+
if current.type == :comma
|
106
|
+
next!
|
107
|
+
right = call_parameter
|
108
|
+
end
|
109
|
+
MathEngine::ParametersNode.new(left, right)
|
110
|
+
end
|
111
|
+
|
112
|
+
def current
|
113
|
+
@lexer.current
|
114
|
+
end
|
115
|
+
|
116
|
+
def peek
|
117
|
+
@lexer.peek
|
118
|
+
end
|
119
|
+
|
120
|
+
def next!
|
121
|
+
@lexer.next
|
122
|
+
end
|
123
|
+
|
124
|
+
def expect_current(type, friendly = nil)
|
125
|
+
raise MathEngine::ParseError.new("Unexpected #{current}, expected: #{friendly ? friendly : type}") unless current.type == type
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: math_engine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Michael Baldry
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-12-02 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -58,9 +58,11 @@ extra_rdoc_files:
|
|
58
58
|
- README.md
|
59
59
|
files:
|
60
60
|
- README.md
|
61
|
+
- lib/errors.rb
|
62
|
+
- lib/lexer.rb
|
61
63
|
- lib/math_engine.rb
|
62
|
-
- lib/
|
63
|
-
- lib/
|
64
|
+
- lib/nodes.rb
|
65
|
+
- lib/parser.rb
|
64
66
|
has_rdoc: true
|
65
67
|
homepage: http://www.forwardtechnology.co.uk
|
66
68
|
licenses: []
|
@@ -95,6 +97,6 @@ rubyforge_project:
|
|
95
97
|
rubygems_version: 1.3.7
|
96
98
|
signing_key:
|
97
99
|
specification_version: 3
|
98
|
-
summary: Evaluates
|
100
|
+
summary: Evaluates mathematical expressions
|
99
101
|
test_files: []
|
100
102
|
|
data/lib/math_lexer.rb
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
MathLexer = Lexr.that {
|
2
|
-
ignores /\s/ => :whitespace
|
3
|
-
matches /[a-z][a-z0-9]*/ => :identifier, :convert_with => lambda { |v| v.to_sym }
|
4
|
-
matches /[-+]?[0-9]*\.?[0-9]+/ => :number, :convert_with => lambda { |v| Float(v) }
|
5
|
-
matches '=' => :assignment
|
6
|
-
matches '+' => :addition
|
7
|
-
matches '-' => :subtraction
|
8
|
-
matches '*' => :multiplication
|
9
|
-
matches '/' => :division
|
10
|
-
matches '(' => :open_parenthesis
|
11
|
-
matches ')' => :close_parenthesis
|
12
|
-
}
|
data/lib/math_parser.rb
DELETED
@@ -1,158 +0,0 @@
|
|
1
|
-
class MathParser
|
2
|
-
def initialize(lexer)
|
3
|
-
@lexer = lexer
|
4
|
-
end
|
5
|
-
|
6
|
-
def parse
|
7
|
-
#statement = { <identifier> <assignment> } expression <end>
|
8
|
-
#expression = term { ( <addition> | <subtraction> ) term }
|
9
|
-
#term = factor { ( <multiplication> | <division> ) factor }
|
10
|
-
#factor = <identifier> | <number> | <open_parenthesis> expression <close_parenthesis>
|
11
|
-
statement
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def statement
|
17
|
-
next!
|
18
|
-
if current.type == :identifier && peek.type == :assignment
|
19
|
-
variable_name = current.value
|
20
|
-
next!
|
21
|
-
expect_current :assignment
|
22
|
-
next!
|
23
|
-
result = AssignmentNode.new(IdentifierNode.new(variable_name), expression)
|
24
|
-
else
|
25
|
-
result = expression
|
26
|
-
end
|
27
|
-
next!
|
28
|
-
expect_current :end
|
29
|
-
result
|
30
|
-
end
|
31
|
-
|
32
|
-
def expression
|
33
|
-
left = term
|
34
|
-
result = nil
|
35
|
-
while [:addition, :subtraction].include? current.type
|
36
|
-
node_type = current.type == :addition ? AdditionNode : SubtractionNode
|
37
|
-
next!
|
38
|
-
left = node_type.new(left, term)
|
39
|
-
end
|
40
|
-
ExpressionNode.new(result || left)
|
41
|
-
end
|
42
|
-
|
43
|
-
def term
|
44
|
-
left = factor
|
45
|
-
result = nil
|
46
|
-
while [:multiplication, :division].include? current.type
|
47
|
-
node_type = current.type == :multiplication ? MultiplicationNode : DivisionNode
|
48
|
-
next!
|
49
|
-
left = node_type.new(left, factor)
|
50
|
-
end
|
51
|
-
result || left
|
52
|
-
end
|
53
|
-
|
54
|
-
def factor
|
55
|
-
if [:number, :identifier].include? current.type
|
56
|
-
node_type = current.type == :number ? LiteralNumberNode : IdentifierNode
|
57
|
-
result = node_type.new(current.value)
|
58
|
-
next!
|
59
|
-
return result
|
60
|
-
end
|
61
|
-
|
62
|
-
expect_current :open_parenthesis, "number, variable or open_parenthesis"
|
63
|
-
next!
|
64
|
-
result = expression
|
65
|
-
expect_current :close_parenthesis
|
66
|
-
next!
|
67
|
-
result
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def current
|
73
|
-
@lexer.current
|
74
|
-
end
|
75
|
-
|
76
|
-
def peek
|
77
|
-
@lexer.peek
|
78
|
-
end
|
79
|
-
|
80
|
-
def next!
|
81
|
-
@lexer.next
|
82
|
-
end
|
83
|
-
|
84
|
-
def expect_current(type, friendly = nil)
|
85
|
-
raise ParseError.new("Unexpected #{current}, expected: #{friendly ? friendly : type}") unless current.type == type
|
86
|
-
end
|
87
|
-
|
88
|
-
class Node
|
89
|
-
attr_reader :left, :right
|
90
|
-
alias :value :left
|
91
|
-
|
92
|
-
def initialize(left, right = nil)
|
93
|
-
@left, @right = left, right
|
94
|
-
end
|
95
|
-
|
96
|
-
def evaluate(engine)
|
97
|
-
raise "Evaluate not overridden in #{self.class.name}"
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
class LiteralNumberNode < Node
|
102
|
-
def evaluate(engine)
|
103
|
-
value
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
class ExpressionNode < Node
|
108
|
-
def evaluate(engine)
|
109
|
-
left.evaluate(engine)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
class IdentifierNode < Node
|
114
|
-
def evaluate(engine)
|
115
|
-
engine.get value
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
class AssignmentNode < Node
|
120
|
-
def evaluate(engine)
|
121
|
-
result = right.evaluate(engine)
|
122
|
-
engine.set(left.value, result)
|
123
|
-
result
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
class AdditionNode < Node
|
128
|
-
def evaluate(engine)
|
129
|
-
left.evaluate(engine) + right.evaluate(engine)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
class SubtractionNode < Node
|
134
|
-
def evaluate(engine)
|
135
|
-
left.evaluate(engine) - right.evaluate(engine)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
class MultiplicationNode < Node
|
139
|
-
def evaluate(engine)
|
140
|
-
left.evaluate(engine) * right.evaluate(engine)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
class DivisionNode < Node
|
144
|
-
def evaluate(engine)
|
145
|
-
left.evaluate(engine) / right.evaluate(engine)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
class ParseError < StandardError
|
150
|
-
def initialize(message)
|
151
|
-
@message = message
|
152
|
-
end
|
153
|
-
|
154
|
-
def to_s
|
155
|
-
@message
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|