"Analyze that!"
Interpreter
Cobol does not have the ability to evaluate expressions dynamically, i.e. defining an expression in a string and having it evaluated during the runtime. OO Cobol is a whole different story. There is no eval command, but we can create our own way to evaluate things using the Interpreter Pattern!
That sounds great, but what is an evalute command anyways? Good question. Language elements stored in a string are nothing, but a string. An eval command take that string and interpret its content, processing the elements accordling. That means power, the ability to define code blocks and have it processed when needed. The Interpreter Pattern allows you to define your own grammar, and process it in order to do some useful task.
UML diagram

The Interpreter Pattern allows much more than simple arithmetic expressions evaluation, thought this is exactly what we are going to show here. The classes in this solutions are:
IExpression: Interface that declares an operation
Context: Global information used by the expression
TerminalExpression: Represent elements in the grammar that do no to get replace, such as symbols
NonTerminalExpression: Represents elements that will be replaced during the evaluation such as variables or even rules
ComputeFormula: Client class that also contains the parser used to break expression's elements.
The ComputeFormula class deals with a lot of concepts not covered in this article, but I recommend you to understand that class in order to get yourself used to things like stacks, collections, string handling and much more.
OO Cobol code
Everything starts with an Interface definition. IExpression declares the contract to which classes must adhere in order to participate in this solution.
1: INTERFACE-ID. IExpression as "InterpreterPattern.IExpression".
2: environment division.
3: configuration section.
4: repository.
5: class ClassContext as "InterpreterPattern.Context".
6:
7: procedure division.
8:
9: method-id. EvaluateExpression as "Evaluate".
10:
11: data division.
12: linkage section.
13:
14: 01 inContext object reference ClassContext.
15: 01 outValue usage comp-2.
16:
17: procedure division using inContext returning outValue.
18: end method EvaluateExpression.
19: END INTERFACE IExpression.
The following classes implements the IExpression interface: AddExpression.cob, SubtractExpression.cob, , MultiplyExpression.cob, DivideExpression.cob, and PowerExpression.cob. These are also the classes that contains the actual calculation performed over operands.
Below the code for PowerExpression code:
1: CLASS-ID. PowerExpression AS "InterpreterPattern.PowerExpression"
2: inherits ClassNonTerminalExpression.
3: environment division.
4: configuration section.
5: repository.
6: class ClassContext as "InterpreterPattern.Context"
7: class SystemString as "System.String"
8: class ClassNonTerminalExpression as "InterpreterPattern.NonTerminalExpression"
9:
10: interface IExpression as "InterpreterPattern.IExpression".
11:
12: *> Instance's data and methods
13: object.
14: data division.
15: working-storage section.
16: copy "types.book".
17:
18: 01 variable object reference SystemString private.
19:
20: procedure division.
21:
22: method-id. NEW.
23: data division.
24: linkage section.
25: 01 inLeftExpression object reference IExpression.
26: 01 inRightExpression object reference IExpression.
27:
28: procedure division using inLeftExpression, inRightExpression.
29:
30: invoke super "NEW" using inLeftExpression, inRightExpression.
31:
32: end method NEW.
33:
34: method-id. EvaluateExpresion as "Evaluate" override.
35: data division.
36: working-storage section.
37:
38: 01 anExpression object reference IExpression.
39:
40: linkage section.
41:
42: 01 inContext object reference ClassContext.
43: 01 outValue type SystemDouble.
44:
45: procedure division using inContext returning outValue.
46:
47: invoke super "GetLeftNode" returning anExpression
48: move anExpression::"Evaluate"(inContext) to outValue
49: invoke super "GetRightNode" returning anExpression
50:
51: *> This is the real "PowerExpression"
52: compute outValue = outValue ** anExpression::"Evaluate"(inContext)
53:
54: end method EvaluateExpresion.
55: end object.
56: END CLASS PowerExpression.
Other operations' classes such as AddExpression or MultiplyExpression have pretty much basically the same code structure.
The TerminalExpression class that defines symbols behavior:
1: CLASS-ID. TerminalExpression AS "InterpreterPattern.TerminalExpression".
2: environment division.
3: configuration section.
4: repository.
5: class ClassContext as "InterpreterPattern.Context"
6: class SystemCollectionHashTable as "System.Collections.Hashtable"
7: class SystemString as "System.String"
8:
9: interface IExpression as "InterpreterPattern.IExpression".
10:
11: *> Instance's data and methods
12: object. implements IExpression.
13: data division.
14: working-storage section.
15: copy "types.book".
16:
17: 01 variable object reference SystemString private.
18:
19: procedure division.
20:
21: method-id. NEW.
22: data division.
23: linkage section.
24: 01 inName object reference SystemString.
25:
26: procedure division using inName.
27:
28: set variable to inName
29:
30: end method NEW.
31:
32: method-id. EvaluateExpression as "Evaluate".
33: data division.
34: linkage section.
35:
36: 01 inContext object reference ClassContext.
37: 01 outValue type SystemDouble.
38:
39: procedure division using inContext returning outValue.
40:
41: move inContext::"GetValue"(variable) to outValue
42:
43: end method EvaluateExpression.
44: end object.
45: END CLASS TerminalExpression.
The NonTerminalExpression class that defines rules:
1: CLASS-ID. NonTerminalExpression AS "InterpreterPattern.NonTerminalExpression".
2: environment division.
3: configuration section.
4: repository.
5: class ClassContext as "InterpreterPattern.Context"
6: class SystemCollectionHashTable as "System.Collections.Hashtable"
7: class SystemString as "System.String"
8:
9: interface IExpression as "InterpreterPattern.IExpression".
10:
11: *> Instance's data and methods
12: object. implements IExpression.
13: data division.
14: working-storage section.
15: copy "types.book".
16:
17: 01 leftNode object reference IExpression private.
18: 01 rightNode object reference IExpression private.
19:
20: procedure division.
21:
22: method-id. NEW.
23: data division.
24: linkage section.
25: 01 inLeftExpression object reference IExpression.
26: 01 inRightExpression object reference IExpression.
27: procedure division using inLeftExpression inRightExpression.
28:
29: invoke self "SetLeftNode" using inLeftExpression
30: invoke self "SetRightNode" using inRightExpression
31:
32: end method NEW.
33:
34: method-id. SetLeftNode.
35: data division.
36: linkage section.
37:
38: 01 inNode object reference IExpression.
39:
40: procedure division using inNode.
41:
42: set leftNode to inNode
43:
44: end method SetLeftNode.
45: method-id. SetRightNode.
46: data division.
47: linkage section.
48:
49: 01 inNode object reference IExpression.
50:
51: procedure division using inNode.
52:
53: set rightNode to inNode
54:
55: end method SetRightNode.
56: method-id. GetLeftNode.
57: data division.
58: linkage section.
59:
60: 01 outNode object reference IExpression.
61:
62: procedure division returning outNode.
63:
64: set outNode to leftNode
65:
66: end method GetLeftNode.
67: method-id. GetRightNode.
68: data division.
69: linkage section.
70:
71: 01 outNode object reference IExpression.
72:
73: procedure division returning outNode.
74:
75: set outNode to rightNode
76:
77: end method GetRightNode.
78: method-id. EvaluateExpression as "Evaluate".
79: data division.
80: linkage section.
81:
82: 01 inContext object reference ClassContext.
83: 01 outValue type SystemDouble.
84:
85: procedure division using inContext returning outValue.
86:
87: *> Implemented because NetCobol.Net does not supports Abstract classes
88:
89: end method EvaluateExpression.
90: end object.
91: END CLASS NonTerminalExpression.
The ComputeFormula class is the "client" class of this example. It contains a parser, the expression tree builder and, of course, uses Interpreter pattern to arrange things.
1: CLASS-ID. ComputeFormula AS "InterpreterPattern.ComputeFormula".
2: environment division.
3: configuration section.
4: repository.
5: class SystemCollectionHashTable as "System.Collections.Hashtable"
6: class SystemString as "System.String"
7: class SystemCollectionStack as "System.Collections.Stack"
8: class SystemDoubleWrapper as "System.Double"
9: class SystemObject as "System.Object"
10:
11: class ClassAddExpression as "InterpreterPattern.AddExpression"
12: class ClassSubtractExpression as "InterpreterPattern.SubtractExpression"
13: class ClassMultiplyExpression as "InterpreterPattern.MultiplyExpression"
14: class ClassDivideExpression as "InterpreterPattern.DivideExpression"
15: class ClassPowerExpression as "InterpreterPattern.PowerExpression"
16: class ClassTerminalExpression as "InterpreterPattern.TerminalExpression"
17: class ClassNonTerminalExpression as "InterpreterPattern.NonTerminalExpression"
18: class ClassContext as "InterpreterPattern.Context"
19:
20: interface IExpression as "InterpreterPattern.IExpression"
21:
22: property StringNull as "Empty"
23: property StringLength as "Length"
24: property StackCount as "Count".
25:
26: *> Instance's data and methods
27: object.
28: data division.
29: working-storage section.
30: copy "types.book".
31: 01 booleanTrue pic 1 value b"1".
32: 01 booleanFalse pic 1 value b"0".
33: 01 expression object reference SystemString.
34: 01 ctx object reference ClassContext.
35: 01 operators object reference SystemCollectionHashTable private.
36: 01 tempObject object reference SystemObject.
37:
38: procedure division.
39:
40: method-id. NEW.
41:
42: procedure division.
43: invoke SystemCollectionHashTable "NEW" returning operators
44:
45: invoke operators "Add" using by value "+" by value "1"
46: invoke operators "Add" using by value "-" by value "1"
47: invoke operators "Add" using by value "/" by value "2"
48: invoke operators "Add" using by value "*" by value "2"
49: invoke operators "Add" using by value "^" by value "2"
50: invoke operators "Add" using by value "(" by value "0" 51: end method NEW.
52:
53: method-id. SetContext as "SetContext".
54: data division.
55: linkage section.
56:
57: 01 inContext object reference ClassContext.
58:
59: procedure division using inContext.
60:
61: set ctx to inContext
62:
63: end method SetContext.
64: method-id. SetExpression as "SetExpression".
65: data division.
66: linkage section.
67:
68: 01 inExpression object reference SystemString.
69:
70: procedure division using inExpression.
71:
72: set expression to inExpression
73:
74: end method SetExpression.
75:
76: method-id. EvaluateExpression as "Evaluate".
77:
78: data division.
79:
80: working-storage section.
81:
82: 01 postFixExp object reference SystemString.
83: 01 rootNode object reference IExpression.
84:
85: linkage section.
86:
87: 01 outValue type SystemDouble.
88:
89: procedure division returning outValue.
90:
91: set postFixExp to self::"infixToPostFix"(expression)
92: set rootNode to self::"BuildTree"(postFixExp)
93: set outValue to rootNode::"Evaluate"(ctx)
94:
95: end method EvaluateExpression.
96:
97: method-id. GetNonTerminalExpression as "GetNonTerminalExpression" private.
98:
99: data division.
100:
101: working-storage section.
102:
103: 01 postFixExp object reference SystemString.
104: 01 rootNode object reference IExpression.
105: 01 singleChar pic x.
106:
107: linkage section.
108:
109: 01 operation object reference SystemString.
110: 01 leftExpression object reference IExpression.
111: 01 rightExpression object reference IExpression.
112: 01 outValue object reference ClassNonTerminalExpression.
113:
114: procedure division using operation leftExpression
115: rightExpression returning outValue.
116: set outValue to null
117: set singleChar to operation::"Trim"()
118:
119: evaluate singleChar
120:
121: when "+"
122: invoke ClassAddExpression "NEW" using leftExpression,
123: rightExpression
124: returning outValue
125:
126: when "-"
127: invoke ClassSubtractExpression "NEW" using leftExpression,
128: rightExpression
129: returning outValue
130: when "*"
131: invoke ClassMultiplyExpression "NEW" using leftExpression,
132: rightExpression
133: returning outValue
134: when "/"
135: invoke ClassDivideExpression "NEW" using leftExpression,
136: rightExpression
137: returning outValue
138:
139: when "^"
140: invoke ClassPowerExpression "NEW" using leftExpression,
141: rightExpression
142: returning outValue
143: end-evaluate
144:
145: end method GetNonTerminalExpression.
146:
147: method-id. BuildTree as "BuildTree" private.
148:
149: data division.
150:
151: working-storage section.
152:
153: 01 currentChar object reference SystemString.
154: 01 leftOperand object reference IExpression.
155: 01 rightOperand object reference IExpression.
156: 01 exp object reference IExpression.
157: 01 stack object reference SystemCollectionStack.
158: 01 rootNode object reference IExpression.
159: 01 indexer pic s9(09) value zeros.
160: linkage section.
161:
162: 01 inFormula object reference SystemString.
163: 01 outExpression object reference IExpression.
164:
165: procedure division using inFormula returning outExpression.
166: invoke SystemCollectionStack "NEW" returning stack
167:
168: move zeros to indexer
169: perform until indexer = StringLength of inFormula
170: set currentChar to inFormula::"Substring"(indexer, 1)
171:
172: if (self::"IsOperator"(currentChar) = booleanFalse)
173: set exp to ClassTerminalExpression::"NEW"(currentChar)
174:
175: invoke stack "Push" using by value exp
176: else
177: set tempObject to stack::"Pop"()
178: set rightOperand to tempObject as IExpression
179: set tempObject to stack::"Pop"()
180: set leftOperand to tempObject as IExpression
181: set exp to self::"GetNonTerminalExpression"
182: (currentChar, leftOperand, rightOperand)
183:
184: invoke stack "Push" using by value exp
185: end-if
186:
187: add 1 to indexer
188:
189: end-perform
190:
191: set tempObject to stack::"Pop"()
192: set outExpression to tempObject as IExpression
193:
194: end method BuildTree.
195:
196: method-id. IsOperator as "IsOperator" private.
197: data division.
198:
199: working-storage section.
200: 01 singleChar pic x.
201:
202: linkage section.
203:
204: 01 inCharacter object reference SystemString.
205: 01 outBool pic 1.
206:
207: procedure division using inCharacter returning outBool.
208: set singleChar to inCharacter
209:
210: if (singleChar = "+" or "-" or "*" or "/" or "^")
211: move booleanTrue to outBool
212: else
213: move booleanFalse to outBool
214: end-if
215:
216: end method IsOperator.
217:
218: method-id. infixToPostFix as "InFixToPostFix".
219: data division.
220: working-storage section.
221: 01 stack object reference SystemCollectionStack.
222: 01 pfExpr object reference SystemString.
223: 01 tempString object reference SystemString.
224: 01 expr object reference SystemString.
225: 01 indexer type SystemInt32.
226: 01 stringVal1 object reference SystemString.
227: 01 stringVal2 object reference SystemString.
228: 01 val1 type SystemDouble.
229: 01 val2 type SystemDouble.
230: 01 currentChar object reference SystemString.
231:
232: linkage section.
233: 01 inString object reference SystemString.
234: 01 outString object reference SystemString.
235:
236: procedure division using inString returning outString.
237: invoke SystemCollectionStack "NEW" returning stack
238:
239: set pfExpr to StringNull of SystemString
240: set tempString to StringNull of SystemString
241: set expr to inString::"Trim"()
242:
243: move zeros to indexer
244: perform until indexer = StringLength of inString
245: set currentChar to inString::"Substring"(indexer, 1)
246:
247: if (currentChar::"Equals"(" ") = booleanTrue) 248: add 1 to indexer
249: exit to test of perform
250: end-if
251: if (self::"IsOperator"(currentChar) = booleanFalse)
252: and (currentChar::"Equals"("(") = booleanFalse) 253: and (currentChar::"Equals"(")") = booleanFalse) 254: set pfExpr to SystemString::"Concat"(pfExpr, currentChar)
255: end-if
256:
257: if (currentChar::"Equals"("(") = booleanTrue) 258: invoke stack "Push" using currentChar
259: end-if
260:
261: if (currentChar::"Equals"(")") = booleanTrue) 262: set tempObject to stack::"Pop"()
263: set tempString to tempObject as SystemString
264: perform until tempString::"Equals"("(") = booleanTrue 265: set pfExpr to SystemString::"Concat"(pfExpr, tempString)
266: set tempObject to stack::"Pop"()
267: set tempString to tempObject as SystemString
268: end-perform
269:
270: set tempString to StringNull of SystemString
271: end-if
272:
273: if (self::"IsOperator"(currentChar) = booleanTrue)
274: if (StackCount of stack >; 0)
275: set tempObject to stack::"Pop"()
276: set tempString to tempObject as SystemString
277: set tempObject to operators::"Get_Item"(tempString)
278: set stringVal1 to tempObject as SystemString
279: set tempObject to operators::"Get_Item"(currentChar)
280: set stringVal2 to tempObject as SystemString
281:
282: move SystemDoubleWrapper::"Parse"(stringVal1) to val1
283: move SystemDoubleWrapper::"Parse"(stringVal2) to val2
284:
285: perform until val1 <; val2
286: set pfExpr to SystemString::"Concat"(pfExpr, tempString)
287:
288: move -100 to val1
289:
290: if (StackCount of stack >; 0)
291: set tempObject to stack::"Pop"()
292: set tempString to tempObject as SystemString
293: set tempObject to operators::"Get_Item"(tempString)
294: set stringVal1 to tempObject as SystemString
295: set val1 to SystemDoubleWrapper::"Parse"(stringVal1)
296: end-if
297:
298: end-perform
299:
300: if (val1 <; val2) and (val1 > -100)
301: invoke stack "Push" using tempString
302: end-if
303: end-if
304:
305: invoke stack "Push" using currentChar
306: end-if
307:
308: add 1 to indexer
309: end-perform
310:
311: perform until StackCount of stack = 0
312: set tempObject to stack::"Pop"()
313: set tempString to tempObject as SystemString
314: set pfExpr to SystemString::"Concat"(pfExpr, tempString)
315: end-perform
316:
317: set outString to pfExpr
318:
319: end method infixToPostFix.
320:
321: end object.
322: END CLASS ComputeFormula.
323:
Testing our expression evaluator
The little application below shows how to use our expression evaluator:
1: IDENTIFICATION DIVISION.
2: PROGRAM-ID. MAIN AS "ConsoleApplication1.Main".
3: ENVIRONMENT DIVISION.
4: CONFIGURATION SECTION.
5: SPECIAL-NAMES.
6: REPOSITORY.
7: class ClassComputeFormula as "InterpreterPattern.ComputeFormula"
8: class ClassContext as "InterpreterPattern.Context".
9:
10: DATA DIVISION.
11: WORKING-STORAGE SECTION.
12: 01 computeFormula object reference ClassComputeFormula.
13: 01 context object reference ClassContext.
14: 01 result comp-2.
15: 01 displayResult pic s9(09)v99.
16:
17: PROCEDURE DIVISION.
18: invoke ClassComputeFormula "NEW" returning computeFormula
19: invoke ClassContext "NEW" returning context
20:
21: invoke computeFormula "SetExpression" using "((a ^ b) + c) * d"
22:
23: invoke context "CreateVariable" using "a", 10
24: invoke context "CreateVariable" using "b", 2
25: invoke context "CreateVariable" using "c", 2
26: invoke context "CreateVariable" using "d", 8.5
27:
28: invoke computeFormula "SetContext" using context
29: invoke computeFormula "Evaluate" returning result
30:
31: move result to displayResult
32:
33: display displayResult
34:
35: END PROGRAM MAIN.
What else can I do with that Pattern?
You name it! Maybe a script language to provide some automation support in your application. Maybe you could create basic Cobol evaluators. That would something really cool! There are no limits!
Download the source
http://cobolrocks.codeplex.com/SourceControl/changeset/view/59104#
Some references in the web
http://www.dofactory.com/Patterns/PatternInterpreter.aspx