Kuk
23 de febrero de 2017, 09:41
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.
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.
INTERFACE-ID. IExpression as "InterpreterPattern.IExpression".
environment division.
configuration section.
repository.
class ClassContext as "InterpreterPattern.Context".
procedure division.
method-id. EvaluateExpression as "Evaluate".
data division.
linkage section.
01 inContext object reference ClassContext.
01 outValue usage comp-2.
procedure division using inContext returning outValue.
end method EvaluateExpression.
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:
CLASS-ID. PowerExpression AS "InterpreterPattern.PowerExpression"
inherits ClassNonTerminalExpression.
environment division.
configuration section.
repository.
class ClassContext as "InterpreterPattern.Context"
class SystemString as "System.String"
class ClassNonTerminalExpression as "InterpreterPattern.NonTerminalExpression"
interface IExpression as "InterpreterPattern.IExpression".
*> Instance's data and methods
object.
data division.
working-storage section.
copy "types.book".
01 variable object reference SystemString private.
procedure division.
method-id. NEW.
data division.
linkage section.
01 inLeftExpression object reference IExpression.
01 inRightExpression object reference IExpression.
procedure division using inLeftExpression, inRightExpression.
invoke super "NEW" using inLeftExpression, inRightExpression.
end method NEW.
method-id. EvaluateExpresion as "Evaluate" override.
data division.
working-storage section.
01 anExpression object reference IExpression.
linkage section.
01 inContext object reference ClassContext.
01 outValue type SystemDouble.
procedure division using inContext returning outValue.
invoke super "GetLeftNode" returning anExpression
move anExpression::"Evaluate"(inContext) to outValue
invoke super "GetRightNode" returning anExpression
*> This is the real "PowerExpression"
compute outValue = outValue ** anExpression::"Evaluate"(inContext)
end method EvaluateExpresion.
end object.
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:
CLASS-ID. TerminalExpression AS "InterpreterPattern.TerminalExpression".
environment division.
configuration section.
repository.
class ClassContext as "InterpreterPattern.Context"
class SystemCollectionHashTable as "System.Collections.Hashtable"
class SystemString as "System.String"
interface IExpression as "InterpreterPattern.IExpression".
*> Instance's data and methods
object. implements IExpression.
data division.
working-storage section.
copy "types.book".
01 variable object reference SystemString private.
procedure division.
method-id. NEW.
data division.
linkage section.
01 inName object reference SystemString.
procedure division using inName.
set variable to inName
end method NEW.
method-id. EvaluateExpression as "Evaluate".
data division.
linkage section.
01 inContext object reference ClassContext.
01 outValue type SystemDouble.
procedure division using inContext returning outValue.
move inContext::"GetValue"(variable) to outValue
end method EvaluateExpression.
end object.
END CLASS TerminalExpression.
The NonTerminalExpression class that defines rules:
CLASS-ID. NonTerminalExpression AS "InterpreterPattern.NonTerminalExpression".
environment division.
configuration section.
repository.
class ClassContext as "InterpreterPattern.Context"
class SystemCollectionHashTable as "System.Collections.Hashtable"
class SystemString as "System.String"
interface IExpression as "InterpreterPattern.IExpression".
*> Instance's data and methods
object. implements IExpression.
data division.
working-storage section.
copy "types.book".
01 leftNode object reference IExpression private.
01 rightNode object reference IExpression private.
procedure division.
method-id. NEW.
data division.
linkage section.
01 inLeftExpression object reference IExpression.
01 inRightExpression object reference IExpression.
procedure division using inLeftExpression inRightExpression.
invoke self "SetLeftNode" using inLeftExpression
invoke self "SetRightNode" using inRightExpression
end method NEW.
method-id. SetLeftNode.
data division.
linkage section.
01 inNode object reference IExpression.
procedure division using inNode.
set leftNode to inNode
end method SetLeftNode.
method-id. SetRightNode.
data division.
linkage section.
01 inNode object reference IExpression.
procedure division using inNode.
set rightNode to inNode
end method SetRightNode.
method-id. GetLeftNode.
data division.
linkage section.
01 outNode object reference IExpression.
procedure division returning outNode.
set outNode to leftNode
end method GetLeftNode.
method-id. GetRightNode.
data division.
linkage section.
01 outNode object reference IExpression.
procedure division returning outNode.
set outNode to rightNode
end method GetRightNode.
method-id. EvaluateExpression as "Evaluate".
data division.
linkage section.
01 inContext object reference ClassContext.
01 outValue type SystemDouble.
procedure division using inContext returning outValue.
*> Implemented because NetCobol.Net does not supports Abstract classes
end method EvaluateExpression.
end object.
END CLASS NonTerminalExpression.
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.
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.
INTERFACE-ID. IExpression as "InterpreterPattern.IExpression".
environment division.
configuration section.
repository.
class ClassContext as "InterpreterPattern.Context".
procedure division.
method-id. EvaluateExpression as "Evaluate".
data division.
linkage section.
01 inContext object reference ClassContext.
01 outValue usage comp-2.
procedure division using inContext returning outValue.
end method EvaluateExpression.
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:
CLASS-ID. PowerExpression AS "InterpreterPattern.PowerExpression"
inherits ClassNonTerminalExpression.
environment division.
configuration section.
repository.
class ClassContext as "InterpreterPattern.Context"
class SystemString as "System.String"
class ClassNonTerminalExpression as "InterpreterPattern.NonTerminalExpression"
interface IExpression as "InterpreterPattern.IExpression".
*> Instance's data and methods
object.
data division.
working-storage section.
copy "types.book".
01 variable object reference SystemString private.
procedure division.
method-id. NEW.
data division.
linkage section.
01 inLeftExpression object reference IExpression.
01 inRightExpression object reference IExpression.
procedure division using inLeftExpression, inRightExpression.
invoke super "NEW" using inLeftExpression, inRightExpression.
end method NEW.
method-id. EvaluateExpresion as "Evaluate" override.
data division.
working-storage section.
01 anExpression object reference IExpression.
linkage section.
01 inContext object reference ClassContext.
01 outValue type SystemDouble.
procedure division using inContext returning outValue.
invoke super "GetLeftNode" returning anExpression
move anExpression::"Evaluate"(inContext) to outValue
invoke super "GetRightNode" returning anExpression
*> This is the real "PowerExpression"
compute outValue = outValue ** anExpression::"Evaluate"(inContext)
end method EvaluateExpresion.
end object.
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:
CLASS-ID. TerminalExpression AS "InterpreterPattern.TerminalExpression".
environment division.
configuration section.
repository.
class ClassContext as "InterpreterPattern.Context"
class SystemCollectionHashTable as "System.Collections.Hashtable"
class SystemString as "System.String"
interface IExpression as "InterpreterPattern.IExpression".
*> Instance's data and methods
object. implements IExpression.
data division.
working-storage section.
copy "types.book".
01 variable object reference SystemString private.
procedure division.
method-id. NEW.
data division.
linkage section.
01 inName object reference SystemString.
procedure division using inName.
set variable to inName
end method NEW.
method-id. EvaluateExpression as "Evaluate".
data division.
linkage section.
01 inContext object reference ClassContext.
01 outValue type SystemDouble.
procedure division using inContext returning outValue.
move inContext::"GetValue"(variable) to outValue
end method EvaluateExpression.
end object.
END CLASS TerminalExpression.
The NonTerminalExpression class that defines rules:
CLASS-ID. NonTerminalExpression AS "InterpreterPattern.NonTerminalExpression".
environment division.
configuration section.
repository.
class ClassContext as "InterpreterPattern.Context"
class SystemCollectionHashTable as "System.Collections.Hashtable"
class SystemString as "System.String"
interface IExpression as "InterpreterPattern.IExpression".
*> Instance's data and methods
object. implements IExpression.
data division.
working-storage section.
copy "types.book".
01 leftNode object reference IExpression private.
01 rightNode object reference IExpression private.
procedure division.
method-id. NEW.
data division.
linkage section.
01 inLeftExpression object reference IExpression.
01 inRightExpression object reference IExpression.
procedure division using inLeftExpression inRightExpression.
invoke self "SetLeftNode" using inLeftExpression
invoke self "SetRightNode" using inRightExpression
end method NEW.
method-id. SetLeftNode.
data division.
linkage section.
01 inNode object reference IExpression.
procedure division using inNode.
set leftNode to inNode
end method SetLeftNode.
method-id. SetRightNode.
data division.
linkage section.
01 inNode object reference IExpression.
procedure division using inNode.
set rightNode to inNode
end method SetRightNode.
method-id. GetLeftNode.
data division.
linkage section.
01 outNode object reference IExpression.
procedure division returning outNode.
set outNode to leftNode
end method GetLeftNode.
method-id. GetRightNode.
data division.
linkage section.
01 outNode object reference IExpression.
procedure division returning outNode.
set outNode to rightNode
end method GetRightNode.
method-id. EvaluateExpression as "Evaluate".
data division.
linkage section.
01 inContext object reference ClassContext.
01 outValue type SystemDouble.
procedure division using inContext returning outValue.
*> Implemented because NetCobol.Net does not supports Abstract classes
end method EvaluateExpression.
end object.
END CLASS NonTerminalExpression.