2012-06-26 19 views
11

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

मान लीजिए कि हमें इस कोड है:

val y = transform { 
    val x = 3 
    x 
} 
println(y) // prints 3 

जहां 'को बदलने' शामिल मैक्रो है। यह प्रतीत हो सकता है हालांकि यह पूरी तरह से कुछ भी नहीं है, यह वास्तव में पता चला ब्लॉक इस अभिव्यक्ति में बदलाव ला रहा है करता है:

def transform(c: Context)(block: c.Expr[Int]): c.Expr[Int] = { 
    import c.universe._ 
    import definitions._ 

    block.tree match { 
    /* { 
    * val xNam = xVal 
    * xExp 
    * } 
    */ 
    case Block(List(ValDef(_, xNam, _, xVal)), xExp) => 
     println("# " + showRaw(xExp)) // prints Ident(newTermName("x")) 
     c.Expr(
     Match(
      xVal, 
      List(CaseDef(
      Bind(xNam, Ident(newTermName("_"))), 
      EmptyTree, 
      /* xExp */ Ident(newTermName("x")))))) 
    case _ => 
     c.error(c.enclosingPosition, "Can't transform block to function") 
     block // keep original expression 
    } 
} 

सूचना है कि xNam साथ मेल खाती है:

3 match { case x => x } 

यह इस मैक्रो कार्यान्वयन के साथ किया जाता है परिवर्तनीय नाम, xVal इसके संबंधित मूल्य से मेल खाता है और अंत में xExp चर युक्त अभिव्यक्ति के साथ मेल खाता है। खैर, अगर मैं xExp कच्चे पेड़ को मुद्रित करता हूं तो मुझे पहचान (नयाTermName ("x")) मिलता है, और यह वही है जो आरएचएस मामले में सेट है। चूंकि अभिव्यक्ति को संशोधित किया जा सकता है (उदाहरण के लिए x के बजाय x + 2), यह मेरे लिए एक वैध समाधान नहीं है। मैं क्या करना चाहता हूं कि xExp पेड़ का पुन: उपयोग करना (xExp टिप्पणी देखें) 'x' अर्थ को बदलते समय (यह इनपुट अभिव्यक्ति में एक परिभाषा है लेकिन आउटपुट में एलएचएस वैरिएबल होगा), लेकिन यह एक लॉन्च करता है लंबे त्रुटि में संक्षेप:

symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details. 

मेरे वर्तमान समाधान xExp की पार्स नए लोगों के साथ सभी idents sustitute करने पर होते हैं, लेकिन यह पूरी तरह से संकलक internals पर निर्भर है, और इसलिए, एक अस्थायी वैकल्पिक हल। यह स्पष्ट है कि xExp शोरो द्वारा प्रदान की जाने वाली अधिक जानकारी के साथ आता है। 'X' को केस वैरिएबल की भूमिका देने के लिए मैं xExp को कैसे साफ़ कर सकता हूं? क्या कोई इस त्रुटि की पूरी तस्वीर समझा सकता है?

पीएस: मैं TreeApi से विकल्प * विधि परिवार का उपयोग करने में असफल प्रयास कर रहा हूं लेकिन मुझे इसके प्रभावों को समझने के लिए मूल बातें गायब हैं।

+0

क्या रीसेटअलएटर्स का काम था, आखिरकार? –

+0

हां, ऐसा हुआ। यह दिखाए गए कोड में काम करता है, साथ ही साथ कई जटिल "परिवर्तन" वाले जटिल पेड़ में भी काम करता है। – jeslg

+0

आपने 'c.Expr' परिणाम पर 'ब्लॉक' पर, या कुछ चयनित पेड़ पर 'resetAllAttrs'' कहा था? –

उत्तर

21

इनपुट अभिव्यक्तियों को अलग करना और उन्हें एक अलग फैशन में फिर से इकट्ठा करना मैक्रोलॉजी में एक महत्वपूर्ण परिदृश्य है (यह हम reify मैक्रो में आंतरिक रूप से करते हैं)। लेकिन दुर्भाग्य से, इस समय यह विशेष रूप से आसान नहीं है।

समस्या यह है कि मैक्रो पहुंच मैक्रो कार्यान्वयन के इनपुट तर्क पहले से टाइप किए गए हैं। यह एक आशीर्वाद और एक अभिशाप दोनों है।

हमारे लिए विशेष रुचि यह तथ्य है कि तर्कों के अनुरूप पेड़ों में परिवर्तनीय बाइंडिंग पहले से स्थापित हैं। इसका मतलब है कि सभी Ident और Select नोड्स में sym फ़ील्ड भर दिए गए हैं, इन नोड्स की परिभाषाओं को इंगित करते हुए।

यहां एक उदाहरण है कि प्रतीक कैसे काम करते हैं।मैं अपनी वार्ता में से एक से एक प्रिंटआउट कॉपी/पेस्ट करूंगा (मैं यहां एक लिंक नहीं देता हूं, क्योंकि मेरी वार्ता में अधिकांश जानकारी अब तक बहिष्कृत है, लेकिन इस विशेष प्रिंटआउट में अनन्त उपयोगिता है):

>cat Foo.scala 
def foo[T: TypeTag](x: Any) = x.asInstanceOf[T] 
foo[Long](42) 

>scalac -Xprint:typer -uniqid Foo.scala 
[[syntax trees at end of typer]]// Scala source: Foo.scala 
def foo#8339 
    [T#8340 >: Nothing#4658 <: Any#4657] 
    (x#9529: Any#4657) 
    (implicit evidence$1#9530: TypeTag#7861[T#8341]) 
    : T#8340 = 
x#9529.asInstanceOf#6023[T#8341]; 
Test#14.this.foo#8339[Long#1641](42)(scala#29.reflect#2514.`package`#3414.mirror#3463.TypeTag#10351.Long#10361) 

संक्षेप में, हम एक छोटा सा स्निपेट लिखते हैं और फिर इसे स्केलैक के साथ संकलित करते हैं, जिससे कंपाइलर टाइपर चरण के बाद पेड़ों को डंप करने के लिए कहता है, पेड़ (यदि कोई हो) को निर्दिष्ट प्रतीकों के अद्वितीय आईडी प्रिंट करता है।

परिणामस्वरूप प्रिंटआउट में हम देख सकते हैं कि पहचानकर्ताओं को इसी परिभाषा से जोड़ा गया है। उदाहरण के लिए, एक तरफ, ValDef("x", ...), जो विधि foo के पैरामीटर का प्रतिनिधित्व करता है, आईडी = 9529 के साथ एक विधि प्रतीक परिभाषित करता है। दूसरी तरफ, विधि के शरीर में Ident("x") को sym फ़ील्ड उसी प्रतीक पर सेट किया गया, जो बाध्यकारी स्थापित करता है।

ठीक है, हमने देखा है कि बाइंडिंग स्केलैक में कैसे काम करती है, और अब मौलिक तथ्य पेश करने का सही समय है।

If a symbol has been assigned to an AST node, 
then subsequent typechecks will never reassign it. 

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

अब हम सभी त्रुटि आप देख रहे हैं समझाने के लिए पूरी तरह तैयार हैं:

symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details. 

transform मैक्रो का तर्क दोनों एक परिभाषा और एक चर x करने के लिए एक संदर्भ है। जैसा कि हमने अभी सीखा है, इसका मतलब है कि संबंधित वालडिफ और पहचान में उनके sym फ़ील्ड सिंक्रनाइज़ होंगे। अब तक सब ठीक है.

हालांकि दुर्भाग्यवश मैक्रो स्थापित बाध्यकारी को दूषित करता है। यह वालडिफ को दोबारा शुरू करता है, लेकिन संबंधित पहचान के sym फ़ील्ड को साफ़ नहीं करता है। बाद में टाइप चेक नए बनाए गए वालडिफ को एक नया प्रतीक निर्दिष्ट करता है, लेकिन परिणाम वर्बैटिम में कॉपी की गई मूल पहचान को स्पर्श नहीं करता है।

typecheck, एक प्रतीक अब मौजूद नहीं है करने के लिए मूल अध्यक्ष अंक (वास्तव में यह त्रुटि संदेश जो कह रहे है :)) है, जो बाईटकोड पीढ़ी के दौरान एक दुर्घटना की ओर जाता है के बाद।

तो हम त्रुटि को कैसे ठीक करते हैं? दुर्भाग्य से कोई आसान जवाब नहीं है।

एक विकल्प c.resetLocalAttrs का उपयोग करना होगा, जो किसी दिए गए एएसटी नोड में सभी प्रतीकों को दोबारा मिटा देता है। बाद के टाइपकेक फिर बाइंडिंग को फिर से स्थापित करेंगे कि आपके द्वारा जेनरेट किया गया कोड उनके साथ गड़बड़ नहीं करता है (यदि, उदाहरण के लिए, आप एक ब्लॉक में xExp लपेटते हैं जो स्वयं x नामक मान को परिभाषित करता है, तो आप परेशानी में हैं)।

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

बिल्कुल अच्छा नहीं, मैं सहमत हूं। हम इसके बारे में जानते हैं और कभी-कभी इस मौलिक मुद्दे को आजमाने और ठीक करने का इरादा रखते हैं। हालांकि अभी हमारे हाथ अंतिम 2.10.0 रिलीज होने से पहले bugfixing साथ भरे हुए हैं, इसलिए हम सबसे पास भविष्य में समस्या का समाधान करने में सक्षम नहीं होगा। upd। कुछ अतिरिक्त जानकारी के लिए https://groups.google.com/forum/#!topic/scala-internals/rIyJ4yHdPDU देखें।


नीचे पंक्ति। बुरी चीजें होती हैं, क्योंकि बाइंडिंग गड़बड़ हो जाती है। रीसेट लॉक्लएटर्स को पहले आज़माएं, और यदि यह काम नहीं करता है, तो खुद को एक कोर के लिए तैयार करें।

संबंधित मुद्दे