List of syntaxes objects becomes syntax of a list of objects when porting code from Racket/R6RS to Racket

hello,

i'm having a bug i have found no explanation for now....

I'm porting my code from Racket/R6RS to Racket.

For now i just have to translate R6RS library into Racket module for each files , so there is only the headers that change.

in Racket/R6RS i will use library and in Racket module and other things provide instead of export , require versus import etc....

for example, for the last modified file:

i have this R6RS code of a library:

#!r6rs


(library (nfx) ; R6RS


  (export $nfx$)

  (import (rnrs base (6))
	  (for (rnrs base (6)) expand) ; import at expand phase (not run phase)
	  (for (rnrs syntax-case (6)) expand)
	  (for (Scheme+R6RS n-arity) expand) ; at phase: 1; the transformer environment
	  (for (Scheme+R6RS infix-with-precedence-to-prefix) expand) ; at phase: 1; the transformer environment
	  (for (Scheme+R6RS operators-list) expand);; at phase: 1; the transformer environment
	  (for (only (rnrs io simple (6)) display newline) expand)
	  )


  


  
;; Welcome to DrRacket, version 8.13 [cs].
;; Language: r6rs, with debugging; memory limit: 8192 MB.
;; > ($nfx$ 3 * 5 + 2)
;; $nfx$ : parsed-args={.#<syntax:15-interactions from an unsaved editor:3:15 +> {.#<syntax:15-interactions from an unsaved editor:3:11 *> .#<syntax:15-interactions from an unsaved editor:3:9 3> .#<syntax:15-interactions from an unsaved editor:3:13 5>} .#<syntax:15-interactions from an unsaved editor:3:17 2>}
;; 17

(define-syntax $nfx$

  (lambda (stx)
    
    (syntax-case stx ()

      ;; note that to have $nfx$ called you need at minimum to have 2 different operator causing an operator precedence question
      ;; and then at least those 2 operators must be between operands each, so there is a need for 3 operand
      ;; the syntax then looks like this : e1 op1 e2 op2 e3 ..., example : 3 * 4 + 2
      (($nfx$ e1 op1 e2 op2 e3 op ...) ; note: i add op because in scheme op ... could be non existent

       ;;#'(list e1 op1 e2 op2 e3 op ...)
       
	 (with-syntax ;; let
			 
		       ((parsed-args
			 ;; TODO : make n-arity for <- and <+ only (because could be false with ** , but not implemented in n-arity for now)
			 (n-arity ;; this avoid : '{x <- y <- z <- t <- u <- 3 * 4 + 1}
			   ;; SRFI-105.scm : !0 result = (<- (<- (<- (<- (<- x y) z) t) u) (+ (* 3 4) 1)) ;; fail set! ...
			   ;; transform in : '(<- x y z t u (+ (* 3 4) 1))
			   (!0-generic #'(e1 op1 e2 op2 e3 op ...) ; apply operator precedence rules
				       infix-operators-lst-for-parser-syntax
				       ;;(get-infix-operators-lst-for-parser-syntax)
				       (lambda (op a b) (list op a b))))))
		       (display "$nfx$ : parsed-args=") (display #'parsed-args) (newline)
		       #'parsed-args)))))

) ; end library


and i port it in a Racket module:

(module nfx racket


  (provide $nfx$)

  ;; (import (rnrs base (6))
  ;; 	  (for (rnrs base (6)) expand) ; import at expand phase (not run phase)
  ;; 	  (for (rnrs syntax-case (6)) expand)
  ;; 	  (for (Scheme+R6RS n-arity) expand) ; at phase: 1; the transformer environment
  ;; 	  (for (Scheme+R6RS infix-with-precedence-to-prefix) expand) ; at phase: 1; the transformer environment
  ;; 	  (for (Scheme+R6RS operators-list) expand);; at phase: 1; the transformer environment
  ;; 	  (for (only (rnrs io simple (6)) display newline) expand)
  ;; 	  )

  (require (for-syntax Scheme+/n-arity)
	   (for-syntax Scheme+/infix-with-precedence-to-prefix)
	   (for-syntax Scheme+/operators-list))

  


  
;; Welcome to DrRacket, version 8.13 [cs].
;; Language: r6rs, with debugging; memory limit: 8192 MB.
;; > ($nfx$ 3 * 5 + 2)
;; $nfx$ : parsed-args={.#<syntax:15-interactions from an unsaved editor:3:15 +> {.#<syntax:15-interactions from an unsaved editor:3:11 *> .#<syntax:15-interactions from an unsaved editor:3:9 3> .#<syntax:15-interactions from an unsaved editor:3:13 5>} .#<syntax:15-interactions from an unsaved editor:3:17 2>}
;; 17

(define-syntax $nfx$

  (lambda (stx)
    
    (syntax-case stx ()

      ;; note that to have $nfx$ called you need at minimum to have 2 different operator causing an operator precedence question
      ;; and then at least those 2 operators must be between operands each, so there is a need for 3 operand
      ;; the syntax then looks like this : e1 op1 e2 op2 e3 ..., example : 3 * 4 + 2
      (($nfx$ e1 op1 e2 op2 e3 op ...) ; note: i add op because in scheme op ... could be non existent

       
	 (with-syntax ;; let
			 
		       ((parsed-args
			 ;; TODO : make n-arity for <- and <+ only (because could be false with ** , but not implemented in n-arity for now)
			 (n-arity ;; this avoids : '{x <- y <- z <- t <- u <- 3 * 4 + 1}
			   ;; SRFI-105.scm : !0 result = (<- (<- (<- (<- (<- x y) z) t) u) (+ (* 3 4) 1)) ;; fail set! ...
			   ;; transform in : '(<- x y z t u (+ (* 3 4) 1))
			   (!0-generic #'(e1 op1 e2 op2 e3 op ...) ; apply operator precedence rules
				       infix-operators-lst-for-parser-syntax
				       ;;(get-infix-operators-lst-for-parser-syntax)
				       (lambda (op a b) (list op a b))))))
	   
	   (display "$nfx$ : parsed-args=") (display #'parsed-args) (newline)
	   #'parsed-args)))))

) ; end module

the conclusion of this is that i did not changed a single line of the procedures or macro that where defined. They are the same in the two codes and in all the pair of files ,one being in R6RS the new one in Racket.

Now the weird thing: when executed the R6RS code works of course but not the Racket one.

When i trace the problem i come immediately to this point :
what was in R6RS a list of syntaxes objects has became in Racket a syntax of a list of objects. And this cause problem when i want to reverse the list.

i can just provide the outputs of codes to show it:

With the Racket/R6RS version the result of 3 * 5 + 2 = 17 is well computed as the value of terms is :

(.#<syntax:15-interactions from an unsaved editor:7:9 3> .#<syntax:15-interactions from an unsaved editor:7:11 *> .#<syntax:15-interactions from an unsaved editor:7:13 5> .#<syntax:15-interactions from an unsaved editor:7:15 +> .#<syntax:15-interactions from an unsaved editor:7:17 2>)

as show in the screenshot below:

but in Racket i got an error because of a different value for terms :

#<syntax:Dropbox/git/Scheme-PLUS-for-Racket/main/Scheme-PLUS-for-Racket/nfx.rkt:67:41 (3 * 5 + 2)>

and also the same code give the good result in Guile or Kawa...

GNU Guile 3.0.9
Copyright (C) 1995-2023 Free Software Foundation, Inc.

Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.

Enter `,help' for help.
scheme@(guile-user)> (use-modules (nfx))
;;; note: source file /usr/share/guile/site/3.0/infix-with-precedence-to-prefix.scm
;;;       newer than compiled /home/mattei/.cache/guile/ccache/3.0-LE-8-4.6/usr/share/guile/site/3.0/infix-with-precedence-to-prefix.scm.go
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;;       or pass the --no-auto-compile argument to disable.
;;; compiling /usr/share/guile/site/3.0/infix-with-precedence-to-prefix.scm
;;; compiled /home/mattei/.cache/guile/ccache/3.0-LE-8-4.6/usr/share/guile/site/3.0/infix-with-precedence-to-prefix.scm.go
scheme@(guile-user)> ($nfx$ 3 * 5 + 2)
!0-generic : terms=(#<syntax:unknown file:2:7 3> #<syntax:unknown file:2:9 *> #<syntax:unknown file:2:11 5> #<syntax:unknown file:2:13 +> #<syntax:unknown file:2:15 2>)
$nfx$ : parsed-args=(#<syntax:unknown file:2:13 +> (#<syntax:unknown file:2:9 *> #<syntax:unknown file:2:7 3> #<syntax:unknown file:2:11 5>) #<syntax:unknown file:2:15 2>)
$1 = 17

Kawa (without displaying terms)

#|kawa:1|# (import (nfx))
#|kawa:2|# ($nfx$ 3 * 5 + 2)
17

You have discovered that the some R6RS implementations represent syntax objects representing lists differently from how Racket does.

The current (and the previous) expander in Racket uses a syntax object to represent a list. For one this makes it possible to attach source location information to syntax objects representing lists. The portable expander psyntax which is used by several implementation instead uses "list of syntax objects" to represent lists.

In practise this means that, when you port code from such an implementation to Racket, you occasionally need to insert syntax->list to explicitly remove the outer syntax object.

In short, this is not a bug - just a different representation choice.

1 Like

i think by psyntax you are talking about :

https://www.scheme.com/syntax-case/old-psyntax.html
https://conservatory.scheme.org/psyntax/
https://conservatory.scheme.org/psyntax/r6rs-libraries/

i did not think of a bug in Racket but in my code first, then i realized there was no difference in code, so i thought of something different between R6RS,R5RS,Racket, then i compared with Guile and Kawa... but this was weird.

Your proposition solve the problem,i modified the code by adding syntax->list at the very beginning of the code this way:

(define-syntax $nfx$

  (lambda (stx)
    
    (syntax-case stx ()

      
      (($nfx$ e1 op1 e2 op2 e3 op ...) ; note: i add op because in scheme op ... could be non existent

       
	 (with-syntax ;; let
			 
		       ((parsed-args
			
			 (n-arity 
			   (!0-generic (syntax->list #'(e1 op1 e2 op2 e3 op ...)) ; apply operator precedence rules
				       infix-operators-lst-for-parser-syntax
				       (lambda (op a b) (list op a b))))))
	   
	   (display "$nfx$ : parsed-args=") (display #'parsed-args) (newline)
	   #'parsed-args)))))

) ; end module

and now it works:

Welcome to DrRacket, version 8.14 [cs].
Language: racket, with debugging; memory limit: 8192 MB.
> (require Scheme+/nfx)
> ($nfx$ 3 * 5 + 2)
$nfx$ : parsed-args=.#<syntax (+ (* 3 5) 2)>
17

as it no more fails in this sub procedure:

;;; evaluates `terms` symbolically or numerically as a basic infix expression
(define (!0-generic terms  operator-precedence creator)

  ;; (newline)
  ;; (display "!0-generic : terms=") (display terms) (newline)
  ;; (display "!0-generic : operator-precedence=") (display operator-precedence) (newline)
  (when (not (list? terms))
    (display "!0-generic : WARNING , terms is not a list, perheaps expander is not psyntax (Portable Syntax)") (newline)
    (display "!0-generic : terms=") (display terms) (newline))

  (define rv
    (if (null? terms) ;; i added this null case but with correct input this should not be necessary
	terms
	(car (!*-generic (reverse terms) ; for exponentiation evaluated from right to left
			 operator-precedence
			 #;#f
			 creator))))

  ;; (display "!0-generic : rv=") (display rv) (newline)
  ;; (newline)
  rv

  )

for security now i check that i'm having a list , so if i port this to another scheme implementation with a different behavior i could know where the problem comes.

thanks

but that's cool... i like to be a discoverer :sweat_smile: