pjs/js/semantics/GrammarSymbol.lisp

540 строки
25 KiB
Common Lisp

;;; The contents of this file are subject to the Netscape Public License
;;; Version 1.0 (the "NPL"); you may not use this file except in
;;; compliance with the NPL. You may obtain a copy of the NPL at
;;; http://www.mozilla.org/NPL/
;;;
;;; Software distributed under the NPL is distributed on an "AS IS" basis,
;;; WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
;;; for the specific language governing rights and limitations under the
;;; NPL.
;;;
;;; The Initial Developer of this code under the NPL is Netscape
;;; Communications Corporation. Portions created by Netscape are
;;; Copyright (C) 1998 Netscape Communications Corporation. All Rights
;;; Reserved.
;;;
;;; LALR(1) and LR(1) parametrized grammar utilities
;;;
;;; Waldemar Horwat (waldemar@netscape.com)
;;;
;;; ------------------------------------------------------------------------------------------------------
;;; UTILITIES
(declaim (inline identifier?))
(defun identifier? (form)
(and form (symbolp form) (not (keywordp form))))
(deftype identifier () '(satisfies identifier?))
; Make sure that form is one of the following:
; A symbol
; An integer
; A float
; A character
; A string
; A list of zero or more forms that also satisfy ensure-proper-form;
; the list cannot be dotted.
; Return the form.
(defun ensure-proper-form (form)
(labels
((ensure-list-form (form)
(or (null form)
(and (consp form)
(progn
(ensure-proper-form (car form))
(ensure-list-form (cdr form)))))))
(unless
(or (symbolp form)
(integerp form)
(floatp form)
(characterp form)
(stringp form)
(ensure-list-form form))
(error "Bad form: ~S" form))
form))
;;; ------------------------------------------------------------------------------------------------------
;;; TERMINALS
; A terminal is any of the following:
; A symbol that is neither nil nor a keyword
; A string;
; A character;
; An integer.
(defun terminal? (x)
(and x
(or (and (symbolp x) (not (keywordp x)))
(stringp x)
(characterp x)
(integerp x))))
; The following terminals are reserved and may not be used in user input:
; $$ Marker for end of token stream
(defconstant *end-marker* '$$)
(defconstant *end-marker-terminal-number* 0)
(deftype terminal () '(satisfies terminal?))
(deftype user-terminal () `(and terminal (not (eql ,*end-marker*))))
; Emit markup for a terminal. subscript is an optional integer.
(defun depict-terminal (markup-stream terminal &optional subscript)
(cond
((characterp terminal)
(depict-char-style (markup-stream ':character-literal)
(depict-character markup-stream terminal)
(when subscript
(depict-char-style (markup-stream ':plain-subscript)
(depict-integer markup-stream subscript)))))
((and terminal (symbolp terminal))
(let ((name (symbol-name terminal)))
(if (and (> (length name) 0) (char= (char name 0) #\$))
(depict-char-style (markup-stream ':terminal)
(depict markup-stream (subseq (symbol-upper-mixed-case-name terminal) 1))
(when subscript
(depict-char-style (markup-stream ':plain-subscript)
(depict-integer markup-stream subscript))))
(progn
(depict-char-style (markup-stream ':terminal-keyword)
(depict markup-stream (string-downcase name)))
(when subscript
(depict-char-style (markup-stream ':terminal)
(depict-char-style (markup-stream ':plain-subscript)
(depict-integer markup-stream subscript))))))))
(t (error "Don't know how to emit markup for terminal ~S" terminal))))
;;; ------------------------------------------------------------------------------------------------------
;;; NONTERMINAL PARAMETERS
(declaim (inline nonterminal-parameter?))
(defun nonterminal-parameter? (x)
(symbolp x))
(deftype nonterminal-parameter () 'symbol)
; Return true if this nonterminal parameter is a constant.
(declaim (inline nonterminal-attribute?))
(defun nonterminal-attribute? (parameter)
(and (symbolp parameter) (not (keywordp parameter))))
(deftype nonterminal-attribute () '(and symbol (not keyword)))
(defun depict-nonterminal-attribute (markup-stream attribute)
(depict-char-style (markup-stream ':nonterminal)
(depict-char-style (markup-stream ':nonterminal-attribute)
(depict markup-stream (symbol-lower-mixed-case-name attribute)))))
; Return true if this nonterminal parameter is a variable.
(declaim (inline nonterminal-argument?))
(defun nonterminal-argument? (parameter)
(keywordp parameter))
(deftype nonterminal-argument () 'keyword)
(defparameter *special-nonterminal-arguments*
'(:alpha :beta :gamma :delta :epsilon :zeta :eta :theta :iota :kappa :lambda :mu :nu
:xi :omicron :pi :rho :sigma :tau :upsilon :phi :chi :psi :omega))
(defun depict-nonterminal-argument-symbol (markup-stream argument)
(depict-char-style (markup-stream ':nonterminal-argument)
(let ((argument (symbol-abbreviation argument)))
(depict markup-stream
(if (member argument *special-nonterminal-arguments*)
argument
(symbol-upper-mixed-case-name argument))))))
(defun depict-nonterminal-argument (markup-stream argument)
(depict-char-style (markup-stream ':nonterminal)
(depict-nonterminal-argument-symbol markup-stream argument)))
;;; ------------------------------------------------------------------------------------------------------
;;; ATTRIBUTED NONTERMINALS
; An attributed-nonterminal is a specific instantiation of a generic-nonterminal.
(defstruct (attributed-nonterminal (:constructor allocate-attributed-nonterminal (symbol attributes))
(:copier nil)
(:predicate attributed-nonterminal?))
(symbol nil :type keyword :read-only t) ;The name of the attributed nonterminal
(attributes nil :type list :read-only t)) ;Ordered list of nonterminal attributes
; Make an attributed nonterminal with the given symbol and attributes. If there
; are no attributes, return the symbol as a plain nonterminal.
; Nonterminals are eq whenever they have identical symbols and attribute lists.
(defun make-attributed-nonterminal (symbol attributes)
(assert-type symbol keyword)
(assert-type attributes (list nonterminal-attribute))
(if attributes
(let ((generic-nonterminals (get symbol 'generic-nonterminals)))
(or (cdr (assoc attributes generic-nonterminals :test #'equal))
(let ((attributed-nonterminal (allocate-attributed-nonterminal symbol attributes)))
(setf (get symbol 'generic-nonterminals)
(acons attributes attributed-nonterminal generic-nonterminals))
attributed-nonterminal)))
symbol))
(defmethod print-object ((attributed-nonterminal attributed-nonterminal) stream)
(print-unreadable-object (attributed-nonterminal stream)
(format stream "a ~@_~W~{ ~:_~W~}"
(attributed-nonterminal-symbol attributed-nonterminal)
(attributed-nonterminal-attributes attributed-nonterminal))))
;;; ------------------------------------------------------------------------------------------------------
;;; GENERIC NONTERMINALS
; A generic-nonterminal is a parametrized nonterminal that can expand into two or more
; attributed-nonterminals.
(defstruct (generic-nonterminal (:constructor allocate-generic-nonterminal (symbol parameters))
(:copier nil)
(:predicate generic-nonterminal?))
(symbol nil :type keyword :read-only t) ;The name of the generic nonterminal
(parameters nil :type list :read-only t)) ;Ordered list of nonterminal attributes or arguments
; Make a generic nonterminal with the given symbol and parameters. If none of
; the parameters is an argument, make an attributed nonterminal instead. If there
; are no parameters, return the symbol as a plain nonterminal.
; Nonterminals are eq whenever they have identical symbols and parameter lists.
(defun make-generic-nonterminal (symbol parameters)
(assert-type symbol keyword)
(if parameters
(let ((generic-nonterminals (get symbol 'generic-nonterminals)))
(or (cdr (assoc parameters generic-nonterminals :test #'equal))
(progn
(assert-type parameters (list nonterminal-parameter))
(let ((generic-nonterminal (if (every #'nonterminal-attribute? parameters)
(allocate-attributed-nonterminal symbol parameters)
(allocate-generic-nonterminal symbol parameters))))
(setf (get symbol 'generic-nonterminals)
(acons parameters generic-nonterminal generic-nonterminals))
generic-nonterminal))))
symbol))
(defmethod print-object ((generic-nonterminal generic-nonterminal) stream)
(print-unreadable-object (generic-nonterminal stream)
(format stream "g ~@_~W~{ ~:_~W~}"
(generic-nonterminal-symbol generic-nonterminal)
(generic-nonterminal-parameters generic-nonterminal))))
;;; ------------------------------------------------------------------------------------------------------
;;; NONTERMINALS
;;; A nonterminal is a keyword or an attributed-nonterminal.
(declaim (inline nonterminal?))
(defun nonterminal? (x)
(or (keywordp x) (attributed-nonterminal? x)))
; The following nonterminals are reserved and may not be used in user input:
; :% Nonterminal that expands to the start nonterminal
(defconstant *start-nonterminal* :%)
(deftype nonterminal () '(or keyword attributed-nonterminal))
(deftype user-nonterminal () `(and nonterminal (not (eql ,*start-nonterminal*))))
;;; ------------------------------------------------------------------------------------------------------
;;; GENERAL NONTERMINALS
;;; A general-nonterminal is a nonterminal or a generic-nonterminal.
(declaim (inline general-nonterminal?))
(defun general-nonterminal? (x)
(or (nonterminal? x) (generic-nonterminal? x)))
(deftype general-nonterminal () '(or nonterminal generic-nonterminal))
; Return the list of parameters in the general-nonterminal. The list is empty if the
; general-nonterminal is a plain nonterminal.
(defun general-nonterminal-parameters (general-nonterminal)
(cond
((attributed-nonterminal? general-nonterminal) (attributed-nonterminal-attributes general-nonterminal))
((generic-nonterminal? general-nonterminal) (generic-nonterminal-parameters general-nonterminal))
(t (progn
(assert-true (keywordp general-nonterminal))
nil))))
; Emit markup for a general-nonterminal. subscript is an optional integer.
; link should be one of:
; :reference if this is a reference of this general-nonterminal;
; :external if this is an external reference of this general-nonterminal;
; :definition if this is a definition of this general-nonterminal;
; nil if this use of the general-nonterminal should not be cross-referenced.
(defun depict-general-nonterminal (markup-stream general-nonterminal link &optional subscript)
(labels
((depict-nonterminal-name (markup-stream symbol)
(let ((name (symbol-upper-mixed-case-name symbol)))
(depict-link (markup-stream link "N-" name t)
(depict markup-stream name))))
(depict-nonterminal-parameter (markup-stream parameter)
(if (nonterminal-attribute? parameter)
(depict-char-style (markup-stream ':nonterminal-attribute)
(depict markup-stream (symbol-lower-mixed-case-name parameter)))
(depict-nonterminal-argument-symbol markup-stream parameter)))
(depict-parametrized-nonterminal (markup-stream symbol parameters)
(depict-nonterminal-name markup-stream symbol)
(depict-char-style (markup-stream ':superscript)
(depict-list markup-stream #'depict-nonterminal-parameter parameters
:separator ",")))
(depict-general (markup-stream)
(depict-char-style (markup-stream ':nonterminal)
(cond
((keywordp general-nonterminal)
(depict-nonterminal-name markup-stream general-nonterminal))
((attributed-nonterminal? general-nonterminal)
(depict-parametrized-nonterminal markup-stream
(attributed-nonterminal-symbol general-nonterminal)
(attributed-nonterminal-attributes general-nonterminal)))
((generic-nonterminal? general-nonterminal)
(depict-parametrized-nonterminal markup-stream
(generic-nonterminal-symbol general-nonterminal)
(generic-nonterminal-parameters general-nonterminal)))
(t (error "Bad nonterminal ~S" general-nonterminal)))
(when subscript
(depict-char-style (markup-stream ':plain-subscript)
(depict-integer markup-stream subscript))))))
(if (or (eq link :definition)
(and (or (eq link :reference) (eq link :external))
(keywordp general-nonterminal)
(null subscript)))
(depict-link (markup-stream link "N-" (symbol-upper-mixed-case-name (general-grammar-symbol-symbol general-nonterminal)) t)
(setq link nil)
(depict-general markup-stream))
(depict-general markup-stream))))
;;; ------------------------------------------------------------------------------------------------------
;;; GRAMMAR SYMBOLS
;;; A grammar-symbol is either a terminal or a nonterminal.
(deftype grammar-symbol () '(or terminal nonterminal))
(deftype user-grammar-symbol () '(or user-terminal user-nonterminal))
;;; A general-grammar-symbol is either a terminal or a general-nonterminal.
(deftype general-grammar-symbol () '(or terminal general-nonterminal))
; Return true if x is a general-grammar-symbol. x can be any object.
(defun general-grammar-symbol? (x)
(or (terminal? x) (general-nonterminal? x)))
; Return true if the two grammar symbols are the same symbol.
(declaim (inline grammar-symbol-=))
(defun grammar-symbol-= (grammar-symbol1 grammar-symbol2)
(eql grammar-symbol1 grammar-symbol2))
; A version of grammar-symbol-= suitable for being the test function for hash tables.
(defconstant *grammar-symbol-=* #'eql)
; Return the general-grammar-symbol's symbol. Return it unchanged if it is not
; an attributed or generic nonterminal.
(defun general-grammar-symbol-symbol (general-grammar-symbol)
(cond
((attributed-nonterminal? general-grammar-symbol) (attributed-nonterminal-symbol general-grammar-symbol))
((generic-nonterminal? general-grammar-symbol) (generic-nonterminal-symbol general-grammar-symbol))
(t (assert-type general-grammar-symbol (or keyword terminal)))))
; Return the list of arguments in the general-grammar-symbol. The list is empty if the
; general-grammar-symbol is not a generic nonterminal.
(defun general-grammar-symbol-arguments (general-grammar-symbol)
(and (generic-nonterminal? general-grammar-symbol)
(remove-if (complement #'nonterminal-argument?) (generic-nonterminal-parameters general-grammar-symbol))))
; Return the general-grammar-symbol expanded into source form that can be interned to yield the same
; general-grammar-symbol.
(defun general-grammar-symbol-source (general-grammar-symbol)
(cond
((attributed-nonterminal? general-grammar-symbol)
(cons (attributed-nonterminal-symbol general-grammar-symbol) (attributed-nonterminal-attributes general-grammar-symbol)))
((generic-nonterminal? general-grammar-symbol)
(cons (generic-nonterminal-symbol general-grammar-symbol) (generic-nonterminal-parameters general-grammar-symbol)))
(t (assert-type general-grammar-symbol (or keyword terminal)))))
; Emit markup for a general-grammar-symbol. subscript is an optional integer.
; link should be one of:
; :reference if this is a reference of this general-grammar-symbol;
; :external if this is an external reference of this general-grammar-symbol;
; :definition if this is a definition of this general-grammar-symbol;
; nil if this use of the general-grammar-symbol should not be cross-referenced.
(defun depict-general-grammar-symbol (markup-stream general-grammar-symbol link &optional subscript)
(if (general-nonterminal? general-grammar-symbol)
(depict-general-nonterminal markup-stream general-grammar-symbol link subscript)
(depict-terminal markup-stream general-grammar-symbol subscript)))
; Styled text can include (:grammar-symbol <grammar-symbol-source> [<subscript>]) as long as
; *styled-text-grammar-parametrization* is bound around the call to depict-styled-text.
(defvar *styled-text-grammar-parametrization*)
(defun depict-grammar-symbol-styled-text (markup-stream grammar-symbol-source &optional subscript)
(depict-general-grammar-symbol markup-stream
(grammar-parametrization-intern *styled-text-grammar-parametrization* grammar-symbol-source)
:reference
subscript))
(setf (styled-text-depictor :grammar-symbol) #'depict-grammar-symbol-styled-text)
;;; ------------------------------------------------------------------------------------------------------
;;; GRAMMAR PARAMETRIZATIONS
; A grammar parametrization holds the rules for converting nonterminal arguments into nonterminal attributes.
(defstruct (grammar-parametrization (:constructor allocate-grammar-parametrization (argument-attributes))
(:predicate grammar-parametrization?))
(argument-attributes nil :type hash-table :read-only t)) ;Hash table of nonterminal-argument -> list of nonterminal-attributes
(defun make-grammar-parametrization ()
(allocate-grammar-parametrization (make-hash-table :test #'eq)))
; Declare that nonterminal arguments with the given name can hold any of the
; given nonterminal attributes given. At least one attribute must be provided.
(defun grammar-parametrization-declare-argument (grammar-parametrization argument attributes)
(assert-type argument nonterminal-argument)
(assert-type attributes (list nonterminal-attribute))
(assert-true attributes)
(when (gethash argument (grammar-parametrization-argument-attributes grammar-parametrization))
(error "Duplicate parametrized grammar argument ~S" argument))
(setf (gethash argument (grammar-parametrization-argument-attributes grammar-parametrization)) attributes))
; Return the attributes to which the given argument may expand.
(defun grammar-parametrization-lookup-argument (grammar-parametrization argument)
(assert-non-null (gethash argument (grammar-parametrization-argument-attributes grammar-parametrization))))
; Create a plain, attributed, or generic grammar symbol from the specification in grammar-symbol-source.
; If grammar-symbol-source is not a cons, it is a plain grammar symbol. If it is a list, its first element
; must be a keyword that is a nonterminal's symbol and the other elements must be nonterminal
; parameters.
; Return two values:
; the grammar symbol
; a list of arguments used in the grammar symbol.
; If allowed-arguments is given, check that each argument is in the allowed-arguments list;
; if not, allow any arguments declared in grammar-parametrization but do not allow duplicates.
(defun grammar-parametrization-intern (grammar-parametrization grammar-symbol-source &optional (allowed-arguments nil allow-duplicates))
(if (consp grammar-symbol-source)
(progn
(assert-type grammar-symbol-source (cons keyword (list nonterminal-parameter)))
(let* ((symbol (car grammar-symbol-source))
(parameters (cdr grammar-symbol-source))
(arguments (remove-if (complement #'nonterminal-argument?) parameters)))
(mapl #'(lambda (arguments)
(let ((argument (car arguments)))
(if allow-duplicates
(unless (member argument allowed-arguments :test #'eq)
(error "Undefined nonterminal argument ~S" argument))
(progn
(unless (gethash argument (grammar-parametrization-argument-attributes grammar-parametrization))
(error "Undeclared nonterminal argument ~S" argument))
(when (member argument (cdr arguments) :test #'eq)
(error "Duplicate nonterminal argument ~S" argument))))))
arguments)
(values (make-generic-nonterminal symbol parameters) arguments)))
(values (assert-type grammar-symbol-source (or keyword terminal)) nil)))
; Call f on each possible binding permutation of the given arguments concatenated with the bindings in
; bound-argument-alist. f takes one argument, an association list that maps arguments to attributes.
(defun grammar-parametrization-each-permutation (grammar-parametrization f arguments &optional bound-argument-alist)
(if arguments
(let ((argument (car arguments))
(rest-arguments (cdr arguments)))
(dolist (attribute (grammar-parametrization-lookup-argument grammar-parametrization argument))
(grammar-parametrization-each-permutation grammar-parametrization f rest-arguments (acons argument attribute bound-argument-alist))))
(funcall f bound-argument-alist)))
; If general-grammar-symbol is a generic-nonterminal, return one possible binding permutation of its arguments;
; otherwise return nil.
(defun nonterminal-sample-bound-argument-alist (grammar-parametrization general-grammar-symbol)
(when (generic-nonterminal? general-grammar-symbol)
(grammar-parametrization-each-permutation
grammar-parametrization
#'(lambda (bound-argument-alist) (return-from nonterminal-sample-bound-argument-alist bound-argument-alist))
(general-grammar-symbol-arguments general-grammar-symbol))))
; If the grammar symbol is a generic nonterminal, convert it into an attributed nonterminal
; by instantiating its arguments with the corresponding attributes from the bound-argument-alist.
; If the grammar symbol is already an attributed or plain nonterminal, return it unchanged.
(defun instantiate-general-grammar-symbol (bound-argument-alist general-grammar-symbol)
(if (generic-nonterminal? general-grammar-symbol)
(make-attributed-nonterminal
(generic-nonterminal-symbol general-grammar-symbol)
(mapcar #'(lambda (parameter)
(if (nonterminal-argument? parameter)
(let ((binding (assoc parameter bound-argument-alist :test #'eq)))
(if binding
(cdr binding)
(error "Unbound nonterminal argument ~S" parameter)))
parameter))
(generic-nonterminal-parameters general-grammar-symbol)))
(assert-type general-grammar-symbol grammar-symbol)))
; If the grammar symbol is a generic nonterminal parametrized on argument, substitute
; attribute for argument in it and return the modified grammar symbol. Otherwise, return it unchanged.
(defun general-grammar-symbol-substitute (attribute argument general-grammar-symbol)
(assert-type attribute nonterminal-attribute)
(assert-type argument nonterminal-argument)
(if (and (generic-nonterminal? general-grammar-symbol)
(member argument (generic-nonterminal-parameters general-grammar-symbol) :test #'eq))
(make-generic-nonterminal
(generic-nonterminal-symbol general-grammar-symbol)
(substitute attribute argument (generic-nonterminal-parameters general-grammar-symbol) :test #'eq))
(assert-type general-grammar-symbol general-grammar-symbol)))
; If the general grammar symbol is a generic nonterminal, return a list of all possible attributed nonterminals
; that can be instantiated from it; otherwise, return a one-element list containing the given general grammar symbol.
(defun general-grammar-symbol-instances (grammar-parametrization general-grammar-symbol)
(if (generic-nonterminal? general-grammar-symbol)
(let ((instances nil))
(grammar-parametrization-each-permutation
grammar-parametrization
#'(lambda (bound-argument-alist)
(push (instantiate-general-grammar-symbol bound-argument-alist general-grammar-symbol) instances))
(general-grammar-symbol-arguments general-grammar-symbol))
(nreverse instances))
(list (assert-type general-grammar-symbol grammar-symbol))))
; Return true if grammar-symbol can be obtained by calling instantiate-general-grammar-symbol on
; general-grammar-symbol.
(defun general-nonterminal-is-instance? (grammar-parametrization general-grammar-symbol grammar-symbol)
(or (grammar-symbol-= general-grammar-symbol grammar-symbol)
(and (generic-nonterminal? general-grammar-symbol)
(attributed-nonterminal? grammar-symbol)
(let ((parameters (generic-nonterminal-parameters general-grammar-symbol))
(attributes (attributed-nonterminal-attributes grammar-symbol)))
(and (= (length parameters) (length attributes))
(every #'(lambda (parameter attribute)
(or (eq parameter attribute)
(and (nonterminal-argument? parameter)
(member attribute (grammar-parametrization-lookup-argument grammar-parametrization parameter) :test #'eq))))
parameters
attributes))))))