;;;  vs-cobol-ii.lisp --- sample grammar for VS-Cobol II

;;;  Copyright (C) 2003 by Walter C. Pelissero

;;;  Author: Walter C. Pelissero <walter@pelissero.de>
;;;  Project: NPG a Naive Parser Generator
;;;  $Id: F-1D03709AEB30BA7644C1CFA2DF60FE8C.lisp,v 1.2 2004/03/09 10:33:07 wcp Exp $

;;; This library is free software; you can redistribute it and/or
;;; modify it under the terms of the GNU Lesser General Public License
;;; as published by the Free Software Foundation; either version 2.1
;;; of the License, or (at your option) any later version.
;;; This library is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;;; Lesser General Public License for more details.
;;; You should have received a copy of the GNU Lesser General Public
;;; License along with this library; if not, write to the Free
;;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
;;; 02111-1307 USA

;;;  Commentary:
;;;
;;; A fairly incomplete VS-Cobol II grammar fro NPG.  It's probably
;;; not very accurate either.

#+cmu (ext:file-comment "$Id: F-1D03709AEB30BA7644C1CFA2DF60FE8C.lisp,v 1.2 2004/03/09 10:33:07 wcp Exp $")

(in-package :grammar)

(defun make-keyword (string)
  "Create a keyword from STRING."
  (intern (string-upcase string) :keyword))

(defun flatten-list (list)
  "Remove one depth level in LIST."
  (mapcan #'identity list))

(deflazy define-grammar
  (let ((*package* #.*package*)
        (*compile-print* (and parser::*debug* t)))
    (reset-grammar)
    (format t "creating Cobol grammar...~%")
    (populate-grammar)
    (let ((grammar (parser:generate-grammar)))
      (reset-grammar)
      (parser:print-grammar-figures grammar)
      grammar)))

(defun populate-grammar ()
;;;
;;; Hereafter PP means Partial Program
;;;

#+nil
(defrule pp--declarations
    := identification-division environment-division? data-division? "PROCEDURE" "DIVISION" using-phrase? "." :rest)

;;; We need to split the parsing of the declarations from the rest
;;; because the declarations may change the lexical rules (ie decimal
;;; point)

(defrule pp--declarations
    := identification-division environment-division? data-division-head-or-procedure-division-head :rest)

(defrule data-division-head-or-procedure-division-head
    := data-division-head
    :reduce :data-division
    := procedure-division-head
    :reduce (list :procedure-division $1))

(defrule pp--data-division
    := data-division-content procedure-division-head :rest)

(defrule pp--sentence
    := sentence :rest
    := :eof)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The real grammar
;;;

(defrule cobol-source-program
    := identification-division environment-division? data-division procedure-division end-program?)

(defrule identification-division
    := identification "DIVISION" "." program-id-cobol-source-program identification-division-content
    :reduce program-id-cobol-source-program)

(defrule priority-number
    := integer)

(defrule level-number
    := integer)

(defrule to-id-or-lit
    := "TO" id-or-lit)

(defrule inspect-by-argument
    := variable-identifier
    := string
    := figurative-constant-simple)

(defrule figurative-constant-simple
    := "ZERO"
    :reduce :zero
    := "ZEROS"
    :reduce :zero
    := "ZEROES"
    :reduce :zero
    := "SPACE"
    :reduce :space
    := "SPACES"
    :reduce :space
    := "HIGH-VALUE"
    :reduce :high
    := "HIGH-VALUES"
    :reduce :high
    := "LOW-VALUE"
    :reduce :low
    := "LOW-VALUES"
    :reduce :low
    := "QUOTE"
    :reduce :quote
    := "QUOTES"
    :reduce :quote
    := "NULL"
    :reduce :null
    := "NULLS"
    :reduce :null)

(defrule write-exceptions
    := at-end-of-page-statement-list? not-at-end-of-page-statement-list? invalid-key-statement-list? not-invalid-key-statement-list?)

(defrule set-statement-phrase
    := variable-identifier+ set-oper set-src)

(defrule set-src
    := variable-identifier
    := literal
    := "TRUE"
    := "ON"
    := "OFF")

(defrule set-oper
    := "TO"
    :reduce :to
    := "UP" "BY"
    :reduce :up
    := "DOWN" "BY"
    :reduce :down)

(defrule fce-phrase
    := reserve-clause
    := fce-organization
    := fce-access-mode
    := record-key-clause
    := password-clause
    := alternate-record-key-clause
    := file-status-clause
    := padding-character-clause
    := record-delimiter-clause)

(defrule fce-organization
    := organization-is? alt-indexed-relative-sequential
    :reduce (list :organization (make-keyword alt-indexed-relative-sequential)))

(defrule fce-access-mode
    := "ACCESS" "MODE"? "IS"? alt-sequential-random-dynamic relative-key-clause?
    :reduce (list :access-mode (make-keyword alt-sequential-random-dynamic)))

(defrule alt-indexed-relative-sequential
    := "INDEXED"
    := "RELATIVE"
    := "SEQUENTIAL")

(defrule is-not
    := "IS"? "NOT"?)

(defrule all-procedures
    := "ALL" "PROCEDURES")

(defrule next-sentence
    := "NEXT" "SENTENCE")

(defrule no-rewind
    := "NO" "REWIND")

(defrule for-removal
    := "FOR"? "REMOVAL")

(defrule values
    := "VALUE"
    := "VALUES")

(defrule records
    := "RECORD"
    := "RECORDS")

(defrule end-program
    := "END" "PROGRAM" program-name ".")

(defrule environment-division
    := "ENVIRONMENT" "DIVISION" "." environment-division-content)

(defrule data-division-head
    := "DATA" "DIVISION" ".")

(defrule data-division
    := data-division-head data-division-content
    :reduce data-division-content)

(defrule identification
    := "IDENTIFICATION"
    := "ID")

(defrule identification-division-content
    := identification-division-phrase*)

(defrule author
    := "AUTHOR" ".")

(defrule installation
    := "INSTALLATION" ".")

(defrule date-written
    := "DATE-WRITTEN" ".")

(defrule date-compiled
    := "DATE-COMPILED" ".")

(defrule security
    := "SECURITY" ".")

(defrule remarks
    := "REMARKS" ".")

(defrule identification-division-phrase
    := author
    := installation
    := date-written
    := date-compiled
    := security
    := remarks)

(defrule program-id-cobol-source-program
    := "PROGRAM-ID" "."? program-name initial-program? "."
    :reduce program-name)

(defrule initial-program
    := "IS"? "INITIAL" "PROGRAM"?)

(defrule environment-division-content
    := configuration-section? input-output-section?)

(defrule input-output-section
    := "INPUT-OUTPUT" "SECTION" "." file-control-paragraph? i-o-control-paragraph?
    :reduce file-control-paragraph)

(defrule file-control-paragraph
    := "FILE-CONTROL" "." file-control-entry*)

(defrule file-control-entry
    := select-clause assign-clause fce-phrase* "."
    :reduce (append select-clause
                    assign-clause
                    (flatten-list fce-phrase)))

(defrule organization-is
    := "ORGANIZATION" "IS"?)

(defrule alt-sequential-random-dynamic
    := "SEQUENTIAL"
    := "RANDOM"
    := "DYNAMIC")

(defrule select-clause
    := "SELECT" "OPTIONAL"? file-name
    :reduce (list file-name :optional (and $2 t)))

(defrule assign-clause
    := "ASSIGN" "TO"? alt-assignment-name-literal+
    :reduce (list :assign alt-assignment-name-literal))

(defrule alt-assignment-name-literal
    := assignment-name
    := literal)

(defrule reserve-clause
    := "RESERVE" integer areas?)

(defrule areas
    := "AREA"
    := "AREAS")

(defrule padding-character-clause
    := "PADDING" "CHARACTER"? "IS"? alt-qualified-data-name-literal)

(defrule record-delimiter-clause
    := "RECORD" "DELIMITER" "IS"? record-delimiter-name)

(defrule record-delimiter-name
    := "STANDARD-1"
    := assignment-name)

(defrule password-clause
    := "PASSWORD" "IS"? data-name)

(defrule file-status-clause
    := "FILE"? "STATUS" "IS"? qualified-data-name qualified-data-name?
    :reduce (list :file-status qualified-data-name))

(defrule relative-key-clause
    := "RELATIVE" "KEY"? "IS"? qualified-data-name
    :reduce (list :relative-key qualified-data-name))

(defrule record-key-clause
    := "RECORD" "KEY"? "IS"? qualified-data-name
    :reduce (list :key qualified-data-name))

(defrule alternate-record-key-clause
    := "ALTERNATE" "RECORD"? "KEY"? "IS"? qualified-data-name password-clause? with-duplicates?
    :reduce (list :alternate-key qualified-data-name with-duplicates))

(defrule with-duplicates
    := "WITH"? "DUPLICATES")

(defrule i-o-control-paragraph
    := "I-O-CONTROL" "." i-o-sam? i-o-sort-merge?)

(defrule i-o-sam
    := qsam-or-sam-or-vsam-i-o-control-entries+ ".")

(defrule i-o-sort-merge
    := sort-merge-i-o-control-entries ".")

(defrule qsam-or-sam-or-vsam-i-o-control-entries
    := qsam-or-sam-or-vsam-i-o-control-entries-1
    := qsam-or-sam-or-vsam-i-o-control-entries-2
    := qsam-or-sam-or-vsam-i-o-control-entries-3
    := qsam-or-sam-or-vsam-i-o-control-entries-4)

(defrule qsam-or-sam-or-vsam-i-o-control-entries-1
    := "RERUN" "ON" alt-assignment-name-file-name "EVERY"? every-phrase "OF"? file-name)

(defrule every-phrase-1
    := integer "RECORDS")

(defrule every-phrase-2
    := "END" "OF"? alt-reel-unit)

(defrule every-phrase
    := every-phrase-1
    := every-phrase-2)

(defrule alt-assignment-name-file-name
    := assignment-name
    := file-name)

(defrule qsam-or-sam-or-vsam-i-o-control-entries-2
    := "SAME" "RECORD"? "AREA"? "FOR"? file-name file-name+)

(defrule qsam-or-sam-or-vsam-i-o-control-entries-3
    := "MULTIPLE" "FILE" "TAPE"? "CONTAINS"? file-name-position+)

(defrule position
    := "POSITION" integer)

(defrule file-name-position
    := file-name position?)

(defrule qsam-or-sam-or-vsam-i-o-control-entries-4
    := "APPLY" "WRITE-ONLY" "ON"? file-name+)

(defrule sort-merge-i-o-control-entries
    := rerun-on? same-area+)

(defrule rerun-on
    := "RERUN" "ON" assignment-name)

(defrule record-sort
    := "RECORD"
    := "SORT"
    := "SORT-MERGE")

(defrule same-area
    := "SAME" record-sort "AREA"? "FOR"? file-name file-name+)

(defrule configuration-section
    := "CONFIGURATION" "SECTION" "." configuration-section-paragraph*
    :reduce (flatten-list configuration-section-paragraph))

(defrule configuration-section-paragraph
    := source-computer-paragraph
    := object-computer-paragraph
    := special-names-paragraph)

(defrule source-computer-paragraph
    := "SOURCE-COMPUTER" "." source-computer-name
    :reduce (list :source-computer source-computer-name))

(defrule with-debugging-mode
    := "WITH"? "DEBUGGING" "MODE")

(defrule source-computer-name
    := computer-name with-debugging-mode? "."
    :reduce computer-name)

(defrule object-computer-paragraph
    := "OBJECT-COMPUTER" "." object-computer-name
    :reduce (list :object-computer object-computer-name))

(defrule memory-size-type
    := "WORDS"
    := "CHARACTERS"
    := "MODULES")

(defrule memory-size
    := "MEMORY" "SIZE"? integer memory-size-type)

(defrule object-computer-name
    := computer-name memory-size? object-computer-paragraph-sequence-phrase "."
    :reduce computer-name)

(defrule object-computer-paragraph-sequence-phrase
    := program-collating-sequence? segment-limit?)

(defrule program-collating-sequence
    := "PROGRAM"? "COLLATING"? "SEQUENCE" "IS"? alphabet-name)

(defrule segment-limit
    := "SEGMENT-LIMIT" "IS"? priority-number)

(defrule special-names-paragraph
    := "SPECIAL-NAMES" "." special-names-paragraph-phrase* special-names-paragraph-clause* "."
    :reduce (flatten-list special-names-paragraph-clause))

(defrule is-mnemonic-name
    := "IS"? mnemonic-name special-names-paragraph-status-phrase?)

(defrule special-names-paragraph-phrase-tail
    := is-mnemonic-name
    := special-names-paragraph-status-phrase)

(defrule special-names-paragraph-phrase
    := environment-name special-names-paragraph-phrase-tail)

(defrule special-names-paragraph-status-phrase
    := special-names-paragraph-status-phrase-1
    := special-names-paragraph-status-phrase-2)

(defrule special-names-paragraph-status-phrase-1
    := "ON" "STATUS"? "IS"? condition off-status?)

(defrule off-status
    := "OFF" "STATUS"? "IS"? condition)

(defrule special-names-paragraph-status-phrase-2
    := "OFF" "STATUS"? "IS"? condition on-status?)

(defrule on-status
    := "ON" "STATUS"? "IS"? condition)

(defrule special-names-paragraph-clause
    ;; := alphabet-clause
    ;; := symbolic-characters-clause
    := currency-sign-clause
    := decimal-point-clause)

(defrule alphabet-clause
    := "ALPHABET" alphabet-name "IS"? alphabet-type)

(defrule alphabet-type-also
    := "ALSO" literal)

(defrule alphabet-type-alsos
    := alphabet-type-also+)

(defrule alphabet-type-also-through
    := through-literal
    := alphabet-type-alsos)

(defrule alphabet-type-other
    := literal alphabet-type-also-through?)

(defrule alphabet-type-others
    := alphabet-type-other+)

(defrule alphabet-type
    := "STANDARD-1"
    := "STANDARD-2"
    := "NATIVE"
    := "EBCDIC"
    := alphabet-type-others)

(defrule symbolic-characters-clause
    := "SYMBOLIC" "CHARACTERS"? symbolic-character-mapping+ in-alphabet-name?)

(defrule are
    := "ARE"
    := "IS")

(defrule symbolic-character-mapping
    := symbolic-character+ are? integer+)

(defrule in-alphabet-name
    := "IN" alphabet-name)

(defrule currency-sign-clause
    := "CURRENCY" "SIGN"? "IS"? literal
    :reduce (list :currency-sign literal))

(defrule decimal-point-clause
    := "DECIMAL-POINT" "IS"? "COMMA"
    :reduce (list :decimal-point #\,))

(defrule data-division-content
    := file-section? working-storage-section? linkage-section?)

(defrule file-section-entry
    := file-and-sort-description-entry data-description-entry+
    :reduce (cons file-and-sort-description-entry data-description-entry))

(defrule file-section-head
    := "FILE" "SECTION" ".")

(defrule file-section
    := file-section-head file-section-entry*
    :reduce $2)

(defrule working-storage-section-head
    := "WORKING-STORAGE" "SECTION" ".")

(defrule working-storage-section
    := working-storage-section-head data-description-entry*
    :reduce $2)

(defrule linkage-section-head
    := "LINKAGE" "SECTION" ".")

(defrule linkage-section
    := linkage-section-head data-description-entry*
    :reduce $2)

(defrule file-and-sort-description-entry
    := alt-fd-sd file-name file-and-sort-description-entry-clause* "."
    :reduce (list (make-keyword alt-fd-sd) file-name file-and-sort-description-entry-clause))

(defrule alt-fd-sd
    := "FD"
    := "SD")

(defrule file-and-sort-description-entry-clause
    := external-clause
    := global-clause
    := block-contains-clause
    := record-clause
    := label-records-clause
    := value-of-clause
    := data-records-clause
    := linage-clause
    := recording-mode-clause
    := code-set-clause)

(defrule integer-to
    := integer "TO")

(defrule block-contains-clause
    := "BLOCK" "CONTAINS"? integer-to? integer alt-characters-records?)

(defrule alt-characters-records
    := "CHARACTERS"
    := "RECORDS"
    := "RECORD")

(defrule record-clause
    := "RECORD" record-clause-tail)

(defrule depending-on
    := "DEPENDING" "ON"? data-name)

(defrule record-clause-tail-1
    := "CONTAINS"? integer "CHARACTERS"?)

(defrule record-clause-tail-2
    := "CONTAINS"? integer "TO" integer "CHARACTERS"?)

(defrule record-clause-tail-3
    := record-varying-phrase depending-on?)

(defrule record-clause-tail
    := record-clause-tail-2
    := record-clause-tail-1
    := record-clause-tail-3)

(defrule record-varying-phrase
    := "IS"? "VARYING" "IN"? "SIZE"? from-integer? to-integer? "CHARACTERS"?)

(defrule from-integer
    := "FROM"? integer)

(defrule to-integer
    := "TO" integer)

(defrule label-records-clause
    := "LABEL" records-are label-records-clause-tail
    :reduce (list :label-record label-records-clause-tail))

(defrule data-names
    := data-name+)

(defrule label-records-clause-tail
    := "STANDARD" :reduce :standard
    := "OMITTED" :reduce :omitted
    := data-names)

(defrule value-of-clause
    := "VALUE" "OF" value-of-clause-tail+)

(defrule alt-qualified-data-name-literal
    := qualified-data-name
    := literal)

(defrule value-of-clause-tail
    := variable-identifier "IS"? alt-qualified-data-name-literal)

(defrule data-records-clause
    := "DATA" records-are data-name+)

(defrule records-are
    := records are?)

(defrule linage-clause
    := "LINAGE" "IS"? alt-data-name-integer "LINES"? linage-footing-phrase)

(defrule linage-footing-phrase
    := footing? lines-top? lines-bottom?)

(defrule alt-data-name-integer
    := data-name
    := integer)

(defrule footing
    := "WITH"? "FOOTING" "AT"? alt-data-name-integer)

(defrule lines-top
    := "LINES"? "AT"? "TOP" alt-data-name-integer)

(defrule lines-bottom
    := "LINES"? "AT"? "BOTTOM" alt-data-name-integer)

(defrule recording-mode-clause
    := "RECORDING" "MODE"? "IS"? variable-identifier)

(defrule code-set-clause
    := "CODE-SET" "IS"? alphabet-name)

(defrule data-description-entry
    := level-number alt-data-name-filler? data-description-entry-clause* "."
    :reduce (append (list level-number alt-data-name-filler)
                    (flatten-list data-description-entry-clause)))

(defrule alt-data-name-filler
    := data-name
    := "FILLER"
    :reduce (list))

(defrule data-description-entry-clause
    := picture-clause
    := redefines-clause
    := blank-when-zero-clause
    := external-clause
    := global-clause
    := justified-clause
    := occurs-clause
    := sign-clause
    := synchronized-clause
    := usage-clause
    := renames-clause
    := value-clause)

(defrule value-clause
    := "VALUE" "IS"? literal
    :reduce (list :value literal))

(defrule redefines-clause
    := "REDEFINES" data-name
    :reduce `(:redefines ,data-name))

(defrule blank-when-zero-clause
    := "BLANK" "WHEN"? zeroes
    :reduce '(:blank-when-zero t))

(defrule zeroes
    := "ZERO"
    := "ZEROS"
    := "ZEROES")

(defrule external-clause
    := "IS"? "EXTERNAL"
    :reduce '(:external t))

(defrule global-clause
    := "IS"? "GLOBAL"
    :reduce '(:global t))

(defrule justified-clause
    := justified "RIGHT"?
    :reduce `(:justified ,(if $2 :right :left)))

(defrule justified
    := "JUSTIFIED"
    := "JUST")

(defrule occurs-clause
    := "OCCURS" integer "TIMES"? occurs-clause-key* indexed-by?
    ;; to be completed -wcp16/7/03.
    :reduce `(:times ,integer)
    := "OCCURS" integer "TO" integer "TIMES"? "DEPENDING" "ON"? qualified-data-name occurs-clause-key* indexed-by?
    ;; to be completed -wcp16/7/03.
    :reduce `(:times (,integer ,integer2 ,qualified-data-name)))

(defrule occurs-clause-key
    := alt-ascending-descending "KEY"? "IS"? qualified-data-name+)

(defrule indexed-by
    := "INDEXED" "BY"? index-name+)

(defrule picture-clause
    := picture "IS"? picture-string
    :reduce `(:picture ,picture-string))

(defrule picture
    := "PICTURE"
    := "PIC")

(defrule sign-clause
    := sign-is? alt-leading-trailing separate-character?
    :reduce `(:separate-sign ,separate-character :sign-position ,alt-leading-trailing))

(defrule sign-is
    := "SIGN" "IS"?)

(defrule separate-character
    := "SEPARATE" "CHARACTER"?
    :reduce t)

(defrule alt-leading-trailing
    := "LEADING"
    :reduce :leading
    := "TRAILING"
    :reduce :trailing)

(defrule synchronized-clause
    := synchronized alt-left-right?
    :reduce `(:synchronized ,(if alt-left-right
                                 alt-left-right
                                 t)))

(defrule alt-left-right
    := "LEFT"
    :reduce :left
    := "RIGHT"
    :reduce :right)

(defrule synchronized
    := "SYNCHRONIZED"
    := "SYNC")

(defrule usage-clause
    := usage-is? usage
    :reduce (list :encoding usage))

(defrule usage-is
    := "USAGE" "IS"?)

(defrule usage
    := "BINARY"
    :reduce :binary
    := "COMP"
    :reduce :comp
    := "COMP-1"
    :reduce :comp1
    := "COMP-2"
    :reduce :comp2
    := "COMP-3"
    :reduce :comp3
    := "COMP-4"
    :reduce :comp4
    := "COMPUTATIONAL"
    :reduce :comp
    := "COMPUTATIONAL-1"
    :reduce :comp1
    := "COMPUTATIONAL-2"
    :reduce :comp2
    := "COMPUTATIONAL-3"
    :reduce :comp3
    := "COMPUTATIONAL-4"
    :reduce :comp4
    := "DISPLAY"
    :reduce :display
    := "DISPLAY-1"
    :reduce :display1
    := "INDEX"
    :reduce :index
    := "PACKED-DECIMAL"
    :reduce :packed-decimal
    := "POINTER"
    :reduce :pointer)

(defrule renames-clause
    := "RENAMES" qualified-data-name through-qualified-data-name?
    :reduce `(:renames ,qualified-data-name ,through-qualified-data-name))

(defrule through-qualified-data-name
    := through qualified-data-name
    :reduce qualified-data-name)

(defrule condition-value-clause
    := values-are literal-through-literal+)

(defrule through-literal
    := through literal)

(defrule literal-through-literal
    := literal through-literal?)

(defrule values-are
    := values are?)

(defrule procedure-division-head
    := "PROCEDURE" "DIVISION" using-phrase? ".")

(defrule procedure-division
    := procedure-division-head sentence+)

(defrule using-phrase
    := "USING" data-name+)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(defrule declaratives
    := "DECLARATIVES" "." declaratives-content+ "END" "DECLARATIVES" ".")

(defrule declaratives-content
    := cobol-identifier "SECTION" "." use-statement "." sentence*)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(defrule paragraph-header
    := cobol-identifier "SECTION"?
    :reduce (list (if $2 :section :label) $1))

(defrule sentence
    := declaratives
    := statement* "."
    :reduce $1
    := paragraph-header "."
    :reduce $1)

(defrule statement
    := move-statement
    := if-statement
    := perform-statement
    := go-to-statement
    := accept-statement
    := add-statement
    := alter-statement
    := call-statement
    := cancel-statement
    := close-statement
    := compute-statement
    := continue-statement
    := delete-statement
    := display-statement
    := divide-statement
    := entry-statement
    := evaluate-statement
    := exit-program-statement
    := exit-statement
    := goback-statement
    := initialize-statement
    := inspect-statement
    := merge-statement
    := multiply-statement
    := open-statement
    := read-statement
    := release-statement
    := return-statement
    := rewrite-statement
    := search-statement
    := set-statement
    := sort-statement
    := start-statement
    := stop-statement
    := string-statement
    := subtract-statement
    := unstring-statement
    := write-statement
    := paragraph-header)

(defrule accept-statement
    := "ACCEPT" variable-identifier "FROM" date
    := "ACCEPT" variable-identifier "AT" screen-coordinates
    :reduce (apply #'list 'accept-at variable-identifier screen-coordinates)
    := "ACCEPT" variable-identifier from-environment-name?)

(defrule from-environment-name
    := "FROM" cobol-identifier)


(defrule date
    := "DATE"
    := "DAY"
    := "DAY-OF-WEEK"
    := "TIME")

(defrule add-statement
    := "ADD" id-or-lit+ to-id-or-lit? "GIVING" cobword-rounded+ on-size-error-statement-list? not-on-size-error-statement-list? "END-ADD"?
    := "ADD" id-or-lit+ "TO" cobword-rounded+ on-size-error-statement-list? not-on-size-error-statement-list? "END-ADD"?
    := "ADD" corresponding variable-identifier "TO" variable-identifier "ROUNDED"? on-size-error-statement-list? not-on-size-error-statement-list? "END-ADD"?)

(defrule statement-list
    := statement+)

(defrule alter-statement
    := "ALTER" procedure-to-procedure+)

(defrule proceed-to
    := "PROCEED" "TO")

(defrule procedure-to-procedure
    := procedure-name "TO" proceed-to? procedure-name)

(defrule call-statement
    := "CALL" id-or-lit using-parameters? call-rest-phrase "END-CALL"?
    :reduce (list 'call id-or-lit (cons 'list using-parameters)))

(defrule by-reference
    := "BY"? "REFERENCE")

(defrule content-parameter-value
    := cobol-identifier
    := literal)

(defrule reference-parameter
    := by-reference? variable-identifier)

(defrule content-parameter
    := "BY"? "CONTENT" content-parameter-value+)

(defrule parameter
    := reference-parameter
    := content-parameter
    := literal)

(defrule using-parameters
    := "USING" parameter+)

(defrule call-rest-phrase
    := on-exception-statement-list? not-on-exception-statement-list? on-overflow-statement-list?)

(defrule on-exception-statement-list
    := "ON"? "EXCEPTION" statement-list)

(defrule not-on-exception-statement-list
    := "NOT" "ON"? "EXCEPTION" statement-list)

(defrule cancel-statement
    := "CANCEL" id-or-lit+)

(defrule close-statement
    := "CLOSE" close-statement-file-name+
    :reduce (list 'close close-statement-file-name))

(defrule alt-removal-no-rewind
    := for-removal
    := with-no-rewind)

(defrule alt-reel-unit
    := "REEL"
    := "UNIT")

(defrule alt-no-rewind-lock
    := no-rewind
    := "LOCK")

(defrule close-statement-options-1
    := alt-reel-unit alt-removal-no-rewind?)

(defrule close-statement-options-2
    := "WITH"? alt-no-rewind-lock)

(defrule close-statement-options
    := close-statement-options-1
    := close-statement-options-2)

(defrule close-statement-file-name
    := file-name close-statement-options?)

(defrule compute-statement
    := "COMPUTE" cobword-rounded+ equal arithmetic-expression on-size-error-statement-list? not-on-size-error-statement-list? "END-COMPUTE"?
    :reduce (list 'compute cobword-rounded arithmetic-expression :on-size-error on-size-error-statement-list
                  :not-on-size-error not-on-size-error-statement-list))

(defrule equal
    := "="
    := "EQUAL")

(defrule continue-statement
    := "CONTINUE")

(defrule delete-statement
    := "DELETE" file-name "RECORD"? invalid-key-statement-list? not-invalid-key-statement-list? "END-DELETE"?
    :reduce (list 'delete file-name :invalid invalid-key-statement-list :not-invalid not-invalid-key-statement-list))

(defrule display-statement
    := "DISPLAY" id-or-lit+ upon-environment-name? with-no-advancing?
    :reduce (list 'display (cons 'list id-or-lit) :upon upon-environment-name :advance (not with-no-advancing))
    := "DISPLAY" id-or-lit "AT" screen-coordinates
    :reduce (apply #'list 'display-at id-or-lit screen-coordinates))

(defrule screen-coordinates
    := integer
    :reduce (multiple-value-list (truncate integer 100)))

(defrule upon-environment-name
    := "UPON" cobol-identifier)

(defrule with-no-advancing
    := "WITH"? "NO" "ADVANCING")

(defrule divide-statement
    := "DIVIDE" id-or-lit "INTO" id-or-lit "GIVING" variable-identifier "ROUNDED"? "REMAINDER" variable-identifier on-size-error-statement-list? not-on-size-error-statement-list? "END-DIVIDE"?
    := "DIVIDE" id-or-lit "BY" id-or-lit "GIVING" variable-identifier "ROUNDED"? "REMAINDER" variable-identifier on-size-error-statement-list? not-on-size-error-statement-list? "END-DIVIDE"?
    := "DIVIDE" id-or-lit "INTO" id-or-lit "GIVING" cobword-rounded+ on-size-error-statement-list? not-on-size-error-statement-list? "END-DIVIDE"?
    := "DIVIDE" id-or-lit "BY" id-or-lit "GIVING" cobword-rounded+ on-size-error-statement-list? not-on-size-error-statement-list? "END-DIVIDE"?
    := "DIVIDE" id-or-lit "INTO" cobword-rounded+ on-size-error-statement-list? not-on-size-error-statement-list? "END-DIVIDE"?)

(defrule entry-statement
    := "ENTRY" literal using-phrase?)

(defrule evaluate-statement
    := "EVALUATE" evaluate-condition also-phrase* when-phrases+ when-other-phrase? "END-EVALUATE"?)

(defrule evaluate-condition
    := condition
    := "TRUE"
    := "FALSE")

(defrule also-phrase
    := "ALSO" evaluate-condition)

(defrule when-phrase-also-phrase
    := "ALSO" evaluate-phrase)

(defrule when-phrase
    := "WHEN" evaluate-phrase when-phrase-also-phrase*)

(defrule when-phrases
    := when-phrase+ statement-list)

(defrule when-other-phrase
    := "WHEN" "OTHER" statement-list)

(defrule evaluate-phrase
    := "ANY"
    := condition
    := "TRUE"
    := "FALSE"
    := evaluate-phrase-1)

(defrule evaluate-phrase-1
    := "NOT"? arithmetic-expression through-arithmetic-expression?)

(defrule through-arithmetic-expression
    := through arithmetic-expression)

(defrule exit-statement
    := "EXIT"
    :reduce '(exit-paragraph))

(defrule exit-program-statement
    := "EXIT" "PROGRAM"
    :reduce '(exit-program))

(defrule goback-statement
    := "GOBACK"
    :reduce '(go-back))

(defrule go-to-statement
    := "GO" "TO"? procedure-name+ "DEPENDING" "ON"? variable-identifier
    :reduce (list 'goto-depending variable-identifier procedure-name)
    := "GO" "TO"? procedure-name
    :reduce (list 'goto procedure-name))

(defrule if-phrase
    := "IF" condition "THEN"? alt-statement-list-next-sentence "ELSE" alt-statement-list-next-sentence
    :reduce (list 'if condition
                  (if (cdr alt-statement-list-next-sentence)
                      (cons 'progn alt-statement-list-next-sentence)
                      (car alt-statement-list-next-sentence))
                  (if (cdr alt-statement-list-next-sentence2)
                      (cons 'progn alt-statement-list-next-sentence2)
                      (car alt-statement-list-next-sentence2)))
    := "IF" condition "THEN"? alt-statement-list-next-sentence
    :reduce (append (list 'when condition) alt-statement-list-next-sentence))

(defrule if-statement
    := if-phrase "END-IF"?
    :reduce $1)

(defrule initialize-statement
    := "INITIALIZE" variable-identifier+ initialize-replacing-phrase?)

(defrule initialize-replacing-type
    := "ALPHABETIC"
    := "ALPHANUMERIC"
    := "NUMERIC"
    := "ALPHANUMERIC-EDITED"
    := "NUMERIC-EDITED"
    := "DBCS"
    := "EGCS")

(defrule initialize-replacing-argument
    := initialize-replacing-type "DATA"? "BY" id-or-lit)

(defrule initialize-replacing-phrase
    := "REPLACING" initialize-replacing-argument+)

(defrule inspect-statement
    := inspect-statement-1
    := inspect-statement-2
    := inspect-statement-3
    := inspect-statement-4)

(defrule inspect-statement-1
    := "INSPECT" variable-identifier "TALLYING" tallying-argument+)

(defrule inspect-statement-2
    := "INSPECT" variable-identifier "CONVERTING" id-or-lit "TO" id-or-lit before-after-phrase*)

(defrule inspect-statement-3
    := "INSPECT" variable-identifier "TALLYING" tallying-argument+ "REPLACING" inspect-replacing-phrase+)

(defrule tallying-for-id-or-lit
    := id-or-lit before-after-phrase*)

(defrule alt-all-leading
    := "ALL"
    := "LEADING")

(defrule tallying-for-argument-1
    := "CHARACTERS" before-after-phrase*)

(defrule tallying-for-argument-2
    := alt-all-leading tallying-for-id-or-lit+)

(defrule tallying-for-argument
    := tallying-for-argument-1
    := tallying-for-argument-2)

(defrule tallying-argument
    := variable-identifier "FOR" tallying-for-argument+)

(defrule inspect-statement-4
    := "INSPECT" variable-identifier "REPLACING" inspect-replacing-phrase+)

(defrule inspect-replacing-argument
    := inspect-by-argument "BY" inspect-by-argument before-after-phrase*)

(defrule alt-all-leading-first
    := "ALL"
    := "LEADING"
    := "FIRST")

(defrule inspect-replacing-phrase-1
    := "CHARACTERS" "BY" id-or-lit before-after-phrase*)

(defrule inspect-replacing-phrase-2
    := alt-all-leading-first inspect-replacing-argument+)

(defrule inspect-replacing-phrase
    := inspect-replacing-phrase-1
    := inspect-replacing-phrase-2)

(defrule before-after-phrase
    := alt-before-after "INITIAL"? id-or-lit)

(defrule merge-statement
    := "MERGE" file-name on-key-phrase+ collating-sequence? "USING" file-name file-name+ merge-statement-tail)

(defrule on-key-phrase
    := "ON"? alt-ascending-descending "KEY"? qualified-data-name+)

(defrule merge-statement-tail
    := output-procedure
    := giving-file-names)

(defrule move-statement
    := "MOVE" id-or-lit "TO" variable-identifier+
    :reduce (apply #'list 'move id-or-lit variable-identifier)
    := "MOVE" corresponding variable-identifier "TO" variable-identifier+
    :reduce (apply #'list 'move-corresponding variable-identifier variable-identifier2))

(defrule multiply-statement
    := "MULTIPLY" id-or-lit "BY" cobword-rounded+ on-size-error-statement-list? not-on-size-error-statement-list? "END-MULTIPLY"?
    :reduce (list 'multiply id-or-lit cobword-rounded :on-size-error on-size-error-statement-list
                  :not-on-size-error not-on-size-error-statement-list)
    := "MULTIPLY" id-or-lit "BY" id-or-lit "GIVING" cobword-rounded+ on-size-error-statement-list? not-on-size-error-statement-list? "END-MULTIPLY"?
    :reduce (list 'multiply id-or-lit id-or-lit2 :giving cobword-rounded
                  :on-size-error on-size-error-statement-list
                  :not-on-size-error not-on-size-error-statement-list))

(defrule open-statement
    := "OPEN" open-statement-phrase+
    :reduce (list 'open open-statement-phrase))

(defrule alt-reversed-with-no-rewind
    := "REVERSED"
    := with-no-rewind)

(defrule open-statement-input-file-name
    := file-name alt-reversed-with-no-rewind?)

(defrule with-no-rewind
    := "WITH"? "NO" "REWIND")

(defrule open-statement-output-file-name
    := file-name with-no-rewind?)

(defrule open-statement-input
    := "INPUT" open-statement-input-file-name+)

(defrule open-statement-output
    := "OUTPUT" open-statement-output-file-name+)

(defrule open-statement-i-o
    := "I-O" file-name+)

(defrule open-statement-extend
    := "EXTEND" file-name+)

(defrule open-statement-phrase
    := open-statement-input
    := open-statement-output
    := open-statement-i-o
    := open-statement-extend)

(defrule perform-statement
    := "PERFORM" procedure-name through-procedure-name? perform-until-phrase
    :reduce `(perform-until ,procedure-name ,through-procedure-name ,perform-until-phrase)
    := "PERFORM" procedure-name through-procedure-name? perform-varying-phrase perform-after-phrase*
    :reduce `(perform-varying ,perform-varying-phrase ,procedure-name ,through-procedure-name ,perform-after-phrase)
    := "PERFORM" procedure-name through-procedure-name? cobword-int "TIMES"
    :reduce `(perform-times ,cobword-int ,procedure-name ,through-procedure-name)
    := "PERFORM" procedure-name through-procedure-name?
    :reduce (append (list 'perform procedure-name) through-procedure-name))

(defrule perform-varying-phrase
    := with-test? "VARYING" variable-identifier "FROM" id-or-lit "BY" id-or-lit "UNTIL" condition)

(defrule perform-after-phrase
    := "AFTER" variable-identifier "FROM" id-or-lit "BY" id-or-lit "UNTIL" condition)

(defrule perform-until-phrase
    := with-test? "UNTIL" condition)

(defrule with-test
    := "WITH"? "TEST" alt-before-after
    :reduce alt-before-after)

(defrule read-statement
    := "READ" file-name "NEXT"? "RECORD"? into-identifier? key-is-qualified-data-name? invalid-key-statement-list? not-invalid-key-statement-list? at-end-statement-list? not-at-end-statement-list? "END-READ"?)

(defrule key-is-qualified-data-name
    := "KEY" "IS"? qualified-data-name)

(defrule release-statement
    := "RELEASE" record-name from-identifier?)

(defrule return-statement
    := "RETURN" file-name "RECORD"? into-identifier? "AT"? "END" statement-list not-at-end-statement-list? "END-RETURN"?)

(defrule into-identifier
    := "INTO" variable-identifier)

(defrule not-at-end-statement-list
    := "NOT" "AT"? "END" statement-list)

(defrule rewrite-statement
    := "REWRITE" record-name from-identifier? invalid-key-statement-list? not-invalid-key-statement-list? "END-REWRITE"?)

(defrule search-statement
    := search-statement-1
    := search-statement-2)

(defrule search-statement-1
    := "SEARCH" cobol-identifier varying-identifier? at-end-statement-list? when-condition-stats+ "END-SEARCH"?)

(defrule varying-identifier
    := "VARYING" variable-identifier)

(defrule when-condition-stats
    := "WHEN" condition alt-statement-list-next-sentence)

(defrule search-statement-2
    := "SEARCH" "ALL" variable-identifier at-end-statement-list? "WHEN" search-statement-condition search-statement-condition-tail* alt-statement-list-next-sentence "END-SEARCH"?)

(defrule at-end-statement-list
    := "AT"? "END" statement-list)

(defrule search-statement-equal-expression
    := variable-identifier "IS"? equal-to arithmetic-expression
    :reduce (list '= variable-identifier arithmetic-expression))

(defrule search-statement-condition
    := search-statement-equal-expression
    := condition-name-reference)

(defrule search-statement-condition-tail
    := "AND" search-statement-condition)

(defrule alt-statement-list-next-sentence
    := statement+
    := next-sentence
    :reduce :next-sentence)

(defrule set-statement
    := "SET" set-statement-phrase+)

(defrule sort-statement
    := "SORT" file-name on-key-is-phrase+ with-duplicates-in-order? collating-sequence? sort-statement-in sort-statement-out)

(defrule key-is
    := "KEY" "IS"?)

(defrule alt-ascending-descending
    := "ASCENDING"
    := "DESCENDING")

(defrule on-key-is-phrase
    := "ON"? alt-ascending-descending key-is? qualified-data-name+)

(defrule with-duplicates-in-order
    := "WITH"? "DUPLICATES" "IN"? "ORDER"?)

(defrule collating-sequence
    := "COLLATING"? "SEQUENCE" "IS"? alphabet-name)

(defrule through
    := "THROUGH"
    := "THRU")

(defrule through-procedure-name
    := through procedure-name
    :reduce procedure-name)

(defrule using-file-names
    := "USING" file-name+)

(defrule input-procedure
    := "INPUT" "PROCEDURE" "IS"? procedure-name through-procedure-name?)

(defrule giving-file-names
    := "GIVING" file-name+)

(defrule output-procedure
    := "OUTPUT" "PROCEDURE" "IS"? procedure-name through-procedure-name?)

(defrule sort-statement-in
    := using-file-names
    := input-procedure)

(defrule sort-statement-out
    := giving-file-names
    := output-procedure)

(defrule start-statement
    := "START" file-name key-is-rel-op-qualified-data-name? invalid-key-statement-list? not-invalid-key-statement-list? "END-START"?)

(defrule rel-op
    := equal-to
    :reduce '=
    := greater-than
    :reduce '>
    := greater-equal
    :reduce '>=)

(defrule key-is-rel-op-qualified-data-name
    := "KEY" "IS"? rel-op qualified-data-name
    :reduce (list rel-op qualified-data-name))

(defrule stop-statement
    := "STOP" alt-run-literal
    :reduce '(stop))

(defrule alt-run-literal
    := "RUN"
    := literal)

(defrule string-statement
    := "STRING" delimited-by-phrase+ "INTO" variable-identifier with-pointer-identifier? on-overflow-statement-list? not-on-overflow-statement-list? "END-STRING"?
    :reduce (list 'string-concat delimited-by-phrase variable-identifier :with-pointer with-pointer-identifier :on-overflow on-overflow-statement-list :not-on-overflow not-on-overflow-statement-list))

(defrule id-or-lit-size
    := literal
    := variable-identifier
    := "SIZE")

(defrule delimited-by-phrase
    := id-or-lit+ "DELIMITED" "BY"? id-or-lit-size
    :reduce (list id-or-lit id-or-lit-size))

(defrule subtract-statement
    := "SUBTRACT" id-or-lit+ "FROM" id-or-lit "GIVING" cobword-rounded+ on-size-error-statement-list? not-on-size-error-statement-list? "END-SUBTRACT"?
    :reduce (list 'subtract-giving id-or-lit id-or-lit2 cobword-rounded
                  :on-size-error on-size-error-statement-list
                  :not-on-size-error not-on-size-error-statement-list)
    := "SUBTRACT" id-or-lit+ "FROM" cobword-rounded+ on-size-error-statement-list? not-on-size-error-statement-list? "END-SUBTRACT"?
    :reduce (list 'subtract id-or-lit cobword-rounded
                  :on-size-error on-size-error-statement-list
                  :not-on-size-error not-on-size-error-statement-list)
    := "SUBTRACT" corresponding variable-identifier "FROM" variable-identifier "ROUNDED"? on-size-error-statement-list? not-on-size-error-statement-list? "END-SUBTRACT"?
    :reduce (list 'subtract-corr variable-identifier variable-identifier
                  :rounded (and $5 t)
                  :on-size-error on-size-error-statement-list
                  :not-on-size-error not-on-size-error-statement-list))

(defrule cobword-rounded
    := variable-identifier "ROUNDED"?
    :reduce (list variable-identifier (and $2 t)))

(defrule on-size-error-statement-list
    := "ON"? "SIZE" "ERROR" statement-list
    :reduce statement-list)

(defrule not-on-size-error-statement-list
    := "NOT" "ON"? "SIZE" "ERROR" statement-list
    :reduce statement-list)

(defrule corresponding
    := "CORRESPONDING"
    := "CORR")

(defrule unstring-statement
    := "UNSTRING" variable-identifier delimited-by-all-phrase? "INTO" unstring-statement-dst+ with-pointer-identifier? tallying-in-identifier? on-overflow-statement-list? not-on-overflow-statement-list? "END-UNSTRING"?
    :reduce (list 'unstring variable-identifier unstring-statement-dst
                  :delimited-by-all delimited-by-all-phrase
                  :with-pointer with-pointer-identifier
                  :tallying tallying-in-identifier
                  :on-overflow on-overflow-statement-list
                  :not-on-overflow not-on-overflow-statement-list))

(defrule id-or-lit
    := literal
    := variable-identifier)

(defrule or-all-id-or-lit
    := "OR" "ALL"? id-or-lit)

(defrule delimited-by-all-phrase
    := "DELIMITED" "BY"? "ALL"? id-or-lit or-all-id-or-lit*)

(defrule delimiter-in-identifier
    := "DELIMITER" "IN"? variable-identifier)

(defrule count-in-identifier
    := "COUNT" "IN"? variable-identifier)

(defrule unstring-statement-dst
    := variable-identifier delimiter-in-identifier? count-in-identifier?)

(defrule with-pointer-identifier
    := "WITH"? "POINTER" variable-identifier)

(defrule tallying-in-identifier
    := "TALLYING" "IN"? variable-identifier)

(defrule on-overflow-statement-list
    := "ON"? "OVERFLOW" statement-list)

(defrule not-on-overflow-statement-list
    := "NOT" "ON"? "OVERFLOW" statement-list)

(defrule write-statement
    := "WRITE" record-name from-identifier? advancing-phrase? write-exceptions "END-WRITE"?)

(defrule lines
    := "LINE"
    := "LINES")

(defrule cobword-int
    := cobol-identifier
    := integer)

(defrule nr-lines-phrase
    := cobword-int lines?)

(defrule page-phrase
    := nr-lines-phrase
    := "PAGE")

(defrule alt-before-after
    := "BEFORE"
    := "AFTER")

(defrule advancing-phrase
    := alt-before-after "ADVANCING"? page-phrase)

(defrule from-identifier
    := "FROM" variable-identifier)

(defrule invalid-key-statement-list
    := "INVALID" "KEY"? statement-list
    :reduce statement-list)

(defrule not-invalid-key-statement-list
    := "NOT" "INVALID" "KEY"? statement-list
    :reduce statement-list)

(defrule end-of-page
    := "END-OF-PAGE"
    := "EOP")

(defrule at-end-of-page-statement-list
    := "AT"? end-of-page statement-list
    :reduce statement-list)

(defrule not-at-end-of-page-statement-list
    := "NOT" "AT"? end-of-page statement-list
    :reduce statement-list)

;; This is left in the grammar but is not used.  COPYs are handled by
;; the lexical scanner.
(defrule copy-statement
    := "COPY" alt-text-name-literal in-library? "SUPPRESS"? copy-statement-replacing-phrase?)

(defrule in
    := "OF"
    := "IN")

(defrule alt-library-name-literal
    := library-name
    := literal)

(defrule in-library
    := in alt-library-name-literal)

(defrule copy-statement-by-phrase
    := copy-operand "BY" copy-operand)

(defrule copy-statement-replacing-phrase
    := "REPLACING" copy-statement-by-phrase+)

(defrule alt-text-name-literal
    := text-name
    := literal)

(defrule copy-operand
    := cobol-identifier
    := literal)

(defrule use-statement
    := use-statement-1
    := use-statement-2
    := use-statement-3)

(defrule use-statement-1
    := "USE" "GLOBAL"? "AFTER" "STANDARD"? alt-exception-error "PROCEDURE" "ON"? alt-file-names-i-o)

(defrule alt-exception-error
    := "EXCEPTION"
    := "ERROR")

(defrule use-statement-2
    := "USE" "GLOBAL"? "AFTER" "STANDARD"? alt-beginning-ending? alt-file-reel-unit? "LABEL" "PROCEDURE" "ON"? alt-file-names-i-o)

(defrule alt-beginning-ending
    := "BEGINNING"
    := "ENDING")

(defrule alt-file-reel-unit
    := "FILE"
    := "REEL"
    := "UNIT")

(defrule file-names
    := file-name+)

(defrule alt-file-names-i-o
    := file-names
    := "INPUT"
    := "OUTPUT"
    := "I-O"
    := "EXTEND")

(defrule use-statement-3
    := "USE" "FOR"? "DEBUGGING" "ON"? alt-procedures-all-procedures)

(defrule procedure-names
    := procedure-name+)

(defrule alt-procedures-all-procedures
    := procedure-names
    := all-procedures)

(defrule condition
    := combinable-condition
    := combinable-condition "AND" condition
    :reduce `(and ,combinable-condition ,condition)
    := combinable-condition "OR" condition
    :reduce `(or ,combinable-condition ,condition)
    := combinable-condition "AND" id-or-lit
    :reduce `(and ,combinable-condition (,(car combinable-condition) ,(cadr combinable-condition) ,id-or-lit))
    := combinable-condition "OR" id-or-lit
    :reduce `(or ,combinable-condition (,(car combinable-condition) ,(cadr combinable-condition) ,id-or-lit)))

(defrule combinable-condition
    := "NOT"? simple-condition
    :reduce (if $1
                (list 'not simple-condition)
                simple-condition))

(defrule simple-condition
    := class-condition
    := relation-condition
    := sign-condition
    := "(" condition ")"
    ;; not sure if it's necessary -wcp15/7/03.
    ;; := arithmetic-expression
    )

(defrule class-condition
    := variable-identifier "IS"? "NOT"? class-type
    :reduce (if $3
                (list 'not (list 'type-of variable-identifier (make-keyword class-type)))
                (list 'type-of variable-identifier (make-keyword class-type))))

(defrule class-type
    := "NUMERIC"
    := "ALPHABETIC"
    := "ALPHABETIC-LOWER"
    := "ALPHABETIC-UPPER"
    := "DBCS")

(defun unfold-subrelations (main-relation subs)
  (destructuring-bind (main-operator main-variable other-variable) main-relation
    (declare (ignore other-variable))
    (labels ((unfold (subs)
               (if (null subs)
                   main-relation
                   (destructuring-bind (connection operator variable) (car subs)
                     (list connection
                           (list (or operator main-operator) main-variable variable)
                           (unfold (cdr subs)))))))
      (unfold subs))))

(defrule relation-condition
    ;; This is too complex
    ;; := arithmetic-expression relational-operator simple-condition
    := id-or-lit relational-operator id-or-lit subordinate-relation*
    :reduce (unfold-subrelations (list relational-operator id-or-lit id-or-lit2) subordinate-relation))

(defrule or-and
    := "OR" :reduce 'or
    := "AND" :reduce 'and)

(defrule subordinate-relation
    := or-and relational-operator? id-or-lit
    :reduce (list or-and relational-operator id-or-lit))

(defrule relational-operator
    := "IS"? relational-operator-type
    :reduce relational-operator-type)

(defrule less-than
    := "LESS" "THAN"?
    := "<")

(defrule greater-equal
    := "GREATER" "THAN"? "OR" "EQUAL" "TO"?
    := ">="
    := ">" "="
    := "NOT" "<"
    := "NOT" "LESS" "THAN"?)

(defrule less-equal
    := "LESS" "THAN"? "OR" "EQUAL" "TO"?
    := "<="
    := "<" "="
    := "NOT" ">"
    := "NOT" "GREATER" "THAN"?)

(defrule greater-than
    := "GREATER" "THAN"?
    := ">")

(defrule equal-to
    := "EQUAL" "TO"?
    := "=")

(defrule relational-operator-type
    := greater-equal
    :reduce 'cob>=
    := less-equal
    :reduce 'cob<=
    := greater-than
    :reduce 'cob>
    := less-than
    :reduce 'cob<
    := equal-to
    :reduce 'cob=
    := "NOT" equal-to
    :reduce 'cob-not=)

(defrule sign-condition
    := arithmetic-expression "IS"? "NOT"? sign-type
    :reduce (if $3
                `(not (,sign-type ,arithmetic-expression))
                `(,sign-type ,arithmetic-expression)))

(defrule sign-type
    := "POSITIVE" :reduce '>
    := "NEGATIVE" :reduce '<
    := "ZERO" :reduce '=
    := "ZEROES" :reduce '=
    := "ZEROS" :reduce '=)

(defrule procedure-name
    := paragraph-or-section-name in-section-name
    :reduce (list paragraph-or-section-name in-section-name)
    := paragraph-or-section-name
    :reduce paragraph-or-section-name)

(defrule in-section-name
    := in cobol-identifier
    :reduce cobol-identifier)

(defrule variable-identifier
    := qualified-data-name subscript-parentheses* ;; reference-modification?
    :reduce (if subscript-parentheses
                (list :aref qualified-data-name subscript-parentheses)
                qualified-data-name))

(defrule reference-modification
    := "(" leftmost-character-position ":" length? ")"
    :reduce (if length
                (list :range leftmost-character-position length)
                leftmost-character-position))

(defrule condition-name-reference
    := condition-name in-data-or-file-or-mnemonic-name* subscript-parentheses*)

(defrule in-data-or-file-or-mnemonic-name
    := in data-or-file-or-mnemonic-name)

(defrule subscript-parentheses
    := "(" subscript ")")

(defrule subscript
    := subscript-expression+)

(defrule plus-minus-integer
    := plus-or-minus integer)

(defrule subscript-expression-ambiguous
    := qualified-data-name plus-minus-integer?)

(defrule subscript-expression
    := literal
    := subscript-expression-ambiguous)

(defrule qualified-data-name
    := data-name in-data-or-file-name*
    :reduce (if in-data-or-file-name
                (list data-name in-data-or-file-name) ; incomplete -wcp15/7/03.
                data-name)
    := "ADDRESS" "OF" data-name
    :reduce (list 'address-of data-name)
    := "LENGTH" "OF" cobol-identifier
    :reduce (list 'length-of cobol-identifier))

(defrule in-data-or-file-name
    := in data-or-file-name)

(defrule leftmost-character-position
    := arithmetic-expression)

(defrule length
    := arithmetic-expression)

(defrule arithmetic-expression
    := times-div
    := times-div "+" arithmetic-expression
    :reduce `(+ ,times-div ,arithmetic-expression)
    := times-div "-" arithmetic-expression
    :reduce `(- ,times-div ,arithmetic-expression))

(defrule times-div
    := power
    := power "*" times-div
    :reduce `(* ,power ,times-div)
    := power "/" times-div
    :reduce `(/ ,power ,times-div))

(defrule power
    := plus-or-minus? basis
    := plus-or-minus? basis "**" power
    :reduce (if plus-or-minus
                `(plus-or-minus (expt basis basis2))
                `(expt basis basis2)))

(defrule plus-or-minus
    := "+"
    :reduce '+
    := "-"
    :reduce '-)

;; (defrule power-tail
;;     := "**" basis)

(defrule basis
    := literal
    := variable-identifier
    := "(" arithmetic-expression ")")

(defrule alphabet-name
    := cobol-identifier)

(defrule condition-name
    := cobol-identifier)

(defrule data-name
    := cobol-identifier)

(defrule cobol-identifier
    := identifier
    :reduce (intern (string-upcase identifier)))

(defrule file-name
    := cobol-identifier)

(defrule data-or-file-name
    := cobol-identifier)

(defrule index-name
    := cobol-identifier)

(defrule mnemonic-name
    := cobol-identifier)

(defrule data-or-file-or-mnemonic-name
    := cobol-identifier)

(defrule record-name
    := qualified-data-name)

(defrule symbolic-character
    := cobol-identifier)

(defrule library-name
    := cobol-identifier)

(defrule program-name
    := cobol-identifier
    := string)

(defrule text-name
    := cobol-identifier)

(defrule paragraph-or-section-name
    := cobol-identifier
    := integer)

(defrule computer-name
    := identifier)

(defrule environment-name
    := cobol-identifier)

(defrule assignment-name
    := cobol-identifier)

(defrule figurative-constant
    := figurative-constant-simple
    := figurative-constant-all)

(defrule figurative-constant-all
    := "ALL" literal)

(defrule literal
    := string
    := float
    := integer
    := figurative-constant)

)					; defun populate-grammar
