diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2025-04-09 13:18:15 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2025-04-09 13:18:15 -0700 |
commit | defe2b676c775705179cd978250b87bfb20675ba (patch) | |
tree | b5be526e948eb2120c5ed409df85dfae730d0577 | |
parent | 156d1bfac12f59a6b60f3afbfae1f01e60f63dbf (diff) | |
download | txr-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.tl | 37 | ||||
-rw-r--r-- | tests/012/infix.tl | 18 | ||||
-rw-r--r-- | txr.1 | 190 |
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) @@ -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 |