मैं एक डीएसएल के लिए एक कंपाइलर लिख रहा हूँ। स्रोत फ़ाइल को एक स्ट्रिंग में पढ़ने के बाद, बाकी सभी चरण (पार्सिंग, टाइप चेकिंग और कोडजेन) सभी शुद्ध कोड हैं, जो कोड को एक प्रतिनिधित्व से दूसरे में बदलते हैं। स्रोत फ़ाइल में निर्भरताएँ होने तक सब ठीक है (#include प्रीप्रोसेसर के बारे में C में सोचें)। पार्सर को निर्भर फाइलों को पढ़ने और उन्हें दोबारा पार्स करने की जरूरत है। यह इसे अब शुद्ध नहीं बनाता है। मुझे इसे AST से IO AST में बदलने से बदलना होगा। साथ ही, बाद के सभी चरणों (टाइप चेकिंग और कोडजेन) को IO प्रकार भी वापस करने होंगे, जिसके लिए महत्वपूर्ण परिवर्तनों की आवश्यकता होती है। इस मामले में निर्भर फाइलों को पढ़ने का एक अच्छा तरीका क्या है?

अनुलेख मैं unsafePerformIO का उपयोग कर सकता हूं, लेकिन यह एक आसान समाधान लगता है जिससे तकनीकी ऋण हो सकता है।

2
sinoTrinity 15 सितंबर 2020, 20:25

1 उत्तर

सबसे बढ़िया उत्तर

एक अच्छा समाधान एक एएसटी युक्त निर्भरता जानकारी में पार्स करना है, फिर निर्भरताओं को अलग से पार्सर के बाहर हल करना है। उदाहरण के लिए, मान लें कि आपके पास एक प्रारूप है जो #include लाइन या सामग्री लाइन हो सकता है:

data WithIncludes = WithIncludes [ContentOrInclude]

data ContentOrInclude
  = Content String
  | Include FilePath

और एक पार्सर parse :: String -> WithIncludes ताकि ये फ़ाइलें:

  • file1:

    before
    #include "file2"
    after
    
  • file2:

    between
    

इन अभ्यावेदन को पार्स करें:

file1 = WithIncludes
  [ Content "before"
  , Include "file2"
  , Content "after"
  ]

file2 = WithIncludes
  [ Content "between"
  ]

आप एक और प्रकार जोड़ सकते हैं जो एक चपटा फ़ाइल का प्रतिनिधित्व करता है जिसमें आयात हल हो गए हैं:

data WithoutIncludes = WithoutIncludes [String]

और पार्सिंग से अलग, लोड और पुनरावर्ती रूप से समतल में शामिल हैं:

flatten :: WithIncludes -> IO WithoutIncludes
flatten (WithIncludes ls) = WithoutIncludes . concat <$> traverse flatten' ls
  where
    flatten' :: ContentOrInclude -> IO [String]
    flatten' (Content content) = pure [content]
    flatten' (Include path) = do
      contents <- readFile path
      let parsed = parse contents
      flatten parsed

फिर परिणाम है:

flatten file1 == WithoutIncludes
  [ "before"
  , "between"
  , "after"
  ]

पार्सिंग शुद्ध रहती है, और आपके पास इसके चारों ओर केवल एक IO आवरण होता है जो यह चला रहा है कि कौन सी फ़ाइलें लोड की जानी हैं। तुम भी एक फ़ाइल लोड करने के लिए यहाँ तर्क का पुन: उपयोग कर सकते हैं:

load :: FilePath -> IO WithoutIncludes
load path = flatten $ WithIncludes [Include path]

आयात चक्रों की जांच के लिए यहां तर्क जोड़ना भी एक अच्छा विचार है, उदाहरण के लिए flatten में एक संचायक जोड़कर जिसमें Set कैनोनिकलाइज्ड FilePath है, और प्रत्येक Include कि आपने पहले से वही FilePath नहीं देखा है।

अधिक जटिल एएसटी के लिए, आप अधिकांश संरचना को अनसुलझे और हल किए गए प्रकारों के बीच साझा करना चाह सकते हैं। उस स्थिति में, आप इस प्रकार को पैरामीटर कर सकते हैं कि क्या यह हल हो गया है, और अनसुलझे और हल किए गए प्रकार अलग-अलग तर्कों के साथ अंतर्निहित एएसटी प्रकार के लिए उपनाम हो सकते हैं, उदाहरण के लिए:

data File i = File [ContentOrInclude i]

data ContentOrInclude i
  = Content String
  | Include i

type WithIncludes = File FilePath
type WithoutIncludes = File [String]
5
Jon Purdy 15 सितंबर 2020, 21:38