add_rule : {term_name : string, fixity : fixity, pp_elements: term_grammar.pp_element list, paren_style : term_grammar.ParenStyle, block_style : term_grammar.PhraseBlockStyle * term_grammar.block_info} -> unit
There are five components in the record argument to add_rule. The term_name component is the name of the term (whether a constant or a variable) that will be generated at the head of the function application. Thus, the term_name component when specifying parsing for conditional expressions is COND.
The following values (all in structure Parse) are useful for constructing fixity values:
val LEFT : HOLgrammars.associativity val RIGHT : HOLgrammars.associativity val NONASSOC : HOLgrammars.associativity val Binder : fixity val Closefix : fixity val Infixl : int -> fixity val Infixr : int -> fixity val Infix : HOLgrammars.associativity * int -> fixity val Prefix : int -> fixity val Suffix : int -> fixity
The remaining fixities all cause add_rule to pay due heed to the pp_elements (“parsing/printing elements”) component of the record. As far as parsing is concerned, the only important elements are TOK and TM values, of the following types:
val TM : term_grammar.pp_element val TOK : string -> term_grammar.pp_element
Closefix : [Kernel] (* no external arguments *) Prefix : [Kernel] _ (* an argument to the right *) Suffix : _ [Kernel] (* an argument to the left *) Infix : _ [Kernel] _ (* arguments on both sides *)
The remaining sorts of possible pp_element values are concerned with pretty-printing. (The basic scheme is implemented on top of a standard Oppen-style pretty-printing package.) They are
(* where type term_grammar.block_info = PP.break_style * int *) val BreakSpace : (int * int) -> term_grammar.pp_element val HardSpace : int -> term_grammar.pp_element val BeginFinalBlock : term_grammar.block_info -> term_grammar.pp_element val EndInitialBlock : term_grammar.block_info -> term_grammar.pp_element val PPBlock : term_grammar.pp_element list * term_grammar.block_info -> term_grammar.pp_element val OnlyIfNecessary : term_grammar.ParenStyle val ParoundName : term_grammar.ParenStyle val ParoundPrec : term_grammar.ParenStyle val Always : term_grammar.ParenStyle val IfNotTop : {realonly:bool} -> term_grammar.ParenStyle val AroundEachPhrase : term_grammar.PhraseBlockStyle val AroundSamePrec : term_grammar.PhraseBlockStyle val AroundSameName : term_grammar.PhraseBlockStyle val NoPhrasing : term_grammar.PhraseBlockStyle
For example, the add_infix function (q.v.) is implemented in terms of add_rule in such a way that a single token infix s, has a pp_element list of
[HardSpace 1, TOK s, BreakSpace(1,0)]
This results in chains of infixes (such as those that occur with conjunctions) that break so as to leave the infix on the right hand side of the line. Under this constraint, printing can’t break so as to put the infix symbol on the start of a line, because that would imply that the HardSpace had in fact been broken. (Consequently, if a change to this behaviour is desired, there is no global way of effecting it, but one can do it on an infix-by-infix basis by deleting the given rule (see, for example, remove_termtok) and then “putting it back” with different pretty-printing constraints.)
The PPBlock function allows the specification of nested blocks (blocks in the Oppen pretty-printing sense) within the list of pp_elements. Because there are sub-terms in all but the Closefix fixities that occur beyond the scope of the pp_element list, the BeginFinalBlock and EndInitialBlock functions can also be used to indicate the boundary of blocks whose outer extent is the term beyond the kernel represented by the pp_element list. There is an example of this below.
The possible ParenStyle values describe when parentheses should be added to terms. The OnlyIfNecessary value will cause parentheses to be added only when required to disambiguate syntax. The ParoundName will cause parentheses to be added if necessary, or where the head symbol has the given term_name and where this term is not the argument of a function with the same head name. This style of parenthesisation is used with tuples, for example. The ParoundPrec value is similar, but causes parentheses to be added when the term is the argument to a function with a different precedence level. The IfNotTop value will cause parentheses to appear whenever the term is not being printed as the “top” term. A term is considered to be “top” if it is the whole term being printed (and this is known as the “real top”), or if it occurs between two tokens that always delimit complete terms. For example, the semi-colons in a list-like form are such delimiters, as are the list-form’s left and right brackets, as are the “if” and “then” tokens in an if-then-else form. Having the realonly parameter set to true will cause parentheses whenever the context is anything not the real top, while having it set to false will cause parentheses if in neither sort of “top”. Finally, the Always value causes parentheses always to be added.
The PhraseBlockStyle values describe when pretty-printing blocks involving this term should be entered. The AroundEachPhrase style causes a pretty-printing block to be created around each term. This is not appropriate for operators such as conjunction however, where all of the arguments to the conjunctions in a list are more pleasingly thought of as being at the same level. This effect is gained by specifying either AroundSamePrec or AroundSameName. The former will cause the creation of a new block for the phrase if it is at a different precedence level from its parent, while the latter creates the block if the parent name is not the same. The former is appropriate for + and - which are at the same precedence level, while the latter is appropriate for /\. Finally, the NoPhrasing style causes there to be no block at all around terms controlled by this rule. The intention in using such a style is to have block structure controlled by the level above.
val _ = add_rule{term_name = "COND", fixity = Infix (HOLgrammars.RIGHT, 3), pp_elements = [HardSpace 1, TOK "=>", BreakSpace(1,0), TM, BreakSpace(1,0), TOK "|", HardSpace 1], paren_style = OnlyIfNecessary, block_style = (AroundEachPhrase, (PP.INCONSISTENT, 0))};
val _ = add_rule{term_name = "COND", fixity = Prefix 70, pp_elements = [PPBlock([TOK "if", BreakSpace(1,2), TM, BreakSpace(1,0), TOK "then"], (PP.CONSISTENT, 0)), BreakSpace(1,2), TM, BreakSpace(1,0), BeginFinalBlock(PP.CONSISTENT, 2), TOK "else", BreakSpace(1,0)], paren_style = OnlyIfNecessary, block_style = (AroundEachPhrase, (PP.INCONSISTENT, 0))};
if P then <very long term> else Q
The pretty-printer prefers later rules over earlier rules by default (though this choice can be changed with prefer_form_with_tok (q.v.)), so if both of these calls were made, conditional expressions would print using the if-then-else syntax rather than the _ => _ | _ syntax.
As with other functions in the Parse structure, there is a companion temp_add_rule function, which has the same effect on the global grammar, but which does not cause this effect to persist when the current theory is exported.
An Isabelle-style concrete syntax for specifying rules would probably be desirable as it would conceal the complexity of the above from most users.