Explanation of the interpreter pattern for javascript design pattern

  • 2020-03-30 03:10:34
  • OfStack


What is "interpreter mode"?

Let's open up GOF and look at Definition:
Given a language, define a representation of its grammar and define an interpreter that USES that representation to interpret sentences in the language.

Here are a few popular concepts to start with:

Abstract syntax tree:

The interpreter pattern does not explain how to create an abstract syntax tree. It does not involve parsing. An abstract syntax tree can be done with a table-driven parser, a handwritten (usually recursive descent) parser, or a direct client.

The parser:

Refers to the description of the client side of the call requirements of the expression, after parsing, form an abstract syntax tree procedures.

Interpreter:

A program that interprets an abstract syntax tree and performs the corresponding function for each node.

An important prerequisite for using the interpreter pattern is to define a set of grammar rules, also known as grammars. Whether the rules of the grammar are simple or complex, they must be there, because the interpreter pattern parses and performs functions according to them.

Let's take a look at the structure diagram and description of the interpreter pattern:

< img SRC = "border = 0 / / files.jb51.net/file_images/article/201406/201465105854142.jpg? 201455105933 ">

AbstractExpression: Define the interface of the interpreter and specify the interpreter's interpretation operations.
TerminalExpression: The terminal interpreter, which is used to implement the operation related to the grammar rules and the terminal, does not contain other interpreters. If the abstract syntax tree is built with the composite pattern, it is equivalent to the leaf object in the composite pattern, and there can be multiple terminal interpreters.
NonterminalExpression: Non-terminal interpreter, used to implement non-final-related operations of grammar rules, usually one interpreter for a grammar rule, can contain other interpreters, if the composition pattern to build an abstract syntax tree, is equivalent to the composition of objects in the pattern. You can have multiple non-terminal interpreters.
Context: Context, usually containing data required by the individual interpreters or common functionality.
Client: The client side, which is the client side that USES the interpreter, typically converts the syntax of the language into an abstract syntax tree described using the interpreter object, and then invokes the explain operation.

Here's an XML example to understand the interpreter schema:

First of all, we need to design a simple grammar for the expression. In order to be general, we should use root to represent the root element, and ABC to represent the element. A simple XML is as follows:


<?xml version="1.0" encoding="UTF-8">
  <root id="rootId">
      <a>
          <b>
              <c name="testC">12345</c>
              <d id="1">d1</d>
              <d id="2">d2</d>
              <d id="3">d3</d>
              <d id="4">d4</d>
          </b>
      </a>
  </root>

The grammar of the convention expression is as follows:

1. Get the value of a single element: from the root element to the element that you want to get the value of, the element is separated by "/" in the middle, and the root element is not added with "/". For example, the expression "root/a/b/c" means to get the value of element c under the root element, under element a, under element b.
2. Get the value of the attribute of a single element: multiple, of course. The attribute to get the value must be the attribute of the last element of the expression. For example, the expression "root/a/b/c.name" means to get the value of the name attribute of the root element, a element, b element, and c element.
3. Get the value of the same element name, more than one, of course. The element to get the value must be the last element of the expression. For example, the expression "root/a/b/d$" represents a collection of values for multiple d elements under the root element, under the a element, and under the b element.
4. Get the value of an attribute with the same element name, and of course more than one: the element to get the value of the attribute must be the last element of the expression, followed by "$". For example, the expression "root/a/b/d$.

The above XML, the corresponding abstract syntax tree, the possible structure is shown as follows:

< img SRC = "border = 0 / / files.jb51.net/file_images/article/201406/201465110009534.jpg? 20145511018 ">

Here's a look at the code:

1. Definition context:



function Context(filePathName) {
    //The last processed element
    this.preEle = null;
    //Document object for XML
    this.document = XmlUtil.getRoot(filePathName);
}
Context.prototype = {
    //Reinitialize the context
    reInit: function () {
        this.preEle = null;
    },
    
    getNowEle: function (pEle, eleName) {
        var tempNodeList = pEle.childNodes;
        var nowEle;
        for (var i = 0, len = tempNodeList.length; i < len; i++) {
            if ((nowEle = tempNodeList[i]).nodeType === 1)
                if (nowEle.nodeName === eleName)
                    return nowEle;
        }
        return null;
    },
    getPreEle: function () {
        return this.preEle;
    },
    setPreEle: function (preEle) {
        this.preEle = preEle;
    },
    getDocument: function () {
        return this.document;
    }
};


 //Tool object
    //Parse the XML and get the corresponding Document object
    var XmlUtil = {
        getRoot: function (filePathName) {
            var parser = new DOMParser();
            var xmldom = parser.parseFromString('<root id="rootId"><a><b><c name="testC">12345</c><d id="1">d1</d><d id="2">d2</d><d id="3">d3</d><d id="4">d4</d></b></a></root>', 'text/xml');
            return xmldom;
        }
    };

Here is the code for the interpreter:


 
    function ElementExpression(eleName) {
        this.eles = [];
        this.eleName = eleName;
    }
    ElementExpression.prototype = {
        addEle: function (eleName) {
            this.eles.push(eleName);
            return true;
        },
        removeEle: function (ele) {
            for (var i = 0, len = this.eles.length; i < len; i++) {
                if (ele === this.eles[i])
                    this.eles.splice(i--, 1);
            }
            return true;
        },
        interpret: function (context) {
            //The current element in the context is first fetched as the parent element
            //Finds the XML element corresponding to the current element name and sets it back into the context
            var pEle = context.getPreEle();
            if (!pEle) {
                //Indicates that you are now getting the root element
                context.setPreEle(context.getDocument().documentElement);
            } else {
                //Gets the current element based on the parent element and the name of the element to look for
                var nowEle = context.getNowEle(pEle, this.eleName);
                //Puts the currently retrieved element into context
                context.setPreEle(nowEle);
            }
            var ss;
            //Loop calls the interpret method for the child element
            for (var i = 0, len = this.eles.length; i < len; i++) {
                ss = this.eles[i].interpret(context);
            }
            //Returns the result of the last interpreter, which is usually the finalizer
            return ss;
        }
    };
    
    function ElementTerminalExpression(name) {
        this.eleName = name;
    }
    ElementTerminalExpression.prototype = {
        interpret: function (context) {
            var pEle = context.getPreEle();
            var ele = null;
            if (!pEle) {
                ele = context.getDocument().documentElement;
            } else {
                ele = context.getNowEle(pEle, this.eleName);
                context.setPreEle(ele);
            }
            //Gets the value of the element
            return ele.firstChild.nodeValue;
        }
    };
    
    function PropertyTerminalExpression(propName) {
        this.propName = propName;
    }
    PropertyTerminalExpression.prototype = {
        interpret: function (context) {
            //Gets the value of the last element attribute directly
            return context.getPreEle().getAttribute(this.propName);
        }
    };

Let's first see how to use the interpreter to get the value of a single element:


void function () {
    var c = new Context();
    //You want to get values for multiple d elements, which is the value of the following expression: "root/a/b/c"
    //The first step is to build an abstract syntax tree for the interpreter
    var root = new ElementExpression('root');
    var aEle = new ElementExpression('a');
    var bEle = new ElementExpression('b');
    var cEle = new ElementTerminalExpression('c');
    //  combination 
    root.addEle(aEle);
    aEle.addEle(bEle);
    bEle.addEle(cEle);
    console.log('c The value is  = ' + root.interpret(c));
}();

Output: the value of c is = 12345

Then we use the above code to get the value of the attributes of a single element:


 void function () {
        var c = new Context();
        //You want to get the id attribute of the d element, which is the value of the following expression: "a/b/c.name"
        //At this point, c is not terminated and needs to be changed to ElementExpression
        var root = new ElementExpression('root');
        var aEle = new ElementExpression('a');
        var bEle = new ElementExpression('b');
        var cEle = new ElementExpression('c');
        var prop = new PropertyTerminalExpression('name');
        //  combination 
        root.addEle(aEle);
        aEle.addEle(bEle);
        bEle.addEle(cEle);
        cEle.addEle(prop);
        console.log('c The properties of the name The value is  = ' + root.interpret(c));
        //If you want to use the same context for continuous parsing, you need to reinitialize the context object
        //For example, to continuously retrieve the value of the attribute name again, you can, of course, recombine elements
        //Reparse, as long as you are using the same context, you need to reinitialize the context object
        c.reInit();
        console.log(' To obtain c The properties of the name The value is  = ' + root.interpret(c));
    }();


