सामान्य लिस्प के एसबीसीएल के कार्यान्वयन का उपयोग करना, क्या किसी आरईपीएल को प्रस्तुत करना संभव है जहां विशिष्ट कीवर्ड का उपयोग प्रतिबंधित है (प्रभावी रूप से सामान्य लिस्प की कार्यक्षमता के केवल एक सबसेट तक पहुंच प्रदान करना)?

1
Belthian 21 सितंबर 2021, 03:47

3 जवाब

सबसे बढ़िया उत्तर
(defpackage :limited-repl
  (:use :cl :named-readtables)
  (:export #:within-sandbox
           #:limited-repl))

(in-package :limited-repl)

इस उत्तर में मैं एक within-sandbox मैक्रो और एक limited-repl फ़ंक्शन को परिभाषित करता हूं, जैसे:

  1. वर्तमान पैकेज एक ताजा पैकेज है जो केवल सैंडबॉक्स के निष्पादन के दौरान मौजूद है, उपयोगकर्ता द्वारा दर्ज किए गए कई अलग-अलग प्रतीकों के साथ एक पैकेज को प्रदूषित करने से बचने के लिए
  2. प्रतीकों की केवल एक अनुमत सूची उपयोगकर्ता को दिखाई देती है
  3. विशेष वर्ण जैसे #\: और #\# निषिद्ध हैं, उपयोगकर्ता को अन्य पैकेजों में प्रतीकों तक पहुंचने से रोकने के लिए, और सिंटैक्स का उपयोग करने से बचने के लिए जो सरणी, बिटवेक्टर, आदि के साथ-साथ पाठक चर (#1= और #1#), जिसका उपयोग वृत्ताकार संरचनाएं बनाने के लिए किया जा सकता है जो मूल्यांकन किए जाने पर अनंत रूप से लूप करती हैं।
  4. खाली सूची COMMON-LISP:NIL के बराबर नहीं है, ताकि कस्टम भाषा में इस प्रतीक को शामिल करने से बचा जा सके; इसके बजाय, यह शून्य के रूप में पढ़ता है।

पढ़ने योग्य

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

(ql:quickload :named-readtables)

आइए निषिद्ध-वर्णों के लिए एक कस्टम स्थिति को परिभाषित करें, यह (error "some string") का उपयोग करने की तुलना में थोड़ा साफ है:

(define-condition forbidden-character (error)
  ((character :initarg :character :reader forbidden-character.character)
   (stream :initarg :stream :reader forbidden-character.stream))
  (:report (lambda (condition stream)
             (format stream
                     "Forbidden character ~@c"
                     (forbidden-character.character condition)))))

आइए एक पाठक फ़ंक्शन को भी परिभाषित करें जो पढ़े जा रहे चरित्र के लिए एक त्रुटि का संकेत देता है:

(defun forbidden-character (stream character)
  (error 'forbidden-character
         :character character
         :stream stream))

यहाँ सूची पाठक है जो एक खाली सूची पर शून्य लौटाता है:

(defun read-zero-list (stream character)
  (assert (char= character #\())
  (let ((list (read-delimited-list #\) stream t)))
    (etypecase list
      (null 0)
      (cons list))))

limited-repl पठनीय दो वर्जित वर्णों और एक कस्टम सूची पाठक को छोड़कर, मानक पठनीयता पर आधारित है:

(defreadtable limited-repl
  (:merge :standard)
  (:macro-char #\( 'read-read-zero-list)
  (:macro-char #\: 'forbidden-character nil)
  (:macro-char #\# 'forbidden-character nil))

भाषा पैकेज

limited-language पैकेज उपयोगकर्ता द्वारा उपयोग की जा सकने वाली मूल भाषा को परिभाषित करता है, यहां केवल मूल अंकगणितीय प्रतीक और quit प्रतीक हैं:

(defpackage limited-language
  (:use)
  (:export #:+
           #:-
           #:*
           #:/
           #:quit))

मैं सीएल कार्यों के चारों ओर रैपर के रूप में कार्यों को फिर से परिभाषित करता हूं, जैसा कि निम्नानुसार है:

(macrolet ((lift (s c) `(defun ,s (&rest args) (apply #',c args))))
  (lift limited-language:- cl:-)
  (lift limited-language:+ cl:+)
  (lift limited-language:* cl:*)
  (lift limited-language:/ cl:/))

यह आवश्यक है क्योंकि केवल सीएल प्रतीकों को पुनः निर्यात करने से अनपेक्षित प्रभाव हो सकते हैं, उदाहरण के लिए उपयोगकर्ता * या / चरों तक पहुंच प्राप्त कर सकते हैं जो लिस्प में बंधे हैं, जो हम यहां नहीं चाहते हैं।

अस्थायी पैकेज

निम्न सहायक फ़ंक्शन फ़ंक्शन को उस संदर्भ में कॉल करता है जहां *package* एक ताज़ा, अस्थायी पैकेज से जुड़ा होता है जो सीमित-भाषा का उपयोग करता है। मैं एक में एक नया प्रतीक उत्पन्न करने के लिए GENTEMP का उपयोग कर रहा हूं पैकेज-नाम पैकेज:

(defpackage package-names
  (:use))

(defun call-with-temporary-package (function)
  (let* ((symbol (gentemp "SANDBOX-" 'package-names))
         (package (make-package symbol :use '(limited-language))))
    (unwind-protect (let ((*package* package))
                      (funcall function))
      (delete-package package)
      (unintern symbol 'package-names))))

जब कोई आरईपीएल नहीं चल रहा होता है, तो package-names खाली होता है, क्योंकि उत्पन्न प्रतीक अनइंडिंग पर अनियंत्रित होते हैं। अस्थायी पैकेज भी हटा दिया गया है। पैकेज के लिए अस्थायी नामों को परिभाषित करने का एक अधिक मजबूत तरीका हो सकता है, यह पहले मसौदे के रूप में पर्याप्त होना चाहिए।

सैंडबॉक्स वातावरण

within-sandbox मैक्रो उस संदर्भ को स्थापित करता है जहां पढ़ने योग्य और पैकेज को हम चाहते हैं। मैं *read-eval* को शून्य से भी बांधता हूं, बस यह सुनिश्चित करने के लिए कि read के प्रदर्शन के दौरान किसी कोड का मूल्यांकन नहीं किया जाता है:

(defmacro within-sandbox (&rest body)
  `(call-with-temporary-package
    (lambda ()
      (let ((*readtable* (find-readtable 'limited-repl))
            (*read-eval* nil))
        ,@body))))

सरल सीमित REPL

अंत में, आरईपीएल को निम्नानुसार परिभाषित किया गया है, जहां आरईपीएल छोड़ने के लिए quit का उपयोग किया जाता है:

(defun limited-repl ()
  (within-sandbox
   (loop
     (format t "~&> ")
     (finish-output)
     (clear-input)
     (handler-case (let ((form (read)))
                     (when (eq form 'limited-language:quit)
                       (return))
                     (eval form))
       (cl:end-of-file ()
         (return))
       (:no-error (v)
         (print v))
       (error (e)
         (format *error-output* "~&Error: ~a~%" e))))))

उदाहरण

CL-USER> (limited-repl:limited-repl)

> 5

5 
> (+ () (* 4 3) (/ 7 9))

115/9 
> #1=(list 0 . #1#)

Error: Forbidden character #\#
> *

Error: The variable * is unbound.
> (cl:in-package 'cl-user)

Error: Forbidden character #\:
> quit
NIL
CL-USER> 

निष्कर्ष

उपयोगकर्ता द्वारा उपयोग किए जा सकने वाले प्रतीकों को सीमित करते समय, आप eval पर भरोसा कर सकते हैं। हालाँकि, यह अभी भी आपकी भाषा में CL विशिष्ट शब्दार्थ का परिचय दे सकता है, जैसे कि * अकेले लिखते समय एक अपरिभाषित चर * की रिपोर्ट करता है (हो सकता है कि आपकी भाषा में कोई परिवर्तनशील अवधारणा न हो)। फिर आपको अपना खुद का evaluate फ़ंक्शन लिखना होगा, जो eval को सौंप सकता है, लेकिन जो सामान्य लिस्प के बाहर अन्य काम भी कर सकता है। कुछ अन्य चीजें हो सकती हैं जिन पर मैं सैंडबॉक्स वाले वातावरण (निष्पादन समय, आदि) पर विचार करने में विफल रहा, सावधान रहें, लेकिन यह पहले से ही उपयोगी होना चाहिए।

2
coredump 21 सितंबर 2021, 12:31

defpackage मैक्रो चेक करें।

यदि आप केवल कुछ कार्यों को प्रतिबंधित करना चाहते हैं, तो आप :shadow कीवर्ड का उपयोग करेंगे:

(defpackage "MY-PACKAGE"
  (:use :common-lisp)
  (:shadow :list :cons))

यदि आप सब कुछ प्रतिबंधित करना चाहते हैं और केवल कुछ कार्यों की अनुमति देना चाहते हैं, तो आप :import-from कीवर्ड का उपयोग करेंगे:

(defpackage "ONLY-MATH"
  (:use)
  (:import-from :common-lisp  :+ :- :* :/ :print))

फिर आरईपीएल में (in-package "MY-PACKAGE") या (in-package "ONLY-MATH") पर कॉल करें और कुछ उदाहरण आज़माएं:

ONLY-MATH 2 > (+ 1 2)
3

ONLY-MATH 3 > (cons 1 2)

Error: Undefined operator CONS in form (CONS 1 2).
2
Martin Půda 21 सितंबर 2021, 11:10

जबकि आप केवल विशिष्ट प्रतीकों को आयात कर सकते हैं, यह उनके उपयोग को प्रतिबंधित नहीं करता है, क्योंकि आप पैकेज उपसर्ग का उपयोग करके उन्हें स्पष्ट रूप से संदर्भित कर सकते हैं।

यदि आप उपयोगकर्ता को एक आरईपीएल दिखाना चाहते हैं कि वे आपके सिस्टम में हेरफेर करने के लिए दुरुपयोग नहीं कर सकते हैं, तो आपको उनके इनपुट को सुरक्षित रूप से पढ़ना होगा और स्पष्ट रूप से मूल्यांकन नियम बनाना होगा।

2
Svante 21 सितंबर 2021, 11:17