वास्तव में एक सार सिंटेक्स ट्री कैसे बनाया जाता है?


47

मुझे लगता है कि मैं एक एएसटी के लक्ष्य को समझता हूं, और मैंने पहले कुछ पेड़ संरचनाओं का निर्माण किया है, लेकिन कभी एएसटी नहीं। मैं ज्यादातर भ्रमित हूं क्योंकि नोड्स टेक्स्ट और नंबर नहीं हैं, इसलिए मैं टोकन / स्ट्रिंग को इनपुट करने का एक अच्छा तरीका नहीं सोच सकता क्योंकि मैं कुछ कोड पार्स कर रहा हूं।

उदाहरण के लिए, जब मैंने एएसटीएस के आरेखों को देखा, तो चर और इसके मूल्य एक समान संकेत के लिए पत्ती नोड्स थे। यह मेरे लिए बिल्कुल सही समझ में आता है, लेकिन मैं इसे कैसे लागू करूंगा? मुझे लगता है कि मैं इसे केस द्वारा केस कर सकता हूं, ताकि जब मैं एक "=" पर ठोकर खाऊं तो मैं इसे नोड के रूप में उपयोग करता हूं, और पत्ती के रूप में "=" के पहले पार्स किए गए मूल्य को जोड़ देता हूं। यह सिर्फ गलत लगता है, क्योंकि मुझे सिंटेक्स पर निर्भर करते हुए टन और टन चीजों के लिए मामले बनाने होंगे।

और फिर मैं एक और समस्या पर आया, कि पेड़ कैसे फंसा है? क्या मैं ऊँचाई से नीचे जाता हूँ, और जब मैं नीचे से टकराता हूँ, और पड़ोसी के लिए भी यही करता हूँ?

मैंने एएसटीएस पर टन के आरेख देखे हैं, लेकिन मुझे कोड में एक का बिल्कुल सरल उदाहरण नहीं मिला, जो शायद मदद करेगा।


आपके द्वारा गुम की जा रही प्रमुख अवधारणा पुनरावृत्ति है । पुनर्संयोजन एक प्रकार का प्रतिवाद है, और यह प्रत्येक शिक्षार्थी के लिए अलग है जब यह अंततः उनके साथ 'क्लिक' करेगा, लेकिन पुनरावृत्ति के बिना, पार्सिंग को समझने का कोई तरीका नहीं है (और संपूर्ण कम्प्यूटेशनल विषयों की एक पूरी बहुत अच्छी तरह से)।
किलन फ़ॉथ

मुझे पुनरावृत्ति मिलती है, मुझे लगा कि इस मामले में इसे लागू करना कठिन होगा। मैं वास्तव में पुनरावृत्ति का उपयोग करना चाहता था और मैंने बहुत सारे मामलों को समाप्त कर दिया जो एक सामान्य समाधान के लिए काम नहीं करेंगे। Gdhoward का जवाब मुझे अभी बहुत मदद कर रहा है।
होकैन

यह एक व्यायाम के रूप में आरपीएन कैलकुलेटर बनाने के लिए व्यायाम हो सकता है । यह आपके प्रश्न का उत्तर नहीं देगा, लेकिन कुछ आवश्यक कौशल सिखा सकता है।

मैंने वास्तव में पहले एक आरपीएन कैलकुलेटर बनाया है। उत्तर ने मुझे बहुत मदद की और मुझे लगता है कि मैं अब एक बुनियादी एएसटी बना सकता हूं। धन्यवाद!
होकेन

जवाबों:


47

संक्षिप्त उत्तर यह है कि आप स्टैक का उपयोग करते हैं। यह एक अच्छा उदाहरण है, लेकिन मैं इसे एएसटी पर लागू करूंगा।

FYI करें, यह Edsger Dijkstra का शंटिंग-यार्ड एलगोरिदम है

इस मामले में, मैं एक ऑपरेटर स्टैक और एक अभिव्यक्ति स्टैक का उपयोग करूंगा। चूंकि अधिकांश भाषाओं में संख्याओं को अभिव्यक्ति माना जाता है, इसलिए मैं उन्हें संग्रहीत करने के लिए अभिव्यक्ति स्टैक का उपयोग करूंगा।

class ExprNode:
    char c
    ExprNode operand1
    ExprNode operand2

    ExprNode(char num):
        c = num
        operand1 = operand2 = nil

    Expr(char op, ExprNode e1, ExprNode e2):
        c = op
        operand1 = e1
        operand2 = e2

# Parser
ExprNode parse(string input):
    char c
    while (c = input.getNextChar()):
        if (c == '('):
            operatorStack.push(c)

        else if (c.isDigit()):
            exprStack.push(ExprNode(c))

        else if (c.isOperator()):
            while(operatorStack.top().precedence >= c.precedence):
                operator = operatorStack.pop()
                # Careful! The second operand was pushed last.
                e2 = exprStack.pop()
                e1 = exprStack.pop()
                exprStack.push(ExprNode(operator, e1, e2))

            operatorStack.push(c)

        else if (c == ')'):
            while (operatorStack.top() != '('):
                operator = operatorStack.pop()
                # Careful! The second operand was pushed last.
                e2 = exprStack.pop()
                e1 = exprStack.pop()
                exprStack.push(ExprNode(operator, e1, e2))

            # Pop the '(' off the operator stack.
            operatorStack.pop()

        else:
            error()
            return nil

    # There should only be one item on exprStack.
    # It's the root node, so we return it.
    return exprStack.pop()

(कृपया मेरे कोड के बारे में अच्छा हो। मुझे पता है कि यह मजबूत नहीं है; यह केवल छद्म शब्द है।)

वैसे भी, जैसा कि आप कोड से देख सकते हैं, मनमाने ढंग से अभिव्यक्ति अन्य अभिव्यक्तियों के लिए ऑपरेंड हो सकती है। यदि आपके पास निम्न इनपुट है:

5 * 3 + (4 + 2 % 2 * 8)

मेरे द्वारा लिखा गया कोड इस एएसटी का उत्पादन करेगा:

     +
    / \
   /   \
  *     +
 / \   / \
5   3 4   *
         / \
        %   8
       / \
      2   2

और फिर जब आप उस एएसटी के लिए कोड का उत्पादन करना चाहते हैं, तो आप एक पोस्ट ऑर्डर ट्री ट्रैवर्सल करते हैं । जब आप एक पत्ती नोड (एक संख्या के साथ) पर जाते हैं, तो आप एक स्थिरांक उत्पन्न करते हैं क्योंकि कंपाइलर को ऑपरेंड मानों को जानने की आवश्यकता होती है। जब आप किसी ऑपरेटर के साथ नोड पर जाते हैं, तो आप ऑपरेटर से उचित निर्देश उत्पन्न करते हैं। उदाहरण के लिए, '+' ऑपरेटर आपको "ऐड" निर्देश देता है।


यह उन ऑपरेटरों के लिए काम करता है जो बाएं से दाएं नहीं, बल्कि समरूपता के लिए छोड़ दिए गए हैं।
साइमन

@ साइमन, राइट-टू-लेफ्ट ऑपरेटरों के लिए क्षमता जोड़ना बेहद सरल होगा। सबसे सरल यह होगा कि एक लुक अप टेबल को जोड़ा जाए और यदि कोई ऑपरेटर दाएँ-से-बाएँ, बस ऑपरेंड के आदेश को उलट दे।
गाविन हॉवर्ड

4
@Simon यदि आप दोनों को सपोर्ट करना चाहते हैं, तो आप शंटिंग यार्ड के एल्गोरिथ्म को पूर्ण रूप से देखना पसंद करेंगे। जैसे ही एल्गोरिदम चलते हैं, वह एक पूर्ण क्रैकर है।
biziclop

19

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

एएसटी का विशिष्ट कार्यान्वयन (एक ओओ भाषा में) बहुरूपता का भारी उपयोग करता है। एएसटी में नोड्स आमतौर पर विभिन्न वर्गों के साथ लागू किए जाते हैं, सभी एक सामान्य ASTNodeवर्ग से प्राप्त होते हैं । आपके द्वारा संसाधित की जा रही भाषा में प्रत्येक वाक्य रचना के लिए, AST में उस निर्माण का प्रतिनिधित्व करने के लिए एक वर्ग होगा, जैसे ConstantNode(स्थिरांक के लिए, जैसे कि 0x10या 42), VariableNode(चर नामों के लिए), AssignmentNode(असाइनमेंट संचालन के लिए), ExpressionNode(जेनेरिक के लिए ) भाव), आदि
प्रत्येक विशिष्ट नोड प्रकार निर्दिष्ट करता है यदि उस नोड में बच्चे हैं, तो कितने और संभवतः किस प्रकार के। A ConstantNodeमें आमतौर पर कोई संतान नहीं AssignmentNodeहोगी , एक वसीयत में दो और ExpressionBlockNodeकिसी भी संख्या में बच्चे हो सकते हैं।

