;;;; Copyright (c) 2001--2020 Stephan Oepen (oe@ifi.uio.no); ;;; see `LICENSE' for conditions. (in-package :mrs) (eval-when (:compile-toplevel :load-toplevel :execute) (shadow '(ed))) ;;; ;;; ToDo ;;; ;;; - identify more wellformedness conditions: e.g. mark all connected pieces ;;; of the graph and check for remaining fragments, or stipulate that certain ;;; relations minimally require certain roles (with certain types, say). ;;; (defparameter *eds-debug-p* nil) (defparameter *eds-pretty-print-p* t) (defparameter *eds-normalize-p* nil) (defparameter *eds-vpm* nil) (defparameter *eds-include-messages-p* nil) (defparameter *eds-include-quantifiers-p* t) (defparameter *eds-include-icons-p* t) (defparameter *eds-include-vacuous-relations-p* t) (defparameter *eds-message-relation* (vsym "message_m_rel")) (defparameter *eds-fragment-relation* (vsym "unknown_rel")) (defparameter *eds-bleached-relations* (list (vsym "selected_rel"))) (defparameter *eds-predicate-filter* nil) (defparameter *eds-argument-filter* (list (list (vsym "L-INDEX") (vsym "L-HNDL") "^h") (list (vsym "R-INDEX") (vsym "R-HNDL") "^h"))) (defparameter *eds-quantifier-argument* (vsym "BV")) (defparameter *eds-untensed* (list (cons (vsym "TENSE") (vsym "untensed")))) (defparameter *eds-non-representatives* (list (vsym "appos_rel") (vsym "id_rel") (vsym "focus_d_rel") (vsym "parg_d_rel"))) (defparameter *eds-predicate-modifiers* (list (ppcre:create-scanner "_x_deg_rel$|^neg_rel$|^_quite_x_rel$"))) (defparameter *eds-show-lnk-p* t) (defparameter *eds-show-properties-p* t) (defparameter *eds-show-status-p* nil) (defparameter %eds-variable-counter% 0) (defparameter %eds-symbol-table% (make-hash-table)) (defparameter %eds-representatives-table% (make-hash-table :test #'equal)) (defparameter %eds-equivalences% (make-hash-table :test #'equal)) (defparameter %eds-filter% nil) (defparameter %eds-relevant-features% '("ARG" "ARG1" "ARG2" "ARG3" "ARG4" "BV" "L-HNDL" "L-INDEX" "R-HNDL" "R-INDEX" "CARG" "SOA" "CONST_VALUE" "TERM1" "TERM2" "FACTOR1" "FACTOR2" "MARG" "L-HANDEL" "R-HANDEL" "MAIN" "SUBORD" "ROLE" "HINST" "NHINST")) (defstruct eds top relations hcons raw status) (defmethod print-object ((object eds) stream) (if *eds-pretty-print-p* (let ((cyclicp (eds-cyclic-p object)) (fragmentedp (eds-fragmented-p object))) (loop initially (format stream "{~@[~(~a~):~]~ ~:[~3*~; (~@[cyclic~*~]~@[ ~*~]~@[fragmented~*~])~]~@[~%~]" (eds-top object) (and *eds-show-status-p* (or cyclicp fragmentedp) ) cyclicp (and cyclicp fragmentedp) fragmentedp (eds-relations object)) for ed in (eds-relations object) unless (or (and (null (ed-status ed)) (or (ed-bleached-p ed) (ed-vacuous-p ed))) (member ed %eds-filter% :test #'eq)) do (format stream "~c~a~%" (cond ((null *eds-show-status-p*) #\Space) ((member :cyclic (ed-status ed)) #\|) ((member :fragmented (ed-status ed)) #\|) (t #\Space)) ed) finally (format stream "}~%"))) (call-next-method))) (defstruct ed handle id properties type variable predicate arguments carg lnk inverse raw status mark abstraction) (defmethod print-object ((object ed) stream) (if *eds-pretty-print-p* (loop with carg = (vsym "CARG") initially (format stream "~(~a~):~(~a~)~@[(~s)~]" (ed-id object) (ed-linked-predicate object) (ed-carg object)) (when (and *eds-show-properties-p* (ed-properties object)) (loop with *package* = (find-package *mrs-package*) with type = (first (ed-properties object)) with properties = (if (extrapair-p type) (ed-properties object) (rest (ed-properties object))) initially (format stream "{~@[~(~a~)~]~@[ ~]" (unless (extrapair-p type) type) properties) finally (format stream "}") for property in properties do (format stream "~:[~;, ~]~a ~(~a~)" (not (eq property (first properties))) (extrapair-feature property) (extrapair-value property)))) (format stream "[") for (role . value) in (ed-arguments object) unless (eq role carg) do (format stream "~:[, ~;~]~:@(~a~) ~@[~(~a~)~]~@[:~(~a~)~]~@[(~s)~]" (eq role (first (first (ed-arguments object)))) role (if (ed-p value) (ed-id value) "") (unless *eds-include-vacuous-relations-p* (if (ed-p value) (ed-predicate value) value)) (unless *eds-include-vacuous-relations-p* (when (ed-p value) (ed-carg value)))) finally (format stream "]")) (call-next-method))) (defun ed-linked-predicate (ed &key (lnkp *eds-show-lnk-p*)) (let ((predicate (or (ed-predicate ed) "_")) (lnk (ed-lnk ed))) ;; ;; _fix_me_ ;; why not use output-lnk(), to avoid code duplication? (27-feb-16; oe) ;; (case (and lnkp (first (ed-lnk ed))) (:id (format nil "~a<@~a>" predicate (second lnk))) (:characters (format nil "~a<~a:~a>" predicate (second lnk) (third lnk))) (:vertices (format nil "~a<~a-~a>" predicate (second lnk) (third lnk))) (:tokens (format nil "~a<~{~a~^,~}>" predicate (rest lnk))) (t (format nil "~a" (ed-predicate ed)))))) (defun ed-linked-abstraction (ed &key (lnkp t)) (let ((abstraction (ed-abstraction ed)) (lnk (ed-lnk ed))) (when abstraction (case (and lnkp (first (ed-lnk ed))) (:characters (format nil "_~(~a~)_<~a:~a>" abstraction (second lnk) (third lnk))) (:vertices (format nil "_~(~a~)_<~a-~a>" abstraction (second lnk) (third lnk))) (:tokens (format nil "_~(~a~)_<~{~a~^,~}>" abstraction (rest lnk))) (t (format nil "_~(~a~)_" abstraction)))))) (defun eds-output-psoa (psoa &key (stream t) (format :ascii) (lnkp *eds-show-lnk-p*) (propertiesp *eds-show-properties-p*) cargp collocationp abstractp sentinelp (vpm *eds-vpm*) (normalizep *eds-normalize-p*) sortp dmrsp (n 0) (filter *eds-predicate-filter*) input id debug (prefix "") (columns *print-right-margin*)) (when (null stream) (return-from eds-output-psoa (with-output-to-string (stream) (eds-output-psoa psoa :stream stream :format format :lnkp lnkp :propertiesp propertiesp :cargp cargp :collocationp collocationp :abstractp abstractp :sentinelp sentinelp :vpm vpm :normalizep normalizep :sortp sortp :dmrsp dmrsp :n n :filter filter :input input :id id :debug debug :prefix prefix :columns columns)))) (when (eq format :svg) (return-from eds-output-psoa (let* ((tmp #+:logon (getenv "LOGONTMP") #-:logon nil) (tmp (or (and tmp (namestring (parse-namestring tmp))) "/tmp")) (user (or #+(and :allegro-version>= (version>= 5 0)) (sys:user-name) #+(and :allegro (not (and :allegro-version>= (version>= 5 0)))) (system:getenv "USER") #+(and :mcl :powerpc) (ccl:process-name ccl:*current-process*) #+:lucid (lcl:environment-variable "USER") "nobody")) (id (gensym "")) (dot (format nil "~a/.eds.dot.~a.~a" tmp user id)) (svg (format nil "~a/.eds.svg.~a.~a" tmp user id)) (command (format nil "dot -Gstylesheet=\"dot.css\" -Gcolor=\"#1a04a5\" -Gbgcolor=\"#f5f5f5\" -Nfontname=\"helvetica, arial, sans-serif\" -Nfontsize=8 -Efontname=\"helvetica, arial, sans-serif\" -Efontsize=8 -Tsvg '~a'" dot))) (with-open-file (stream dot :direction :output :if-exists :supersede) (eds-output-psoa psoa :stream stream :format :dot :lnkp lnkp :propertiesp propertiesp :cargp cargp :collocationp collocationp :abstractp abstractp :sentinelp sentinelp :vpm vpm :normalizep normalizep :sortp sortp :dmrsp dmrsp :n n :filter filter :input input :id id :debug debug :prefix prefix :columns columns)) ;; ;; _fix_me_ ;; it would seem desirable to directly send the generated SVG to our ;; own output .stream., but (at least in ACL), run-shell-command() is ;; restricted to a sub-set of stream types only. ;; (run-process command :wait t :output svg) (with-open-file (svg svg) (loop for line = (read-line svg nil nil) while line do (write-string line stream) (terpri stream)))))) (let ((eds (if (eds-p psoa) psoa (eds-convert-psoa psoa :vpm vpm :normalizep normalizep))) (%eds-filter% nil) (*eds-show-lnk-p* lnkp) (*eds-show-properties-p* propertiesp)) (when filter (when (stringp filter) (setf filter (ppcre::create-scanner filter))) (loop for ed in (eds-relations eds) when (and (ppcre:scan filter (ed-predicate ed)) (null (ed-inverse ed)) (not (string= (ed-id ed) (eds-top eds)))) do (push ed %eds-filter%))) (case format ((:ascii :native) (cond ((eds-p psoa) (format stream "~a~%" psoa)) ((eds-p eds) (format stream "~a~%" eds)) (t (format stream "{}~%")))) (:triples (let* ((triples (if dmrsp (dmrs-explode (rmrs-to-dmrs (mrs-to-rmrs psoa))) (eds-explode eds :cargp cargp :collocationp collocationp :abstractp abstractp)))) (when sortp (setf triples (sort triples #'(lambda (foo bar) (or (string< (first foo) (first bar)) (and (string= (first foo) (first bar)) (string< (second foo) (second bar)))))))) (loop with *package* = (find-package :lkb) initially (unless sentinelp (format stream "{~%")) for triple in triples do (format stream "~:[ ~; ~]~{~a~^ ~}~:[ ~; ~]~%" sentinelp triple sentinelp) finally (unless sentinelp (format stream "}~%~%"))) (length triples))) (:lui (let ((attic (make-hash-table :test #'equal)) (id 0)) (labels ((record (object) (or (gethash object attic) (let ((n id)) (setf (gethash object attic) n) (incf id) n)))) (format stream "#X[~a \"{~(~a~)\" \":\" newline~%\" \" #X[" (record (eds-top eds)) (eds-top eds)) (loop with carg = (vsym "CARG") with firstp = t for ed in (eds-relations eds) do (format stream "~@[newline~*~] ~a \"~(~a~)\" \":~(~a~)~@[(~a)~][\" #X[" (not firstp) (record (ed-id ed)) (ed-id ed) (ed-linked-predicate ed) (ed-carg ed)) (setf firstp nil) (loop with firstp = t for (role . value) in (ed-arguments ed) unless (eq role carg) do (format stream "~@[\", \" ~*~]~@[wrap ~*~]\"~a \" ~ ~@[~a ~]~@[\"~(~a~)\" ~]~ ~@[\":~(~a~)\"~]~@[\"(~(~a~))\"~]" (not firstp) (and (not firstp) (null *eds-include-vacuous-relations-p*)) role (and (ed-p value) (record (ed-id value))) (and (ed-p value) (ed-id value)) (unless *eds-include-vacuous-relations-p* (if (ed-p value) (ed-predicate value) value)) (unless *eds-include-vacuous-relations-p* (and (ed-p value) (ed-carg value)))) (setf firstp nil) finally (format stream "] \"]\"~%")) finally (format stream "] newline \"}\"]~%~ #M[]"))))) (:html (format stream "~%") (format stream "~%") (loop for ed in (eds-relations eds) for lnk = (ed-lnk ed) for properties = (when (and propertiesp (ed-properties ed)) (let* ((string (make-string-output-stream))) (format string "
") (mrs-variable-html (eds-top eds) nil n nil stream) (format stream ":
") (loop with type = (first (ed-properties ed)) with properties = (if (extrapair-p type) (ed-properties ed) (rest (ed-properties ed))) initially (unless (extrapair-p type) (format string "~ " type)) for property in properties do (format string "" (extrapair-feature property) (extrapair-value property))) (format string "
~(~a~) 
~a~ ~(~a~)
") (get-output-stream-string string))) unless (or (ed-bleached-p ed) (ed-vacuous-p ed)) do (format stream " ") (mrs-variable-html (ed-id ed) properties n nil stream) (format stream ":~(~a~)~a" (and lnk (eq (first lnk) :characters)) (second lnk) (third lnk) (ed-predicate ed) (output-lnk (ed-lnk ed) :stream nil :format :html)) (format stream "~@[(~s)~][" (ed-carg ed)) (loop with firstp = t for (role . value) in (ed-arguments ed) when (ed-p value) do (format stream "~:[, ~;~]~a " firstp role) (mrs-variable-html (ed-id value) nil n nil stream) (setf firstp nil)) (format stream "]~%")) (format stream "~%")) (:latex (labels ((variable (id &optional properties) (let ((type (latex-escape-string (subseq id 0 1))) (index (parse-integer id :start 1 :junk-allowed t))) (format nil "\\svar{~a}{~a}{~@[~a~]}" type index properties)))) (loop initially (format stream "\\eds{~a}{~%" (variable (eds-top eds))) for relations on (eds-relations eds) for ed = (first relations) for predicate = (latex-escape-string (ed-predicate ed)) for carg = (and (ed-carg ed) (latex-escape-string (ed-carg ed))) for lnk = (when (ed-lnk ed) (output-lnk (ed-lnk ed) :format :latex :stream nil)) unless (ed-bleached-p ed) do (format stream " \\sep{~a}{\\spred{~a~@[~a~]~@[(~a)~]}}{" (variable (ed-id ed)) predicate lnk carg) (loop for (role . value) in (ed-arguments ed) for firstp = t then nil when (ed-p value) do (format stream "~:[, ~;~% ~]\\srole{~a}{~a}" firstp role (variable (ed-id value)))) (format stream "}~@[~*\\\\~%~]" (rest relations)) finally (format stream "}~%")))) (:amr (when id (format stream "~&# ::id ~a~%" id)) (when input (format stream "~&# ::snt ~a~%" input)) (let ((attic (make-hash-table :test #'eq)) (*package* (find-package :lkb)) (*print-right-margin* columns) (*standard-output* stream)) (labels ((output (ed &optional ignore) (let ((match (gethash ed attic))) (if match (write-string match) (let* ((id (ed-id ed))) (setf (gethash ed attic) id) (write-char #\() (pprint-logical-block (nil nil) (write-string id) (write-string " / ") (if (position #\/ (ed-predicate ed)) (write (ed-predicate ed)) (write-string (ed-predicate ed))) (when (and lnkp (ed-lnk ed)) (write-string " :lnk ") (write (output-lnk (ed-lnk ed) :stream nil))) (when (ed-carg ed) (write-string " :carg ") (write (ed-carg ed))) (loop for (role . value) in (ed-inverse ed) for match = (find role ignore :key #'first) unless (or (not (ed-p value)) (member value %eds-filter%) (eq value (rest match))) do (write-char #\space) (pprint-newline :linear) (write-char #\:) (write role) (write-string "-of ") (output value (cons (cons role ed) ignore))) (loop for (role . value) in (ed-arguments ed) for match = (find role ignore :key #'first) unless (or (not (ed-p value)) (member value %eds-filter%) (eq value (rest match))) do (write-char #\space) (pprint-newline :linear) (write-char #\:) (write role) (write-char #\space) (output value (cons (cons role ed) ignore))) (write-char #\)))))))) (let* ((top (find (eds-top eds) (eds-relations eds) :key #'ed-id :test #'string=))) (when top (output top) (terpri stream) (terpri stream))) (when (and debug (< (hash-table-count attic) (length (eds-relations eds)))) (format debug "eds-output-psoa(): lost in AMR syntax ~@[[~a]~]~%" id)) (loop for ed in (eds-relations eds) unless (gethash ed attic) do (format debug " ~a~%" ed))))) (:dot (loop initially ;; ;; _fix_me_ ;; validate that the top is a valid node? (4-aug-19; oe) ;; (format stream "digraph \"\" {~%") (format stream " root [ style=invis ];~% root -> ~a;~%" (eds-top eds)) finally (format stream "}~%") for ed in (eds-relations eds) for id = (ed-id ed) for predicate = (ed-predicate ed) for carg = (ed-carg ed) do (format stream " ~a [ label=~s ];~%" id (if (and carg (eq cargp :inline)) (format nil "~a(~s)" predicate carg) predicate)) (when (and carg cargp (not (eq cargp :inline))) (format stream " ~a -> ~a [ label=\"CARG\" ];~%" id carg)) (loop for (role . value) in (ed-arguments ed) when (ed-p value) do (format stream " ~a -> ~a [ label=\"~a\" ];~%" id (ed-id value) role)))) (:json (loop initially (format stream "~a{~:[~2*~;\"id\": ~a,~%~a~]~ ~:[~3*~;~:[~; ~]\"input\": ~s,~%~a~]~ ~:[~; ~]\"top\": ~s,~%~a \"nodes\": {~%" prefix (numberp id) id prefix (stringp input) (numberp id) input prefix (or (numberp id) (stringp input)) (eds-top eds) prefix) with last = (first (last (eds-relations eds))) for ed in (eds-relations eds) for predicate = (ed-predicate ed) for carg = (ed-carg ed) for lnk = (when (ed-lnk ed) (output-lnk (ed-lnk ed) :format :json :stream nil)) unless (or (ed-bleached-p ed) (member ed %eds-filter%)) do (format stream "~a ~s: {\"label\": ~s~@[, \"lnk\": ~a~]~@[, \"carg\": ~s~]" prefix (ed-id ed) predicate lnk carg) (when (and *eds-show-properties-p* (ed-properties ed)) (loop with type = (first (ed-properties ed)) with properties = (if (extrapair-p type) (ed-properties ed) (rest (ed-properties ed))) initially (format stream "~@[, \"type\": ~s~], \"properties\": {" (unless (extrapair-p type) type)) finally (format stream "}") for property in properties do (format stream "~:[, ~;~]\"~a\": \"~(~a~)\"" (eq property (first properties)) (extrapair-feature property) (extrapair-value property)))) (loop initially (format stream ", \"edges\": {") for (role . value) in (ed-arguments ed) for firstp = t then nil when (and (ed-p value) (not (member value %eds-filter% :test #'eq))) do (format stream "~:[, ~;~]~s: ~s" firstp (string-upcase role) (ed-id value)) finally (format stream "}}")) (unless (eq ed last) (format stream ",~%")) finally (format stream "}}")))))) (defun eds-variable-html (name properties) (let ((string (make-string-output-stream))) (format string "") (format string "" name) (loop for property in properties do (format string "" (extrapair-feature property) (extrapair-value property))) (format string "
~a
~a~ ~(~a~)
") (get-output-stream-string string))) #+:lkb (defun eds-convert-edge (edge &key (vpm *eds-vpm*) (normalizep *eds-normalize-p*)) (when (lkb::edge-p edge) (let ((mrs (or (lkb::edge-mrs edge) (extract-mrs edge)))) (eds-convert-psoa mrs :vpm vpm :normalizep normalizep)))) (defun eds-convert-psoa (psoa &key (vpm *eds-vpm*) (normalizep *eds-normalize-p*) (filter *eds-predicate-filter*)) (when (psoa-p psoa) (when vpm (setf psoa (mt:map-mrs psoa vpm))) #+:lkb (when (and normalizep (fboundp 'normalize-mrs)) (setf psoa (funcall (symbol-function 'normalize-mrs) (mt::clone-mrs psoa)))) (loop with eds = (make-eds :hcons (psoa-h-cons psoa) :raw psoa) initially (ed-reset) ;; ;; in a first pass through the EPs of the input MRS, create EDS graph ;; nodes, one per EP. these will have their key properties set ;; (predicate, handle, distinguished variable, CARG, LNK, et al.) but ;; not yet contain any outgoing arcs. ;; for relation in (psoa-liszt psoa) for ed = (ed-convert-relation relation psoa) when ed do (push ed (eds-relations eds)) finally (when *eds-include-icons-p* (loop for icons in (psoa-icons psoa) for ed = (ed-convert-icons icons) when ed do (push ed (eds-relations eds)))) (setf (eds-relations eds) (nreverse (eds-relations eds))) ;; ;; next, post-process the EDS graph: first, classify `special' EDs, ;; i.e. messages, quantifiers, and so-called fragment EDs (which are ;; pseudo relations, much like underspecified conjunctions, used in ;; the LOGON NoEn MT system, to glue together XLE fragment analyses). ;; in the case of messages, there is optional support to equate the ;; label of the message with its SOA or MARG argument, i.e. `bleach' ;; the message, in the sense of making it gratuitous for the graph. ;; (eds-bleach-eds eds) ;; ;; next, actually fill in argument arcs: for each role in each ED, ;; find the ED that is assumed to be the `representative' for the ;; value (i.e. MRS variable) of this role; in doing so, consider =q ;; handle constraints as if the top and bottom handle were equated. ;; in the case of multiple candidate representative EDs, various ;; disambiguation heuristics apply, see ed-select-representative(). ;; (eds-augment-eds eds) ;; ;; as a matter of convenience, for example to output in AMR syntax, ;; cache inverse argument relations in each node. ;; (eds-inverse eds) ;; ;; finally, we need to make sure that all EDs end up with unique ;; identifiers, which from here on only serve to uniquely name the ;; nodes of the EDS dependency graph. seeing that, in the initial ;; ED creation phase, ED identifiers were locally determined by the ;; distinguished variable, there is no protection against multiple ;; EDs sharing one distinguished variable (even though, with current ;; versions of the ERG at least, that seems likely only in illformed ;; input MRSs). ;; (eds-uniq-ids eds) ;; ;; finally, determine what should be the root node of the dependency ;; graph: until early 2012, we always used to grab the INDEX, but in ;; a recent moment of clarity, we realized that the root node should ;; be parallel to the structure we create for clause subordination, ;; e.g. pairs like: `[Kim knows that] Abrams probably arrived.' ;; (let* ((ltop (ed-find-representative eds (psoa-top-h psoa))) (index (ed-find-representative eds (psoa-index psoa)))) (setf (eds-top eds) (or (and (ed-p ltop) (ed-id ltop)) (and (ed-p index) (ed-id index)) (and (var-p (psoa-index psoa)) (var-string (psoa-index psoa)))))) ;; ;; we used to only filter (e.g. the pesky discourse relations in 1214) ;; during output, but really when a filter is in effect, these nodes ;; should not be part of the EDS in the first place. ;; (when filter (when (stringp filter) (setf filter (ppcre::create-scanner filter))) (setf (eds-relations eds) (loop for ed in (eds-relations eds) unless (and (ppcre:scan filter (ed-predicate ed)) (null (ed-inverse ed)) (not (string= (ed-id ed) (eds-top eds)))) collect ed))) (ed-reset) (return eds)))) (defun ed-convert-relation (relation mrs) (let (type) (when (and *eds-quantifier-argument* (is-quant-rel relation)) ;; ;; to simplify downstream treatment of quantifiers, make sure the role ;; name label of the bound variable is not ARG0. but avoid destructive ;; changes to our original input; this is potentially hazardous, as the ;; hash of relations to variables (%eds-symbol-table%) now uses a local ;; copy; for all i can tell just now, access to the hash table is within ;; the scope of ed-convert-relation(), however. (15-jun-12; oe) ;; (setf relation (copy-rel relation)) (setf (rel-flist relation) (loop with inherent = (list (vsym "INST") (vsym "ARG0")) for fvpair in (rel-flist relation) when (member (fvpair-feature fvpair) inherent :test #'eq) collect (make-fvpair :feature *eds-quantifier-argument* :value (fvpair-value fvpair)) else collect fvpair)) (setf type :quantifier)) ;; ;; arguably hacky: in the ERG analysis of degree specifiers on quantifiers, ;; say ‘nearly all’, there is no connection other than label identification ;; between the degree specifier and the quantifier. this means there is no ;; connection in terms of an argument relation or actual logical variables, ;; and by default degree specifiers come out unconnected to the dependency ;; graph. our somewhat hand-wavy interpretation of this analysis for some ;; ten years now has been in term of ‘predicate modification’, i.e. roughly ;; like ‘nearly(all)[x, h1, h2]’. one may think the degree specifier should ;; take the (label of the) quantifier as its ARG1, but that structure cannot ;; be scope-resolved within current assumptions. hence, mimic that argument ;; relation on the degree specifier in EDS conversion. (8-feb-14; oe) ;; (when *eds-predicate-modifiers* (let* ((predicate (when (rel-pred relation) (string-downcase (string (rel-pred relation))))) (arg1 (loop with arg1 = (vsym "ARG1") for argument in (rel-flist relation) when (eq (fvpair-feature argument) arg1) return argument))) (when (and predicate (loop for pattern in *eds-predicate-modifiers* thereis (if (functionp pattern) (ppcre:scan pattern predicate) (ed-compare-predicates predicate pattern :type :subsumption))) (or (null arg1) (and arg1 (var-p (fvpair-value arg1)) (string-equal (var-type (fvpair-value arg1)) "u")))) (let* ((label (rel-handel relation))) ;; ;; _fix_me_ ;; as pointed out by mike on the 'developers' list (15-jul-18), ;; there can be more than predication involved, as for example in ;; |quite nearly all] [chairs arrived]. maybe extract the full set ;; and prefer quantifiers as the target of predicate modification? ;; or maybe just stack them in surface order, but avoid cyclic ;; modifications, e.g. quite(nearly)(all)[...] for the above? ;; (1-aug-20; oe) (when (loop for relation in (psoa-liszt mrs) thereis (eq (rel-handel relation) label)) (setf relation (copy-rel relation)) (setf (rel-flist relation) (if arg1 (loop with arg1 = (vsym "ARG1") for argument in (rel-flist relation) when (eq (fvpair-feature argument) arg1) collect (make-fvpair :feature arg1 :value label) else collect argument) (append (rel-flist relation) (list (make-fvpair :feature (vsym "ARG1") :value label))))) (setf type :specifier)))))) (let* ((*package* (find-package :lkb)) (handle (let ((handle (rel-handel relation))) (when (ed-handle-p handle) (var-string handle)))) (identifier (ed-find-identifier relation)) (predicate (when (rel-pred relation) (string-downcase (string (rel-pred relation))))) (abstraction #+:cl-ppcre (when predicate (cond ((ppcre:scan "_a(?:_[^_]+)?_rel" predicate) :a) ((ppcre:scan "_n(?:_[^_]+)?_rel" predicate) :n) ((ppcre:scan "_p(?:_[^_]+)?_rel" predicate) :p) ((ppcre:scan "_q(?:_[^_]+)?_rel" predicate) :q) ((ppcre:scan "_v(?:_[^_]+)?_rel" predicate) :v) (t :x)))) (predicate (if (and predicate (stringp *sem-relation-suffix*)) (remove-right-sequence (string-downcase *sem-relation-suffix*) predicate) predicate)) (flist (rel-flist relation)) (carg (loop with carg = (list (vsym "CARG") (vsym "NAMED") (vsym "CONST_VALUE")) for fvpair in flist when (member (fvpair-feature fvpair) carg :test #'eq) return (fvpair-value fvpair))) (lnk (let* ((lnk (rel-lnk relation)) (from (rel-cfrom relation)) (to (rel-cto relation))) (or lnk (and (numberp from) (numberp to) (>= from 0) (>= to 0) (list :characters from to)))))) (make-ed :handle handle :id (first identifier) :properties (rest identifier) :predicate predicate :lnk lnk :carg carg :abstraction abstraction :type type :raw relation)))) (defun ed-convert-icons (icons) (let* ((id (format nil "_~d" (incf %eds-variable-counter%))) (predicate (icons-relation icons)) (predicate (cond ((string-equal predicate "topic") ">t") ((string-equal predicate "non-topic") "!t") ((string-equal predicate "focus") ">f") ((string-equal predicate "non-focus") "!f") (predicate (string-downcase predicate))))) (make-ed :id id :predicate predicate :type :icons :raw icons))) (defun eds-bleach-eds (eds) (loop for ed in (eds-relations eds) when (and (null (ed-type ed)) (ed-message-p ed)) do (let* ((handle (ed-handle ed)) (relation (ed-raw ed)) (soa (loop with soa = (list (vsym "SOA") (vsym "MARG")) for fvpair in (rel-flist relation) thereis (when (member (fvpair-feature fvpair) soa :test #'eq) (fvpair-value fvpair))))) (when (ed-handle-p soa) (setf (gethash handle %eds-equivalences%) soa)) (setf (ed-type ed) :message)) when (and (null (ed-type ed)) (ed-quantifier-p ed)) do (setf (ed-type ed) :quantifier) when (and (null (ed-type ed)) (ed-fragment-p ed)) do (setf (ed-type ed) :fragment))) (defun eds-augment-eds (eds) (loop for ed in (eds-relations eds) when (eq (ed-type ed) :icons) do (let* ((icons (ed-raw ed)) (left (ed-find-representative eds (icons-iarg1 icons))) (right (ed-find-representative eds (icons-iarg2 icons)))) (when (ed-p right) (push (cons (vsym "LO") right) (ed-arguments ed))) (when (ed-p left) (push (cons (vsym "HI") left) (ed-arguments ed)))) unless (or (ed-bleached-p ed) (eq (ed-type ed) :icons)) do (loop with old = (vsym "CONST_VALUE") with relation = (ed-raw ed) with flist = (rel-flist relation) for feature in %eds-relevant-features% for key = (vsym feature) for filter = (rest (assoc key *eds-argument-filter*)) for match = (when filter (let* ((match (find (first filter) flist :key #'fvpair-feature)) (value (and match (fvpair-value match)))) (when value (ppcre:scan (second filter) (var-string value))))) for value = (loop for fvpair in flist thereis (when (eq (fvpair-feature fvpair) key) (fvpair-value fvpair))) for representative = (when (and value (not match)) (ed-find-representative eds value ed)) when representative do (when (eq key old) (setf key (vsym "CARG"))) #+:null (when (ed-p representative) (setf (ed-variable representative) value)) (push (cons key representative) (ed-arguments ed))) (setf (ed-arguments ed) (nreverse (ed-arguments ed))))) (defun eds-inverse (eds) (loop for ed in (eds-relations eds) do (loop for (role . value) in (ed-arguments ed) when (ed-p value) do (push (cons role ed) (ed-inverse value))))) (defun eds-uniq-ids (eds) (loop for ed in (eds-relations eds) for id = (ed-id ed) for collisions = (loop for ed in (eds-relations eds) when (equal (ed-id ed) id) collect ed) when (rest collisions) do (let ((representative (loop for foo being each hash-key in %eds-representatives-table% when (and (var-p foo) (string= (var-string foo) id)) return (gethash foo %eds-representatives-table%)))) (when representative (setf collisions (delete representative collisions))) (setf collisions (sort collisions #'> :key #'(lambda (ed) (length (ed-arguments ed))))) (loop for ed in (if representative collisions (rest collisions)) do (setf (ed-id ed) (format nil "_~d" (incf %eds-variable-counter%))))))) (defun ed-find-identifier (relation) (or (gethash relation %eds-symbol-table%) (let* ((flist (and (rel-p relation) (rel-flist relation))) instance event) (loop for fvpair in flist for feature = (fvpair-feature fvpair) when (eq feature (vsym "EVENT")) do (setf event (fvpair-value fvpair)) when (unless (ed-message-p relation) (or (eq feature (vsym "INST")) (eq feature (vsym "ARG0")) (eq feature (vsym "C-ARG")))) do (setf instance (fvpair-value fvpair))) (let* ((variable (or (and (var-p instance) instance) (and (var-p event) event))) (name (if variable (var-string variable) (format nil "_~d" (incf %eds-variable-counter%)))) (type (and variable (var-type variable))) (properties (and variable (cons type (var-extra variable))))) (setf (gethash relation %eds-symbol-table%) (cons name properties)))))) (defun eds-arguments (node eds &key (type :all) transitive cache) ;; ;; _fix_me_ ;; this function (like a few others, no doubt), is a little funny in that it ;; conceptually operates over the MRS, still, but needs to take advantage of ;; some of the notions used in conversion to EDS, notably the identification ;; of the distinguished variable and interpretation of =q handle constraints ;; as a kind of equality. review more thoroughly, one day. (2-jul-16; oe) ;; (loop for fvpair in (unless (eq (ed-type node) :icons) (rel-flist (ed-raw node))) for value = (fvpair-value fvpair) for id = (and (var-p value) (var-string value)) when (and id (not (string= id (ed-id node)))) append (loop with cache = (cons node cache) with key = (if (is-handel-var value) (and (member type '(:handle :all) :test #'eq) #'ed-handle) (and (member type '(:variable :all) :test #'eq) #'ed-id)) with qeq = (when (and (is-handel-var value) (member type '(:handle :all) :test #'eq)) (let ((qeq (ed-hcons-qeq eds value))) (and (is-handel-var qeq) (var-string qeq)))) for ed in (eds-relations eds) when (and (not (eq node ed)) key (or (string= id (funcall key ed)) (and qeq (string= qeq (ed-handle ed))))) append (cons ed (when (and transitive (not (member node (rest cache) :test #'eq))) (eds-arguments ed eds :type type :transitive transitive :cache cache)))))) (defun ed-find-representative (eds variable &optional functor) (or (gethash variable %eds-representatives-table%) (setf (gethash variable %eds-representatives-table%) (cond ((ed-handle-p variable) (loop with alternate = (ed-variable-equivalence variable) with name = (var-string (or alternate variable)) with qeq = (ed-hcons-qeq eds name) for ed in (eds-relations eds) for handle = (ed-handle ed) when (and (not (ed-bleached-p ed)) (or (equal name handle) (and (ed-handle-p qeq) (equal (var-string qeq) handle))) (not (eq (ed-type ed) :specifier))) collect ed into candidates finally (return (if (and candidates (null (rest candidates))) (first candidates) (ed-select-representative eds (append (when (ed-handle-p qeq) (ed-find-representative qeq nil)) (when (var-p alternate) (ed-find-representative alternate nil)) candidates) functor))))) ((var-p variable) (loop with foo = (var-string variable) with name = (let ((bar (ed-variable-equivalence foo))) (if bar (var-string bar) foo)) for ed in (eds-relations eds) for id = (unless (or (ed-bleached-p ed) (ed-quantifier-p ed)) (ed-id ed)) when (equal name id) collect ed into candidates finally (return (if (and candidates (null (rest candidates))) (first candidates) (ed-select-representative eds candidates functor))))) ((stringp variable) variable))))) (defun ed-select-representative (eds candidates functor &key (debug *eds-debug-p*)) ;; ;; given a set of candidate representatives (typically corresponding to EPs ;; within one LF conjunct, i.e. sharing the same handle), heuristically find ;; one to stand in for the conjunction: give preference to EPs with a `real' ;; intrinsic variable (i.e. ones introducing an event or instance variable) ;; or look at the dependency topology among the candidate EPs and choose one ;; that occurs as an argument to the other(s). ;; (labels ((filter (predicate list) (let ((result (remove-if predicate list))) #+:null (format t "filter(): ~a --> ~a~%" predicate result) (if (null result) list result))) (modifierp (node eds) (loop for fvpair in (rel-flist (ed-raw node)) for value = (fvpair-value fvpair) for id = (and (var-p value) (var-string value)) thereis (when (and id (not (string= id (ed-id node)))) (loop for ed in eds thereis (unless (eq ed node) (string= id (ed-id ed))))))) (argumentp (node target) (loop for fvpair in (rel-flist (ed-raw node)) for value = (fvpair-value fvpair) for id = (and (var-p value) (var-string value)) thereis (and id (not (string= id (ed-id node))) (string= id (ed-id target))))) (report (context candidates) (when debug (format t "ed-select-representative(): ~a after ~a filter:~% ~w~%~%" (length candidates) context candidates)))) ;; ;; a singleton .candidates. set presents a trivial selection problem ;; (when (null (rest candidates)) (return-from ed-select-representative (first candidates))) ;; ;; if we were ‘bleaching’ select nodes, these cannot be representatives ;; (report :no candidates) (setf candidates (filter #'ed-bleached-p candidates)) (report :bleach candidates) (when (null (rest candidates)) (return-from ed-select-representative (first candidates))) ;; ;; dis-prefer any candidates that are on the grammar-specific `black list’ ;; (setf candidates (filter #'ed-non-representative-p candidates)) (report :non-representative candidates) (when (null (rest candidates)) (return-from ed-select-representative (first candidates))) ;; ;; the following two disambiguation attempts are only historically relevant, ;; dis-preferring messages and nodes whose identifier was synthesized (i.e. ;; where there was no distinguished variable available, or it was shared ;; with another node, who got to own it); for the special case of predicate ;; modifiers (a .functor. of type :specifier), these will often look for a ;; quantifier, hence disable disambiguation heuristics that would disprefer ;; quantifiers. ;; (setf candidates (filter #'ed-message-p candidates)) (report :message candidates) (when (null (rest candidates)) (return-from ed-select-representative (first candidates))) (unless (and functor (eq (ed-type functor) :specifier)) (setf candidates (filter #'(lambda (ed) (char= (char (ed-id ed) 0) #\_)) candidates)) (report :identifier candidates) (when (null (rest candidates)) (return-from ed-select-representative (first candidates)))) ;; ;; also mostly in the spirit of historic backwards compatibility (where we ;; never expect to trigger this case, actually), dis-prefer quantifiers. ;; (unless (and functor (eq (ed-type functor) :specifier)) (setf candidates (filter #'(lambda (ed) (eq (ed-type ed) :quantifier)) candidates)) (report :quantifier candidates) (when (null (rest candidates)) (return-from ed-select-representative (first candidates)))) ;; ;; as a general policy, try to avoid cyclic dependencies, i.e. do not pick ;; a representative that has .functor. among its own arguments. ;; (when functor (let ((acycles (filter #'(lambda (ed) (argumentp ed functor)) candidates))) (report :cycles acycles) (when (null (rest acycles)) (return-from ed-select-representative (first acycles))) (when acycles (setf candidates acycles)))) ;; ;; the most common cause of one-to-many correspondences between a variable ;; and a set of EPs are handles shared with (intersective) modifiers, e.g. ;; in a structure like [believe that] `she arrived very quickly.' here, ;; the degree specifier is an intersective modifier on the adverb, which in ;; turn is an intersective modifier on the arriving event; thus, all three ;; share one label, and `arrive' is the ARG1 of `quickly', which is the ;; ARG1 of `very'. to pick out `arrive' in this scenario, dis-prefer any ;; predications that take other .candidates. elements as their arguments. ;; (setf candidates (filter #'(lambda (ed) (modifierp ed candidates)) candidates)) (report :modifier candidates) (when (null (rest candidates)) (return-from ed-select-representative (first candidates))) ;; ;; as part of the grand unification of EDS implementations (with bec and ;; mike, mid-2016), we found that non-scopal modifiers can also attach to ;; scopal modifiers, e.g. in csli/795: ‘browne merely doesn't work.’ at ;; first, we explored whether linkage counts into or out of the candidate ;; nodes might suffice to prefer the scopal modifier over the non-scopal ;; one (i.e. make predicate–argument dominance relations mirror the scope ;; hierarchy), but that turns out to not differentiate in this case at all. ;; hence, we need to detect ‘merely’ as a non-scopal modifier on one of the ;; scopal arguments of ‘neg’, and in principle that argument relation could ;; be transitively nested, of course. ;; ;; _fix_me_ ;; in this form, the code below can trigger on a non-empty intersection of ;; the two sets of descendants for the same predication; probably, we need ;; to guard against that. (2-jul-16; oe) ;; (let* ((scopal (loop for ed in candidates collect (eds-arguments ed eds :type :handle :transitive t))) (predicate #'(lambda (ed) (loop with arguments = (eds-arguments ed eds :type :variable :transitive nil) for targets in scopal thereis (intersection arguments targets :test #'eq))))) (setf candidates (filter predicate candidates))) (report :scopal candidates) (when (null (rest candidates)) (return-from ed-select-representative (first candidates))) ;; ;; in 1214 at least, mrs/991 (‘there were cats in the garden’) has ‘in_p’ ;; share its label with the existential be, but the external argument of ;; the preposition actually is the instance variable of ‘cats’. not sure ;; this really is the intended (or correct) analysis, as it fails to give a ;; parallel structure for ‘in the garden, there were cats’. but either ;; way, to pick the existential ‘be’ over the preposition, we need to ;; dis-prefer untensed events. ;; (setf candidates (filter #'ed-untensed-p candidates)) (report :untensed candidates) (when (null (rest candidates)) (return-from ed-select-representative (first candidates))) ;; ;; from here on, we are grasping at straws (and likely looking at input ;; structures that are not perfectly well-formed). if there still is a ;; need for disambiguation at this point, we used to give preference to ;; nodes that have more incoming links, i.e. are more connected to the ;; structure at large (as arguments), and then (if need) consider outgoing ;; link counts as a final tie-breaker. ;; (report :final candidates) (first candidates) #+:null (labels ((referrers (target candidates) (loop with id = (ed-id target) for ed in candidates for flist = (unless (eq ed target) (rel-flist (ed-raw ed))) unless (ed-bleached-p ed) when (loop for fvpair in flist for value = (fvpair-value fvpair) thereis (when (var-p value) (equal (var-string value) id))) append (cons ed (referrers ed (remove target candidates))))) (incoming (candidates &optional (eds candidates)) (let ((referrers (loop for candidate in candidates for referrers = (referrers candidate eds) when referrers collect (cons (length referrers) candidate)))) (setf referrers (sort referrers #'> :key #'first)) (loop with n = (first (first referrers)) for referrer in referrers while (= (first referrer) n) collect (rest referrer)))) (outgoing (candidates) (let ((references (loop for candidate in candidates for n = (length (ed-arguments candidate)) collect (cons n candidate)))) (setf references (sort references #'> :key #'first)) (loop with n = (first (first references)) for reference in references while (= (first reference) n) collect (rest reference)))))))) (defun ed-handle-p (variable) (when (var-p variable) (is-handel-var variable))) (defun ed-hcons-qeq (eds handle) (loop with name = (if (stringp handle) handle (when (ed-handle-p handle) (var-string handle))) for hcons in (eds-hcons eds) for scarg = (when (string-equal (hcons-relation hcons) "qeq") (var-string (hcons-scarg hcons))) thereis (when (equal name scarg) (hcons-outscpd hcons)))) (defun ed-variable-equivalence (variable) (if (var-p variable) (gethash (var-string variable) %eds-equivalences%) (gethash variable %eds-equivalences%))) (defun ed-quantifier-p (ed) (or (eq (ed-type ed) :quantifier) (let* ((raw (ed-raw ed)) (flist (and raw (psoa-p raw) (rel-flist raw)))) (find *scope-feat* flist :key #'fvpair-feature)) (let ((pred (ed-predicate ed))) (and (stringp pred) (string= (subseq pred (- (length pred) 2)) "_q"))))) (defun ed-compare-predicates (predicate1 predicate2 &key (type :equivalence)) (if *normalize-predicates-p* (let ((predicate1 (normalize-predicate predicate1)) (predicate2 (normalize-predicate predicate2))) (or (string= predicate1 predicate2) (when (eq type :subsumption) (mt:semi-compare-predicates predicate1 predicate2 :type type)))) (or (eq predicate1 predicate2) (when (and (stringp predicate1) (stringp predicate2)) (string-equal predicate1 predicate2)) (when (eq type :subsumption) (let ((type1 (vsym predicate1)) (type2 (vsym predicate2))) (or (eq type1 type2) (when (and (is-valid-type type1) (is-valid-type type2)) (ignore-errors (mrs:equal-or-subtype type1 type2))))))))) (defun ed-message-p (thing) (when *eds-message-relation* (typecase thing (ed (let ((predicate (ed-predicate thing))) (or (eq (ed-type thing) :message) (ed-compare-predicates predicate *eds-message-relation* :type :subsumption) (and (ed-raw thing) (ed-message-p (ed-raw thing))) (string= (subseq predicate (- (length predicate) 2)) "_m")))) (rel (let ((predicate (rel-pred thing))) (or (eq predicate *eds-message-relation*) (when (stringp predicate) (search "_m_rel" predicate)) (ignore-errors (equal-or-subtype predicate *eds-message-relation*)))))))) (defun ed-untensed-p (properties) (if (ed-p properties) (ed-untensed-p (ed-properties properties)) (loop for pair in properties for property = (and (extrapair-p pair) (extrapair-feature pair)) for test = (and property (rest (assoc property *eds-untensed*))) when test return (eq (extrapair-value pair) test) ;; ;; also, consider variables untensed when there is no TENSE property ;; finally (return t)))) (defun ed-fragment-p (ed) (when *eds-fragment-relation* (or (eq (ed-type ed) :fragment) (ed-compare-predicates (ed-predicate ed) *eds-fragment-relation* :type :subsumption) (let ((pred (and (rel-p (ed-raw ed)) (rel-pred (ed-raw ed))))) (when pred (ignore-errors (equal-or-subtype pred (vsym *eds-fragment-relation*)))))))) (defun ed-bleached-p (ed) (or (and (null *eds-include-messages-p*) (eq (ed-type ed) :message)) (and (null *eds-include-quantifiers-p*) (eq (ed-type ed) :quantifier)) (when (and *eds-bleached-relations* (not (eq (ed-type ed) :icons))) (loop with predicate = (ed-predicate ed) with pred = (and (rel-p (ed-raw ed)) (rel-pred (ed-raw ed))) for relation in *eds-bleached-relations* thereis (or (ed-compare-predicates predicate relation :type :subsumption) (ignore-errors (equal-or-subtype pred (vsym relation)))))))) (defun ed-non-representative-p (ed) (when (and *eds-non-representatives* (not (eq (ed-type ed) :icons))) (loop with predicate = (ed-predicate ed) with pred = (and (rel-p (ed-raw ed)) (rel-pred (ed-raw ed))) for relation in *eds-non-representatives* thereis (or (ed-compare-predicates predicate relation :type :subsumption) (ignore-errors (equal-or-subtype pred (vsym relation))))))) (defun ed-vacuous-p (ed) (unless *eds-include-vacuous-relations-p* (unless (and *eds-include-quantifiers-p* (eq (ed-type ed) :quantifier)) (or (null (ed-arguments ed)) (and (null (rest (ed-arguments ed))) (eq (first (first (ed-arguments ed))) (vsym "CARG"))))))) (defun eds-suspicious-p (eds) (append (when (eds-cyclic-p eds) '(:cyclic)) (when (eds-fragmented-p eds) '(:fragmented)))) (defun eds-cyclic-p (eds) (loop with return = nil for ed in (eds-relations eds) unless (or (ed-bleached-p ed) (null (ed-arguments ed))) do (unless (ed-walk ed) (pushnew :cyclic (ed-status ed)) (setf return t)) finally (when return (pushnew :cyclic (eds-status eds))) (return return))) (defun ed-walk (ed &optional (start (list (ed-id ed)) startp)) ;; ;; _fix_me_ ;; on certain platforms (notably Linux), Allegro CL 6.2 will fail to detect a ;; stack overflow in (certain) recursive functions; this was pointed out to ;; Franz as [spr27625] in apr-03, and soon my favourite tech person, Lois ;; Wolf, suggested to make those functions not-inline. (19-may-03; oe) ;; #+:null (declare (notinline ed-walk)) ;; ;; _fix_me_ ;; why not use the actual ED structures, to protect against cycles? ;; (25-feb-12; oe) (unless (and startp (member (ed-id ed) start)) (loop for (role . value) in (unless (ed-bleached-p ed) (ed-arguments ed)) when (ed-p value) do (setf role role) (unless (ed-walk value (adjoin (ed-id ed) start)) (return nil)) finally (return t)))) (defun eds-fragmented-p (eds) (let ((mark (gensym)) (agenda (loop with top = (eds-top eds) for ed in (eds-relations eds) when (string= (ed-id ed) top) collect ed))) ;; ;; put .mark. on all EDs that are `reachable' from the top variable ;; (loop for ed = (pop agenda) for id = (and ed (ed-id ed)) while ed do (unless (or (eq (ed-mark ed) mark) (ed-bleached-p ed)) (setf (ed-mark ed) mark) ;; ;; put all arguments of current ED on agenda (for future marking) ;; (loop for argument in (ed-arguments ed) when (ed-p (rest argument)) do (push (rest argument) agenda)) ;; ;; also, add all EDs for which the current one is an argument, i.e. ;; the inverse link. ;; (loop for ed in (eds-relations eds) unless (or (eq (ed-mark ed) mark) (ed-bleached-p ed)) do (loop for argument in (ed-arguments ed) when (and (ed-p (rest argument)) (equal (ed-id (rest argument)) id)) do (push ed agenda))))) (loop with return = nil for ed in (eds-relations eds) when (and (not (ed-bleached-p ed)) (not (eq (ed-mark ed) mark))) do (pushnew :fragmented (ed-status ed)) (setf return t) else when (eq (ed-mark ed) mark) do (setf (ed-status ed) (delete :fragmented (ed-status ed))) finally (loop for ed in (eds-relations eds) when (eq (ed-type ed) :fragment) do (setf return nil)) (when return (pushnew :fragmented (eds-status eds))) (return return)))) (defun eds-explode (eds &key (lnkp *eds-show-lnk-p*) (propertiesp *eds-show-properties-p*) (cargp t) collocationp tagp abstractp) ;; ;; _fix_me_ ;; to be more informative, particularly when predicates occur more than once ;; in an MRS, this would have to include characterization. (23-jan-04; oe) ;; --- or another way of linking to surface positions, e.g. the new LNK set ;; of surface token identifiers (in the VM and YY spirit :-). ;; ;; ;; _fix_me_ ;; not sure what the `variable' slot was intended for, but it appears to be ;; exclusively used in eds-explode(); make sure all EDs have a correct value. ;; (26-nov-04; oe) (loop with key = (vsym "ARG0") for ed in (eds-relations eds) for raw = (ed-raw ed) for roles = (and (rel-p raw) (rel-flist raw)) for arg0 = (loop for role in roles when (eq (fvpair-feature role) key) return (fvpair-value role)) do (setf (ed-variable ed) arg0)) (nconc (loop for ed in (eds-relations eds) for functor = (format nil "~a~@[(~a)~]" (ed-linked-predicate ed :lnkp lnkp) (and cargp (ed-carg ed))) unless (and (null (ed-status ed)) (or (ed-bleached-p ed) (ed-vacuous-p ed))) nconc (nconc ;; ;; _fix_me_ ;; this is getting somewhat baroque: we want a way of including ;; quantifiers in this list, jointly with the EP introducing the ;; variable bound by the quantifier. (13-aug-04; oe) ;; (when (ed-quantifier-p ed) (let* ((target (loop with id = (ed-id ed) for ed in (eds-relations eds) when (and (equal (ed-id ed) id) (not (ed-quantifier-p ed))) return ed)) (result (when target (list functor (vsym "ARG0") (format nil "~a~@[(~a)~]" (ed-linked-predicate target :lnkp lnkp) (and cargp (ed-carg target)))))) (abstraction (when (and target abstractp) (ed-linked-abstraction target :lnkp lnkp))) (abstraction (when abstraction (list functor (vsym "ARG0") abstraction)))) (nconc (when result (list (append (and tagp '(:ep)) result))) (when abstraction (list (append (and tagp '(:ap)) abstraction)))))) (loop for (role . value) in (ed-arguments ed) when (and (null tagp) (ed-p value)) collect (let ((argument (format nil "~a~@[(~a)~]" (ed-linked-predicate value :lnkp lnkp) (and cargp (ed-carg value))))) (list functor role argument)) into eps when (and abstractp (null tagp) (ed-p value) (ed-abstraction value)) collect (let ((argument (format nil "~a~@[(~a)~]" (ed-linked-abstraction value :lnkp lnkp) (and cargp (ed-carg value))))) (list functor role argument)) into abstractions when (and tagp (ed-p value)) nconc (let ((argument (format nil "~a~@[(~a)~]" (ed-linked-predicate value :lnkp lnkp) (and cargp (ed-carg value))))) (list role argument)) into eps when (and abstractp tagp (ed-p value) (ed-abstraction value)) nconc (let ((argument (format nil "~a~@[(~a)~]" (ed-linked-abstraction value :lnkp lnkp) (and cargp (ed-carg value))))) (list role argument)) into abstractions finally (return (nconc (if tagp (and eps (list (append (list :ep functor) eps))) eps) (if tagp (and abstractions (list (append (list :ap functor) abstractions))) abstractions)))))) (when propertiesp (loop for ed in (eds-relations eds) unless (or (and (null (ed-status ed)) (ed-bleached-p ed)) (ed-quantifier-p ed) (ed-message-p ed) (not (var-p (ed-variable ed)))) nconc (loop with id = (format nil "~a~@[(~a)~]" (ed-linked-predicate ed :lnkp lnkp) (and cargp (ed-carg ed))) with ad = (ed-linked-abstraction ed :lnkp lnkp) for extra in (var-extra (ed-variable ed)) for value = (format nil "~(~a~)" (extrapair-value extra)) when (and (null tagp) value) collect (list id (extrapair-feature extra) value) into indices when (and abstractp ad (null tagp) value) collect (list ad (extrapair-feature extra) value) into abstractions when (and tagp value) nconc (list (extrapair-feature extra) value) into indices when (and abstractp ad tagp value) nconc (list (extrapair-feature extra) value) into abstractions finally (return (nconc (if tagp (and indices (list (append (list :index id) indices))) indices) (if tagp (and abstractions (list (append (list :andex ad) abstractions))) abstractions)))))) (when collocationp (loop with functors = (loop for ed in (eds-relations eds) for functor = (format nil "~a~@[(~a)~]" (ed-linked-predicate ed :lnkp lnkp) (and cargp (ed-carg ed))) for abstraction = (ed-linked-abstraction ed :lnkp lnkp) unless (and (null (ed-status ed)) (ed-bleached-p ed)) collect (cons functor abstraction) into functors finally (return (sort functors #'string< :key #'first))) for functor in functors for i from 1 nconc (loop for match in (nthcdr i functors) collect (list (first functor) (first match)) when (and abstractp (rest functor)) collect (list (rest functor) (first match)) when (and abstractp (rest match)) collect (list (first functor) (rest match)) when (and abstractp (rest functor) (rest match)) collect (list (rest functor) (rest match))))))) (defun ed-reset () (setf %eds-variable-counter% 0) (unless *eds-debug-p* (clrhash %eds-symbol-table%) (clrhash %eds-representatives-table%) (clrhash %eds-equivalences%))) (defun ed-flag (eds &optional ed &key (up t) (down t) n top bottom (mark (gensym ""))) (loop for ed in (eds-relations eds) do (setf (ed-mark ed) nil)) (labels ((prefixp (prefix list &key (test #'eql)) (loop for foo in prefix for bar in list always (funcall test foo bar)))) (let* ((ed (or ed (find (eds-top eds) (eds-relations eds) :key #'ed-id :test #'string=))) (agenda (and ed (list (list ed))))) ;; ;; put .mark. on all EDs that are `reachable' from the top-level .ed. ;; (loop with *package* = (find-package :lkb) for bucket = (pop agenda) for current = (first bucket) for path = (rest bucket) while bucket unless (or (ed-bleached-p current) (when (eq (first (ed-mark current)) mark) (member path (rest (ed-mark current)) :test #'prefixp))) do #-:null (format t "~a [~{~a~^ ~}] <-- ~a~%" (ed-predicate current) (rest (ed-mark current)) path) (if (ed-mark current) (push path (rest (ed-mark current))) (setf (ed-mark current) (list mark path))) when (and down (or (null n) (<= (length path) n)) (not (and bottom (funcall bottom eds current)))) do ;; ;; put all arguments of current ED on agenda (for future marking) ;; (loop for (role . value) in (ed-arguments current) when (and (ed-p value) (not (and (consp (first path)) (eq role (first (first path)))))) do (push (cons value (cons role path)) agenda)) when (and up (or (null n) (<= (length path) n)) (not (and top (funcall top eds current)))) do ;; ;; also, add all EDs for which the current one is an argument, i.e. ;; the inverse link. ;; (loop for ed in (eds-relations eds) unless (or (ed-bleached-p ed) (eq (first (ed-mark ed)) mark)) do (loop for (role . value) in (ed-arguments ed) when (eq value current) do (push (cons ed (cons (list role) path)) agenda))))) (loop for ed in (eds-relations eds) when (eq (first (ed-mark ed)) mark) do (setf (rest (ed-mark ed)) (loop for path in (rest (ed-mark ed)) collect (reverse path)))) eds)) (defun ed-heads (eds ed &optional (type :direct)) (let ((mark (gensym ""))) (ed-flag eds ed :up t :down nil :n (and (eq type :direct) 1) :mark mark) (loop for ed in (eds-relations eds) when (and (consp (ed-mark ed)) (eq (first (ed-mark ed)) mark)) collect ed))) (defun ed-dependents (eds ed &optional (type :direct)) (let ((mark (gensym ""))) (ed-flag eds ed :up nil :down t :n (and (eq type :direct) 1) :mark mark) (loop for ed in (eds-relations eds) when (and (consp (ed-mark ed)) (eq (first (ed-mark ed)) mark)) collect ed))) (defun dmrs-explode (dmrs) (labels ((label (id) (loop for node in (dmrs-nodes dmrs) when (= (dmrs-node-id node) id) return (let* ((pred (dmrs-node-pred node)) (pred (if (realpred-p pred) (format nil "_~a_~a~@[_~a~]" (realpred-lemma pred) (realpred-pos pred) (realpred-sense pred)) (ppcre::regex-replace "_rel$" pred ""))) (from (dmrs-node-cfrom node)) (to (dmrs-node-cto node)) (carg (dmrs-node-carg node))) (format nil "~a~:[~*~*~;<~a:~a>~]~@[(~a)~]" pred (and from to) from to carg))))) (loop with rstr = (vsym "RSTR") with arg0 = (vsym "ARG0") for link in (dmrs-links dmrs) for from = (label (dmrs-link-from link)) for to = (label (dmrs-link-to link)) for role = (let* ((role (dmrs-link-pre link)) (role (cond ((null role) (vsym (format nil "/~a" (dmrs-link-post link)))) ((stringp role) (vsym role)) (t role)))) (if (eq role rstr) arg0 role)) when role collect (list from role to)))) (defun eds-to-mrs (eds &key semi (errorp t)) (declare (special mt:*semis*)) (unless (mt:semi-p semi) (setf semi (first mt:*semis*))) (unless (mt:semi-p semi) (mt:construct-semi) (unless (mt:semi-p (setf semi (first mt:*semis*))) (if errorp (error "eds-to-mrs(): unable to locate or construct a SEM-I.") (return-from eds-to-mrs)))) (let ((mrs (make-psoa)) (variables (make-hash-table :test #'eq)) (relations (make-hash-table :test #'eq)) (generator (create-variable-generator)) (bv (vsym "BV")) (arg0 (vsym "ARG0"))) (labels ((quantifierp (ed) (member (vsym "BV") (ed-arguments ed) :key #'first)) (canonical-role (label) (cond ((eq label bv) arg0) (t label))) (variable (&optional (type "u") properties) (make-var :type type :id (funcall generator) :extra properties))) (setf (psoa-top-h mrs) (variable "h")) (loop for node in (eds-relations eds) for predicate = (ed-predicate node) for sps = (mt:semi-lookup :semi semi :predicate predicate :alias predicate) for lnk = (ed-lnk node) for properties = (ed-properties node) for arguments = (ed-arguments node) when sps do (let* ((synopses (mt::sps-synopses sps)) (synopsis (first synopses)) (pred (and synopses (mt::ep-pred synopsis))) (variable (or (gethash node variables) (setf (gethash node variables) (let* ((type (first properties)) (type (unless (extrapair-p type) type))) (variable (or type "i") (if type (rest properties) properties)))))) (roles (unless (quantifierp node) (list (make-fvpair :feature (vsym "ARG0") :value variable)))) (relation (make-rel :handel (variable "h") :pred pred :lnk lnk :flist roles))) (when (ed-carg node) (push (make-fvpair :feature (vsym "CARG") :value (ed-carg node)) (rel-flist relation))) (loop for (dependency . value) in arguments when (ed-p value) do (let* ((variable (or (gethash value variables) (setf (gethash value variables) (let* ((properties (ed-properties value)) (type (first properties)) (type (unless (extrapair-p type) type))) (variable (or type "i") (if type (rest properties) properties)))))) (role (make-fvpair :feature (canonical-role dependency) :value (or variable value)))) (push role (rel-flist relation)))) (setf (gethash node relations) relation) (setf (rel-flist relation) (sort (rel-flist relation) #'(lambda (foo bar) (let ((foo (position foo *feat-priority-list*)) (bar (position bar *feat-priority-list*))) (if foo (if bar (< foo bar) t) bar))) :key #'fvpair-feature)) (push relation (psoa-liszt mrs))) else do (if errorp (error "eds-to-mrs(): invalid predicate ‘~a’." predicate) (return-from eds-to-mrs))) (let* ((top (find (eds-top eds) (eds-relations eds) :key #'ed-id :test #'string=)) (index (or (gethash top variables) (variable "i")))) (when top (let* ((larg (rel-handel (gethash top relations))) (qeq (make-hcons :relation "QEQ" :scarg (psoa-top-h mrs) :outscpd larg))) (push qeq (psoa-h-cons mrs)))) (setf (psoa-index mrs) index)) mrs))) (defun eds-read (file &key decoder) (cond (decoder (multiple-value-bind (stream foo pid) (run-process decoder :wait nil :input file :output :stream :error-output nil) (declare (ignore foo #-:allegro pid)) (let ((eds (eds-read stream))) (close stream) #+:allegro (sys:os-wait nil pid) eds))) ((streamp file) (labels ((|{|-reader (stream char) (declare (ignore char)) (read-delimited-list #\} stream t)) (|[|-reader (stream char) (declare (ignore char)) (read-delimited-list #\] stream t)) (read-ed (stream) (let ((c (peek-char t stream nil nil))) (unless (and c (char= c #\})) (let ((ed (make-ed))) (setf (ed-id ed) (read stream nil nil)) (setf (ed-predicate ed) (read stream nil nil)) (setf (ed-lnk ed) (read-lnk stream)) (let ((c (peek-char t stream nil nil))) (when (and c (char= c #\()) (read-char stream nil nil) (setf (ed-carg ed) (read stream nil nil)) (read-char stream nil nil))) (let ((c (peek-char t stream nil nil))) (when (and c (char= c #\{)) (setf (ed-properties ed) (read stream nil nil)))) (let ((c (peek-char t stream nil nil))) (when (and c (char= c #\[)) (setf (ed-arguments ed) (read stream nil nil)))) (when (and (ed-id ed) (ed-predicate ed)) (setf (ed-id ed) (string (ed-id ed))) (setf (ed-predicate ed) (string (ed-predicate ed))) (let ((type (when (oddp (length (ed-properties ed))) (first (ed-properties ed))))) (setf (ed-properties ed) (loop with properties = (if type (rest (ed-properties ed)) (ed-properties ed)) while (rest properties) collect (let* ((feature (pop properties)) (value (pop properties)) (value (if (or (symbolp value) (stringp value)) value (format nil "~a" value)))) (make-extrapair :feature (vsym feature) :value (vsym value))))) (when type (push type (ed-properties ed)))) (setf (ed-arguments ed) (loop with arguments = (ed-arguments ed) while (rest arguments) collect (cons (vsym (pop arguments)) (pop arguments)))) ed)))))) (loop with *package* = (find-package *mrs-package*) with *readtable* = (copy-readtable) with eds = (make-eds) initially (setf (readtable-case *readtable*) :preserve) (set-macro-character #\{ #'|{|-reader nil) (set-macro-character #\} (get-macro-character #\) nil)) (set-macro-character #\[ #'|[|-reader nil) (set-macro-character #\] (get-macro-character #\) nil)) (set-syntax-from-char #\: #\space) (set-syntax-from-char #\< #\") (set-syntax-from-char #\> #\") (set-syntax-from-char #\, #\space) (unless (char= #\{ (loop for c = (read-char file nil nil) while (and c (not (char= c #\{))) finally (return c))) (error "eds-read(): missing or invalid preamble.")) (unless (setf (eds-top eds) (read file nil nil)) (error "eds-read(): missing or invalid top node.")) for ed = (read-ed file) while ed do (push ed (eds-relations eds)) finally (setf (eds-top eds) (string (eds-top eds))) (setf (eds-relations eds) (nreverse (eds-relations eds))) (loop with nodes = (eds-relations eds) for node in nodes do (setf (ed-arguments node) (loop for (role . id) in (ed-arguments node) for value = (when id (find (string id) nodes :key #'ed-id :test #'string=)) when value collect (cons role value)))) (eds-inverse eds) (return eds)))) ((and (stringp file) (let ((c (with-input-from-string (stream file) (peek-char t stream nil nil)))) (and c (char= c #\{)))) (with-input-from-string (stream file) (eds-read stream))) ((and (or (stringp file) (pathnamep file)) (probe-file file)) (with-open-file (stream file :direction :input) (eds-read stream))) (t (error "eds-read(): invalid input source ‘~a’." file)))) (defun eds (edge) (with-output-to-string (stream) (let ((*package* (find-package *mrs-package*)) (*eds-show-properties-p* t)) (write (eds-convert-edge edge) :stream stream)))) #+:null (labels ((iid (item) (tsdb:get-field :i-id item)) (output (profile stream &optional (format :amr) active) (loop with filter = "^focus_d$|^parg_d$" with items = (tsdb::analyze profile :condition "readings > 0 && t-active > 0" :thorough '(:mrs)) for item in items for id = (tsdb:get-field :i-id item) for results = (tsdb:get-field :results item) for mrs = (tsdb:get-field :mrs (first results)) when (and (mrs::psoa-p mrs) (or (null active) (member id active :key #'iid))) do (if (eq format :ascii) (let ((file (format nil "~~/lib/sdp/release/2015/eds/~a.eds" id))) (with-open-file (stream file :direction :output :if-exists :supersede) (mrs:eds-output-psoa mrs :format format :stream stream :lnkp t :propertiesp nil :filter filter :id id :input (tsdb:get-field :i-input item)))) (mrs:eds-output-psoa mrs :format format :stream stream :lnkp t :propertiesp nil :filter filter :id id :input (tsdb:get-field :i-input item)))) (tsdb::purge-profile-cache profile))) (let* ((format :ascii) (dm (when (eq format :ascii) (tsdb::read-items-from-conll-file "~/lib/sdp/train/en.dm.sdp" :type :sdp+ :cycle t :rawp t)))) (with-open-file (stream "/tmp/train.amr" :direction :output :if-exists :supersede) (loop with dm = (tsdb::read-items-from-conll-file "~/lib/sdp/train/en.dm.sdp" :type :sdp+ :cycle t :rawp t) for segment in '("wsj00a" "wsj00b" "wsj00c" "wsj00d" "wsj01a" "wsj01b" "wsj01c" "wsj01d" "wsj02a" "wsj02b" "wsj02c" "wsj02d" "wsj03a" "wsj03b" "wsj03c" "wsj04a" "wsj04b" "wsj04c" "wsj04d" "wsj04e" "wsj05a" "wsj05b" "wsj05c" "wsj05d" "wsj05e" "wsj06a" "wsj06b" "wsj06c" "wsj06d" "wsj07a" "wsj07b" "wsj07c" "wsj07d" "wsj07e" "wsj08a" "wsj09a" "wsj09b" "wsj09c" "wsj09d" "wsj10a" "wsj10b" "wsj10c" "wsj10d" "wsj11a" "wsj11b" "wsj11c" "wsj11d" "wsj11e" "wsj12a" "wsj12b" "wsj12c" "wsj12d" "wsj13a" "wsj13b" "wsj13c" "wsj13d" "wsj13e" "wsj14a" "wsj14b" "wsj14c" "wsj14d" "wsj14e" "wsj15a" "wsj15b" "wsj15c" "wsj15d" "wsj15e" "wsj16a" "wsj16b" "wsj16c" "wsj16d" "wsj16e" "wsj16f" "wsj17a" "wsj17b" "wsj17c" "wsj17d" "wsj18a" "wsj18b" "wsj18c" "wsj18d" "wsj18e" "wsj19a" "wsj19b" "wsj19c" "wsj19d" "wsj20a" "wsj20b" "wsj20c" "wsj20d") do (output (format nil "gold/erg/~a" segment) stream format dm))) (with-open-file (stream "/tmp/test.amr" :direction :output :if-exists :supersede) (loop for segment in '("wsj21a" "wsj21b" "wsj21c" "wsj21d") do (output (format nil "gold/erg/~a" segment) stream format dm))))) #+:null (let ((train (tsdb::read-items-from-conll-file "~/lib/sdp/train/en.dm.sdp" :type :sdp+ :cycle t :rawp t)) (test (tsdb::read-items-from-conll-file "~/lib/sdp/test/en.id.dm.sdp" :type :sdp+ :cycle t :rawp t)) (path "~/lib/sdp/release/2015/eds/") (decoder "gzip -d -c")) (labels ((output (items stream format) (loop with filter = "^focus_d$|^parg_d$" for item in items for id = (tsdb:get-field :i-id item) for input = (let ((file (format nil "~a~a.txt.gz" path id))) (multiple-value-bind (stream foo pid) (run-process decoder :wait nil :input file :output :stream :error-output nil) (declare (ignore foo)) (let ((line (read-line stream nil nil))) (close stream) #+:allegro (sys:os-wait nil pid) line))) for mrs = (let ((file (format nil "~a~a.mrs.gz" path id))) (read-mrs-from-file file :decoder decoder)) when stream do (eds-output-psoa mrs :format format :stream stream :lnkp t :propertiesp (eq format :json) :filter filter :id id :input input :columns 79) (when (eq format :json) (terpri stream)) else do (with-open-file (stream (format nil "~a~a.eds" path id) :direction :output :if-exists :supersede) (eds-output-psoa mrs :format format :stream stream :lnkp t :propertiesp t :filter filter :id id :input input))))) (output train nil :ascii) (output test nil :ascii) (with-open-file (stream "~/lib/sdp/release/2015/eds/train.amr" :direction :output :if-exists :supersede) (output train stream :amr)) (with-open-file (stream "~/lib/sdp/release/2015/eds/test.amr" :direction :output :if-exists :supersede) (output test stream :amr)) (with-open-file (stream "~/lib/sdp/release/2015/eds/train.json" :direction :output :if-exists :supersede) (output train stream :json)) (with-open-file (stream "~/lib/sdp/release/2015/eds/test.json" :direction :output :if-exists :supersede) (output test stream :json))))