Register  Login
OO Cobol Examples » Design Patterns » Interpreter
 

Interpreter Pattern

"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

 

Comments

 Name  
 Email
 Comment  
CAPTCHA image
Enter the code shown above

Sponsors


Terms Of Use | Privacy Statement | Copyright 2009-2010 by RedRailsDynnamite DotNetNuke Skins & Modules