summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2025-04-06 21:00:15 -0700
committerKaz Kylheku <kaz@kylheku.com>2025-04-06 21:00:15 -0700
commit15824c1a29cf2ee7cf8909eceeb810c77149b29f (patch)
treeb875a7078674aa449d49f8de9ec9bf088af4c083
parent3aa1225433518f1cda525225cb3f29ebf73b38d9 (diff)
downloadtxr-15824c1a29cf2ee7cf8909eceeb810c77149b29f.tar.gz
txr-15824c1a29cf2ee7cf8909eceeb810c77149b29f.tar.bz2
txr-15824c1a29cf2ee7cf8909eceeb810c77149b29f.zip
infix: document.
* txr.1: New major section Infix Syntax.
-rw-r--r--txr.1355
1 files changed, 355 insertions, 0 deletions
diff --git a/txr.1 b/txr.1
index b9a68529..3d4d367d 100644
--- a/txr.1
+++ b/txr.1
@@ -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