PL/I has no reserved words. This parser made peace with that. Mostly.
A clean-room parser for PL/I F, the 1965 IBM dialect described in IBM Operating System/360 PL/I: Language Specifications (form C28-6571). Written in OCaml with menhir. It started life as a parser for XPL and just kept going until it spoke full PL/I F: structures, PICTURE, pointers, BASED storage, FILE I/O, ON-units, the lot.
KEYWORDS: PROCEDURE;
DECLARE FORMAT FIXED;
DECLARE RECORD FIXED;
DECLARE DISPLAY FIXED;
FORMAT = 1;
RECORD = 2;
DISPLAY = FORMAT + RECORD; /* yes, those are all keywords. yes, it parses. */
RETURN;
END KEYWORDS;
PL/I reserves nothing. You can name a variable FORMAT, assign it to RECORD,
and pass it to a procedure called ENTRY, and the language just lets you. The
1965 committee looked at the concept of reserved keywords and said "no thanks."
So the parser does a little dance: the lexer eagerly turns every keyword into a
token, and then the grammar laboriously turns a big chunk of them back into
identifiers, by context, wherever a name is allowed. The lexer giveth, the
grammar taketh away. (Structural words like IF/THEN/DO/END are the one
exception — let those double as variable names and the grammar catches fire.)
Building this grammar, menhir reports 654 shift/reduce conflicts, arbitrarily resolved. That is not sloppiness. That is the bill for "every keyword is also a valid variable name," itemised. A grammar that can't tell a keyword from a name until it sees what comes next is conflict-heavy by construction, and menhir just picks the sensible branch a few hundred times. It's bloody good!
- Procedures — params, return types,
RECURSIVE, nested,END label - DECLARE —
FIXED/FLOAT/CHARACTER/BIT/POINTER, precision, arrays with bounds,INITIAL,LITERALLY, identifier lists,LIKE,DEFINED - Structures — level numbers (
1 EMPLOYEE, 2 NAME ...) - PICTURE specifications
- Storage —
STATIC/AUTOMATIC/CONTROLLED,BASED,ALLOCATE/FREE - Control flow —
IF/THEN/ELSE,DO WHILE/UNTIL/CASE/stepTO...BY,SELECT/WHEN/OTHERWISE,GO TO, labels - Expressions — arithmetic,
**, concatenation||,^not, relations, subscripts,A.Bmember access - I/O —
GET/PUT/READ/WRITE/OPEN/FILE/FORMAT - Conditions —
ON/REVERT/SIGNAL - And the headline feature: none of those names are reserved
dune build
dune test # the unit suite
dune exec bin/main.exe -- test/corpus/*.pli # parse files, report a count
dune exec bin/main.exe -- --dump path/to/prog.pli # pretty-print the ASTtest/corpus/ has small programs to poke at, including
04_no_reserved_words.pli if you want to watch the party trick run.
The grammar follows IBM's PL/I: Language Specifications, form C28-6571 (1965). That manual is IBM-copyrighted Systems Reference Library material, so it is not included in this repo. If you want to read along, the scanned PDF lives on bitsavers.
This grew out of a parser for XPL, the little PL/I-subset language compilers
were once written in. It cut its teeth on real XPL source before being pushed up
to full PL/I F. (Some XPL-isms survive as accepted synonyms, e.g. FUNCTION for
PROCEDURE, CLOSE for END.)
It parses, and it parses a genuinely large slice of the language. It is a parser — front end only, AST out, no semantic analysis or codegen. If you feed it valid PL/I F it builds you a tree; that is the whole job and it does it. It's how I made my HALMAT project work, so if you have any legacy NASA code, there's a chance it could work, as a substantial portion of it was originally built on a subset of PL/I.
Apache 2.0.