Update of "Getting Started"

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview

Artifact ID: 816671f31932fda23359d8df0438c59e3963d28c
Page Name:Getting Started
Date: 2016-11-17 14:21:03
Original User: User
Parent: 682b700009613e07de3ae7d7f1472fc407d89b13 (diff)
Next 3797bbbf0d2a3e25240d8466dbaf33bed9a83b6d
Content

Getting Started

So, you are interested in using PComb? Doing so is very easy. Start by taking a good look at the demo app in the app/ subfolder, and when you are ready, here are the steps:

1. Include the file pcomb.ts in  your project. If it is a TypeScript project, adjust your build script to make sure that it gets compiled along with any other dependencies.

2. In any source file that is going to use PComb for parsing, add a reference or an import statement to include it, like this:  import * as pcomb from "../src/pcomb" Of course, the path will be different depending on how you have your project files laid out.

3. You will need to provide two classes: one that implements the pcomb.ParserInput interface and one that implements the pcomb.ParserOutput interface The class that implements pcomb.ParserInput doesn't need to do much: just provide the one property and one method required by the interface. pcomb.ParserOutput on the other hand, should implement the required fields, but it also needs to store whatever kind of state is needed by your application to do its work.

Here is the ParserInput from the demo app:

class ChattyInput implements pcomb.ParserInput {
    text: string;
    copy(): pcomb.ParserInput {
        let newInput = new ChattyInput();
        newInput.text = this.text;
        return newInput;
    }
}
And here is the ParserOutput:
class ChattyData implements pcomb.ParserOutput {
    matched: string[];
    leftOperand: number;
    rightOperand: number;
    operator: MathOps;
    accumulator: number = 0;
    copy(): ChattyData {
        let newData = new ChattyData();

if (newData.matched) { newData.matched = this.matched.slice(); } else { newData.matched = Array<string>(); }

newData.leftOperand = this.leftOperand; newData.rightOperand = this.rightOperand; newData.operator = this.operator; newData.accumulator = this.accumulator; return newData; } }

As you can see, the ParserOutput implementing class adds a number of fields -- leftOperand, rightOperand, operator and accumulator -- that are used to store the data that is collected by the parsers that the app defines. This tutorial will cover the mechanism for getting data into the object later.

4. Now it is time to actually create a parser. Most begin with one or more lit parsers; they match a fixed, caseless string of one-or-more charatcers. Again taking an example from the demo app, let's create parsers that represent the arithmetic operators for addition, subtraction, multiplication and division.

let operandPlus: pcomb.Parser = pcomb.lit("+"); 

Here we are declaring a variable of type pcomb.Parser and call the lit function to create a parser that will match a plus sign. That is all well and good, but nothing will happen aside from parsing continuing. What we want is for the operator field in our ChattyData object to be set correctly. To do that, we will need to define a ParserAction.

5. Add a ParserAction function that can be called by the parser that matches math operators:

    private operandSet(matched: string, output: pcomb.ParserOutput): pcomb.ParserOutput {
        let result: ChattyData = <ChattyData>output.copy();

        switch (matched) {             case "+":             case "plus":                 result.operator = MathOps.Add;                 break;             case "-":             case "minus":                 result.operator = MathOps.Subtract;                 break;             case "*":             case "times":                 result.operator = MathOps.Multipy;                 break;             case "/":             case "divided by":                 result.operator = MathOps.Divide;                 break;         }

        return result;     }

Note that this function matches the signature of the ParserAction type as defined in pcomb.ts:

export type ParserAction = (matchedText: string, output: ParserOutput) => ParserOutput;

It is a function that takes two parameters: matchedText, and output. The first holds the actual text that was matched by the parser. This may seem trivial; after all you are attaching the function to a parser and you know what the parser matches, right? Well, a ParserAction can be attached to a parser of arbitrary complexity, and it is possible that one of very many different patterns will have been matched, so knowing what was matched is necessary for writing anything other than the most trivial function.

The output parameter is the current state of the parse: all of the texts matched to date as well as whatever application-specific data has been collected. You can see that the first thing that the function does is create of copy of that which is passed in, and it makes all changes to and returns that copy.

The function matches our expected value of +, but it also knows about the synonym plus and all the other operators and their synonyms. However, we haven't yet written a parser that will match any of those.

 First, let's rewrite our definition of operandPlus so the action will be called:

let operandPlus: pcomb.Parser = pcomb.lit("+", this.operandSet);

Now we can rewrite our parser so that it will match either + or plus:

<verbatim>

let operandPlus: pcomb.Parser = pcomb.or(pcomb.lit("+", this.operandSet), pcomb.lit("plus", this.operandSet));
</verbatim>

Since the or combinator can take a ParserAction, we can rewrite the above to eliminate redundancy: