c++ Git से cparse लाइब्रेरी का उपयोग करके उपयोगकर्ता इनपुट के तार खींचे जा रहे हैं



windows parsing (1)

मैं c ++ प्रोग्रामिंग के लिए नया हूं और अपने प्रोजेक्ट में यहां पाए गए cparse लाइब्रेरी का उपयोग करना चाहता हूं https://github.com/cparse/cparse । मैं " a*(b+3) " (जैसे चर a और b ) के लिए उपयोगकर्ता इनपुट के तारों की व्याख्या करना चाहता हूं और इनपुट के विभिन्न सेटों पर बार-बार एक फ़ंक्शन के रूप में इसका उपयोग करता हूं।

उदाहरण के लिए, प्रति पंक्ति 2 double नंबरों वाले इनपुट के रूप में एक टेक्स्ट फ़ाइल लेने पर मेरा कोड प्रत्येक लाइन पर " a*(b+3) " के परिणाम के साथ एक नई फ़ाइल लिखेगा (यह मानते हुए कि पहला नंबर है और b दूसरा है )।

मेरी समस्या तब पैदा होती है जब मैं कोशिश करता हूं और जीआरटी से cparse लाइब्रेरी शामिल करता हूं । मैंने सेटअप निर्देशों का सहजता से पालन किया (git में नया):

$ cd cparse
$ make release

लेकिन मैं विंडोज़ बनाने के लिए कमांड कमांड का उपयोग नहीं कर सकता। मैंने ज़िप फ़ाइल डाउनलोड करने की कोशिश की .cpp सीधे प्रोजेक्ट में .cpp और .h फ़ाइलों को कॉपी किया और विजुअल स्टूडियो में "मौजूदा शामिल" विकल्प का उपयोग किया, लेकिन बड़ी संख्या में संकलक त्रुटियां प्राप्त कीं और कोड को स्वयं काम नहीं कर सकता।

क्या मुझे किसी तरह बात याद आ रही है? मैं इसे काम पर कैसे लगाऊं?

असफल होना, क्या उपयोगकर्ता इनपुट के तारों को पार्स करने और उन्हें कार्यों के रूप में उपयोग करने का एक और तरीका है?


असफल होना, क्या उपयोगकर्ता इनपुट के तारों को पार्स करने और उन्हें कार्यों के रूप में उपयोग करने का एक और तरीका है?

मैं आपके प्रश्न के इस भाग का उत्तर देना चाहूंगा क्योंकि मुझे लगता है कि एक पूर्ण विशेषताओं वाला सी पार्सर आपके इरादे के लिए थोड़ा भारी हो सकता है। (Btw। एक बार जब आपको C पार्सर चल पड़ा - तो इसके आउटपुट को कैसे प्रोसेस किया जाए? डायनामिक लिंकिंग?)

इसके बजाय, मैं आपको यह दिखाना चाहता हूं कि अपने दम पर एक साधारण कैलकुलेटर (पुनरावर्ती वंश पार्सर के साथ) कैसे बनाया जाए। मेरे द्वारा उपयोग की जाने वाली तकनीकों के दस्तावेज़ीकरण के लिए, मैं अहो, लाम, सेठी, उल्मान (जिसे "ड्रैगन पुस्तकें" के रूप में जाना जाता है) और विशेष रूप से अध्याय 4 द्वारा कम्पाइलर (सिद्धांत, तकनीक और उपकरण) की गर्मजोशी से अनुशंसा करता हूं।

टिनी कैलकुलेटर प्रोजेक्ट

निम्नलिखित में मैं भाग द्वारा अपने नमूना समाधान भाग का वर्णन करता हूं।

भाषा डिजाइन

संकलक या दुभाषिया लिखना शुरू करने से पहले, यह एक भाषा को परिभाषित करने के लिए उचित है जिसे स्वीकार किया जाएगा। मैं C: अभिव्यक्ति से मिलकर बहुत सीमित उप-सेट का उपयोग करना चाहता हूं

  • C फ्लोटिंग पॉइंट नंबरों (स्थिरांक) की तरह
  • C पहचानकर्ताओं (चर) की तरह
  • अपर संचालक + और -
  • बाइनरी ऑपरेटरों + , - , * , और /
  • कोष्ठक ()
  • अर्धविराम ; (एक अभिव्यक्ति के अंत को चिह्नित करने के लिए, अनिवार्य)।

व्हाट्सएप (लाइन-ब्रेक सहित) को केवल नजरअंदाज किया जाएगा, लेकिन इसका इस्तेमाल चीजों को अलग करने के साथ-साथ मानव पठनीयता में सुधार करने के लिए भी किया जा सकता है। C या C ++ जैसे टिप्पणियाँ (और बहुत सारी चीनी) मैंने स्रोत कोड को यथासंभव कम से कम रखने पर विचार नहीं किया। (उस सब के लिए, मुझे लगभग 500 लाइनें मिलीं।)

ओपी का विशिष्ट उदाहरण इस भाषा में एक जोड़ा अर्धविराम के साथ फिट होगा:

a*(b+3);

समर्थित केवल एक प्रकार होगा: double । इस प्रकार, मुझे किसी प्रकार की या किसी घोषणा की आवश्यकता नहीं है जो चीजों को आसान बनाती है।

इससे पहले कि मैं इस भाषा के grammar का चित्रण करना शुरू करूँ, मैं संकलन के "लक्ष्य" के बारे में सोच रहा था और कक्षाएँ बनाने का फैसला किया ...

एक सार सिंटेक्स ट्री

पहले - चर को स्टोर करने के लिए एक वर्ग:

// storage of variables

class Var {
  private:
    double _value;
  public:
    Var(): _value() { }
    ~Var() = default;
    double get() const { return _value; }
    void set(double value) { _value = value; }
};

चर एक मान के लिए भंडारण प्रदान करते हैं लेकिन पहचानकर्ता के लिए नहीं। उत्तरार्द्ध को अलग से संग्रहीत किया जाता है क्योंकि यह चर के वास्तविक उपयोग के लिए आवश्यक नहीं है, लेकिन केवल इसे नाम से खोजने के लिए:

typedef std::map<std::string, Var> VarTable;

एक std::map का उपयोग एक चर के निर्माण को स्वचालित करता है। जैसा कि कई उच्च स्तरीय भाषाओं से जाना जाता है, एक चर का अस्तित्व तब होता है जब इसकी पहली बार पहुंच होती है।

सार सिंटेक्स ट्री एक है

एक प्रोग्रामिंग भाषा में लिखे गए स्रोत कोड के सार सिंटैक्टिक संरचना का एक पेड़ प्रतिनिधित्व। पेड़ का प्रत्येक नोड स्रोत कोड में होने वाले एक निर्माण को दर्शाता है।

मैंने इस लिंक को उपर्युक्त विकिपीडिया लेख से लिया - इसे छोटा नहीं कहा जा सकता। एएसटी के लिए निम्नलिखित मेरी कक्षाओं में:

// abstract syntax tree -> storage of "executable"

namespace AST {

class Expr {
  protected:
    Expr() = default;
  public:
    virtual ~Expr() = default;
  public:
    virtual double solve() const = 0;
};

class ExprConst: public Expr {
  private:
    double _value;
  public:
    ExprConst(double value): Expr(), _value(value) { }
    virtual ~ExprConst() = default;
    virtual double solve() const { return _value; }
};

class ExprVar: public Expr {
  private:
    Var *_pVar;
  public:
    ExprVar(Var *pVar): Expr(), _pVar(pVar) { }
    virtual ~ExprVar() = default;
    virtual double solve() const { return _pVar->get(); }
};

class ExprUnOp: public Expr {
  protected:
    Expr *_pArg1;
  protected:
    ExprUnOp(Expr *pArg1): Expr(), _pArg1(pArg1) { }
    virtual ~ExprUnOp() { delete _pArg1; }
};

class ExprUnOpNeg: public ExprUnOp {
  public:
    ExprUnOpNeg(Expr *pArg1): ExprUnOp(pArg1) { }
    virtual ~ExprUnOpNeg() = default;
    virtual double solve() const
    {
      return -_pArg1->solve();
    }
};

class ExprBinOp: public Expr {
  protected:
    Expr *_pArg1, *_pArg2;
  protected:
    ExprBinOp(Expr *pArg1, Expr *pArg2):
      Expr(), _pArg1(pArg1), _pArg2(pArg2)
    { }
    virtual ~ExprBinOp() { delete _pArg1; delete _pArg2; }
};

class ExprBinOpAdd: public ExprBinOp {
  public:
    ExprBinOpAdd(Expr *pArg1, Expr *pArg2): ExprBinOp(pArg1, pArg2) { }
    virtual ~ExprBinOpAdd() = default;
    virtual double solve() const
    {
      return _pArg1->solve() + _pArg2->solve();
    }
};

class ExprBinOpSub: public ExprBinOp {
  public:
    ExprBinOpSub(Expr *pArg1, Expr *pArg2): ExprBinOp(pArg1, pArg2) { }
    virtual ~ExprBinOpSub() = default;
    virtual double solve() const
    {
      return _pArg1->solve() - _pArg2->solve();
    }
};

class ExprBinOpMul: public ExprBinOp {
  public:
    ExprBinOpMul(Expr *pArg1, Expr *pArg2): ExprBinOp(pArg1, pArg2) { }
    virtual ~ExprBinOpMul() = default;
    virtual double solve() const
    {
      return _pArg1->solve() * _pArg2->solve();
    }
};

class ExprBinOpDiv: public ExprBinOp {
  public:
    ExprBinOpDiv(Expr *pArg1, Expr *pArg2): ExprBinOp(pArg1, pArg2) { }
    virtual ~ExprBinOpDiv() = default;
    virtual double solve() const
    {
      return _pArg1->solve() / _pArg2->solve();
    }
};

इस प्रकार, एएसटी वर्गों का उपयोग करते हुए, नमूने का प्रतिनिधित्व a*(b+3) होगा

VarTable varTable;
Expr *pExpr
= new ExprBinOpMul(
    new ExprVar(&varTable["a"]),
    new ExprBinOpAdd(
      new ExprVar(&varTable["b"]),
      new ExprConst(3)));

ध्यान दें:

कोष्ठकों () का प्रतिनिधित्व करने के लिए Expr से प्राप्त कोई वर्ग नहीं है क्योंकि यह केवल आवश्यक नहीं है। कोष्ठक के प्रसंस्करण को पेड़ का निर्माण करते समय माना जाता है। आम तौर पर, उच्च वरीयता वाले ऑपरेटर कम पूर्वता वाले ऑपरेटरों के बच्चे बन जाते हैं। नतीजतन, पूर्व की गणना उत्तरार्द्ध से पहले की जाती है। उपरोक्त उदाहरण में, ExprBinOpAdd का उदाहरण ExprBinOpAdd के उदाहरण का एक बच्चा है (हालांकि गुणा की पूर्ववर्तीता ऐड की पूर्ववर्तीता से अधिक है) जो कि कोष्ठकों के उचित विचार के परिणामस्वरूप होती है।

एक पार्स की गई अभिव्यक्ति को संग्रहीत करने के अलावा, इस पेड़ को Expr::solve() रूट नोड की विधि को कॉल करके अभिव्यक्ति की गणना करने के लिए इस्तेमाल किया जा सकता है:

double result = pExpr->solve();

हमारे टिनी कैलकुलेटर के लिए एक बैकएंड होने के बाद, अगला छोर है।

एक व्याकरण

एक grammar द्वारा एक औपचारिक भाषा का सबसे अच्छा वर्णन किया गया है।

program
  : expr Semicolon program
  | <empty>
  ;

expr
  : addExpr
  ;

addExpr
  : mulExpr addExprRest
  ;

addExprRest
  : addOp mulExpr addExprRest
  | <empty>
  ;

addOp
  : Plus | Minus
  ;

mulExpr
  : unaryExpr mulExprRest
  ;

mulExprRest
  : mulOp unaryExpr mulExprRest
  | <empty>
  ;

mulOp
  : Star | Slash
  ;

unaryExpr
  : unOp unaryExpr
  | primExpr
  ;

unOp
  : Plus | Minus
  ;

primExpr
  : Number
  | Id
  | LParen expr RParen
  ;

प्रारंभ प्रतीक program

नियम होते हैं

  • टर्मिनल प्रतीक (अपरकेस अक्षर से शुरू) और
  • गैर-टर्मिनल प्रतीक (लोअरकेस के साथ शुरू)
  • एक बृहदान्त्र (:) दाईं ओर से बाईं ओर अलग करने के लिए (बाईं ओर गैर-टर्मिनल दाईं ओर प्रतीकों का विस्तार हो सकता है)।
  • ऊर्ध्वाधर सलाखों ( | ) को अलग करने के लिए विकल्प
  • <empty> प्रतीक का विस्तार कुछ भी नहीं करने के लिए (पुनरावर्तन समाप्त करने के लिए उपयोग किया जाता है)।

टर्मिनल प्रतीकों से, मैं स्कैनर के लिए टोकन प्राप्त करूंगा।

गैर-टर्मिनल प्रतीकों को पार्सर कार्यों में बदल दिया जाएगा।

addExpr और mulExpr का पृथक्करण जानबूझकर किया गया है। इस प्रकार, additive ऑपरेटरों पर गुणक ऑपरेटरों की पूर्वता व्याकरण में "जली हुई" होगी। जाहिर है, अस्थायी बिंदु स्थिरांक, चर पहचानकर्ता या कोष्ठक में अभिव्यक्तियाँ ( primExpr में स्वीकार किए primExpr ) में सर्वोच्च प्राथमिकता होगी।

नियमों में केवल सही पुनरावर्ती शामिल हैं। यह पुनरावर्ती-वंशीय पार्सर्स के लिए एक आवश्यकता है (जैसा कि मैंने सैद्धांतिक रूप से ड्रैगन पुस्तकों में और डिबगर में व्यावहारिक अनुभवों से सीखा है, जब तक कि मैं इस कारण को पूरी तरह से समझ नहीं पाया)। एक पुनरावर्ती-वंशीय पार्सर में बाएं पुनरावर्ती नियम को लागू करने से गैर-समाप्त होने वाली पुनरावृत्ति होती है, जो बदले में, स्टैकऑवरफ्लो के साथ समाप्त होती है।

स्कैनर

संकलक को स्कैनर और पार्सर में विभाजित करना सामान्य है।

स्कैनर इनपुट कैरेक्टर स्ट्रीम को प्रोसेस करता है और टोक्स को कैरेक्टर्स को एक साथ जोड़ता है। टोकन का उपयोग पार्सर में टर्मिनल प्रतीकों के रूप में किया जाता है।

टोकन के उत्पादन के लिए, मैंने एक वर्ग बनाया। मेरी व्यावसायिक परियोजनाओं में, इसके मूल को निरूपित करने के लिए अतिरिक्त फ़ाइल स्थिति संग्रहीत करता है। यह स्रोत कोड संदर्भों के साथ-साथ त्रुटि संदेशों और डीबगिंग जानकारी के किसी भी आउटपुट के साथ निर्मित वस्तुओं को टैग करने के लिए सुविधाजनक है। (... इसे यथासंभव कम से कम रखने के लिए यहां छोड़ दिया गया ...)

// token class - produced in scanner, consumed in parser
struct Token {
  // tokens
  enum Tk {
    Plus, Minus, Star, Slash, LParen, RParen, Semicolon,
    Number, Id,
    EOT, Error
  };
  // token number
  Tk tk;
  // lexem as floating point number
  double number;
  // lexem as identifier
  std::string id;

  // constructors.
  explicit Token(Tk tk): tk(tk), number() { }
  explicit Token(double number): tk(Number), number(number) { }
  explicit Token(const std::string &id): tk(Id), number(), id(id) { }
};

विशेष टोकन के लिए दो प्रगणक हैं:

  • EOT ... पाठ का अंत (इनपुट के अंत में टिप्पणी)
  • Error ... किसी भी वर्ण के लिए उत्पन्न होती है जो किसी अन्य टोकन में फिट नहीं होती है।

टोकन का उपयोग वास्तविक स्कैनर के आउटपुट के रूप में किया जाता है:

// the scanner - groups characters to tokens
class Scanner {
  private:
    std::istream &_in;
  public:
    // constructor.
    Scanner(std::istream &in): _in(in) { }
    /* groups characters to next token until the first character
     * which does not match (or end-of-file is reached).
     */
    Token scan()
    {
      char c;
      // skip white space
      do {
        if (!(_in >> c)) return Token(Token::EOT);
      } while (isspace(c));
      // classify character and build token
      switch (c) {
        case '+': return Token(Token::Plus);
        case '-': return Token(Token::Minus);
        case '*': return Token(Token::Star);
        case '/': return Token(Token::Slash);
        case '(': return Token(Token::LParen);
        case ')': return Token(Token::RParen);
        case ';': return Token(Token::Semicolon);
        default:
          if (isdigit(c)) {
            _in.unget(); double value; _in >> value;
            return Token(value);
          } else if (isalpha(c) || c == '_') {
            std::string id(1, c);
            while (_in >> c) {
              if (isalnum(c) || c == '_') id += c;
              else { _in.unget(); break; }
            }
            return Token(id);
          } else {
            _in.unget();
            return Token(Token::Error);
          }
      }
    }
};

स्कैनर का उपयोग पार्सर में किया जाता है।

The Parser

class Parser {
  private:
    Scanner _scanner;
    VarTable &_varTable;
    Token _lookAhead;

  private:
    // constructor.
    Parser(std::istream &in, VarTable &varTable):
      _scanner(in), _varTable(varTable), _lookAhead(Token::EOT)
    {
      scan(); // load look ahead initially
    }

    // calls the scanner to read the next look ahead token.
    void scan() { _lookAhead = _scanner.scan(); }

    // consumes a specific token.
    bool match(Token::Tk tk)
    {
      if (_lookAhead.tk != tk) {
        std::cerr << "SYNTAX ERROR! Unexpected token!" << std::endl;
        return false;
      }
      scan();
      return true;
    }

    // the rules:

    std::vector<AST::Expr*> parseProgram()
    {
      // right recursive rule
      // -> can be done as iteration
      std::vector<AST::Expr*> pExprs;
      for (;;) {
        if (AST::Expr *pExpr = parseExpr()) {
          pExprs.push_back(pExpr);
        } else break;
        // special error checking for missing ';' (usual error)
        if (_lookAhead.tk != Token::Semicolon) {
          std::cerr << "SYNTAX ERROR: Semicolon expected!" << std::endl;
          break;
        }
        scan(); // consume semicolon
        if (_lookAhead.tk == Token::EOT) return pExprs;
      }
      // error handling
      for (AST::Expr *pExpr : pExprs) delete pExpr;
      pExprs.clear();
      return pExprs;
    }

    AST::Expr* parseExpr()
    {
      return parseAddExpr();
    }

    AST::Expr* parseAddExpr()
    {
      if (AST::Expr *pExpr1 = parseMulExpr()) {
        return parseAddExprRest(pExpr1);
      } else return nullptr; // ERROR!
    }

    AST::Expr* parseAddExprRest(AST::Expr *pExpr1)
    {
      // right recursive rule for left associative operators
      // -> can be done as iteration
      for (;;) {
        switch (_lookAhead.tk) {
          case Token::Plus:
            scan(); // consume token
            if (AST::Expr *pExpr2 = parseMulExpr()) {
              pExpr1 = new AST::ExprBinOpAdd(pExpr1, pExpr2);
            } else {
              delete pExpr1;
              return nullptr; // ERROR!
            }
            break;
          case Token::Minus:
            scan(); // consume token
            if (AST::Expr *pExpr2 = parseMulExpr()) {
              pExpr1 = new AST::ExprBinOpSub(pExpr1, pExpr2);
            } else {
              delete pExpr1;
              return nullptr; // ERROR!
            }
            break;
          case Token::Error:
            std::cerr << "SYNTAX ERROR: Unexpected character!" << std::endl;
            delete pExpr1;
            return nullptr;
          default: return pExpr1;
        }
      }
    }

    AST::Expr* parseMulExpr()
    {
      if (AST::Expr *pExpr1 = parseUnExpr()) {
        return parseMulExprRest(pExpr1);
      } else return nullptr; // ERROR!
    }

    AST::Expr* parseMulExprRest(AST::Expr *pExpr1)
    {
      // right recursive rule for left associative operators
      // -> can be done as iteration
      for (;;) {
        switch (_lookAhead.tk) {
          case Token::Star:
            scan(); // consume token
            if (AST::Expr *pExpr2 = parseUnExpr()) {
              pExpr1 = new AST::ExprBinOpMul(pExpr1, pExpr2);
            } else {
              delete pExpr1;
              return nullptr; // ERROR!
            }
            break;
          case Token::Slash:
            scan(); // consume token
            if (AST::Expr *pExpr2 = parseUnExpr()) {
              pExpr1 = new AST::ExprBinOpDiv(pExpr1, pExpr2);
            } else {
              delete pExpr1;
              return nullptr; // ERROR!
            }
            break;
          case Token::Error:
            std::cerr << "SYNTAX ERROR: Unexpected character!" << std::endl;
            delete pExpr1;
            return nullptr;
          default: return pExpr1;
        }
      }
    }

    AST::Expr* parseUnExpr()
    {
      // right recursive rule for right associative operators
      // -> must be done as recursion
      switch (_lookAhead.tk) {
        case Token::Plus:
          scan(); // consume token
          // as a unary plus has no effect it is simply ignored
          return parseUnExpr();
        case Token::Minus:
          scan();
          if (AST::Expr *pExpr = parseUnExpr()) {
            return new AST::ExprUnOpNeg(pExpr);
          } else return nullptr; // ERROR!
        default:
          return parsePrimExpr();
      }
    }

    AST::Expr* parsePrimExpr()
    {
      AST::Expr *pExpr = nullptr;
      switch (_lookAhead.tk) {
        case Token::Number:
          pExpr = new AST::ExprConst(_lookAhead.number);
          scan(); // consume token
          break;
        case Token::Id: {
          Var &var = _varTable[_lookAhead.id]; // find or create
          pExpr = new AST::ExprVar(&var);
          scan(); // consume token
        } break;
        case Token::LParen:
          scan(); // consume token
          if (!(pExpr = parseExpr())) return nullptr; // ERROR!
          if (!match(Token::RParen)) {
            delete pExpr; return nullptr; // ERROR!
          }
          break;
        case Token::EOT:
          std::cerr << "SYNTAX ERROR: Premature EOF!" << std::endl;
          break;
        case Token::Error:
          std::cerr << "SYNTAX ERROR: Unexpected character!" << std::endl;
          break;
        default:
          std::cerr << "SYNTAX ERROR: Unexpected token!" << std::endl;
      }
      return pExpr;
    }

  public:

    // the parser function
    static std::vector<AST::Expr*> parse(
      std::istream &in, VarTable &varTable)
    {
      Parser parser(in, varTable);
      return parser.parseProgram();
    }
};

मूल रूप से, पार्सर में अनिवार्य रूप से नियम कार्यों (व्याकरण नियमों के अनुसार) का एक गुच्छा होता है जो एक दूसरे को बुला रहे हैं। नियम कार्यों के आसपास का वर्ग कुछ वैश्विक पार्सर संदर्भ को प्रबंधित करने के लिए जिम्मेदार है। इसलिए, class Parser की एकमात्र सार्वजनिक विधि है

static std::vector<AST::Expr*> Parser::parse();

जो एक उदाहरण (निजी कंस्ट्रक्टर के साथ) का निर्माण करता है और फ़ंक्शन को Parser::parseProgram() स्टार्ट सिंबल program अनुरूप कहता है।

आंतरिक रूप से, पार्सर अपने लुक-फॉरवर्ड टोकन को भरने के लिए Scanner::scan() विधि को कॉल करता है।

यह Parser::scan() किया जाता है जिसे हमेशा टोकन कहा जाता है।

करीब देखते हुए, एक पैटर्न दिखाई देता है कि नियमों को पार्सर कार्यों में कैसे अनुवादित किया गया है:

  • बाईं ओर प्रत्येक गैर-टर्मिनल एक पार्स फ़ंक्शन बन जाता है। (स्रोत कोड में करीब से देखने पर, आपको महसूस होगा कि मैंने ऐसा बिल्कुल नहीं किया है। कुछ नियम "इनबिल्ड" किए गए हैं। - अपने दृष्टिकोण से, मैंने व्याकरण को सरल बनाने के लिए कुछ अतिरिक्त नियम सम्मिलित किए हैं जो मैंने नहीं किए थे। ' t शुरुआत से बदलने का इरादा है। क्षमा करें।)

  • विकल्प ( | ) switch (_lookAhead.tk) रूप में कार्यान्वित किए जाते हैं। इस प्रकार, प्रत्येक केस लेबल पहले टर्मिनल (एस) (टोकन (एस)) से मेल खाता है, जिस पर बाएं सबसे अधिक प्रतीक का विस्तार हो सकता है। (मेरा मानना ​​है कि क्यों इसे "लुक-फॉरवर्ड पार्सर" कहा जाता है - नियम लागू करने के निर्णय हमेशा टोकन के आगे के लुक के आधार पर किए जाते हैं।) ड्रैगन बुक में FIRST-FOLLOW सेट के बारे में एक विषय है जो इसे और अधिक विस्तार से बताता है।

  • टर्मिनल प्रतीकों के लिए, Parser::scan() कहा जाता है। विशेष मामलों में, इसे Parser::match() द्वारा प्रतिस्थापित किया जाता है, यदि ठीक एक टर्मिनल (टोकन) अपेक्षित है।

  • गैर-टर्मिनल प्रतीकों के लिए, संबंधित फ़ंक्शन का कॉल किया जाता है।

  • सिंबल सिक्वेंस बस उपरोक्त कॉल्स के सीक्वेंस के रूप में किया जाता है।

इस पार्सर की त्रुटि-हैंडलिंग सबसे सरल है जो मैंने कभी किया था। इसे बहुत अधिक समर्थन (अधिक प्रयास करना, अर्थात कोड की अतिरिक्त लाइनें) करना चाहिए। (... लेकिन यहां मैंने इसे न्यूनतम रखने की कोशिश की ...)

एक साथ रखा

परीक्षण और प्रदर्शन के लिए, मैंने कुछ अंतर्निहित नमूनों (प्रक्रिया के स्रोत कोड और प्रक्रिया के लिए डेटा main() साथ एक main() फ़ंक्शन तैयार किया:

// a sample application

using namespace std;

int main()
{
  // the program:
  const char *sourceCode =
    "1 + 2 * 3 / 4 - 5;\n"
    "a + b;\n"
    "a - b;\n"
    "a * b;\n"
    "a / b;\n"
    "a * (b + 3);\n";
  // the variables
  const char *vars[] = { "a", "b" };
  enum { nVars = sizeof vars / sizeof *vars };
  // the data
  const double data[][nVars] = {
    { 4.0, 2.0 },
    { 2.0, 4.0 },
    { 10.0, 5.0 },
    { 42, 6 * 7 }
  };
  // compile program
  stringstream in(sourceCode);
  VarTable varTable;
  vector<AST::Expr*> program = Parser::parse(in, varTable);
  if (program.empty()) {
    cerr << "ERROR: Compile failed!" << endl;
    string line;
    if (getline(in, line)) {
      cerr << "Text at error: '" << line << "'" << endl;
    }
    return 1;
  }
  // apply program to the data
  enum { nDataSets = sizeof data / sizeof *data };
  for (size_t i = 0; i < nDataSets; ++i) {
    const char *sep = "";
    cout << "Data Set:" << endl;
    for (size_t j = 0; j < nVars; ++j, sep = ", ") {
      cout << sep << vars[j] << ": " << data[i][j];
    }
    cout << endl;
    // load data
    for (size_t j = 0; j < nVars; ++j) varTable[vars[j]].set(data[i][j]);
    // perform program
    cout << "Compute:" << endl;
    istringstream in(sourceCode);
    for (const AST::Expr *pExpr : program) {
      string line; getline(in, line);
      cout << line << ": " << pExpr->solve() << endl;
    }
    cout << endl;
  }
  // clear the program
  for (AST::Expr *pExpr : program) delete pExpr;
  program.clear();
  // done
  return 0;  
}

मैंने VS2013 (विंडोज 10) पर संकलित और परीक्षण किया और मिला:

Data Set:
a: 4, b: 2
Compute:
1 + 2 * 3 / 4 - 5;: -2.5
a + b;: 6
a - b;: 2
a * b;: 8
a / b;: 2
a * (b + 3);: 20

Data Set:
a: 2, b: 4
Compute:
1 + 2 * 3 / 4 - 5;: -2.5
a + b;: 6
a - b;: -2
a * b;: 8
a / b;: 0.5
a * (b + 3);: 14

Data Set:
a: 10, b: 5
Compute:
1 + 2 * 3 / 4 - 5;: -2.5
a + b;: 15
a - b;: 5
a * b;: 50
a / b;: 2
a * (b + 3);: 80

Data Set:
a: 42, b: 42
Compute:
1 + 2 * 3 / 4 - 5;: -2.5
a + b;: 84
a - b;: 0
a * b;: 1764
a / b;: 1
a * (b + 3);: 1890

कृपया, विचार करें कि पार्सर स्वयं किसी भी स्थान और लाइन-ब्रेक को अनदेखा करता है। हालांकि, नमूना आउटपुट को सरल बनाने के लिए, मुझे प्रति पंक्ति एक अर्धविराम-समाप्ति अभिव्यक्ति का उपयोग करना होगा। अन्यथा, स्रोत कोड लाइनों को संबंधित संकलित अभिव्यक्तियों के साथ जोड़ना मुश्किल होगा। ( Token बारे में ऊपर मेरे नोट को याद रखें जिसमें स्रोत कोड संदर्भ (उर्फ फ़ाइल स्थिति) जोड़ा जा सकता है।)

पूरा नमूना

... सोर्स कोड और टेस्ट रन के साथ ideone पर पाया जा सकता है।





compilation