summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2025-04-09 13:18:15 -0700
committerKaz Kylheku <kaz@kylheku.com>2025-04-09 13:18:15 -0700
commitdefe2b676c775705179cd978250b87bfb20675ba (patch)
treeb5be526e948eb2120c5ed409df85dfae730d0577
parent156d1bfac12f59a6b60f3afbfae1f01e60f63dbf (diff)
downloadtxr-defe2b676c775705179cd978250b87bfb20675ba.tar.gz
txr-defe2b676c775705179cd978250b87bfb20675ba.tar.bz2
txr-defe2b676c775705179cd978250b87bfb20675ba.zip
infix: revise auto-detection and parsing.
* stdlib/infix.tl (parse-infix): Change how we treat fn (arg ...) and elem [arg ...] forms. These now translate to (fn (arg ...)) and [elem (arg ...)] rather than (fn arg ...) and [elem arg ...]. (detect-infix): detect certain op [arg ...] forms as infix. (infix-expand-hook): Revise detection logic to handle bracket expression forms, and parenthesized single terms. The latter are needed to reduce [elem (atom)] to [elem atom]. * tests/012/infix.tl: Fix up some tests. * txr.1: Documented.
-rw-r--r--stdlib/infix.tl37
-rw-r--r--tests/012/infix.tl18
-rw-r--r--txr.1190
3 files changed, 203 insertions, 42 deletions
diff --git a/stdlib/infix.tl b/stdlib/infix.tl
index f783bf9d..1469e1e7 100644
--- a/stdlib/infix.tl
+++ b/stdlib/infix.tl
@@ -171,7 +171,7 @@
(push (list oper.lispsym x y) nodestack))))))
(while-true-match-case exp
((@[[chain ifx-uops .?funp] @op] (@arg . @args) . @rest)
- (set exp ^((,op ,arg ,*args) ,*rest)))
+ (set exp ^((,op (,arg ,*args)) ,*rest)))
((@[[chain ifx-uops .?funp] @op] () . @rest)
(set exp ^((,op) ,*rest)))
((@(with @(@o1 [(if ucheck ifx-uops ifx-ops)]) @prec o1.prec) . @rest)
@@ -200,9 +200,9 @@
((@(@o [ifx-ops]) . @nil)
(infix-error oexp "operator ~s needs left operand" o.sym))
((@op [. @args] . @rest)
- (set exp ^([,op ,*args] ,*rest)))
+ (set exp ^([,op ,args] ,*rest)))
((@op (@arg . @args) . @rest)
- (set exp ^((,op ,arg ,*args) ,*rest)))
+ (set exp ^((,op (,arg ,*args)) ,*rest)))
((@op () . @rest)
(set exp ^((,op) ,*rest)))
((@tok . @rest)
@@ -223,6 +223,11 @@
(((@(@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)))
@@ -232,12 +237,26 @@
(ignore env)
(cond
((eq type-sym :macro) exp)
- ((detect-infix exp) (parse-infix exp))
- (t (if-match (@(require (@x @y . @rest)
- (and (not (fboundp x)) (fboundp y))))
- exp
- ^(,y ,x ,*rest)
- exp))))
+ ((and (meq type-sym :fun nil) (detect-infix exp))
+ (parse-infix exp))
+ ((match-case exp
+ ([] exp)
+ ([@nil] exp)
+ ([. @toks]
+ (let ((iexp (infix-expand-hook toks env nil)))
+ (if (eq iexp toks)
+ exp
+ ^[,iexp])))
+ (@(require (@tok)
+ (null type-sym)
+ (or (not (bindable tok))
+ (lexical-var-p env tok)
+ (boundp tok)))
+ tok)
+ (@(require (@x @y . @rest)
+ (and (not (fboundp x)) (fboundp y)))
+ ^(,y ,x ,*rest))))
+ (exp)))
(defmacro usr:ifx (. body)
^(expander-let ((*expand-hook* [expand-hook-combine infix-expand-hook
diff --git a/tests/012/infix.tl b/tests/012/infix.tl
index 79767f31..67b0373c 100644
--- a/tests/012/infix.tl
+++ b/tests/012/infix.tl
@@ -35,8 +35,8 @@
^(a ,fn) :error
^(,fn) :error
^(,fn b) ^(,fn b)
- ^(,fn (:)) ^(,fn :)
- ^(,fn (: :)) ^(,fn : :)
+ ^(,fn (:)) ^(,fn (:))
+ ^(,fn (: :)) ^(,fn (: :))
^(,fn a + b) ^(,fn (+ a b))
^(,fn ,fn ,fn a + b) ^(,fn (,fn (,fn (+ a b))))
^(,fn a + b + ,fn b + c) ^(+ (,fn (+ a b)) (,fn (+ b c)))
@@ -56,15 +56,15 @@
(mtest-pif
([i]) [i]
- (a[i]) [a i]
- (a[i][j]) [[a i] j]
- (a[i][j][k]) [[[a i] j] k])
+ (a[i]) [a (i)]
+ (a[i][j]) [[a (i)] (j)]
+ (a[i][j][k]) [[[a (i)] (j)] (k)])
(mtest-pif
- (x ** a[i][j][k]) (expt x [[[a i] j] k])
- (x ** a[i][j][k] ++) (expt x (pinc [[[a i] j] k]))
- (x ** -- a[i][j][k]) (expt x (dec [[[a i] j] k]))
- (x ** -- a[i + y][j ++][-- k]) (expt x (dec [[[a i + y] j ++] -- k])))
+ (x ** a[i][j][k]) (expt x [[[a (i)] (j)] (k)])
+ (x ** a[i][j][k] ++) (expt x (pinc [[[a (i)] (j)] (k)]))
+ (x ** -- a[i][j][k]) (expt x (dec [[[a (i)] (j)] (k)]))
+ (x ** -- a[i + y][j ++][-- k]) (expt x (dec [[[a (i + y)] (j ++)] (-- k)])))
(ifx
(defun quadratic-roots (a b c)
diff --git a/txr.1 b/txr.1
index 5db0bbef..e33eefac 100644
--- a/txr.1
+++ b/txr.1
@@ -54087,15 +54087,24 @@ which will be transformed to
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.
+macro's expansion hook examines each form and decides among four
+alternatives:
+.RS
+.IP 1.
+Transform the form through
+.codn parse-infix .
+.IP 2.
+Recognize the form as one of several cases to be rewritten into
+a different form by the auto-detection logic itself.
+.IP 3.
+Recognize the form as "phony infix" and apply a trivial transformation.
+.IP 4.
+Pass the form through; decline to perform a transformation.
+.RE
A form is recognized as applicable for
.code parse-infix
-if either of these following two conditions hold:
+if any of these three conditions hold:
.RS
.IP 1.
@@ -54106,6 +54115,11 @@ 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.
.RE
For instance, the form
@@ -54131,8 +54145,118 @@ The expression
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
+The expression
+.code "(a [i])"
+is recognized by rule 3, because the second element is a square
+bracket expression containing one element.
+The
+.code parse-infix
+function will transform it to
+.codn "[a (i)]" ,
+in which recursive processing by
+.code ifx
+will discover the
+.code "(i)"
+form, turning it into
+.codn i .
+
+The expression
+.code "(a [i + j])"
+is also recognized by rule 3, 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.
+The
+.code parse-infix
+function will transform it to
+.codn "[a (i + j)]" ,
+in which recursive processing by the
+.code ifx
+expansion hook will replace
+.code "(i + j)"
+by
+.codn "(+ i j)" .
+
+A form not eligible for
+.code parse-infix
+may be transformed by infix the auto-detection logic. Three cases
+are treated this way:
+
+.RS
+.IP 1.
+A square brackets form containing one element is left untransformed.
+.IP 2.
+A square brackets form is temporarily converted to an ordinary
+form, and processed recursively through the auto-detection rules.
+If the recursive rules provide a transformation, then the transformed form is
+inserted as the single element of a new square brackets form and
+returned instead of the original square brackets form. Otherwise no
+transformation takes place to the original square
+.IP 3.
+An ordinary compound form containing one element is replaced by
+that element if these conditions hold: the form is not a valid function
+call form, and if the element is a bindable symbol, that symbol
+has a binding as a lexical variable or a global variable.
+.RE
+
+For instance, the form
+.code "[a + b]"
+falls under rule 2, and is temporarily considered to be
+.codn "(a + b)" .
+That expression is detected for infix transformation, and translated to
+.codn "(+ a b)" .
+Therefore, the original
+.code "[a + b]"
+form is replaced by
+.codn "[(+ a b)]" .
+
+After this replacement,
+.code "[(+ a b)]"
+is again subject to the auto-detection logic. This time it is recognized
+as a square brackets form which contains one element, and is left
+untransformed.
+
+The form
+.code "(42)"
+is replaced by
+.code 42
+under rule 3.
+
+The form
+.code "(list)"
+falls under rule 3, but doesn't meet its conditions, assuming that the
+.code list
+symbol refers to the standard function. The form is then a valid function
+call.
+
+In the form
+.codn "(let ((a 3)) (a))" ,
+the subform
+.code "(a)"
+meets rule 3, provided there isn't a function named
+.code a
+in scope. In that case it is transformed to
+.codn a .
+
+The form
+.code "((a [i + 1]))"
+is transformed to
+.code "(a [i + 1])"
+by rule 3 and processed again. This time it falls under one of the earlier
+rules for applying
+.code parse-infix
+which will transform it to
+.codn "[a (i + 1)]" .
+Recursive processing by the
+.code ifx
+expansion hook will then turn
+.code "(i + 1)"
+into
+.codn "(+ i 1)" .
+
+A form not falling into any of the above rules 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
@@ -54325,19 +54449,37 @@ symbols. They are recognized as special patterns within
and effectively given the highest precedence.
Whenever a prefix operator which is the name of a built-in Math
-function, in other words a
+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)" .
+operator given in the precedence table above) or a non-operator element, is
+followed by a compound expression, it is turned into a function call whose
+single argument is that expression. For example, the tokens
+.code "sin a + a"
+become
+.codn "(sin (a + a))" ,
+and
+.code "list a + b"
+becomes
+.codn "(list (a + b))" .
+This is because
+.code sin
+is a prefix operator that is also a function, and
+.code list
+is a non-operator term.
+
+In contrast, the sequence
+.code "- (a + b)"
+isn't recognized this way because
+.code -
+is a prefix operator that isn't a
+.metn mathfn ;
+this expression becomes
+.codn "(- (+ a b))" .
+The sequence
+.code "&& (a + b)"
+is a syntax error because
+.code &&
+is an infix operator, requiring a left operand.
Whenever a non-operator expression element is followed by
a bracket expression, it is transformed by inserting the
@@ -54345,13 +54487,13 @@ element into the bracket expression. For instance the two
elements
.code "a [i]"
turn into
-.codn "[a i]" .
+.codn "[a (i)]" .
This is applied repeatedly; for instance the sequence
-.code "a [i] [j]"
+.code "a [i] [j + 1]"
first transforms to
-.code "[a i] [j]"
+.code "[a (i)] [j + 1]"
and then by the same rule to
-.codn "[[a i] j]" .
+.codn "[[a (i)] (j + 1)]" .
.coNP Function @ parse-infix
.synb