diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2025-04-06 21:00:15 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2025-04-06 21:00:15 -0700 |
commit | 15824c1a29cf2ee7cf8909eceeb810c77149b29f (patch) | |
tree | b875a7078674aa449d49f8de9ec9bf088af4c083 | |
parent | 3aa1225433518f1cda525225cb3f29ebf73b38d9 (diff) | |
download | txr-15824c1a29cf2ee7cf8909eceeb810c77149b29f.tar.gz txr-15824c1a29cf2ee7cf8909eceeb810c77149b29f.tar.bz2 txr-15824c1a29cf2ee7cf8909eceeb810c77149b29f.zip |
infix: document.
* txr.1: New major section Infix Syntax.
-rw-r--r-- | txr.1 | 355 |
1 files changed, 355 insertions, 0 deletions
@@ -53959,6 +53959,361 @@ which must be an integer. .um logcount .um bitset +.SS* Infix Syntax + +.NP* Overview + +\*(TL provides a way to express calculations using a syntax which +more closely resembles conventional arithmetic syntax, consisting +of prefix, infix and suffix operators arranged on different levels +of precedence. + +Using infix syntax is opt-in in \*(TL. The +.code ifx +macro creates a lexical scope in which the use of infix syntax is automatically +detected in every form (evaluated expression), at any nesting level. Whenever +any form is automatically detected to be infix, it is transformed to +conventional Lisp syntax. + +Infix syntax is transformed to conventional notation by the function +.codn parse-infix . +This function does not recurse into sub-expressions: it scans a list of +atoms, considering them to be the tokens of infix syntax. + +The +.code ifx +macro establishes an expansion hook as if by binding the variable +.codn *expander-hook* . +Whenever the expansion hook transforms a form, the expression's +constituent forms are then recursively visited, and subject to +automatic infix expansion. + +Thus, use of infix can be freely mixed with standard Lisp notation. + +For instance, the following is possible: + +.verb + (ifx (let ((a 3) (b 4)) + ((* a a) + (* b b)))) + -> 25 +.brev + +The expression +.code "((* a a) + (* b b))" +is recognized and treated as infix. The +.code parse-infix +function is invoked, transforming the expression into +.code "(+ (* a a) (* b b))" +without treating the subexpressions +.code "(* a a)" +and +.codn "(* a b)" . + +The macro-expansion code walk then recurses into the transformed +expression, where it encounters these subexpressions. They are +identified as standard syntax and not processed as infix. + +The following is also possible. + +.verb + (ifx (let ((a 3) (b 4)) + (a * a + (b * b))))) + -> 25 +.brev + +Here, the +.code "(b * b)" +is deliberately parenthesized, even though this is not necessary. +In this situation, the expand hook established by +.code ifx +will detect the expression as infix, like before, and +process it through +.codn parse-infix . +That function produces +.codn "(+ (* a a) (b * b))" : +the multiplicative expression on the left hand of the +.code + +operator is treated, as is that operator itself. +The right operand is a compound subexpression represented +as a list, and therefore left untransformed. +The macro-expander will then visit +.codn "(* a a)" , +for which no infix processing takes place, and also +.codn "(b * b)" , +which will be transformed to +.codn "(* b b)" . + +.NP* Infix Auto-Detection Algorithm + +The +.code ifx +macro's expansion hook examines each form and decides among three +alternatives: transform the form through +.codn parse-infix ; +recognize the form as "phony infix" and apply a trivial transformation; +or else not to perform a transformation. + +A form is recognized as applicable for +.code parse-infix +if either of these following two conditions hold: + +.RS +.IP 1. +The first element of the form is a prefix operator which is not the name of a +built-in math function; or else the first element is a operator which is also +the name of a built-in math function, and at least one of the remaining +arguments is an operator. +.IP 2 +The form form contains at least two elements, and at least one operator +occurs among the second and subsequent arguments. +.RE + +For instance, the form +.code "(++ x)" +is recognized as an infix expression by rule 1 above, because +it begins with a prefix operator whose name isn't the name of +a math function. + +In contrast +.code "(sin x)" +is not recognized as infix by rule 1, because since +.code sin +is a prefix operator which is also the name of a math library +function, there is a requirement that there be additional arguments +at least one of which is an operator. There are no such arguments. +Note that, in fact, this expression does not require transformation; +though it is a valid infix expression that could be parsed by +.codn parse-infix , +it is also valid Lisp. + +The expression +.code "(a + a)" +is recognized as infix by rule 2 above. The form contains at least +two elements. The second element is an operator, which is sufficient. + +A form is recognized as "phony infix" if: it consists of at least +two elements, the first element is not not an object satisfying the +.code fboundp +function, but the second element +.B is +an object satisfying that function. When a form is thus recognized, +it is transformed by exchanging the first and second position. +This is a fallback strategy applied by the +.code ifx +macro's expander hook. If an expression isn't recognized as infix +by the above two rules, it is tried as a phony infix expression. +Phony infix allows expressions like +.code "(a cons b)" +to be transformed to +.code "(cons a b)" +in spite of the +.code cons +function not being registered as an infix operator. + +Only compound expressions are recognized as infix. A sequence +of atoms like +.code "a * b" +isn't an expression. It may be a subexpression of an infix +expression, but a whole infix expression is always a Lisp list, +which means that it is notated with parentheses: +.codn "(a * b)" . +Informally speaking, the Infix Syntax feature does not eliminate the +outer parentheses. + +.NP* Operator Table + +.TS +tab(!); +l l l l. +Operators!Class!Precedence!Associativity +mathfn \f[4]=\f[]!prefix!0!right +\f[4]:=\f[]!infix!1!right +\f[4]or\f[]!infix!2!left +\f[4]and\f[]!infix!3!left +\f[4]not\f[]!prefix!3!right +\f[4]< > <= >= =\f[]!infix!4!left +\f[4]eq eql equal\f[]!!! +\f[4]neq neql nequal\f[]!!! +\f[4]+= -=\f[]!infix!5!left +\f[4]+ -\f[]!infix!6!left +\f[4]+ -\f[]!prefix!7!right +\f[4]* /\f[]!infix!8!left +\f[4]**\f[]!infix!9!right +\f[4]++ --\f[]!prefix!10!right +\f[4]++ --\f[]!postfix!11!left +e1\f[4][\f[]e2\f[4]]\f[]!postfix!12!left-to-right +mathfn\f[4](\f[]...\f[4])\f[]!!! +.TE + +In this table, +.meta e1 +and +.meta e2 +represent any subexpression. + +The operator denoted as +.meta mathfn +refers to any one of these math functions, which are either unary +or can be invoked with one argument: + +.verb + abs signum isqrt square zerop plusp minusp evenp oddp sin cos tan asin + acos atan log log2 log10 exp sqrt width logcount cbrt erf erfc exp10 + exp2 expm1 gamma lgamma log1p logb nearbyint rint significand tgamma + tofloat toint trunc floor ceil round lognot +.brev + +The +.code = +prefix operator found at precedence level 0 denotes the +.code identity +function, whereas its infix counterpart at level 4 denotes the same-named +.code = +function. The prefix +.code = +is provided for situations in which the +.code ifx +macro is unable to auto-detect an infix expression. An example of +this is an expression which uses only the precedence 12 array +referencing notation: +.codn "(= a[i][j])" , +where without the operator, the expression +.code "(a[i][j])" +would not be detected as infix, since it doesn't contain an +infix operator: it consists of the symbol +.code a +and the two compound expressions +.code "(dwim i)" +and +.codn "(dwim j)" . +None of these three elements is an operator operators; +however, if the prefix operator +.code = +is inserted in front, the resulting expression is then recognized +as infix by rule 1. + +Other than as noted above, in all cases when prefix, infix or postfix +operators have names which correspond to functions, they are +transformed to standard syntax by these patterns: + +.verb + a fn b -> (fn a b) + fn b -> (fn b) + a fn -> (fn a) +.brev + +The following operators which are not named directly after Lisp +macros or functions, are treated according to the following patterns: + +.verb + a := b -> (set a b) + a += b -> (inc a b) + a +- b -> (dec a b) + ++ b -> (inc b) + -- b -> (dec b) + a ++ -> (pinc a) + b ++ -> (pinc b) + a ** b -> (expt a b) +.brev + +The function calls and array notations are not represented by operator +symbols. They are recognized as special patterns within +.code parse-infix +and effectively given the highest precedence. + +Whenever a prefix operator which is the name of a built-in Math +function, in other words a +.meta mathfn +operator given in the precedence table above, is followed by +a compound expression, it is merged with that expression by +inserting it as the head symbol of that expression. +The result may be a Lisp expression, such as when the two elements +.code "sin (a)" +are combined to form +.codn "(sin a)" , +or it may be an infix expression, exemplified by +.code "sin (a + a)" +transforming to +.codn "(sin a + a)" . + +Whenever a non-operator expression element is followed by +a bracket expression, it is transformed by inserting the +element into the bracket expression. For instance the two +elements +.code "a [i]" +turn into +.codn "[a i]" . +This is applied repeatedly; for instance the sequence +.code "a [i] [j]" +first transforms to +.code "[a i] [j]" +and then by the same rule to +.codn "[[a i] j]" . + +.coNP Function @ parse-infix +.synb +.mets (parse-infix << list ) +.syne +.desc +The +.code parse-infix +function converts a list of elements representing an infix +expression into the Lisp expression they imply, according to the +descriptions of the previous Operator Table section, +and returns that expression. + +If +.meta list +contains invalid syntax, an error exception is thrown as if by +.codn compile-error . +The following situations are erroneous: an infix operator is +missing a left or right operand; a prefix or postfix operator +is missing an operand; an operator is missing between +consecutive operands. + +Elements of +.meta list +which are compound expressions are not recursively processed by +.codn parse-infix . + +.coNP Macro @ ifx +.synb +.mets (ifx << form *) +.syne +.desc +The +.code ifx +macro is similar to +.code progn +in the sense that it evaluates zero or more +.metn form s, +returning the value of the last one, or +.code nil +if no +.metn form s +are specified. + +Additionally, +.code ifx +establishes an environment in which forms that are expressed +in infix, or else "phony infix", are automatically recognized +and converted into Lisp expressions. + +The recognition takes place according to the descriptions +given in the previous section titled Infix Auto-Detection Algorithm. +The concept and treatment "phony infix" is described there. + +Expressions which are recognized as infix are transformed via the +.code parse-infix +function. Then the output of +.code parse-infix +is further traversed by the macro expander, and since the environment +established by +.code ifx +continues to apply, infix subexpressions produced by +.code parse-infix +are recursively detected and expanded. + .SS* Enumerated Constants The enumerated constants module provides ways for defining |