एएसटी का निर्माण पार्सर द्वारा किया जाता है, जो जानता है कि किस निर्माण ने इसे केवल पार्स किया है, इसलिए यह सही प्रकार के एएसटी नोड का निर्माण कर सकता है।

एएसटी का पता लगाने के दौरान, नोड्स का बहुरूपता वास्तव में खेलने में आता है। आधार ASTNodeउन कार्यों को परिभाषित करता है जो नोड्स पर किए जा सकते हैं, और प्रत्येक विशिष्ट नोड प्रकार उस विशेष भाषा के निर्माण के लिए विशिष्ट तरीके से उन कार्यों को लागू करता है।


9

स्रोत पाठ से एएसटी का निर्माण "बस" पार्सिंग है । वास्तव में यह कैसे किया जाता है यह पार्स की गई औपचारिक भाषा और कार्यान्वयन पर निर्भर करता है। आप पार्शर जनरेटर का उपयोग कर सकते हैं जैसे कि मेनहिर (ओकेएमएल के लिए) , जीएनयू के bisonसाथ flex, या एएनटीएलआर आदि आदि। यह अक्सर कुछ पुनरावर्ती वंश पार्सर को कोड करके "मैन्युअल रूप से" किया जाता है (देखें कि यह उत्तर क्यों समझा रहा है)। पार्सिंग का प्रासंगिक पहलू अक्सर कहीं और किया जाता है (प्रतीक तालिकाओं, विशेषताओं, ....)।

हालांकि, व्यवहार में एएसटी आप जो मानते हैं उससे कहीं अधिक जटिल हैं। उदाहरण के लिए, जीसीसी जैसे कंपाइलर में एएसटी स्रोत स्थान की जानकारी और कुछ टाइपिंग जानकारी रखता है। जेसीसी में जेनेरिक पेड़ों के बारे में पढ़ें और इसके gcc / tree.def के अंदर देखें । BTW, GCC MELT (जिसे मैंने डिज़ाइन और कार्यान्वित किया है) के अंदर भी देखें , यह आपके प्रश्न के लिए प्रासंगिक है।


मैं स्रोत पाठ को पार्स करने और JS में एक सरणी में रूपांतरित करने के लिए एक Lua दुभाषिया बना रहा हूं। क्या मैं इसे एएसटी मान सकता हूं? मैं ऐसा कुछ करने वाला हूं: --My comment #1 print("Hello, ".."world.") "[{" प्रकार ":" - "," सामग्री ":" मेरी टिप्पणी # 1 "}, {" प्रकार ":" कॉल "," नाम ":" प्रिंट "," तर्क ": [[{" प्रकार ":" str "," कार्रवाई ":" .. "," सामग्री ":" हैलो, "}, {" प्रकार ":" str "," सामग्री ": "विश्व।" }]]}] `मुझे लगता है कि यह जेएस में किसी भी अन्य भाषा की तुलना में बहुत सरल है!
हाइड्रोपर

@ TheProHands यह टोकन माना जाएगा, एएसटी नहीं।
YoYoYonnY

2

मुझे पता है कि यह प्रश्न 4+ वर्ष पुराना है, लेकिन मुझे लगता है कि मुझे और विस्तृत उत्तर जोड़ना चाहिए।

सार सिंटैक्स पेड़ अन्य पेड़ों से अलग नहीं बनाए जाते हैं; इस मामले में अधिक सच बयान यह है कि सिंटैक्स ट्री नोड्स में नोड्स के रूप में पर्याप्त मात्रा में नोड्स की आवश्यकता होती है।

एक उदाहरण बाइनरी एक्सप्रेशंस है जैसे कि 1 + 2 एक सरल अभिव्यक्ति जो कि दाएं और बाएं नोड को पकड़े हुए एक एकल रूट नोड बनाएगी जो डेटा को संख्याओं के बारे में रखती है। सी भाषा में, यह कुछ इस तरह दिखेगा

