3.7 Completing a Parser

Building Parsers with Java
By Steven  John  Metsker

Table of Contents
Chapter  3.   Building a Parser


The class RobotRefactored is the result of a translation from the track robot command language grammar into code. It is complete in that it provides a parser that recognizes the desired language. This parser is not complete, however, in the sense of doing anything useful. To go beyond recognition of a language to taking some useful action based on the recognition, a parser must control the pushing of terminals onto an assembly's stack, and it must plug assemblers in to the appropriate subparsers.

3.7.1 Control Pushing

By default, all terminals push whatever they recognize onto an assembly's stack. For most terminals, this is a useful and often necessary function. For example, when a parser recognizes a Word or a Num , the parser usually needs to do something with whatever Word or Num it recognizes. On the other hand, when a parser recognizes a Literal or CaselessLiteral , such as "carrier" , the parser normally does not need to do any work with the literal it sees. For example, consider the subparser for pickCommand :

 pickCommand = "pick" "carrier" "from" location; 

In this subparser, the words "pick" , "carrier" , and "from" serve to identify a particular type of command. The pickCommand subparser successfully matches only text that begins with these three words. There is no reason to stack these words; you know what they are, and you know you must be recognizing a pickCommand if the match succeeds. Typically, you will want to ask all the Literal parsers in your parser to discard the terminal they see. To ask a Literal not to push itself, send it a discard() message. Making this change in RobotRefactored.pickCommand() results in the following:

 protected Parser pickCommand() {      Sequence s = new Sequence();     s.add(new CaselessLiteral("pick").discard());     s.add(new CaselessLiteral("carrier").discard());     s.add(new CaselessLiteral("from").discard());     s.add(location());     return s; } 

Keeping the literals from pushing means that after a pickCommand() matches an assembly's text, the assembly's stack will contain only the value of the location for the command. You will see shortly how to plug in assemblers to work on the assembly's stack and target.

3.7.2 Design the Target

Having developed the code to recognize a robot command language, the next step is to arrange for the parser to actually build a command object from input command text. In this example, the target of parsing text is an object from a hierarchy of commands. Figure 3.5 shows the targets for the robot language, namely the three subclasses of RobotCommand .

Figure 3.5. A command hierarchy. You can run a little factory with commands for picking, placing, and scanning carriers .


Here is the code for RobotCommand :

 package sjm.examples.robot;  /**  * A <code>RobotCommand</code> encapsulates the work that  * lies behind a high-level command such as "pick carrier from  * input1". In this package, the commands are just sample  * targets of a parser; their <code>execute()</code> methods  * are not implemented.  */ public class RobotCommand     implements sjm.utensil.PubliclyCloneable {     protected String location; /**  * Return a copy of this object. If the location attribute  * becomes something more complicated than a String, then  * this method will become insufficient if location is not  * immutable.  */ public Object clone() {     try {         return super.clone();     } catch (CloneNotSupportedException e) {         // this shouldn't happen, since we are Cloneable         throw new InternalError();     } } /**  * If we were really driving a factory, this is where we  * would turn high-level commands into the protocols that  * various machines would understand. For example, a pick  * command might send messages to both a conveyor and  * a track robot.  */ public void execute() { } /**  * Return the location that this command is for.  */ public String getLocation() {     return location; } /**  * Set the location for this command.  */ public void setLocation(String location) {     this.location = location; } } 

The purpose of a RobotCommand object is to operate real equipment when the command's execute() method runs. In a real factory system you would have to write the code that makes this execution happen. Each subclass of RobotCommand would override execute() appropriately. In this example you are aiming only to translate text into the right kind of command object, so the subclasses are empty. For example, the code for PickCommand is

 package sjm.examples.robot;  /**  * Just for demonstration.  */ public class PickCommand extends RobotCommand { /**  * Return a textual description of this object.  */ public String toString() {     return "pick " + location; } } 

3.7.3 Plug In Assemblers

The assemblers for your parser set an assembly's target and inform the target of its associated location. Each subclass of the command needs a corresponding assembler to set the appropriate target. For example, you will need a PickAssembler class:

 package sjm.examples.robot;  import sjm.parse.*; import sjm.parse.tokens.Token; /**  * Sets an assembly's target to be a <code>PickCommand  * </code> object and notes its location.  */ public class PickAssembler extends Assembler { public void workOn(Assembly a) {     PickCommand pc = new PickCommand();     Token t = (Token) a.pop();     pc.setLocation(t.sval());     a.setTarget(pc); } } 

The approach taken here assumes the parser will parse a single command and construct a corresponding RobotCommand object. Which subclass of RobotCommand to instantiate and to set as the target of the parse depends on the input text. The PickAssembler object plugs in to the pickCommand subparser, which will successfully match a "pick" command. The assembler's workOn() method executes after an (entire) pickCommand subparser matches. At this time, a pick location will be on the stack. When the workOn() method executes, it creates a PickCommand object and sets this object as the target of the parse. The assembler pops the location object and uses it to establish the location of the PickCommand object.

You can now convert the code for RobotRefactored into RobotParser , plugging in the assemblers and adding some comments. The result is as follows :

 package sjm.examples.robot;  import sjm.parse.*; import sjm.parse.tokens.*; /**  * This class's start() method provides a parser that  * will recognize a command for a track robot and build a  * corresponding command object.  * <p>  * The grammar for the language that this class recognizes  * is:  *  * <blockquote><pre>  *     command      = pickCommand  placeCommand   *                    scanCommand;  *     pickCommand  = "pick" "carrier" "from" location;  *     placeCommand = "place" "carrier" "at" location;  *     scanCommand  = "scan" location;  *     location     = Word;  * </pre></blockquote>  */ public class RobotParser { /**  * Returns a parser that will recognize a command for a  * track robot and build a corresponding command  */ public static Parser start() {     return new RobotParser().command(); } /**  * Returns a parser that will recognize a command for a  * track robot and build a corresponding command object.  *  * (This method returns the same value as  * <code>start()</code>).  */ public Parser command() {     Alternation a = new Alternation();     a.add(pickCommand());     a.add(placeCommand());     a.add(scanCommand());     return a; } /*  * Returns a parser that will recognize the grammar:  *  *     pickCommand = "pick" "carrier" "from" location;  */  protected Parser pickCommand() {     Sequence s = new Sequence();     s.add(new CaselessLiteral("pick"));     s.add(new CaselessLiteral("carrier"));     s.add(new CaselessLiteral("from"));     s.add(location());     s.setAssembler(new PickAssembler());     return s; } /*  * Returns a parser that will recognize the grammar:  *  *     placeCommand = "place" "carrier" "at" location;  */  protected Parser placeCommand() {     Sequence s = new Sequence();     s.add(new CaselessLiteral("place"));     s.add(new CaselessLiteral("carrier"));     s.add(new CaselessLiteral("at"));     s.add(location());     s.setAssembler(new PlaceAssembler());     return s; } /*  * Returns a parser that will recognize the grammar:  *  *     scanCommand = "scan" location;  */ protected Parser scanCommand() {     Sequence s = new Sequence();     s.add(new CaselessLiteral("scan"));     s.add(location());     s.setAssembler(new ScanAssembler());     return s; } /*  * Returns a parser that will recognize the grammar:  *  *     location = Word;  */ protected Parser location() {     return new Word(); } } 

You can use the RobotParser.start() parser as follows:

 package sjm.examples.robot;  import sjm.parse.*; import sjm.parse.tokens.*; /**  * Show how to use the <code>RobotParser</code> class.  */ public class ShowRobotParser { public static void main(String[] args) {     Parser p = RobotParser.start();     String[] tests = new String[]{         "pick carrier from LINE_IN",         "place carrier at DB101_IN",         "pick carrier from DB101_OUT",         "place carrier at WB500_IN",         "pick carrier from WB500_OUT",         "place carrier at LINE_OUT",         "scan DB101_OUT"};     for (int i = 0; i < tests.length; i++) {         TokenAssembly ta = new TokenAssembly(tests[i]);         Assembly out = p.bestMatch(ta);         System.out.println(out.getTarget());     } } } 

Running this class prints the results of parsing a few sample commands:

 pick LINE_IN  place DB101_IN pick DB101_OUT place WB500_IN pick WB500_OUT place LINE_OUT scan DB101_OUT 

These are the results of the toString() methods of the commands built by the RobotParser.start() parser. If the command target objects were wired into a factory with functional execute() methods, you could use these commands to control the factory.


Building Parsers with Java
Building Parsers With Javaв„ў
ISBN: 0201719622
EAN: 2147483647
Year: 2000
Pages: 169

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net