summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2025-04-21 09:48:55 -0700
committerKaz Kylheku <kaz@kylheku.com>2025-04-21 09:48:55 -0700
commitf527bb871318ec5a721ccf410654fd76e4dfa271 (patch)
treea3c10176f094af31d1fb7cef0239ec065a93036c
parentb296d0a20eabd9ea19bd66d9a822452fce8038e9 (diff)
downloadtxr-f527bb871318ec5a721ccf410654fd76e4dfa271.tar.gz
txr-f527bb871318ec5a721ccf410654fd76e4dfa271.tar.bz2
txr-f527bb871318ec5a721ccf410654fd76e4dfa271.zip
infix: revise auto-detection algorithm.
* stdlib/infix.tl (detect-infix): Redesign. New algorithm looks only at the first two or three elements. Arguments that are not operators are only considered operands if they don't have function bindings. This is important because sometimes the logic is applied to the arguments in a DWIM bracket form, like [apply / args], which we don't want to treat as (/ apply args). * tests/012/infix.tl: New test. * txr.1: Redocumented.
-rw-r--r--stdlib/infix.tl32
-rw-r--r--tests/012/infix.tl12
-rw-r--r--txr.1101
3 files changed, 95 insertions, 50 deletions
diff --git a/stdlib/infix.tl b/stdlib/infix.tl
index 6a19f957..f455a755 100644
--- a/stdlib/infix.tl
+++ b/stdlib/infix.tl
@@ -220,18 +220,26 @@
(first nodestack)))
(defun-match detect-infix
- (((@(@o [ifx-uops]) . @rest))
- (or (neq o.sym o.lispsym)
- (find-if [orf ifx-uops ifx-ops] rest)))
- ((@(require (@nil [. @toks] . @nil)
- (or (atom toks)
- (null (cdr toks))
- (detect-infix toks))))
- t)
- (((@nil @y . @rest))
- (or [ifx-uops y] [ifx-ops y]
- (find-if [orf ifx-uops ifx-ops] rest)))
- ((@nil)))
+ (((@x @y . @rest))
+ (let* ((xu [ifx-uops x])
+ (yu [ifx-uops y])
+ (yo [ifx-ops y])
+ (yb (if-match [. @toks] y
+ (or (and (consp toks)
+ (null (cdr toks)))
+ (detect-infix toks))))
+ (ya (and (not yu) (not yo) (not (fboundp y))))
+ (xa (and (not xu) (not (fboundp x)))))
+ (or
+ (and xa (or yo yb))
+ (and xu ya (neq xu.sym xu.lispsym))
+ (if-match (@z . @nil) rest
+ (let* ((zu [ifx-uops z])
+ (zo [ifx-ops z])
+ (za (and (not zu) (not zo) (not (fboundp z)))))
+ (or
+ (and xu yu (or zu za))
+ (and xu ya zo))))))))
(defun infix-expand-hook (exp env type-sym)
(ignore env)
diff --git a/tests/012/infix.tl b/tests/012/infix.tl
index 0da0abeb..10b98930 100644
--- a/tests/012/infix.tl
+++ b/tests/012/infix.tl
@@ -126,9 +126,9 @@
data)))
(msstest
- (fft #(nil 0.0 0.0 0.0 0.0) 2 1)
- #(nil 0.0 0.0 0.0 0.0)
- (fft #(nil 1.0 1.0 1.0 1.0) 2 1)
- #(nil 2.0 2.0 0.0 0.0)
- (fft #(nil 0 1 0 2 0 3 0 4) 4 1)
- #(nil 0.0 10.0 2.0 -2.0 0.0 -2.0 -2.0 -2.0))
+ (fft #(nil 0.0 0.0 0.0 0.0) 2 1) #(nil 0.0 0.0 0.0 0.0)
+ (fft #(nil 1.0 1.0 1.0 1.0) 2 1) #(nil 2.0 2.0 0.0 0.0)
+ (fft #(nil 0 1 0 2 0 3 0 4) 4 1) #(nil 0.0 10.0 2.0 -2.0 0.0 -2.0 -2.0 -2.0))
+
+(msstest
+ (map (lop / 4) #(10 20 30)) #(2.5 5.0 7.5))
diff --git a/txr.1 b/txr.1
index 52c3c207..fc991584 100644
--- a/txr.1
+++ b/txr.1
@@ -54209,51 +54209,84 @@ Pass the form through; decline to perform a transformation.
A form is recognized as applicable for
.code parse-infix
-if any of these three conditions hold:
+by examining the first two or three elements of the form according
+to the following patterns. In these patterns, the letter
+.code u
+denotes a prefix (unary) operator; the letter
+.code o
+denotes an infix operator and the letter
+.code A
+denotes an argument which is not the name of a function or operator.
+Additionally, the letter
+.code u*
+indicates a prefix operator which is not also the name of a function.
+The patterns which indicate a form for processing via
+.code parse-infix
+are:
.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.
-.IP 3
-The form contains at least two elements, the second of which is a
-DWIM brackets form, which encloses either a single element or
-multiple elements which, if taken as an ordinary form, satisfy
-any of these three rules.
+.coIP "(A o ...)"
+A two-element pattern beginning with an argument followed by an infix operator.
+.coIP "(A [...] ...)
+A pattern beginning with an argument followed by a bracket expression
+is treated as infix, if the bracket expression encloses exactly one
+element or else if the content of the bracket expression recursively meets
+these rules, after conversion to ordinary expression. That is to say, for
+the purposes of applying these rules, the bracket expression is turned into the
+ordinary expression
+.code "(...)"
+with the same content. If this converted expression in indicated as infix,
+then the parent expression is indicates as infix, otherwise not.
+.coIP (u u u ...)
+Three consecutive leading prefix operators.
+.coIP (u u A ...)
+Two consecutive leading prefix operators followed by an argument.
+.coIP (u A o ...)
+A prefix operator followed by an argument, followed by an infix
+operator.
+.coIP (u* A ...)
+A prefix operator which is not also the name of a function,
+followed by an argument.
.RE
For instance, the form
.code "(++ x)"
-is recognized as an infix expression by rule 1 above, because
+is recognized as an infix expression by the last rule above, since
it begins with a prefix operator whose name isn't the name of
-a math function.
+a math function, followed by the argument
+.code x
+which is not the name of a function.
-In contrast
+The valid infix expression
.code "(sin x)"
-is not recognized as infix by rule 1, because since
+is not recognized as infix. Though
.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.
+is a prefix operator, it is one which is also a Lisp function,
+and thus this does not match the
+.code "(u* A ...)"
+pattern. It doesn't match the
+.code "(u A o ...)"
+pattern due to a missing third element that wold correspond to
+.codn o .
+Though not treated as infix, this expression does not require it,
+because
+.code parse-infix
+will return an identical expression.
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.
+is recognized as infix by the
+.code "(A o ...)"
+pattern. The form contains at least two elements. The first is
+an argument and the second an infix operator.
The expression
.code "(a [i])"
-is recognized by rule 3, because the second element is a square
-bracket expression containing one element.
+is recognized by the
+.code "(A [...] ...)"
+pattern, and its additional constraints, because the first element is an
+argument and the second element is a square bracket expression containing
+exactly one element.
The
.code parse-infix
function will transform it to
@@ -54267,13 +54300,17 @@ form, turning it into
The expression
.code "(a [i + j])"
-is also recognized by rule 3, because the second element is a square
+is also recognized by the pattern
+.code "(A [...] ...)"
+and its additional constraints, because the second element is a square
bracket expression, whose elements, if they are isolated as the expression
.codn "(i + j)" ,
-satisfy one of the three rules, namely rule 2.
+satisfy the
+.code "(A o ...)"
+pattern.
The
.code parse-infix
-function will transform it to
+function will transform it this expression
.codn "[a (i + j)]" ,
in which recursive processing by the
.code ifx