struct ASTNode;
union SyntaxNode {
    int64_t         llVal;
    uint64_t        ullVal;
    struct {
        struct ASTNode *left, *right;
    } BinaryExpr;
};

enum SyntaxNodeType {
    AST_IntVal, AST_Add, AST_Sub, AST_Mul, AST_Div, AST_Mod,
};

struct ASTNode {
    union SyntaxNode *Data;
    enum SyntaxNodeType Type;
};

आपका सवाल यह भी था कि कैसे किया जाए? इस मामले में ट्रैवर्सिंग को विजिटिंग नोड्स कहा जाता है । प्रत्येक नोड का दौरा करने के लिए आवश्यक है कि आप प्रत्येक नोड प्रकार का उपयोग करके यह निर्धारित करें कि प्रत्येक सिंटैक्स नोड के डेटा का मूल्यांकन कैसे करें।

यहाँ C में इसका एक और उदाहरण दिया गया है जहाँ मैं प्रत्येक नोड की सामग्री को प्रिंट करता हूँ:

void AST_PrintNode(const ASTNode *node)
{
    if( !node )
        return;

    char *opername = NULL;
    switch( node->Type ) {
        case AST_IntVal:
            printf("AST Integer Literal - %lli\n", node->Data->llVal);
            break;
        case AST_Add:
            if( !opername )
                opername = "+";
        case AST_Sub:
            if( !opername )
                opername = "-";
        case AST_Mul:
            if( !opername )
                opername = "*";
        case AST_Div:
            if( !opername )
                opername = "/";
        case AST_Mod:
            if( !opername )
                opername = "%";
            printf("AST Binary Expr - Oper: \'%s\' Left:\'%p\' | Right:\'%p\'\n", opername, node->Data->BinaryExpr.left, node->Data->BinaryExpr.right);
            AST_PrintNode(node->Data->BinaryExpr.left); // NOTE: Recursively Visit each node.
            AST_PrintNode(node->Data->BinaryExpr.right);
            break;
    }
}

ध्यान दें कि फ़ंक्शन किस प्रकार के नोड के अनुसार हम प्रत्येक नोड पर जाते हैं।

चलो एक अधिक जटिल उदाहरण जोड़ते हैं, एक ifबयान निर्माण! याद है कि अगर बयानों में एक वैकल्पिक विकल्प भी हो सकता है। आइए हमारे मूल नोड संरचना में if-else स्टेटमेंट जोड़ें। याद रखें कि यदि स्टेटमेंट्स में स्वयं स्टेटमेंट भी हो सकते हैं, तो हमारे नोड सिस्टम के भीतर एक प्रकार की पुनरावृत्ति हो सकती है। वैकल्पिक विवरण वैकल्पिक होते हैं इसलिए elsestmtफ़ील्ड NULL हो सकती है जिसे पुनरावर्ती आगंतुक फ़ंक्शन अनदेखा कर सकता है।

struct ASTNode;
union SyntaxNode {
    int64_t         llVal;
    uint64_t        ullVal;
    struct {
        struct ASTNode *left, *right;
    } BinaryExpr;
    struct {
        struct ASTNode *expr, *stmt, *elsestmt;
    } IfStmt;
};

enum SyntaxNodeType {
    AST_IntVal, AST_Add, AST_Sub, AST_Mul, AST_Div, AST_Mod, AST_IfStmt, AST_ElseStmt, AST_Stmt
};

struct ASTNode {
    union SyntaxNode *Data;
    enum SyntaxNodeType Type;
};

हमारे नोड विजिटर प्रिंट फंक्शन में वापस बुलाया जाता है AST_PrintNode, हम ifइस C कोड को जोड़कर कथन AST निर्माण को समायोजित कर सकते हैं :

case AST_IfStmt:
    puts("AST If Statement\n");
    AST_PrintNode(node->Data->IfStmt.expr);
    AST_PrintNode(node->Data->IfStmt.stmt);
    AST_PrintNode(node->Data->IfStmt.elsestmt);
    break;

इतना सरल है! अंत में, सिंटेक्स ट्री एक टैग किए गए पेड़ के पेड़ और उसके डेटा से बहुत अधिक नहीं है!

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.