Output: c's attribute name value is = testC refetch c's attribute name value is = testC

Explanation:

1. Function of interpreter mode:

The interpreter pattern USES an interpreter object to represent and process the corresponding syntax rules, typically one for each interpreter. In theory, the interpreter pattern can be used as long as the interpreter object can be used to represent grammatical expressions and to construct an abstract syntax tree.

2. Grammar rules and interpreters

There is a correspondence between grammar rules and interpreters. Generally, one interpreter handles one grammar rule, but the opposite is not true. A grammar rule can be interpreted and processed in multiple ways, that is, a grammar rule can correspond to multiple interpreters.

3. Commonality of context

Context plays a very important role in the interpreter pattern. Because the context is passed to all the interpreters. Therefore, the state of the interpreter can be stored and accessed in the context. For example, the interpreter in the front can store some data in the context, and the interpreter in the back can get the values.

You can also pass some data outside the interpreter through the context, but the data that the interpreter needs can also be some global, public data.

The context also has the ability to provide public functionality for all interpreter objects, similar to object composition, rather than using inheritance to obtain public functionality, which can be invoked in each interpreter object

4. Who will build the abstract syntax tree

In the previous example, manually building the abstract syntax tree on the client side was cumbersome, but in the interpreter mode, this part of the functionality is not covered, just interpreted. Later, you'll see that you can provide a parser to convert an expression into an abstract syntax tree.

There is a problem, is a grammatical rule can be multiple interpreter object, that is to say the same element, is the object can be converted into multiple interpreters, this also means that the same expression, can form without the abstract syntax tree, which cause it difficult to construct the abstract syntax tree, and the workload is very big.

5. Who is responsible for explaining the operation

As long as the abstract syntax tree is defined, the interpreter must be responsible for interpreting the execution. Although there are different syntax rules, the interpreter is not responsible for choosing which interpreter object to use to interpret and execute the syntax rules.

6. The order in which the interpreter pattern is invoked

1) create context objects

2) create multiple interpreter objects and combine abstract syntax trees

3) invoke the explain operation of the interpreter object

3.1) store and access the state of the interpreter through the context.

For a non-terminal interpreter object, the child interpreter object it contains is called recursively.

The nature of the interpreter pattern: * separate implementations, interpret execution *

The interpreter module separates complex functions by using an interpreter object to handle a single syntax rule. Then select the functions that need to be executed and combine them into an abstract syntax tree that needs to be interpreted. According to the abstract syntax tree to explain the implementation of the corresponding function.

On the surface, the interpreter pattern focuses on the handling of custom syntax that we don't usually use; But essentially, the idea of the interpreter pattern is then to separate, encapsulate, simplify, as it is with many patterns.

For example, you can use the interpreter mode to simulate the functionality of the state mode. If the interpreter pattern to deal with the grammar of the simplified to only one tag, the interpreter is handling of state object, for the same show the grammar of the state, can have a lot of unused interpreter, is there are a lot of different processing of the state of the object, and then create the abstract syntax tree, simplified according to the state of the tags to create the corresponding interpreter, don't have to build the tree.

In the same way, the interpreter pattern can simulate the realization of the function of the policy pattern, the function of the decorator pattern, etc., especially the function of the decorator pattern. The process of constructing the abstract syntax tree naturally corresponds to the process of the composite decorator.

Interpreter pattern execution speed is usually unhappy (most of the time very slow), error and debugging more difficult (note: although the debugging more difficult, but in fact it reduces the possibility of error), but its advantage is obvious, it can effectively control the complexity of the interface between modules, for the execution frequency is not high but code frequency is high enough, diversity and strong function, the interpreter is very suitable for the model. Another less noticed advantage of the interpreter is its ability to easily cross languages and platforms.

Advantages and disadvantages of the interpreter model:

Advantages:

1. Easy to implement syntax

In the interpreter pattern, a grammar rule is interpreted by an interpreter object. For the implementation of the interpreter, the function is relatively simple, just need to consider the implementation of this one grammar rule is enough, nothing else. 2. Easy to extend new syntax

It is the way an interpreter object is responsible for a single syntax rule that makes it easy to extend the new syntax. The new syntax is extended by simply creating the corresponding interpreter object, which is used when creating an abstract syntax tree.

Disadvantages:

Not suitable for complex syntax

If the syntax is particularly complex, the work of building the abstract syntax tree required for the interpreter pattern can be daunting, plus you may need to build more than one abstract syntax tree. So the interpreter pattern is not suitable for complex syntax. It might be better to use a parser or compiler generator.

When to use?

When there is a language that needs to be interpreted and the sentences in that language can be represented as an abstract syntax tree, consider using the interpreter pattern.

In the use of the interpreter mode, there are two characteristics to consider, one is that the grammar should be relatively simple, too responsible syntax is not suitable for the use of the interpreter mode lingling one is not very efficient, high efficiency requirements, not suitable for use.

Introduces how to get in front of the individual elements of the value of the attribute value and a single element, take a look at how to obtain the value of the multiple elements and multiple elements embraced in the name of value, and tests are artificial assembled in front of the abstract syntax tree, we also implement the following simple parser, by the way, to comply with the previously defined syntax expression, converted into implementation of the interpreter in front of the abstract syntax tree: I will direct stick code:


 //Reads the values of multiple elements or attributes
    (function () {
        
        function Context(filePathName) {
            //Multiple elements that were processed by the previous one
            this.preEles = [];
            //Document object for XML
            this.document = XmlUtil.getRoot(filePathName);
        }
        Context.prototype = {
            //Reinitialize the context
            reInit: function () {
                this.preEles = [];
            },
            
            getNowEles: function (pEle, eleName) {
                var elements = [];
                var tempNodeList = pEle.childNodes;
                var nowEle;
                for (var i = 0, len = tempNodeList.length; i < len; i++) {
                    if ((nowEle = tempNodeList[i]).nodeType === 1) {
                        if (nowEle.nodeName === eleName) {
                            elements.push(nowEle);
                        }
                    }
                }
                return elements;
            },
            getPreEles: function () {
                return this.preEles;
            },
            setPreEles: function (nowEles) {
                this.preEles = nowEles;
            },
            getDocument: function () {
                return this.document;
            }
        };
        //Tool object
        //Parse the XML and get the corresponding Document object
        var XmlUtil = {
            getRoot: function (filePathName) {
                var parser = new DOMParser();
                var xmldom = parser.parseFromString('<root id="rootId"><a><b><c name="testC">12345</c><d id="1">d1</d><d id="2">d2</d><d id="3">d3</d><d id="4">d4</d></b></a></root>', 'text/xml');
                return xmldom;
            }
        };
        
        function ElementExpression(eleName) {
            this.eles = [];
            this.eleName = eleName;
        }
        ElementExpression.prototype = {
            addEle: function (eleName) {
                this.eles.push(eleName);
                return true;
            },
            removeEle: function (ele) {
                for (var i = 0, len = this.eles.length; i < len; i++) {
                    if (ele === this.eles[i]) {
                        this.eles.splice(i--, 1);
                    }
                }
                return true;
            },
            interpret: function (context) {
                //The current element in the context is first fetched as the parent element
                //Finds the XML element corresponding to the current element name and sets it back into the context
                var pEles = context.getPreEles();
                var ele = null;
                var nowEles = [];
                if (!pEles.length) {
                    //Indicates that you are now getting the root element
                    ele = context.getDocument().documentElement;
                    pEles.push(ele);
                    context.setPreEles(pEles);
                } else {
                    var tempEle;
                    for (var i = 0, len = pEles.length; i < len; i++) {
                        tempEle = pEles[i];
                        nowEles = nowEles.concat(context.getNowEles(tempEle, this.eleName));
                        //Find one and stop
                        if (nowEles.length) break;
                    }
                    context.setPreEles([nowEles[0]]);
                }
                var ss;
                //Loop calls the interpret method for the child element
                for (var i = 0, len = this.eles.length; i < len; i++) {
                    ss = this.eles[i].interpret(context);
                }
                return ss;
            }
        };
        
        function ElementTerminalExpression(name) {
            this.eleName = name;
        }
        ElementTerminalExpression.prototype = {
            interpret: function (context) {
                var pEles = context.getPreEles();
                var ele = null;
                if (!pEles.length) {
                    ele = context.getDocument().documentElement;
                } else {
                    ele = context.getNowEles(pEles[0], this.eleName)[0];
                }
                //Gets the value of the element
                return ele.firstChild.nodeValue;
            }
        };
        
        function PropertyTerminalExpression(propName) {
            this.propName = propName;
        }
        PropertyTerminalExpression.prototype = {
            interpret: function (context) {
                //Gets the value of the last element attribute directly
                return context.getPreEles()[0].getAttribute(this.propName);
            }
        };
        
        function PropertysTerminalExpression(propName) {
            this.propName = propName;
        }
        PropertysTerminalExpression.prototype = {
            interpret: function (context) {
                var eles = context.getPreEles();
                var ss = [];
                for (var i = 0, len = eles.length; i < len; i++) {
                    ss.push(eles[i].getAttribute(this.propName));
                }
                return ss;
            }
        };
        
        function ElementsTerminalExpression(name) {
            this.eleName = name;
        }
        ElementsTerminalExpression.prototype = {
            interpret: function (context) {
                var pEles = context.getPreEles();
                var nowEles = [];
                for (var i = 0, len = pEles.length; i < len; i++) {
                    nowEles = nowEles.concat(context.getNowEles(pEles[i], this.eleName));
                }
                var ss = [];
                for (i = 0, len = nowEles.length; i < len; i++) {
                    ss.push(nowEles[i].firstChild.nodeValue);
                }
                return ss;
            }
        };
        
        function ElementsExpression(name) {
            this.eleName = name;
            this.eles = [];
        }
        ElementsExpression.prototype = {
            interpret: function (context) {
                var pEles = context.getPreEles();
                var nowEles = [];
                for (var i = 0, len = pEles.length; i < len; i++) {
                    nowEles = nowEles.concat(context.getNowEles(pEles[i], this.eleName));
                }
                context.setPreEles(nowEles);
                var ss;
                for (i = 0, len = this.eles.length; i < len; i++) {
                    ss = this.eles[i].interpret(context);
                }
                return ss;
            },
            addEle: function (ele) {
                this.eles.push(ele);
                return true;
            },
            removeEle: function (ele) {
                for (var i = 0, len = this.eles.length; i < len; i++) {
                    if (ele === this.eles[i]) {
                        this.eles.splice(i--, 1);
                    }
                }
                return true;
            }
        };
        void function () {
            // "root/a/b/d$"
            var c = new Context('Interpreter.xml');
            var root = new ElementExpression('root');
            var aEle = new ElementExpression('a');
            var bEle = new ElementExpression('b');
            var dEle = new ElementsTerminalExpression('d');
            root.addEle(aEle);
            aEle.addEle(bEle);
            bEle.addEle(dEle);
            var ss = root.interpret(c);
            for (var i = 0, len = ss.length; i < len; i++) {
                console.log('d The value is  = ' + ss[i]);
            }
        }();
        void function () {
            // a/b/d$.id$
            var c = new Context('Interpreter.xml');
            var root = new ElementExpression('root');
            var aEle = new ElementExpression('a');
            var bEle = new ElementExpression('b');
            var dEle = new ElementsExpression('d');
            var prop = new PropertysTerminalExpression('id');
            root.addEle(aEle);
            aEle.addEle(bEle);
            bEle.addEle(dEle);
            dEle.addEle(prop);
            var ss = root.interpret(c);
            for (var i = 0, len = ss.length; i < len; i++) {
                console.log('d The properties of the id The value is  = ' + ss[i]);
            }
        }();
        //The parser
        
        
        function ParserModel() {
            //Single value
            this.singleValue;
            //Attributes or not, either attributes or elements
            this.propertyValue;
            //Terminal or not
            this.end;
        }
        ParserModel.prototype = {
            isEnd: function () {
                return this.end;
            },
            setEnd: function (end) {
                this.end = end;
            },
            isSingleValue: function () {
                return this.singleValue;
            },
            setSingleValue: function (oneValue) {
                this.singleValue = oneValue;
            },
            isPropertyValue: function () {
                return this.propertyValue;
            },
            setPropertyValue: function (propertyValue) {
                this.propertyValue = propertyValue;
            }
        };
        var Parser = function () {
            var BACKLASH = '/';
            var DOT = '.';
            var DOLLAR = '$';
            //Record the names of the elements to be resolved in order of the breakdown
            var listEle = null;
            //Start to realize the first step ----
            
            function parseMapPath(expr) {
                //Split the string by/first
                var tokenizer = expr.split(BACKLASH);
                //A table used to hold the decomposed values
                var mapPath = {};
                var onePath, eleName, propName;
                var dotIndex = -1;
                for (var i = 0, len = tokenizer.length; i < len; i++) {
                    onePath = tokenizer[i];
                    if (tokenizer[i + 1]) {
                        //And then there's the next value, which means that this is not the last element
                        //In today's syntax, an attribute must be last, and therefore not an attribute
                        setParsePath(false, onePath, false, mapPath);
                    } else {
                        //That's the end of the explanation
                        dotIndex = onePath.indexOf(DOT);
                        if (dotIndex >= 0) {
                            //The specification is to get the value of the property, so split by ".
                            //The first is the name of the element, followed by the name of the attribute
                            eleName = onePath.substring(0, dotIndex);
                            propName = onePath.substring(dotIndex + 1);
                            //The element that precedes setting the attribute is not, of course, the last one, nor is it an attribute
                            setParsePath(false, eleName, false, mapPath);
                            //Set the property, which can only be the last one, as defined by the current syntax
                            setParsePath(true, propName, true, mapPath);
                        } else {
                            //The statement takes the value of the element, and the value of the last element
                            setParsePath(true, onePath, false, mapPath);
                        }
                        break;
                    }
                }
                return mapPath;
            }
            
            function setParsePath(end, ele, propertyValue, mapPath) {
                var pm = new ParserModel();
                pm.setEnd(end);
                //If there is a "$" sign, it is not a value
                pm.setSingleValue(!(ele.indexOf(DOLLAR) >= 0));
                pm.setPropertyValue(propertyValue);
                //Remove the "$"
                ele = ele.replace(DOLLAR, '');
                mapPath[ele] = pm;
                listEle.push(ele);
            }
            //Start to realize the second step ----
            
            function mapPath2Interpreter(mapPath) {
                var list = [];
                var pm, key;
                var obj = null;
                //Be sure to convert to the interpreter object in the order of decomposition
                for (var i = 0, len = listEle.length; i < len; i++) {
                    key = listEle[i];
                    pm = mapPath[key];
                    //Not the last one
                    if (!pm.isEnd()) {
                        if (pm.isSingleValue())
                        //It's a value, transformation
                            obj = new ElementExpression(key);
                        else
                        //Multiple values. Convert
                            obj = new ElementsExpression(key);
                    } else {
                        //The last one
                        //Is the attribute value
                        if (pm.isPropertyValue()) {
                            if (pm.isSingleValue())
                                obj = new PropertyTerminalExpression(key);
                            else
                                obj = new PropertysTerminalExpression(key);
                            //Take the value of the element
                        } else {
                            if (pm.isSingleValue())
                                obj = new ElementTerminalExpression(key);
                            else
                                obj = new ElementsTerminalExpression(key);
                        }
                    }
                    list.push(obj);
                }
                return list;
            }
            //Start to realize the third step ----
            
            function buildTree(list) {
                //The first object, which is also the returned object, is the root of the abstract syntax tree
                var returnReadXMLExpr = null;
                //Define the previous object
                var preReadXmlExpr = null;
                var readXml, ele, eles;
                for (var i = 0, len = list.length; i < len; i++) {
                    readXml = list[i];
                    //The description is the first element
                    if (preReadXmlExpr === null) {
      &nb
                

Related articles: