Jump to content

Comma quibbling

From Rosetta Code
Task
Comma quibbling
You are encouraged to solve this task according to the task description, using any language you may know.

Comma quibbling is a task originally set by Eric Lippert in his blog.


Task

Write a function to generate a string output which is the concatenation of input words from a list/sequence where:

  1. An input of no words produces the output string of just the two brace characters "{}".
  2. An input of just one word, e.g. ["ABC"], produces the output string of the word inside the two braces, e.g. "{ABC}".
  3. An input of two words, e.g. ["ABC", "DEF"], produces the output string of the two words inside the two braces with the words separated by the string " and ", e.g. "{ABC and DEF}".
  4. An input of three or more words, e.g. ["ABC", "DEF", "G", "H"], produces the output string of all but the last word separated by ", " with the last word separated by " and " and all within braces; e.g. "{ABC, DEF, G and H}".


Test your function with the following series of inputs showing your output here on this page:

  • [] # (No input words).
  • ["ABC"]
  • ["ABC", "DEF"]
  • ["ABC", "DEF", "G", "H"]


Note: Assume words are non-empty strings of uppercase characters for this task.


Other tasks related to string operations:
Metrics
Counting
Remove/replace
Anagrams/Derangements/shuffling
Find/Search/Determine
Formatting
Song lyrics/poems/Mad Libs/phrases
Tokenize
Sequences



11l

F quibble(words)
   R S words.len
      0
         ‘{}’
      1
         ‘{’words[0]‘}’
      E
         ‘{’words[0.<(len)-1].join(‘, ’)‘ and ’words.last‘}’

print(quibble([‘’] * 0))
print(quibble([‘ABC’]))
print(quibble([‘ABC’, ‘DEF’]))
print(quibble([‘ABC’, ‘DEF’, ‘G’, ‘H’]))
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

360 Assembly

*        Comma quibbling           13/03/2017
COMMAQUI CSECT
         USING  COMMAQUI,R13       base register
         B      72(R15)            skip savearea
         DC     17F'0'             savearea
         STM    R14,R12,12(R13)    save previous context
         ST     R13,4(R15)         link backward
         ST     R15,8(R13)         link forward
         LR     R13,R15            set addressability
         LA     R6,1               i=1
       DO WHILE=(C,R6,LE,=A(N))    do i=1 to hbound(t)
         LR     R1,R6                i
         SLA    R1,5                 *32
         LA     R2,T-32              @t(0)
         AR     R1,R2                @t(i)
         MVC    S1,0(R1)             s1=t(i)
         MVC    S2,=CL32'{'          s2='{'
         LA     R8,S2+1              s2ins=1
         MVC    I2,=F'0'             i2=0
         LA     R7,1                 j=1
       DO WHILE=(C,R7,LE,=A(L'T))    do j=1 to length(t)
         LA     R1,S1                  @s1
         BCTR   R1,0                   @s1-1
         AR     R1,R7                  @s1-1+j
         MVC    CJ,0(R1)               cj=mid(s1,j,1)
         CLI    CJ,C' '                if cj=' '
         BE     EXITJ                  then goto exitj
       IF CLI,CJ,EQ,C',' THEN          if cj="," then
         MVC    0(2,R8),=C', '           s2=s2||", "
         LA     R8,2(R8)                 s2ins=s2ins+2
         LR     R0,R8                    s2ins
         LA     R1,S2+1                  @s2+1
         SR     R0,R1                    len(s2)-1
         ST     R0,I2                    i2=len(s2)-1
       ELSE     ,                      else
         MVC    0(1,R8),CJ               s2=s2||cj
         LA     R8,1(R8)                 s2ins=s2ins+1
       ENDIF    ,                      endif
         LA     R7,1(R7)               j++
       ENDDO    ,                    enddo j
EXITJ    MVI    0(R8),C'}'           s2=s2||"}"
         LA     R8,1(R8)             s2ins=s2ins+1
         L      R0,I2                i2
       IF LTR,R0,NZ,R0 THEN          if i2<>0 then
         MVC    S2B,S2                 s2b=mid(s2,1,i2-1)
         LA     R1,S2B-1               @s2b-1
         A      R1,I2                  +i2
         MVC    0(5,R1),=C' and '      s2b||" and "
         LA     R1,5(R1)               +5 
         LA     R2,S2+1                @s2+1
         A      R2,I2                  +i2
         LR     R3,R8                  s2ins
         LA     R0,S2+1                @s2+1
         SR     R3,R0                  s2ins-(@s2+1)
         S      R3,I2                  -i2
         BCTR   R3,0                   -1
         EX     R3,XMVC                s2b||=mid(s2,i2+2)
         MVC    S2,S2B     s2=mid(s2,1,i2-1)||" and "||mid(s2,i2+2)
       ENDIF    ,                    endif
         XPRNT  S2,L'S2              print s2
         LA     R6,1(R6)             i++
       ENDDO    ,                  enddo i
         L      R13,4(0,R13)       restore previous savearea pointer
         LM     R14,R12,12(R13)    restore previous context
         XR     R15,R15            rc=0
         BR     R14                exit
XMVC     MVC    0(0,R1),0(R2)      mvc @r1,@r2
N        EQU    (TEND-T)/L'T       items of t
T        DC     CL32' ',CL32'ABC',CL32'ABC,DEF',CL32'ABC,DEF,G,H'
TEND     DS     0C
I2       DS     F
S1       DS     CL(L'T)
S2       DS     CL(L'T)
S2B      DS     CL(L'T)
CJ       DS     CL1
         YREGS
         END    COMMAQUI
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

8080 Assembly

		org	100h
		jmp	demo
		;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
		;; Given a list of strings in HL, and a pointer in DE, write
		;; the resulting string starting at DE.
quibble:	mvi	a,'{'		; Write the first {,
		stax	d
		inx 	d		; And increment the pointer
		push	h		; Keep start of list
		call	strseqlen	; Get length of list
		pop	h		; Restore start of list
		xra	a		; Is the list empty?
		ora	b
		jz	quibend		; If empty list, we're done.
quibcopy:	call	strcpy		; Copy current string into output
		inx	h		; Advance input pointer to next string
		dcr	b		; Decrement counter
		jz	quibend		; If zero, that was the last string
		push	h		; Push input pointer
		mov	a,b		; Is the counter 1 now?
		cpi	1
		lxi	h,quibcomma 	; Add a comma and space,
		jnz	quibsep		; unless the counter was 1,
		lxi	h,quiband	; then use " and "
quibsep:	call	strcpy		; Copy the separator into the output
		pop	h		; Restore the input pointer
		jmp	quibcopy	; Do the next string in the list
quibend:	mvi	a,'}'		; Write the final '}'
		stax	d
		inx	d
		mvi	a,'$'		; And write a string terminator
		stax 	d
		ret
quibcomma:	db	', $'
quiband:	db	' and $'	
		;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
		;; Copy the string under HL to DE until the terminator $.
		;; The terminator is not copied; HL and DE are left one byte
		;; beyond the last byte copied.
strcpy:		mov	a,m		; Get byte from input
		cpi	'$'		; Are we at the end?
		rz			; Then stop. 
		stax	d		; Otherwise, store byte at output
		inx	h		; Increment the pointers
		inx	d
		jmp	strcpy		; Copy next byte.
		;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
		;; Return in B the amount of strings in the string list in HL
strseqlen:	mvi	a,'$'		; String end
		mvi	b,0		; String counter
count:		cmp 	m		; Empty string?
		rz			; Then we're done
		inr	b		; Otherwise, we have a string
strsrch:	cmp	m		; Find the end of the string
		inx 	h
		jnz	strsrch
		jmp	count		
		;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
		;; Demo code: run 'quibble' on the examples
demo:		mvi	c,4		; Four examples
		lxi	h,examples	; Pointer to first example
example:	push	b		; Push example count
		lxi	d,buffer	; Into the buffer,
		call	quibble		; write the output of comma-quibbling
		inx	h		; Point to next example
		push	h		; Save pointer to next example
		lxi	d,buffer	; Write the output to the console
		mvi	c,9
		call	5
		lxi	d,newline	; Write a newline to the console
		mvi	c,9
		call	5
		pop	h		; Restore example pointer
		pop	b		; Restore example counter
		dcr	c		; If not zero,
		jnz 	example		; do the next example.
		ret
newline:	db	10,13,'$'				
examples:	db	'$'
		db	'ABC$$'
		db	'ABC$DEF$$'
		db	'ABC$DEF$G$H$$'
buffer:
Output:
A>quib
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

AArch64 Assembly

Works with: as version Raspberry Pi 3B version Buster 64 bits
or android 64 bits with application Termux
/* ARM assembly AARCH64 Raspberry PI 3B */
/*  program Comma quibbling   */

/*******************************************/
/* Constantes                              */
/*******************************************/
/* for this file see task include a file in language AArch64 assembly*/
.include "../includeConstantesARM64.inc"
.equ BUFFERSIZE,    80

/*******************************************/
/* macros                             */
/*******************************************/
//.include "../../ficmacros64.inc"            // for developper debugging

/*********************************/
/* Initialized data              */
/*********************************/
.data
szMessDebutPgm:       .asciz "Program 64 bits start. \n"
szCarriageReturn:     .asciz "\n"
szMessFinOK:          .asciz "Program normal end. \n"
szMessStringError:    .asciz "Error : Empty string !!!\n"
szMessBufferError:    .asciz "Error : Buffer too small !!\n"
szMessEndStringError: .asciz "Error: End string, not ].\n"
szMessWordErr:        .asciz "Error word empty.\n"
szAnd:                .asciz " and "
szString1:            .asciz "[]"
szString2:            .asciz "[\"ABC\"]"
szString3:            .asciz "[\"ABC\",\"DEF\"]"
szString4:            .asciz "[\"ABC\",\"DEF\",\"G\",\"H\"]"
szString5:            .asciz "[\"AB"
szString6:            .asciz "[\"ABC\",,\"DEF\"]"

.align 4 
/*********************************/
/* UnInitialized data            */
/*********************************/
.bss   
sBuffer:          .skip BUFFERSIZE
.align 4

/*********************************/
/*  code section                 */
/*********************************/
.text
.global main 
main:
    ldr x0,qAdrszMessDebutPgm
    bl affichageMess            // start message 
 
    ldr x0,qAdrszString1
    bl execTest
    ldr x0,qAdrszString2
    bl execTest
    ldr x0,qAdrszString3
    bl execTest
    ldr x0,qAdrszString4
    bl execTest
    ldr x0,qAdrszString5
    bl execTest
    ldr x0,qAdrszString6
    bl execTest
    
    ldr x0,qAdrszMessFinOK
    bl affichageMess       

100: 
   mov x8,EXIT 
    svc #0                      // system call
qAdrszMessDebutPgm:          .quad szMessDebutPgm
qAdrszMessFinOK:             .quad szMessFinOK
qAdrszCarriageReturn:        .quad szCarriageReturn
qAdrszString1:               .quad szString1
qAdrszString2:               .quad szString2
qAdrszString3:               .quad szString3
qAdrszString4:               .quad szString4
qAdrszString5:               .quad szString5
qAdrszString6:               .quad szString6
qAdrsBuffer:                 .quad sBuffer
/******************************************************************/
/*            test execution                                        */ 
/******************************************************************/
/* x0 contains string address  */
execTest:
    stp x1,lr,[sp,-16]!
    stp x2,x4,[sp,-16]!
    mov x4,x0
    bl affichageMess       // display start string
    ldr x0,qAdrszCarriageReturn
    bl affichageMess
    mov x0,x4              // string address
    ldr x1,qAdrsBuffer     // buffer address
    mov x2,#BUFFERSIZE
    bl stringAnalyse
    cmp x0,#-1             // error ?
    beq 100f
    ldr x0,qAdrsBuffer     // buffer display
    bl affichageMess
    ldr x0,qAdrszCarriageReturn
    bl affichageMess
100:
    ldp x2,x4,[sp],16
    ldp x1,lr,[sp],16
    ret
/******************************************************************/
/*            string conversion                                       */ 
/******************************************************************/
/* x0 contains string address  */
/* x1 contains buffer address */
/* x2 contains buffer length */
stringAnalyse:
    stp x1,lr,[sp,-16]!
    stp x2,x3,[sp,-16]!
    stp x4,x5,[sp,-16]!
    stp x6,x7,[sp,-16]!
    stp x8,x9,[sp,-16]!
    sub sp,sp,#BUFFERSIZE   // reserve area on stack for temporary buffer
    mov fp,sp
    mov x7,#0               // indice write buffer
    mov x5,#0               // word counter
    mov x6,#0               // word char count
    
    mov x3,#0               // indice string
1:
    ldrb w4,[x0,x3]         // load string char
    cmp x4,#0               // end ?
    bne 2f
    cmp x3,#0               // first char ?
    beq 97f                 // error empty string
    b 97f                   // end string error
2:             
    cmp x4,#'['             // first symbol ?
    bne 3f
    mov x4,#'{'             // write symbol
    strb w4,[x1,x7]
    add x7,x7,#1
    cmp x7,x2
    bge 99f                 // buffer error
    add x3,x3,#1            // increment indice       
    b 1b                    // and loop 
 3:
    cmp x4,#']'             // end symbol ?
    bne 8f
    cmp x5,#0               // no word ?
    beq 7f 
    cmp x5,#1
    beq 5f                 // last and first word 

    mov x9,#0
    ldr x12,qAdrszAnd
4:                         // loop write and
    ldrb w4,[x12,x9]
    strb w4,[x1,x7]
    add x7,x7,#1
    cmp x7,x2
    bge 99f                 // buffer error
    add x9,x9,#1
    cmp x9,#5
    blt 4b
    
5:                         // last word    
    mov x9,#0
6:                         // loop write temporary buffer
    ldrb w4,[fp,x9]
    strb w4,[x1,x7]
    add x7,x7,#1
    cmp x7,x2
    bge 99f                 // buffer error
    add x9,x9,#1
    cmp x9,x6
    blt 6b
    mov x6,#0               // raz indice temporary buffer
7:    
    mov x4,#'}'
    strb w4,[x1,x7]
    add x7,x7,#1
    cmp x7,x2
    bge 99f                 // buffer error
    mov x4,#0
    strb w4,[x1,x7]        // final 0
    b 100f
8:    
    cmp x4,#','             // comma ?
    beq 9f
    cmp x6,#0
    cinc x5,x5,eq           // new word increment word counter
    //addeq x5,x5,#1           // new word increment word counter
    strb w4,[fp,x6]         // store char in temporary buffer
    add x6,x6,#1
    add x3,x3,#1
    b 1b                    // loop
    
9:
    cmp x6,#0               // word empty ?
    beq 96f
    cmp x5,#1               // first word ?
    bne 11f
                            // first word, write only the word
    mov x9,#0
10:                         // loop write temporary buffer
    ldrb w4,[fp,x9]
    strb w4,[x1,x7]
    add x7,x7,#1
    cmp x7,x2
    bge 99f                 // buffer error
    add x9,x9,#1
    cmp x9,x6
    blt 10b
    mov x6,#0               // raz indice temporary buffer
    add x3,x3,#1
    b 1b                    // loop
11:
    mov x4,#','
    strb w4,[x1,x7]
    add x7,x7,#1
    cmp x7,x2
    bge 99f                 // buffer error

    mov x9,#0
12:                          // loop write temporary buffer
    ldrb w4,[fp,x9]
    strb w4,[x1,x7]
    add x7,x7,#1
    cmp x7,x2
    bge 99f                 // buffer error
    add x9,x9,#1
    cmp x9,x6
    blt 12b
    mov x6,#0               // raz indice temporary buffer
    add x3,x3,#1
    b 1b                    // loop
    
96:                         // errors messages
    ldr x0,qAdrszMessWordErr
    bl affichageMess
    mov x0,#-1 
    b 100f
97:
    ldr x0,qAdrszMessEndStringErr
    bl affichageMess
    mov x0,#-1 
    b 100f
98:
    ldr x0,qAdrszMessStringError
    bl affichageMess
    mov x0,#-1 
    b 100f
99:
    ldr x0,qAdrszMessBufferError
    bl affichageMess
    mov x0,#-1    
100:
    add sp,sp,#BUFFERSIZE
    ldp x8,x9,[sp],16
    ldp x6,x7,[sp],16
    ldp x4,x5,[sp],16
    ldp x2,x3,[sp],16
    ldp x1,lr,[sp],16
    ret
qAdrszMessStringError:    .quad szMessStringError
qAdrszMessBufferError:    .quad szMessBufferError  
qAdrszMessEndStringErr:   .quad szMessEndStringError       
qAdrszMessWordErr:        .quad szMessWordErr
qAdrszAnd:                .quad szAnd
/***************************************************/
/*      ROUTINES INCLUDE                 */
/***************************************************/
/* for this file see task include a file in language AArch64 assembly*/
.include "../includeARM64.inc"
Output:
Program 64 bits start.
[]
{}
["ABC"]
{"ABC"}
["ABC","DEF"]
{"ABC" and "DEF"}
["ABC","DEF","G","H"]
{"ABC","DEF","G" and "H"}
["AB
Error: End string, not ].
["ABC",,"DEF"]
Error word empty.
Program normal end.

ABC

HOW TO RETURN quibble words:
    PUT "" IN result
    PUT #words IN remaining
    FOR word IN words:
        PUT result^word IN result
        PUT remaining-1 IN remaining
        IF remaining = 1: PUT result^" and " IN result
        IF remaining > 1: PUT result^", " IN result
    RETURN "{" ^ result ^ "}"

PUT {} IN tests
INSERT {} IN tests
INSERT {[1]: "ABC"} IN tests
INSERT {[1]: "ABC"; [2]: "DEF"} IN tests
INSERT {[1]: "ABC"; [2]: "DEF"; [3]: "G"; [4]: "H"} IN tests
FOR test IN tests:
    WRITE quibble test/
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Acornsoft Lisp

There's no string data type; symbols are used instead. The implode function is used to concatenate a list of symbols. When writing a symbol in source code, exclamation mark is an escape character that allows characters such as spaces and exclamation marks to be treated as part of the symbol's name.

(defun examples ()
  (map '(lambda (words) (printc (quibble words)))
       '(() (ABC) (ABC DEF) (ABC DEF G H))))

(defun quibble (words)
  (implode (list '{ (quibbles words) '})))

(defun quibbles (words)
  (implode (conjunction words)))

(defun conjunction (words)
  (cond ((null words)
         '())
        ((null (cdr words))
         words)
        ((null (cddr words))
         (list (car words) '! and!  (cadr words)))
        (t
         (cons (car words)
           (cons ',!  (conjunction (cdr words)))))))
Output:

Calling (examples) will output:

{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Action!

DEFINE PTR="CARD"

PROC Append(CHAR ARRAY text,suffix)
  BYTE POINTER srcPtr,dstPtr
  BYTE len

  len=suffix(0)
  IF text(0)+len>255 THEN
    len=255-text(0)
  FI
  IF len THEN
    srcPtr=suffix+1
    dstPtr=text+text(0)+1
    MoveBlock(dstPtr,srcPtr,len)
    text(0)==+suffix(0)
  FI
RETURN

PROC Quibble(PTR ARRAY items INT count CHAR ARRAY result)
  INT i

  result(0)=0
  Append(result,"(")
  FOR i=0 TO count-1
  DO
    Append(result,items(i))
    IF i=count-2 THEN
      Append(result," and ")
    ELSEIF i<count-2 THEN
      Append(result,", ")
    FI
  OD
  Append(result,")")
RETURN

PROC Test(PTR ARRAY items BYTE count)
  CHAR ARRAY result(256)

  Quibble(items,count,result)
  PrintE(result)
RETURN

PROC Main()
  PTR ARRAY items(5)

  Test(items,0)

  items(0)="ABC"
  Test(items,1)

  items(1)="DEF"
  Test(items,2)

  items(2)="G"
  Test(items,3)

  items(3)="H"
  Test(items,4)
RETURN
Output:

Screenshot from Atari 8-bit computer

()
(ABC)
(ABC and DEF)
(ABC, DEF and G)
(ABC, DEF, G and H)

Ada

with Ada.Text_IO, Ada.Command_Line; use Ada.Command_Line;

procedure Comma_Quibble is
   
begin
   case Argument_Count is
      when 0 => Ada.Text_IO.Put_Line("{}");
      when 1 => Ada.Text_IO.Put_Line("{" & Argument(1) & "}");
      when others => 
	 Ada.Text_IO.Put("{");
	 for I in 1 .. Argument_Count-2 loop
	    Ada.Text_IO.Put(Argument(I) & ", ");
	 end loop;
	 Ada.Text_IO.Put(Argument(Argument_Count-1) & " and " &
		         Argument(Argument_Count) & "}");
   end case;
end Comma_Quibble;
Output:
./comma_quibble
{}
./comma_quibble abc
{abc}
./comma_quibble abc def
{abc and def}
./comma_quibble abc def g h
{abc, def, g and h}

ALGOL 68

Works with: ALGOL 68G version Any - tested with release 2.8.win32
# returns a string ( assumed to be of space-separated words ) with the words  #
# separated by ", ", except for the last which is separated from the rest by  #
# " and ". The list is enclosed by braces                                     #
PROC to list = ( STRING words ) STRING:
    BEGIN
        # count the number of words                                           #
        INT  word count := 0;
        BOOL in word    := FALSE;
        FOR char pos FROM LWB words TO UPB words
        DO
            IF NOT is upper( words[ char pos ] )
            THEN
                # not an upper-case letter, possibly a word has been ended    #
                in word := FALSE
            ELSE
                # not a delimitor, possibly the start of a word               #
                IF NOT in word
                THEN
                    # we are starting a new word                              #
                    word count +:= 1;
                    in word     := TRUE
                FI
            FI
        OD;

        # format the result                                                   #
        STRING result    := "{";
        in word          := FALSE;
        INT  word number := 0;
        FOR char pos FROM LWB words TO UPB words
        DO
            IF NOT is upper( words[ char pos ] )
            THEN
                # not an upper-case letter, possibly a word has been ended    #
                in word := FALSE
            ELSE
                # not a delimitor, possibly the start of a word               #
                IF NOT in word
                THEN
                    # we are starting a new word                              #
                    word number +:= 1;
                    in word      := TRUE;
                    IF word number > 1
                    THEN
                        # second or subsequent word - need a separator        #
                        result +:= IF word number = word count
                                   THEN # final word                          #
                                       " and "
                                   ELSE # non-final word                      #
                                       ", "
                                   FI
                    FI
                FI;
                # add the character to the result                             #
                result +:= words[ char pos ]
            FI
        OD;

        result + "}"
    END # to list # ;


    # procedure to test the to list PROC                                      #
    PROC test to list = ( STRING words ) VOID:
        print( ( ( words
                 + ": "
                 + to list( words )
                 )
               , newline
               )
             );

    # test the to list PROC                                                   #
    test to list( "" );
    test to list( "ABC" );
    test to list( "ABC DEF" );
    test to list( "ABC DEF G H" )
Output:
: {}
ABC: {ABC}
ABC DEF: {ABC and DEF}
ABC DEF G H: {ABC, DEF, G and H}

ALGOL W

begin

    % returns a list of the words contained in wordString, separated by ", ", %
    % except for the last which is separated from the rest by " and ".        %
    % The words are enclosed by braces                                        %
    string(256) procedure toList ( string(256) value  words ) ;
    begin
        string(256) list;
        integer     wordCount, wordNumber, listPos;
        logical     inWord;

        % returns true if ch is an upper-case letter, false otherwise         %
        %         assumes the letters are consecutive in the character set    %
        %         (as in ascii) would not be correct if the character set was %
        %         ebcdic (as in the original implementations of Algol W)      %
        logical procedure isUpper ( string(1) value ch ) ; ch >= "A" and ch <= "Z" ;

        % adds a character to the result                                      %
        procedure addChar( string(1) value ch ) ;
        begin
            list( listPos // 1 ) := ch;
            listPos := listPos + 1;
        end addChar ;

        % adds a string to the result                                        %
        procedure addString( string(256) value str
                           ; integer     value len
                           ) ;
            for strPos := 0 until len - 1 do addChar( str( strPos // 1 ) );

        % count the number of words                                           %
        
        wordCount := 0;
        inWord    := false;
        for charPos := 0 until 255
        do begin
            if isUpper( words( charPos // 1 ) ) then begin
                % not an upper-case letter, possibly a word has been ended    %
                inWord := false
                end
            else begin
                % not a delimitor, possibly the start of a word               %
                if not inWord then begin
                    % we are starting a new word                              %
                    wordCount := wordCount + 1;
                    inWord    := true
                end if_not_inWord
            end 
        end for_charPos;

        % format the result                                                   %

        list       := "";
        listPos    := 0;
        inWord     := false;
        wordNumber := 0;

        addChar( "{" );

        for charPos := 0 until 255
        do begin
            if not isUpper( words( charPos // 1 ) ) then begin
                % not an upper-case letter, possibly a word has been ended    %
                inWord := false
                end
            else begin
                % not a delimitor, possibly the start of a word               %
                if not inWord then begin
                    % we are starting a new word                              %
                    wordNumber := wordNumber + 1;
                    inWord     := true;
                    if wordNumber > 1 then begin
                        % second or subsequent word - need a separator        %
                        if wordNumber = wordCount then addString( " and ", 5 ) % final word %
                                                  else addString( ", ",    2 ) % non-final word %
                    end
                end;
                % add the character to the result                             %
                addChar( words( charPos // 1 ) )
            end
        end for_charPos ;

        addChar( "}" );

        list
    end toList ;


    % procedure to test the toList procedure                                 %
    procedure testToList ( string(256) value words ) ;
    begin
        string(256) list;
        list := toList( words );
        write( s_w := 0
             , words( 0 // 32 )
             , ": "
             , list(  0 // 32 )
             )
    end testToList ;

    % test the toList procedure                                              %
    testToList( "" );
    testToList( "ABC" );
    testToList( "ABC DEF" );
    testToList( "ABC DEF G H" );

end.
Output:
                                : {}
ABC                             : {ABC}
ABC DEF                         : {ABC and DEF}
ABC DEF G H                     : {ABC, DEF, G and H}

APL

quibble  1'}{',(∊⊢,¨2(' and ' ''),(', '))
Output:
      quibble ⍬
{}
      quibble ⊂'ABC'
{ABC}
      quibble 'ABC' 'DEF'
{ABC and DEF}
      quibble 'ABC' 'DEF' 'G' 'H'
{ABC, DEF, G and H}

AppleScript

Translation of: JavaScript
-- quibble :: [String] -> String
on quibble(xs)
    if length of xs > 1 then
        set applyCommas to ¬
            compose([curry(my intercalate)'s |λ|(", "), my |reverse|, my tail])
        
        intercalate(" and ", ap({applyCommas, my head}, {|reverse|(xs)}))
    else
        concat(xs)
    end if
end quibble

-- TEST -----------------------------------------------------------------------
on run
    script braces
        on |λ|(x)
            "{" & x & "}"
        end |λ|
    end script
    
    unlines(map(compose({braces, quibble}), ¬
        append({{}, {"ABC"}, {"ABC", "DEF"}, {"ABC", "DEF", "G", "H"}}, ¬
            map(|words|, ¬
                {"One two three four", "Me myself I", "Jack Jill", "Loner"}))))
end run


-- GENERIC FUNCTIONS ----------------------------------------------------------

-- A list of functions applied to a list of arguments
-- (<*> | ap) :: [(a -> b)] -> [a] -> [b]
on ap(fs, xs)
    set {intFs, intXs} to {length of fs, length of xs}
    set lst to {}
    repeat with i from 1 to intFs
        tell mReturn(item i of fs)
            repeat with j from 1 to intXs
                set end of lst to |λ|(contents of (item j of xs))
            end repeat
        end tell
    end repeat
    return lst
end ap

-- (++) :: [a] -> [a] -> [a]
on append(xs, ys)
    xs & ys
end append

-- compose :: [(a -> a)] -> (a -> a)
on compose(fs)
    script
        on |λ|(x)
            script
                on |λ|(a, f)
                    mReturn(f)'s |λ|(a)
                end |λ|
            end script
            
            foldr(result, x, fs)
        end |λ|
    end script
end compose

-- concat :: [[a]] -> [a] | [String] -> String
on concat(xs)
    script append
        on |λ|(a, b)
            a & b
        end |λ|
    end script
    
    if length of xs > 0 and class of (item 1 of xs) is string then
        set unit to ""
    else
        set unit to {}
    end if
    foldl(append, unit, xs)
end concat

-- curry :: (Script|Handler) -> Script
on curry(f)
    script
        on |λ|(a)
            script
                on |λ|(b)
                    |λ|(a, b) of mReturn(f)
                end |λ|
            end script
        end |λ|
    end script
end curry

-- foldl :: (a -> b -> a) -> a -> [b] -> a
on foldl(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from 1 to lng
            set v to |λ|(v, item i of xs, i, xs)
        end repeat
        return v
    end tell
end foldl

-- foldr :: (a -> b -> a) -> a -> [b] -> a
on foldr(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from lng to 1 by -1
            set v to |λ|(v, item i of xs, i, xs)
        end repeat
        return v
    end tell
end foldr

-- head :: [a] -> a
on head(xs)
    if length of xs > 0 then
        item 1 of xs
    else
        missing value
    end if
end head

-- intercalate :: Text -> [Text] -> Text
on intercalate(strText, lstText)
    set {dlm, my text item delimiters} to {my text item delimiters, strText}
    set strJoined to lstText as text
    set my text item delimiters to dlm
    return strJoined
end intercalate

-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map

-- Lift 2nd class handler function into 1st class script wrapper 
-- mReturn :: Handler -> Script
on mReturn(f)
    if class of f is script then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

-- |reverse| :: [a] -> [a]
on |reverse|(xs)
    if class of xs is text then
        (reverse of characters of xs) as text
    else
        reverse of xs
    end if
end |reverse|

-- tail :: [a] -> [a]
on tail(xs)
    if length of xs > 1 then
        items 2 thru -1 of xs
    else
        {}
    end if
end tail

-- unlines :: [String] -> String
on unlines(xs)
    intercalate(linefeed, xs)
end unlines

-- words :: String -> [String]
on |words|(s)
    words of s
end |words|
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
{One, two, three and four}
{Me, myself and I}
{Jack and Jill}
{Loner}

ARM Assembly

Works with: as version Raspberry Pi
or android 32 bits with application Termux
/* ARM assembly Raspberry PI  */
/*  program Comma quibbling   */
   
/* REMARK 1 : this program use routines in a include file 
   see task Include a file language arm assembly 
   for the routine affichageMess conversion10 
   see at end of this program the instruction include */

/*******************************************/
/* Constantes                              */
/*******************************************/
.include "../constantes.inc"
.equ BUFFERSIZE,    80

/*******************************************/
/* macros                             */
/*******************************************/
//.include "../../ficmacros32.inc"            @ for developper debugging

/*********************************/
/* Initialized data              */
/*********************************/
.data
szMessDebutPgm:       .asciz "Program 32 bits start. \n"
szCarriageReturn:     .asciz "\n"
szMessFinOK:          .asciz "Program normal end. \n"
szMessStringError:    .asciz "Error : Empty string !!!\n"
szMessBufferError:    .asciz "Error : Buffer too small !!\n"
szMessEndStringError: .asciz "Error: End string, not ].\n"
szMessWordErr:        .asciz "Error word empty.\n"
szAnd:                .asciz " and "
szString1:            .asciz "[]"
szString2:            .asciz "[\"ABC\"]"
szString3:            .asciz "[\"ABC\",\"DEF\"]"
szString4:            .asciz "[\"ABC\",\"DEF\",\"G\",\"H\"]"
szString5:            .asciz "[\"AB"
szString6:            .asciz "[\"ABC\",,\"DEF\"]"

.align 4 
/*********************************/
/* UnInitialized data            */
/*********************************/
.bss   
sBuffer:          .skip BUFFERSIZE
.align 4

/*********************************/
/*  code section                 */
/*********************************/
.text
.global main 
main:
    ldr r0,iAdrszMessDebutPgm
    bl affichageMess            @ start message 
 
    ldr r0,iAdrszString1
    bl execTest
    ldr r0,iAdrszString2
    bl execTest
    ldr r0,iAdrszString3
    bl execTest
    ldr r0,iAdrszString4
    bl execTest
    ldr r0,iAdrszString5
    bl execTest
    ldr r0,iAdrszString6
    bl execTest
    
    ldr r0,iAdrszMessFinOK
    bl affichageMess       

100: 
    mov r7,#EXIT                @ program end
    svc #0                      @ system call
iAdrszMessDebutPgm:          .int szMessDebutPgm
iAdrszMessFinOK:             .int szMessFinOK
iAdrszCarriageReturn:        .int szCarriageReturn
iAdrszString1:               .int szString1
iAdrszString2:               .int szString2
iAdrszString3:               .int szString3
iAdrszString4:               .int szString4
iAdrszString5:               .int szString5
iAdrszString6:               .int szString6
iAdrsBuffer:                 .int sBuffer
/******************************************************************/
/*            test execution                                        */ 
/******************************************************************/
/* r0 contains string address  */
execTest:
    push {r1-r4,lr}        @ save registers
    mov r4,r0
    bl affichageMess       @ display start string
    ldr r0,iAdrszCarriageReturn
    bl affichageMess
    mov r0,r4              @ string address
    ldr r1,iAdrsBuffer     @ buffer address
    mov r2,#BUFFERSIZE
    bl stringAnalyse
    cmp r0,#-1             @ error ?
    beq 100f
    ldr r0,iAdrsBuffer     @ buffer display
    bl affichageMess
    ldr r0,iAdrszCarriageReturn
    bl affichageMess
100:
    pop {r1-r4,pc}  
/******************************************************************/
/*            string conversion                                       */ 
/******************************************************************/
/* r0 contains string address  */
/* r1 contains buffer address */
/* r2 contains buffer length */
stringAnalyse:
    push {r1-r9,fp,lr}      @ save registers
    sub sp,sp,#BUFFERSIZE   @ reserve area on stack for temporary buffer
    mov fp,sp
    mov r7,#0               @ indice write buffer
    mov r5,#0               @ word counter
    mov r6,#0               @ word char count
    
    mov r3,#0               @ indice string
1:
    ldrb r4,[r0,r3]         @ load string char
    cmp r4,#0               @ end ?
    bne 2f
    cmp r3,#0               @ first char ?
    beq 97f                 @ error empty string
    b 97f                   @ end string error
2:             
    cmp r4,#'['             @ first symbol ?
    bne 3f
    mov r4,#'{'             @ write symbol
    strb r4,[r1,r7]
    add r7,r7,#1
    cmp r7,r2
    bge 99f                 @ buffer error
    add r3,r3,#1            @ increment indice       
    b 1b                    @ and loop 
 3:
    cmp r4,#']'             @ end symbol ?
    bne 8f
    cmp r5,#0               @ no word ?
    beq 7f 
    cmp r5,#1
    beq 5f                 @ last and first word 

    mov r9,#0
    ldr r12,iAdrszAnd
4:                         @ loop write and
    ldrb r4,[r12,r9]
    strb r4,[r1,r7]
    add r7,r7,#1
    cmp r7,r2
    bge 99f                 @ buffer error
    add r9,r9,#1
    cmp r9,#5
    blt 4b
    
5:                         @ last word    
    mov r9,#0
6:                         @ loop write temporary buffer
    ldrb r4,[fp,r9]
    strb r4,[r1,r7]
    add r7,r7,#1
    cmp r7,r2
    bge 99f                 @ buffer error
    add r9,r9,#1
    cmp r9,r6
    blt 6b
    mov r6,#0               @ raz indice temporary buffer
7:    
    mov r4,#'}'
    strb r4,[r1,r7]
    add r7,r7,#1
    cmp r7,r2
    bge 99f                 @ buffer error
    mov r4,#0
    strb r4,[r1,r7]        @ final 0
    b 100f
8:    
    cmp r4,#','             @ comma ?
    beq 9f
    cmp r6,#0
    addeq r5,r5,#1          @ new word increment word counter
    strb r4,[fp,r6]         @ store char in temporary buffer
    add r6,r6,#1
    add r3,r3,#1
    b 1b                    @ loop
    
9:
    cmp r6,#0               @ word empty ?
    beq 96f
    cmp r5,#1               @ first word ?
    bne 11f
                            @ first word, write only the word
    mov r9,#0
10:                         @ loop write temporary buffer
    ldrb r4,[fp,r9]
    strb r4,[r1,r7]
    add r7,r7,#1
    cmp r7,r2
    bge 99f                 @ buffer error
    add r9,r9,#1
    cmp r9,r6
    blt 10b
    mov r6,#0               @ raz indice temporary buffer
    add r3,r3,#1
    b 1b                    @ loop
11:
    mov r4,#','
    strb r4,[r1,r7]
    add r7,r7,#1
    cmp r7,r2
    bge 99f                 @ buffer error

    mov r9,#0
12:                          @ loop write temporary buffer
    ldrb r4,[fp,r9]
    strb r4,[r1,r7]
    add r7,r7,#1
    cmp r7,r2
    bge 99f                 @ buffer error
    add r9,r9,#1
    cmp r9,r6
    blt 12b
    mov r6,#0               @ raz indice temporary buffer
    add r3,r3,#1
    b 1b                    @ loop

96:                         @ errors messages
    ldr r0,iAdrszMessWordErr
    bl affichageMess
    mov r0,#-1 
    b 100f
97:
    ldr r0,iAdrszMessEndStringErr
    bl affichageMess
    mov r0,#-1 
    b 100f
98:
    ldr r0,iAdrszMessStringError
    bl affichageMess
    mov r0,#-1 
    b 100f
99:
    ldr r0,iAdrszMessBufferError
    bl affichageMess
    mov r0,#-1    
100:
    add sp,sp,#BUFFERSIZE
    pop {r1-r9,fp,pc}
iAdrszMessStringError:    .int szMessStringError
iAdrszMessBufferError:    .int szMessBufferError  
iAdrszMessEndStringErr:   .int szMessEndStringError       
iAdrszMessWordErr:        .int szMessWordErr
iAdrszAnd:                .int szAnd
/***************************************************/
/*      ROUTINES INCLUDE                 */
/***************************************************/
.include "../affichage.inc"
Output:
Program 32 bits start.
[]
{}
["ABC"]
{"ABC"}
["ABC","DEF"]
{"ABC" and "DEF"}
["ABC","DEF","G","H"]
{"ABC","DEF","G" and "H"}
["AB
Error: End string, not ].
["ABC",,"DEF"]
Error word empty.
Program normal end.

Arturo

quibble: $[sequence :block][
    if? 0 = size sequence
    -> return "{}"
    
    if? 1 = size sequence
    -> return ~"{|sequence\0|}"
    
    last: pop 'sequence
    return  {|join.with: ", " sequence| and |last|}
]

sentences: [
	[] 
	["ABC"] 
	["ABC" "DEF"] 
	["ABC" "DEF" "G" "H"]
]

loop sentences 'sentence [
	print quibble sentence
]
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Astro

fun quibble(s):
    let result = s.join(' and ').replace(|| and ||, ", ", length(s) - 1)
    return "{ $result }"

let s = [
    []
    ["ABC"]
    ["ABC", "DEF"]
    ["ABC", "DEF", "G", "H"]
]

for i in s:
    print(quibble i)

AutoHotkey

MsgBox % quibble([])
MsgBox % quibble(["ABC"])
MsgBox % quibble(["ABC", "DEF"])
MsgBox % quibble(["ABC", "DEF", "G", "H"])

quibble(d) {
	s:=""
	for i, e in d
	{
		if (i<d.MaxIndex()-1)
			s:= s . e . ", "
		else if (i=d.MaxIndex()-1)
			s:= s . e . " and "
		else
			s:= s . e
	}
	return "{" . s . "}"
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

AWK

function quibble(a, n,    i, s) {
	for (i = 1; i < n - 1; i++) s = s a[i] ", "
	i = n - 1; if (i > 0) s = s a[i] " and "
	if (n > 0) s = s a[n]
	return "{" s "}"
}

BEGIN {
	print quibble(a, 0)
	n = split("ABC", b); print quibble(b, n)
	n = split("ABC DEF", c); print quibble(c, n)
	n = split("ABC DEF G H", d); print quibble(d, n)
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Batch File

@echo off
setlocal enabledelayedexpansion

::THE MAIN THING...
echo.
set inp=[]
call :quibble
set inp=["ABC"]
call :quibble
set inp=["ABC","DEF"]
call :quibble
set inp=["ABC","DEF","G","H"]
call :quibble
echo.
pause
exit /b
::/THE MAIN THING...

::THE FUNCTION
:quibble
set cont=0
set proc=%inp:[=%
set proc=%proc:]=%

for %%x in (%proc%) do (
	set /a cont+=1
	set x=%%x
	set str!cont!=!x:"=!
)
set /a bef=%cont%-1
set output=%str1%
if %cont%==2 (set output=%str1% and %str2%)
if %cont% gtr 2 (
	for /l %%y in (2,1,%bef%) do (
		set output=!output!^, !str%%y!
	)
	set output=!output! and !str%cont%!
)
echo {!output!}
goto :EOF
::/THE FUNCTION
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Press any key to continue . . .

BCPL

get "libhdr"

// Add a character to the end of a string
let addch(s, ch) be
$(  s%0 := s%0 + 1
    s%(s%0) := ch
$)
// Add s2 to the end of s1
and adds(s1, s2) be
    for i = 1 to s2%0 do
        addch(s1, s2%i)
        
// Comma quibbling on strs, which should be a 0-terminated
// vector of string pointers.
let quibble(strs, buf) = valof
$(  buf%0 := 0
    addch(buf, '{')
    until !strs = 0 do 
    $(  addch(buf, '"')
        adds(buf, !strs)
        addch(buf, '"')
        unless strs!1 = 0
            test strs!2 = 0
                then adds(buf, " and ")
                else adds(buf, ", ")
        strs := strs + 1
    $)
    addch(buf, '}')
    resultis buf
$)

let start() be
$(  let words = vec 4
    let buf = vec 63
    
    words!0 := 0
    writef("%S*N", quibble(words, buf))
    
    words!0 := "ABC" ; words!1 := 0
    writef("%S*N", quibble(words, buf))
    
    words!1 := "DEF" ; words!2 := 0
    writef("%S*N", quibble(words, buf))
    
    words!2 := "G" ; words!3 := "H" ; words!4 := 0
    writef("%S*N", quibble(words, buf))
$)
Output:
{}
{"ABC"}
{"ABC" and "DEF"}
{"ABC", "DEF", "G" and "H"}

Bracmat

( :?L1
& ABC:?L2
& ABC DEF:?L3
& ABC DEF G H:?L4
& L1 L2 L3 L4:?names
& ( quibble
  =   w
    .     !arg:%?w (% %:?arg)
        & !w ", " quibble$!arg
      | !arg:%?w %?arg&!w " and " quibble$!arg
      | !arg
  )
& (concat=.str$("{" quibble$!arg "}"))
&   whl
  ' (!names:%?name ?names&out$(!name concat$!!name))
);
Output:
L1 {}
L2 {ABC}
L3 {ABC and DEF}
L4 {ABC, DEF, G and H}

C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *quib(const char **strs, size_t size)
{

    size_t len = 3 + ((size > 1) ? (2 * size + 1) : 0);
    size_t i;

    for (i = 0; i < size; i++)
        len += strlen(strs[i]);

    char *s = malloc(len * sizeof(*s));
    if (!s)
    {
        perror("Can't allocate memory!\n");
        exit(EXIT_FAILURE);
    }

    strcpy(s, "{");
    switch (size) {
        case 0:  break;
        case 1:  strcat(s, strs[0]);
                 break;
        default: for (i = 0; i < size - 1; i++)
                 {
                     strcat(s, strs[i]);
                     if (i < size - 2)
                         strcat(s, ", ");
                     else
                         strcat(s, " and ");
                 }
                 strcat(s, strs[i]);
                 break;
    }  
    strcat(s, "}");
    return s;
}

int main(void)
{
    const char *test[] = {"ABC", "DEF", "G", "H"};
    char *s;

    for (size_t i = 0; i < 5; i++)
    {
        s = quib(test, i);
        printf("%s\n", s);
        free(s);
    }
    return EXIT_SUCCESS;
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF and G}
{ABC, DEF, G and H}

C#

using System;
using System.Linq;

namespace CommaQuibbling
{
    internal static class Program
    {
        #region Static Members
	private static string Quibble(string[] input)
	{
            return
                String.Format("{{{0}}}",
                    String.Join("",
                        input.Reverse().Zip(
                            new [] { "", " and " }.Concat(Enumerable.Repeat(", ", int.MaxValue)),
                            (x, y) => x + y).Reverse()));
	}


        private static void Main()
        {
            Console.WriteLine( Quibble( new string[] {} ) );
            Console.WriteLine( Quibble( new[] {"ABC"} ) );
            Console.WriteLine( Quibble( new[] {"ABC", "DEF"} ) );
            Console.WriteLine( Quibble( new[] {"ABC", "DEF", "G", "H"} ) );

            Console.WriteLine( "< Press Any Key >" );
            Console.ReadKey();
        }

        #endregion
    }
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
< Press Any Key >

C++

#include <iostream>

template<class T>
void quibble(std::ostream& o, T i, T e) {
  o << "{";
  if (e != i) {
    T n = i++;
    const char* more = "";
    while (e != i) {
      o << more << *n;
      more = ", ";
      n = i++;
    }
    o << (*more?" and ":"") << *n;
  }
  o << "}";
}

int main(int argc, char** argv) {
  char const* a[] = {"ABC","DEF","G","H"};
  for (int i=0; i<5; i++) {
    quibble(std::cout, a, a+i);
    std::cout << std::endl;
  }
  return 0;
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF and G}
{ABC, DEF, G and H}

Clojure

(defn quibble [sq]
  (let [sep (if (pos? (count sq)) " and " "")]
    (apply str
      (concat "{" (interpose ", " (butlast sq)) [sep (last sq)] "}"))))

; Or, using clojure.pprint's cl-format, which implements common lisp's format:
(defn quibble-f [& args]
  (clojure.pprint/cl-format nil "{~{~a~#[~; and ~:;, ~]~}}" args))

(def test
  #(doseq [sq [[]
               ["ABC"]
               ["ABC", "DEF"]
               ["ABC", "DEF", "G", "H"]]]
     ((comp println %) sq)))

(test quibble)
(test quibble-f)
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

CLU

quibble = proc (words: array[string]) returns (string)
    out: string := "{"
    last: int := array[string]$high(words)
    
    for i: int in array[string]$indexes(words) do
        out := out || words[i]
        if i < last-1 then
            out := out || ", "
        elseif i = last-1 then
            out := out || " and "
        end
    end
    return(out || "}")
end quibble

start_up = proc ()
    as = array[string]
    aas = array[as]
    po: stream := stream$primary_output()
    
    testcases: aas := aas$
        [as$[],
         as$["ABC"],
         as$["ABC","DEF"],
         as$["ABC","DEF","G","H"]]
    
    for testcase: as in aas$elements(testcases) do
        stream$putl(po, quibble(testcase))
    end 
end start_up
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

COBOL

Works with: OpenCOBOL version 2.0
       >>SOURCE FORMAT IS FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. comma-quibbling-test.

ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
    FUNCTION comma-quibbling
    .
DATA DIVISION.
WORKING-STORAGE SECTION.
01  strs-area.
    03  strs-len                  PIC 9.
    03  strs                      PIC X(5)
                                  OCCURS 0 TO 9 TIMES
                                  DEPENDING ON strs-len.

PROCEDURE DIVISION.
    MOVE "ABC" TO strs (1)
    MOVE "DEF" TO strs (2)
    MOVE "G" TO strs (3)
    MOVE "H" TO strs (4)

    PERFORM VARYING strs-len FROM 0 BY 1 UNTIL strs-len > 4
        DISPLAY FUNCTION comma-quibbling(strs-area)
    END-PERFORM
    .
END PROGRAM comma-quibbling-test.

 
IDENTIFICATION DIVISION.
FUNCTION-ID. comma-quibbling.

DATA DIVISION.
LOCAL-STORAGE SECTION.
01  i                             PIC 9.

01  num-extra-words               PIC 9.

LINKAGE SECTION.
01  strs-area.
    03  strs-len                  PIC 9.
    03  strs                      PIC X(5)
                                  OCCURS 0 TO 9 TIMES
                                  DEPENDING ON strs-len.

01  str                           PIC X(50).

PROCEDURE DIVISION USING strs-area RETURNING str.
    EVALUATE strs-len
        WHEN ZERO
            MOVE "{}" TO str
            GOBACK

        WHEN 1
            MOVE FUNCTION CONCATENATE("{", FUNCTION TRIM(strs (1)), "}")
                TO str
            GOBACK
    END-EVALUATE

    MOVE FUNCTION CONCATENATE(FUNCTION TRIM(strs (strs-len - 1)),
        " and ", FUNCTION TRIM(strs (strs-len)), "}")
        TO str

    IF strs-len > 2
        SUBTRACT 2 FROM strs-len GIVING num-extra-words
        PERFORM VARYING i FROM num-extra-words BY -1 UNTIL i = 0
            MOVE FUNCTION CONCATENATE(FUNCTION TRIM(strs (i)), ", ", str)
                TO str
        END-PERFORM
    END-IF
    
    MOVE FUNCTION CONCATENATE("{", str) TO str
    .
END FUNCTION comma-quibbling.
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF and G}
{ABC, DEF, G and H}

CoffeeScript

quibble = ([most..., last]) -> 
  '{' + 
    (most.join ', ') +
    (if most.length then ' and ' else '')  + 
    (last or '') + 
  '}'

console.log quibble(s) for s in [ [], ["ABC"], ["ABC", "DEF"], 
                                  ["ABC", "DEF", "G", "H" ]   ]
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Commodore BASIC

The Commodore character set has no curly braces, so I substituted square brackets. The solution could no doubt be improved, but some of the elegance of other solutions is not possible simply because a FOR loop always executes at least once, even if its parameters would seem to indicate that it should not.

100 DIM A$(3)
110 FOR TC=1 TO 4
120 : READ A: IF A=0 THEN 160
130 : FOR I=0 TO A-1
140 :   READ A$(I)
150 : NEXT I
160 : GOSUB 200
170 : PRINT CQ$
180 NEXT TC
190 END
200 CQ$="["
210 IF A < 1 THEN 290
220 CQ$ = CQ$ + A$(0)
230 IF A < 2 THEN 290
240 IF A < 3 THEN 280
250 FOR I=1 TO A - 2
260 : CQ$ = CQ$ + ", " + A$(I)
270 NEXT I
280 CQ$ = CQ$ + " AND " + A$(A - 1)
290 CQ$ = CQ$ + "]"
300 RETURN
310 DATA 0
320 DATA 1, ABC
330 DATA 2, ABC, DEF
340 DATA 4, ABC, DEF, G, H
Output:
[]
[ABC]
[ABC AND DEF]
[ABC, DEF, G AND H]

Common Lisp

(defun quibble (&rest args)
  (format t "{~{~a~#[~; and ~:;, ~]~}}" args))

(quibble)
(quibble "ABC")
(quibble "ABC" "DEF")
(quibble "ABC" "DEF" "G" "H")
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Cowgol

include "cowgol.coh";

sub quibble(words: [[uint8]], 
            length: intptr, 
            buf: [uint8]):
           (out: [uint8]) is
    sub append(s: [uint8]) is
        while [s] != 0 loop
            [buf] := [s];
            buf := @next buf;
            s := @next s;
        end loop;
    end sub;
    
    out := buf;
    
    append("{");
    while length > 0 loop
        append([words]);
        words := @next words;
        case length is
            when 1: break;
            when 2: append(" and ");
            when else: append(", ");
        end case;
        length := length - 1;
    end loop;
    append("}");
    
    [buf] := 0;
end sub;

var w1: [uint8][] := {};
var w2: [uint8][] := {"ABC"};
var w3: [uint8][] := {"ABC","DEF"};
var w4: [uint8][] := {"ABC","DEF","G","H"};

print(quibble(&w1[0], @sizeof w1, LOMEM)); print_nl();
print(quibble(&w2[0], @sizeof w2, LOMEM)); print_nl();
print(quibble(&w3[0], @sizeof w3, LOMEM)); print_nl();
print(quibble(&w4[0], @sizeof w4, LOMEM)); print_nl();
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

D

import std.stdio, std.string;

string quibbler(in string[] seq) pure /*nothrow*/ {
    if (seq.length <= 1)
        return format("{%-(%s, %)}", seq);
    else
        return format("{%-(%s, %) and %s}", seq[0 .. $-1], seq[$-1]);
}

void main() {
    //foreach (immutable test; [[],
    foreach (const test; [[],
                          ["ABC"],
                          ["ABC", "DEF"],
                          ["ABC", "DEF", "G", "H"]])
        test.quibbler.writeln;
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Alternative Version

import std.stdio, std.string, std.algorithm, std.conv, std.array;

enum quibbler = (in string[] a) pure =>
    "{%-(%s and %)}".format(a.length < 2 ? a :
                            [a[0 .. $-1].join(", "), a.back]);

void main() {
    [[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]]
    .map!quibbler.writeln;
}
Output:
["{}", "{ABC}", "{ABC and DEF}", "{ABC, DEF, G and H}"]

dc

[(q)uibble: main entry point. print brackets, calling n in between if stack not
 empty.]sx
[ [{]n z 0 !=n [}]pR ]sq

[(n)onempty: if more than 1 item, call m. then print top of stack.]sx
[ z 1 !=m n ]sn

[(m)ore: call f to flip stack into r register, call p to print most of it,
 then pop the last item back onto the main stack so it's there to be printed
 after we return]sx
[ lfx lpx Lr ]sm

[(f)lip: utility routine to reverse the stack into the r register]sx
[ Sr z 0 !=f ]sf

[(p)rint: get next item from stack in r register and print it. If there are
 more than 2 items left on the register stack (which never drops below one
 item), print a comma (c) and recurse. If there are exactly two items left,
 print " and " (a) and return.]sx
[ Lr n 2 yr >c 2 yr =a 2 yr >p]sp

[(c)omma: utility routine to print a comma followed by a space]sx
[ [, ]n ]sc

[(a)and: utility routine to  print " and "]sx
[ [ and ]n ]sa

[run tests]sx
lqx
[ABC] lqx
[ABC] [DEF] lqx
[ABC] [DEF] [G] [H] lqx
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

DCL

$ list = "[]"
$ gosub comma_quibbling
$ write sys$output return_string
$
$ list = "[""ABC""]"
$ gosub comma_quibbling
$ write sys$output return_string
$
$ list = "[""ABC"", ""DEF""]"
$ gosub comma_quibbling
$ write sys$output return_string
$
$ list = "[""ABC"", ""DEF"", ""G"", ""H""]"
$ gosub comma_quibbling
$ write sys$output return_string
$
$ exit
$
$ comma_quibbling:
$ list = list - "[" - "]"
$ return_string = "{}"
$ if list .eqs. "" then $ return
$ return_string = "{" + f$element( 0, ",", list ) - """" - """"
$ if f$locate( ",", list ) .eq. f$length( list ) then $ goto done2
$ i = 1
$ loop:
$  word = f$element( i, ",", list ) - """" - """"
$  if word .eqs. "," then $ goto done1
$  return_string = return_string - "^" + "^," + word
$  i = i + 1
$  goto loop
$ done1:
$ return_string = f$element( 0, "^", return_string ) + " and" + ( f$element( 1, "^", return_string ) - "," )
$ done2:
$ return_string = return_string + "}"
$ return

{}out}}

$ @comma_quibbling
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Delphi

See Pascal.

Déjà Vu

comma-quibble lst:
	"}" )
	if lst:
		pop-from lst
		if lst:
			" and "
			pop-from lst
			for item in lst:
				item ", "
	concat( "{"

!. comma-quibble []
!. comma-quibble [ "ABC" ]
!. comma-quibble [ "ABC" "DEF" ]
!. comma-quibble [ "ABC" "DEF" "G" "H" ]
Output:
"{}"
"{ABC}"
"{ABC and DEF}"
"{ABC, DEF, G and H}"

EasyLang

func$ tolist s$ .
   s$[] = strsplit s$ " "
   r$ = "{"
   n = len s$[]
   for i = 1 to n - 2
      r$ &= s$[i] & ", "
   .
   if n > 0
      if n > 1
         r$ &= s$[n - 1] & " and "
      .
      r$ &= s$[n]
   .
   r$ &= "}"
   return r$
.
print tolist ""
print tolist "ABC"
print tolist "ABC DEF"
print tolist "ABC DEF G H"

EchoLisp

(lib 'match)

(define (quibble words)
    (match words
         [ null "{}"]
         [ (a)  (format "{ %a }" a)]
         [ (a b) (format "{ %a and %a }" a b)]
         [( a ... b c) (format "{ %a %a and %a }" (for/string ([w a]) (string-append w ", "))  b c)]
         [else 'bad-input]))
 

;; output

 (for ([t '(() ("ABC") ("ABC" "DEF") ("ABC" "DEF" "G" "H"))])
    (writeln t '----> (quibble t)))

null     ---->     "{}"    
("ABC")     ---->     "{ ABC }"    
("ABC" "DEF")     ---->     "{ ABC and DEF }"    
("ABC" "DEF" "G" "H")     ---->     "{ ABC, DEF, G and H }"

ed

Uses basic regular expressions (BREs).

# by Artyom Bologov
H
,p
g/.*/s/ /, /g
g/[,]/s/,\([^,]*\)$/ and\1/
g/.*/s//{&}/
,p
Q
Output:
$ ed -s comma-quibling.input < comma-quibling.ed
Newline appended

ABC
ABC DEF
ABC DEF G H
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Eiffel

class
	APPLICATION

create
	make

feature

		make
			-- Test of the feature comma_quibbling.
		local
			l: LINKED_LIST [STRING]
		do
			create l.make
			io.put_string (comma_quibbling (l) + "%N")
			l.extend ("ABC")
			io.put_string (comma_quibbling (l) + "%N")
			l.extend ("DEF")
			io.put_string (comma_quibbling (l) + "%N")
			l.extend ("G")
			l.extend ("H")
			io.put_string (comma_quibbling (l) + "%N")
		end

	comma_quibbling (l: LINKED_LIST [STRING]): STRING
			-- Elements of 'l' seperated by a comma or an and where appropriate.
		require
			l_not_void: l /= Void
		do
			create Result.make_empty
			Result.extend ('{')
			if l.is_empty then
				Result.append ("}")
			elseif l.count = 1 then
				Result.append (l [1] + "}")
			else
				Result.append (l [1])
				across
					2 |..| (l.count - 1) as c
				loop
					Result.append (", " + l [c.item])
				end
				Result.append (" and " + l [l.count] + "}")
			end
		end

end
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Elixir

Translation of: Erlang
defmodule RC do
  def generate( list ), do: "{#{ generate_content(list) }}"
   
  defp generate_content( [] ), do: ""
  defp generate_content( [x] ), do: x
  defp generate_content( [x1, x2] ), do: "#{x1} and #{x2}"
  defp generate_content( xs ) do
    [last, second_to_last | t] = Enum.reverse( xs )
    with_commas = for x <- t, do: x <> ","
    Enum.join(Enum.reverse([last, "and", second_to_last | with_commas]), " ")
  end
end

Enum.each([[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]], fn list ->
  IO.inspect RC.generate(list)
end)
Output:
"{}"
"{ABC}"
"{ABC and DEF}"
"{ABC, DEF, G and H}"

Erlang

-module( comma_quibbling ).

-export( [task/0] ).

task() -> [generate(X) || X <- [[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]]].



generate( List ) -> "{" ++ generate_content(List) ++ "}".

generate_content( [] ) -> "";
generate_content( [X] ) -> X;
generate_content( [X1, X2] ) -> string:join( [X1, "and", X2], " " );
generate_content( Xs ) ->
	[Last, Second_to_last | T] = lists:reverse( Xs ),
	With_commas = [X ++ "," || X <- T],
	string:join(lists:reverse([Last, "and", Second_to_last | With_commas]), " ").
Output:
36> comma_quibbling:task().
["{}","{ABC}","{ABC and DEF}","{ABC, DEF, G and H}"]

F#

One Way

let quibble list = 
    let rec inner = function
        | [] -> ""
        | [x] -> x
        | [x;y] -> sprintf "%s and %s" x y
        | h::t -> sprintf "%s, %s" h (inner t)
    sprintf "{%s}" (inner list)

// test interactively
quibble []            
quibble ["ABC"]
quibble ["ABC"; "DEF"]
quibble ["ABC"; "DEF"; "G"]
quibble ["ABC"; "DEF"; "G"; "H"]

Output from testing (in F# Interactive 3.0, Open Source version):

> quibble [];;
val it : string = "{}"
> quibble ["ABC"];;
val it : string = "{ABC}"
> quibble ["ABC"; "DEF"];;
val it : string = "{ABC and DEF}"
> quibble ["ABC"; "DEF"; "G"];;
val it : string = "{ABC, DEF and G}"
> quibble ["ABC"; "DEF"; "G"; "H"];;
val it : string = "{ABC, DEF, G and H}"

or Another

The Function

let quibble quibbler quibblee = Seq.zip quibblee quibbler //Sorry, just too good a line to miss, back in my Latin classes

The Task

let fN n = quibble (List.mapi(fun n _->match n with 0->"" |1-> " and " |_->", ") n |> List.rev) n
printf "{"; fN ["ABC"; "DEF"; "G"; "H"] |> Seq.iter(fun(n,g)->printf "%s%s" n g); printfn"}"
printf "{"; fN ["ABC"; "DEF"; "G"] |> Seq.iter(fun(n,g)->printf "%s%s" n g); printfn"}"
printf "{"; fN ["ABC"; "DEF"] |> Seq.iter(fun(n,g)->printf "%s%s" n g); printfn"}"
printf "{"; fN ["ABC"] |> Seq.iter(fun(n,g)->printf "%s%s" n g); printfn"}"
printf "{"; fN [] |> Seq.iter(fun(n,g)->printf "%s%s" n g); printfn"}"
Output:
{ABC, DEF, G and H}
{ABC, DEF and G}
{ABC and DEF}
{ABC}
{}

Factor

This example uses the inverse vocabulary, which builds on the concept of invertible quotations as the basis for pattern matching. It is discussed at length in this approachable paper.

USING: inverse qw sequences ;
 
: (quibble) ( seq -- seq' )
    {
        { [ { } ] [ "" ] }
        { [ 1array ] [ ] }
        { [ 2array ] [ " and " glue ] }
        [ unclip swap (quibble) ", " glue ]
    } switch ;

: quibble ( seq -- str ) (quibble) "{%s}" sprintf ;

{ } qw{ ABC } qw{ ABC DEF } qw{ ABC DEF G H }
[ quibble print ] 4 napply
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Forth

The efficient and beautiful way to solve the task is to keep a sliding triplet of consequent words. First we unconditionally read the first two words; if there are none, "read" gives us an empty word. Then we read the input stream, typing third to last word together with a comma. When the loop ends, we have two (possible empty) words on the stack, and the only thing left to do is to output it accordingly.

: read  bl parse ;
: not-empty? ( c-addr u -- c-addr u true | false ) ?dup-if true else drop false then ;
: third-to-last  2rot ;
: second-to-last  2swap ;

: quibble
	." {"
	read read begin read not-empty? while third-to-last type ." , " repeat
	second-to-last not-empty? if type then
	not-empty? if ."  and " type then
	." }" cr ;
	
quibble
quibble ABC
quibble ABC DEF
quibble ABC DEF G H

Fortran

The usual problem of "How long is a piece of string?" is answered in the usual way with a declaration that is "surely long enough", at least for anticipated problems. Thus, variable TEXT is declared as 666 characters long. The input statement reads up to that number of characters, or the length of the record if shorter, and supplies trailing spaces to pad the recipient variable to its full length. There is unfortunately no read feature that will create a recipient storage area that matches the size of the record being read. There is such a facility in pl/i, except that the recipient variable still has a pre-specified upper bound to its size.

Subroutine QUIBBLE doesn't have to worry about this because it works with TEXT as a parameter, whatever its size (various integer limits apply) however, it too has the same problem because it locates the start and end positions of each word, and, how many words are going to be found? So once again, the arrays are made "surely large enough" for the expected class of problem. The first stage is to locate the words separated by any amount of "white space", which, thanks to the inability to rely on the evaluation of compound boolean expressions (of the form IF (in bounds & Array indexing)) in the "shortcut" manner, employs a battery of IF-statements. Fortran does not offer a data type "list of ..." so there is no prospect of placing the words into such an entity then inserting commas and "and" elements into the list to taste. Instead, the list of words is represented by a sequence of values in ordinary arrays.

The source style is Fortran 77, thus the use of COMMON to pass some I/O unit numbers. The plan initially was to engage in trickery with the variable FORMAT features, of the form <expression>(blah blah) to signify some number of repetitions of (blah blah), which number might be zero, but alas, although <0>X works, it proved not to work for grouped items in place of a format code. So the <..> extension had to be abandoned, and plainer F77 results.

      SUBROUTINE QUIBBLE(TEXT,OXFORDIAN)	!Punctuates a list with commas and stuff.
       CHARACTER*(*) TEXT	!The text, delimited by spaces.
       LOGICAL OXFORDIAN	!Just so.
       INTEGER IST(6),LST(6)	!Start and stop positions.
       INTEGER N,L,I		!Counters.
       INTEGER L1,L2		!Fingers for the scan.
       INTEGER MSG		!Output unit.
       COMMON /IODEV/MSG	!Share.
Chop the text into words.
        N = 0		!No words found.
        L = LEN(TEXT)	!Multiple trailing spaces - no worries.
        L2 = 0		!Syncopation: where the previous chomp ended.
   10   L1 = L2		!Thus, where a fresh scan should follow.
   11   L1 = L1 + 1		!Advance one.
        IF (L1.GT.L) GO TO 20		!Finished yet?
        IF (TEXT(L1:L1).LE." ") GO TO 11	!No. Skip leading spaces.
        L2 = L1			!Righto, L1 is the first non-blank.
   12   L2 = L2 + 1		!Scan through the non-blanks.
        IF (L2.GT.L) GO TO 13	!Is it safe to look?
        IF (TEXT(L2:L2).GT." ") GO TO 12	!Yes. Speed through non-blanks.
   13   N = N + 1			!Righto, a word is found in TEXT(L1:L2 - 1)
        IST(N) = L1		!So, recall its first character.
        LST(N) = L2 - 1		!And its last.
        IF (L2.LT.L) GO TO 10	!Perhaps more text follows.
Comma time...
   20   WRITE (MSG,21) "{"	!Start the output.
   21   FORMAT (A,$)		!The $, obviously, specifies that the line is not finished.
        DO I = 1,N		!Step through the texts, there possibly being none.
          IF (I.GT.1) THEN		!If there has been a predecessor, supply separators.
            IF (I.LT.N) THEN			!Up to the last two, it's easy.
              WRITE (MSG,21) ", "			!Always just a comma.
            ELSE IF (OXFORDIAN) THEN		!But after the penultimate item, what?
              WRITE (MSG,21) ", and "			!Supply the comma omitted above: a double-power separator.
            ELSE				!One fewer comma, with possible ambiguity arising.
              WRITE (MSG,21) " and "			!A single separator.
            END IF				!So much for the style.
          END IF			!Enough with the separation.
          WRITE (MSG,21) TEXT(IST(I):LST(I))	!The text at last!
        END DO			!On to the next text.
        WRITE (MSG,"('}')")	!End the line, marking the end of the text.
      END		!That was fun.

      PROGRAM ENCOMMA	!Punctuate a list with commas.
      CHARACTER*(666) TEXT	!Holds the text. Easily long enough.
      INTEGER KBD,MSG,INF	!Now for some messing.
      COMMON /IODEV/MSG,KBD	!Pass the word.
      KBD = 5	!Standard input.
      MSG = 6	!Standard output.
      INF = 10	!Suitable for a disc file.
      OPEN (INF,FILE="List.txt",ACTION = "READ")	!Attach one.

   10 WRITE (MSG,11) "To insert commas into lists..."	!Announce.
   11 FORMAT (A)			!Just the text.
   12 READ (INF,11,END = 20) TEXT	!Grab the text, with trailing spaces to fill out TEXT.
      CALL QUIBBLE(TEXT,.FALSE.)	!One way to quibble.
      GO TO 12				!Try for another.

   20 REWIND (INF)			!Back to the start of the file.
      WRITE (MSG,11)			!Set off a bit.
      WRITE (MSG,11) "Oxford style..."	!Announce the proper style.
   21 READ (INF,11,END = 30) TEXT	!Grab the text.
      CALL QUIBBLE(TEXT,.TRUE.)		!The other way to quibble.
      GO TO 21				!Have another try.

Closedown
   30 END	!All files are closed by exiting.

Output:

To insert commas into lists...
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Oxford style...
{}
{ABC}
{ABC, and DEF}
{ABC, DEF, G, and H}

FreeBASIC

' FB 1.05.0 Win64

Sub Split(s As String, sep As String, result() As String)
  Dim As Integer i, j, count = 0
  Dim temp As String
  Dim As Integer position(Len(s) + 1)
  position(0) = 0
  For i = 0 To Len(s) - 1
    For j = 0 To Len(sep) - 1
      If s[i] = sep[j] Then 
        count += 1
        position(count) = i + 1 
      End If
    Next j
  Next i
  position(count + 1) = Len(s) + 1
  Redim result(count)
  For i = 1 To count + 1    
    result(i - 1) = Mid(s, position(i - 1) + 1, position(i) - position(i - 1) - 1)
  Next
End Sub

Function CommaQuibble(s As String) As String
  Dim i As Integer
  Dim As String result
  Dim As String words() 
  s = Trim(s, Any "[]""") 
  ' Now remove internal quotes
  Split s, """", words()
  s = ""
  For i = 0 To UBound(words)
     s &= words(i)
  Next
  ' Now split 's' using the comma as separator
  Erase words  
  Split s, ",", words()
  ' And re-assemble the string in the desired format
  result = "{"
  For i = 0 To UBound(words) 
    If i = 0 Then
      result &= words(i)
    ElseIf i = UBound(words) Then 
      result &= " and " & words(i)       
    Else
      result &= ", " + words(i)
    EndIf   
  Next
  Return result & "}"
End Function

' As 3 of the strings contain embedded quotes these need to be doubled in FB
Print CommaQuibble("[]")
Print CommaQuibble("[""ABC""]")
Print CommaQuibble("[""ABC"",""DEF""]")
Print CommaQuibble("[""ABC"",""DEF"",""G"",""H""]")
Print
Print "Press any key to quit the program"
Sleep
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Frink

quibble[enum] :=
{
   list = toArray[enum]  // This makes it work on any enumerating expression
   size = length[list]
   if size >= 2
      return "{" + join[", ", first[list, size-1]] + " and " + last[list] + "}"
   else
      return "{" + join["", list] + "}"
}

data = [[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]]
for line = data
   println[quibble[line]]
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

FutureBasic

Long solution:

include "NSLog.incl"

local fn CommaQuibber( string as CFStringRef ) as CFStringRef
  CFStringRef tempStr
  NSUInteger  i
  
  tempStr = fn StringByReplacingOccurrencesOfString( string,  @"[",  @"" )
  tempStr = fn StringByReplacingOccurrencesOfString( tempStr, @"]",  @"" )
  tempStr = fn StringByReplacingOccurrencesOfString( tempStr, @" ",  @"" )
  tempStr = fn StringByReplacingOccurrencesOfString( tempStr, @"\"", @"" )
  
  CFMutableStringRef quibStr = fn MutableStringWithCapacity(0)
  
  CFArrayRef   arr = fn StringComponentsSeparatedByString( tempStr, @"," )
  NSUInteger count = len(arr)
  select switch ( count )
    case 0   : MutableStringSetString( quibStr, @"{}" ) : break
    case 1   : MutableStringSetString( quibStr, fn StringWithFormat( @"{%@}", arr[0] ) ) : break
    case 2   : MutableStringSetString( quibStr, fn StringWithFormat( @"{%@ and %@}", arr[0], arr[1] ) ) : break
    case else
      MutableStringAppendFormat( quibStr, @"{" )
      for i = 0 to count -1
        if ( i != count -1 )
          MutableStringAppendFormat( quibStr, @"%@, ", arr[i] )
        else
          MutableStringAppendFormat( quibStr, @"and %@}", arr[i] )
        end if
      next
  end select
end fn = quibStr

NSLog( @"%@", fn CommaQuibber( @"[]" ) )
NSLog( @"%@", fn CommaQuibber( @"[\"ABC\"]" ) )
NSLog( @"%@", fn CommaQuibber( @"[\"ABC\", \"DEF\"]" ) )
NSLog( @"%@", fn CommaQuibber( @"[\"ABC\", \"DEF\", \"G\", \"H\"]" ) )

HandleEvents

Short solution:

local fn CommaQuibbler( s as CFArrayRef ) as CFStringRef
  CFStringRef result = NULL
  
  select ( len(s) )
    case 0: exit fn = @"{}"
    case 1: exit fn = fn StringWithFormat( @"{%@}", s[0] )
    case 2: exit fn = fn StringWithFormat( @"{%@ and %@}", s[0], s[1] )
    case else
      result = fn StringWithFormat( @"{%@}", fn ArrayComponentsJoinedByString( s, @", " ) )
      CFRange lastComma = fn StringRangeOfStringWithOptions( result, @",", NSBackwardsSearch )
      result = fn StringByReplacingCharactersInRange( result, lastComma, @" and" )
  end select
end fn = result

print fn CommaQuibbler( @[] )
print fn CommaQuibbler( @[@"ABC"] )
print fn CommaQuibbler( @[@"ABC", @"DEF"] )
print fn CommaQuibbler( @[@"ABC", @"DEF", @"G", @"H"] )

HandleEvents

Shorter solution:

local fn Quibble( string as CFStringRef ) as CFStringRef
  CfArrayRef  arr = fn StringComponentsSeparatedByString( string, @" " )
  CFStringRef rst
  if ( len(arr) == 0 ) then rst = @"" else rst = fn StringWithFormat( @"%@", fn ArrayLastObject( arr ) )
  if ( len(arr) > 1 )
    CFArrayRef subArray = fn ArraySubarrayWithRange( arr, fn CFRangeMake( 0, len(arr) - 1 ) )
    rst = fn StringWithFormat( @"%@ and %@", fn ArrayComponentsJoinedByString( subArray, @", " ), rst )
  end if
end fn = fn StringWithFormat( @"{%@}", rst )

print fn Quibble( @"" )
print fn Quibble( @"ABC")
print fn Quibble( @"ABC DEF" )
print fn Quibble( @"ABC DEF G H" )

HandleEvents
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G, and H}

Gambas

Click this link to run this code

Public Sub Main()
Dim sInput As String[] = ["", "ABC", "ABC DEF", "ABC DEF G H"]
Dim sTemp As String

For Each sTemp In sInput
  Print sTemp & " = ";
  sTemp = Replace(sTemp, " ", ",")
  If RInStr(sTemp, ",") > 0 Then 
    sTemp = Mid(sTemp, 1, RInStr(sTemp, ",") - 1) & " and " & Mid(sTemp, RInStr(sTemp, ",") + 1)
  End If
  sTemp = "{" & sTemp & "}"
  Print sTemp
Next

End

Output:

 = {}
ABC = {ABC}
ABC DEF = {ABC and DEF}
ABC DEF G H = {ABC,DEF,G and H}

Go

The blog mentioned code maintenence. The idea here is to make the code easy for maintainers to understand by making it correspond as directly as possible to the problem description.

package main

import (
    "fmt"
    "strings"
)

func q(s []string) string {
    switch len(s) {
    case 0:
        return "{}"
    case 1:
        return "{" + s[0] + "}"
    case 2:
        return "{" + s[0] + " and " + s[1] + "}"
    default:
        return "{" +
            strings.Join(s[:len(s)-1], ", ") +
            " and " +
            s[len(s)-1] +
            "}"
    }
}

func main() {
    fmt.Println(q([]string{}))
    fmt.Println(q([]string{"ABC"}))
    fmt.Println(q([]string{"ABC", "DEF"}))
    fmt.Println(q([]string{"ABC", "DEF", "G", "H"}))
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Groovy

def commaQuibbling = { it.size() < 2 ? "{${it.join(', ')}}" : "{${it[0..-2].join(', ')} and ${it[-1]}}" }

Testing:

['{}': [], '{ABC}': ['ABC'], '{ABC and DEF}': ['ABC', 'DEF'], '{ABC, DEF, G and H}': ['ABC', 'DEF', 'G', 'H']].each { expected, input ->
    println "Verifying commaQuibbling($input) == $expected"
    assert commaQuibbling(input) == expected
}
Output:
Verifying commaQuibbling([]) == {}
Verifying commaQuibbling([ABC]) == {ABC}
Verifying commaQuibbling([ABC, DEF]) == {ABC and DEF}
Verifying commaQuibbling([ABC, DEF, G, H]) == {ABC, DEF, G and H}

Haskell

quibble ws = "{" ++ quibbles ws ++ "}"
  where quibbles [] = ""
        quibbles [a] = a
        quibbles [a,b] = a ++ " and " ++ b
        quibbles (a:bs) = a ++ ", " ++ quibbles bs

main = mapM_ (putStrLn . quibble) $
  [[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]] ++ 
  (map words ["One two three four", "Me myself I", "Jack Jill", "Loner" ])
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
{One, two, three and four}
{Me, myself and I}
{Jack and Jill}
{Loner}

Or, defining just two cases, and drawing more on standard libraries than on hand-crafted pattern-matching and recursion:

import Data.List (intercalate)

--------------------- COMMA QUIBBLING --------------------

quibble :: [String] -> String
quibble ws@(_ : _ : _) =
  intercalate
    " and "
    ( [intercalate ", " . reverse . tail, head]
        <*> [reverse ws]
    )
quibble xs = concat xs

--------------------------- TEST -------------------------
main :: IO ()
main =
  mapM_ (putStrLn . (`intercalate` ["{", "}"]) . quibble) $
    [[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]]
      <> ( words
             <$> [ "One two three four",
                   "Me myself I",
                   "Jack Jill",
                   "Loner"
                 ]
         )
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
{One, two, three and four}
{Me, myself and I}
{Jack and Jill}
{Loner}

Icon and Unicon

The following works in both languages:

procedure main()
    every write(quibble([] | ["ABC"] | ["ABC","DEF"] | ["ABC","DEF","G","H"]))
end

procedure quibble(A)
    join := s := ""
    while s := pull(A)||join||s do join := if *join = 0 then " and " else ", "
    return "{"||s||"}"
end

Sample run:

->cq
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
->

J

quibLast2=: ' and ' joinstring (2 -@<. #) {. ]
withoutLast2=: ([: # _2&}.) {. ]
quibble=: '{', '}' ,~ ', ' joinstring withoutLast2 , <@quibLast2

Testing:

   Tests=: (<<<3){(i.5)<@{."0 1;:'ABC DEF G H'
   quibble every Tests
{}                 
{ABC}              
{ABC and DEF}      
{ABC, DEF, G and H}

Alternative implementation:

commaand=: 1 ;@}.&, ] ,.~ 1 |.!.(<' and ') (<', ')"0
quibble=: '{','}',~ commaand

(same results)

Java

public class Quibbler {

	public static String quibble(String[] words) {
		String qText = "{";
		for(int wIndex = 0; wIndex < words.length; wIndex++) {
			qText += words[wIndex] + (wIndex == words.length-1 ? "" : 
						  wIndex == words.length-2 ? " and " :
						  ", ";
		}
		qText += "}";
		return qText;
	}
	
	public static void main(String[] args) {
		System.out.println(quibble(new String[]{}));
		System.out.println(quibble(new String[]{"ABC"}));
		System.out.println(quibble(new String[]{"ABC", "DEF"}));
		System.out.println(quibble(new String[]{"ABC", "DEF", "G"}));
		System.out.println(quibble(new String[]{"ABC", "DEF", "G", "H"}));
	}
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

JavaScript

ES5

function quibble(words) {
  return "{" + 
    words.slice(0, words.length-1).join(",") +
   (words.length > 1 ? " and " : "") +
   (words[words.length-1] || '') +
  "}";
}

[[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]].forEach(
  function(s) {
    console.log(quibble(s));
  }
);
Output:
{}
{ABC}
{ABC and DEF}
{ABC,DEF,G and H}

ES6

Translation of: Haskell

Composing from a set of generic functions:

(() => {
    'use strict';

    // ----------------- COMMA QUIBBLING -----------------

    // quibble :: [String] -> String
    const quibble = xs =>
        1 < xs.length ? (
            intercalate(' and ')(
                ap([
                    compose(
                        intercalate(', '), 
                        reverse, 
                        tail
                    ),
                    head
                ])([reverse(xs)])
            )
        ) : concat(xs);


    // ---------------------- TEST -----------------------
    const main = () =>
        unlines(
            map(compose(x => '{' + x + '}', quibble))(
                append([
                    [],
                    ["ABC"],
                    ["ABC", "DEF"],
                    ["ABC", "DEF", "G", "H"]
                ])(
                    map(words)([
                        "One two three four",
                        "Me myself I",
                        "Jack Jill",
                        "Loner"
                    ])
                )
            ));


    // ---------------- GENERIC FUNCTIONS ----------------

    // ap (<*>) :: [(a -> b)] -> [a] -> [b]
    const ap = fs =>
        // The sequential application of each of a list
        // of functions to each of a list of values.
        // apList([x => 2 * x, x => 20 + x])([1, 2, 3])
        //     -> [2, 4, 6, 21, 22, 23]
        xs => fs.flatMap(f => xs.map(f));


    // append (++) :: [a] -> [a] -> [a]
    const append = xs =>
        // A list defined by the
        // concatenation of two others.
        ys => xs.concat(ys);


    // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
    const compose = (...fs) =>
        // A function defined by the right-to-left
        // composition of all the functions in fs.
        fs.reduce(
            (f, g) => x => f(g(x)),
            x => x
        );


    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs => (
        ys => 0 < ys.length ? (
            ys.every(Array.isArray) ? (
                []
            ) : ''
        ).concat(...ys) : ys
    )(xs);


    // head :: [a] -> a
    const head = xs => (
        ys => ys.length ? (
            ys[0]
        ) : undefined
    )(list(xs));


    // intercalate :: String -> [String] -> String
    const intercalate = s =>
        // The concatenation of xs
        // interspersed with copies of s.
        xs => xs.join(s);


    // list :: StringOrArrayLike b => b -> [a]
    const list = xs =>
        // xs itself, if it is an Array,
        // or an Array derived from xs.
        Array.isArray(xs) ? (
            xs
        ) : Array.from(xs || []);


    // map :: (a -> b) -> [a] -> [b]
    const map = f =>
        // The list obtained by applying f
        // to each element of xs.
        // (The image of xs under f).
        xs => [...xs].map(f);


    // reverse :: [a] -> [a]
    const reverse = xs =>
        'string' !== typeof xs ? (
            xs.slice(0).reverse()
        ) : xs.split('').reverse().join('');


    // tail :: [a] -> [a]
    const tail = xs =>
        // A new list consisting of all
        // items of xs except the first.
        xs.slice(1);


    // unlines :: [String] -> String
    const unlines = xs =>
        // A single string formed by the intercalation
        // of a list of strings with the newline character.
        xs.join('\n');


    // words :: String -> [String]
    const words = s =>
        // List of space-delimited sub-strings.
        s.split(/\s+/);

    // MAIN ---
    return main();
})();
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
{One, two, three and four}
{Me, myself and I}
{Jack and Jill}
{Loner}

Alternative implementation:

function quibble(words) {
  var words2 = words.join()

  var words3 = [...words2].reverse().join('');
  var res = words3.replace(",", " dna ");
  var words4 = [...res].reverse().join('');

  return '{'+words4+'}';
}

jq

Works with: jq version 1.4
def quibble:
  if length == 0 then "" 
  elif length == 1 then .[0]
  else (.[0:length-1] | join(", ")) + " and " + .[length-1]
  end
  | "{" + . + "}";

Example:

( [], ["ABC"],  ["ABC", "DEF"],  ["ABC", "DEF", "G", "H"]) | quibble
Output:
jq -n -r -f Comma_quibbling.jq
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Julia

Works with: Julia version 0.6
function quibble(arr::Array)
    if isempty(arr) rst = "" else rst = "$(arr[end])" end
    if length(arr) > 1 rst = join(arr[1:end-1], ", ") * " and " * rst end
    return "{" * rst * "}"
end

@show quibble([])
@show quibble(["ABC"])
@show quibble(["ABC", "DEF"])
@show quibble(["ABC", "DEF", "G", "H"])
Output:
quibble([]) = "{}"
quibble(["ABC"]) = "{ABC}"
quibble(["ABC", "DEF"]) = "{ABC and DEF}"
quibble(["ABC", "DEF", "G", "H"]) = "{ABC, DEF, G and H}"

Kotlin

// version 1.0.6

fun commaQuibble(s: String): String {
    val t = s.trim('[', ']').replace(" ", "").replace("\"", "") 
    val words = t.split(',')
    val sb = StringBuilder("{")
    for (i in 0 until words.size) {
        sb.append(when (i) {
            0                -> ""
            words.lastIndex  -> " and "
            else             -> ", "    
        })
        sb.append(words[i])
    }
    return sb.append("}").toString()
}

fun main(args: Array<String>) {
    val inputs = arrayOf(
        """[]""",
        """["ABC"]""",
        """["ABC", "DEF"]""",
        """["ABC", "DEF", "G", "H"]"""
    )
    for (input in inputs) println("${input.padEnd(24)}  ->  ${commaQuibble(input)}")
}
Output:
[]                        ->  {}
["ABC"]                   ->  {ABC}
["ABC", "DEF"]            ->  {ABC and DEF}
["ABC", "DEF", "G", "H"]  ->  {ABC, DEF, G and H}

Lang

fp.quibble = (&words) -> {
	$len $= @&words
	
	$output = \{\e
	
	$i
	repeat($[i], $len) {
		$output += &words[$i] ||| ($i == -|$len?\e:($i == $len - 2?\sand\s:\,\s))
	}
	
	$output += \}\e
	
	return $output
}

fn.println(fp.quibble(fn.arrayOf()))
fn.println(fp.quibble(fn.arrayOf(ABC)))
fn.println(fp.quibble(fn.arrayOf(ABC, DEF)))
fn.println(fp.quibble(fn.arrayOf(ABC, DEF, G, H)))
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Lasso

#!/usr/bin/lasso9

local(collection =
	array(
		array,
		array("ABC"),
		array("ABC", "DEF"),
		array("ABC", "DEF", "G", "H")
	)
)

with words in #collection do {
	if(#words -> size > 1) => {
		local(last = #words -> last)
		#words -> removelast
		stdoutnl('{' + #words -> join(', ') + ' and ' + #last'}')
	else(#words -> size == 1)
		stdoutnl('{' + #words -> first + '}')
	else
		stdoutnl('{}')
	}

}

Output:

{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Liberty BASIC

do
        read in$
        if in$ ="END" then wait
        w =wordCount( in$)
        select case w
            case 0
                o$ ="{}"
            case 1
                o$ ="{" +in$ +"}"
            case 2
                o$ ="{" +word$( in$, 1) +" and " +word$( in$, 2) +"}"
            case else
                o$ ="{"
                o$ =o$ +word$( in$, 1)
                for k =2 to w -1
                    o$ =o$ +", " +word$( in$, k)
                next k
                o$ =o$ +" and " +word$( in$, w) +"}"
        end select
        if w =1 then
            print "'"; in$; "'"; " held "; w; " word. "; tab( 30); o$
        else
            print "'"; in$; "'"; " held "; w; " words. "; tab( 30); o$
        end if
    loop until 0

    wait

    function wordCount( IN$)
        wordCount =1
        for i =1 to len( IN$)
            if mid$( IN$, i, 1) =" " then wordCount =wordCount +1
        next i
    end function

    end

    data ""                 'No input words.
    data "ABC"              'One input word.
    data "ABC DEF"          'Two words.
    data "ABC DEF G"        'Three words.
    data "ABC DEF G H"      'Four words.

    data "END"              'Sentinel for EOD.
Output:
'' held 1 word.              {}
'ABC' held 1 word.           {ABC}
'ABC DEF' held 2 words.      {ABC and DEF}
'ABC DEF G' held 3 words.    {ABC, DEF and G}
'ABC DEF G H' held 4 words.  {ABC, DEF, G and H}

to join :delimiter :list [:result []]
  output cond [
    [ [empty? :list]   :result ]
    [ [empty? :result] (join :delimiter butfirst :list first :list) ]
    [ else             (join :delimiter butfirst :list 
                                        (word :result :delimiter first :list)) ]
  ]
end

to quibble :list
  local "length
  make "length count :list
  make "text (
    ifelse [:length <= 2] [
      (join "\ and\  :list)
    ] [ 
      (join "\ and\  (sentence join ",\  butlast :list last :list))
    ])
  output ifelse [empty? :text] "\{\} [(word "\{ :text "\})]
end

foreach [ [] [ABC] [ABC DEF] [ABC DEF G H] ] [
  print quibble ?
]

bye
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Lua

function quibble (strTab)
    local outString, join = "{"
    for strNum = 1, #strTab do
        if strNum == #strTab then
            join = ""
        elseif strNum == #strTab - 1 then
            join = " and " 
        else
            join = ", "
        end
        outString = outString .. strTab[strNum] .. join
    end
    return outString .. '}'
end

local testCases = {
    {},
    {"ABC"},
    {"ABC", "DEF"},
    {"ABC", "DEF", "G", "H"}
}
for _, input in pairs(testCases) do print(quibble(input)) end
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

M2000 Interpreter

Using string as argument

Module Checkit {
      function f$ {
            what$=mid$(trim$(letter$),2)
            what$=Left$(what$, len(what$)-1)
            flush ' erase any argument from stack
            Data param$(what$)
            m=stack.size
            document resp$="{" 
             if m>2 then {
                  shift m-1, 2    ' get last two as first two
                  push letter$+" and "+letter$
                  m--   ' one less
                  shiftback m   ' move to last position
            }
            while not empty {
                   resp$=letter$+if$(not empty->", ", "")
             }
            =resp$+"}"   
       
      }
      \\ we use ? for Print
      ? f$({[]})
      ? f$({["ABC"]})
      ? f$({["ABC", "DEF"]})
      ? f$({["ABC","DEF", "G", "H"]})
}
Checkit

Using String functions only

Module Checkit {
      function f$ {
            what$=filter$(trim$(letter$), chr$(34)) 
            what$=Mid$(what$, 2, len(what$)-2)
            count=Len(what$)-Len(filter$(what$,","))
            if count>2 then m=rinstr(what$, ", ")  :  insert m, 2 what$=" and " 
            ="{"+what$+"}"
      }
      ? f$({[]})
      ? f$({["ABC"]})
      ? f$({["ABC", "DEF"]})
      ? f$({["ABC","DEF", "G", "H"]})
}
Checkit

Using array as argument

Module Checkit {
      function f$(ar) {
            flush
            Data ! ar
            m=stack.size
            document resp$="{" 
             if m>2 then {
                  shift m-1, 2    ' get last two as first two
                  push letter$+" and "+letter$
                  m--   ' one less
                  shiftback m   ' move to last position
            }
            while not empty {
                   resp$=letter$+if$(not empty->", ", "")
             }
            =resp$+"}"   
      }
      ? f$((,))
      ? f$(("ABC",))
      ? f$(("ABC", "DEF"))
      ? f$(("ABC","DEF", "G", "H"))
}
Checkit
Output:
{}
{ABC}
{ABC, DEF}
{ABC, DEF, G and H}

Maple

Quibble := proc( los )
  uses  StringTools;
  Fence( proc()
        if los = [] then
          ""
        elif numelems( los ) = 1 then
          los[ 1 ]
        else
          cat( Join( los[ 1 .. -2 ], ", " ), " and ", los[ -1 ] )
        end if
  end(), "{", "}" )
end proc:

Check it on the required inputs:

> Quibble([]);
                                  "{}"

> Quibble( [ "ABC" ] );
                                "{ABC}"

> Quibble( [ "ABC", "DEF" ] );
                            "{ABC and DEF}"

> Quibble( ["ABC", "DEF", "G", "H"] );
                         "{ABC, DEF, G and H}"

Mathematica / Wolfram Language

quibble[words___] :=
    ToString@{StringJoin@@
        Replace[Riffle[{words}, ", "],
            {most__, ", ", last_} -> {most, " and ", last}]}
Output:
In[2]:= quibble[]
Out[2]= {}

In[3]:= quibble["ABC"]
Out[3]= {ABC}

In[4]:= quibble["ABC","DEF"]
Out[4]= {ABC and DEF}

In[5]:= quibble["ABC","DEF","G","H"]
Out[5]= {ABC, DEF, G and H}

MATLAB / Octave

function r = comma_quibbling(varargin)
	if isempty(varargin)
		r = '';
	elseif length(varargin)==1;
		r = varargin{1};
	else
		r = [varargin{end-1},' and ', varargin{end}];
		for k=length(varargin)-2:-1:1,
			r = [varargin{k}, ', ', r];
		end
	end
end;


Output:
octave:73> comma_quibbling('')
ans = 
octave:74> comma_quibbling('ABC')
ans = ABC
octave:75> comma_quibbling('ABC','DEF')
ans = ABC and DEF
octave:76> comma_quibbling('ABC','DEF','G')
ans = ABC, DEF and G
octave:77> comma_quibbling('ABC','DEF','G','H')
ans = ABC, DEF, G and H

MAXScript

fn separate words: =
(
	if words == unsupplied or words == undefined or classof words != array then return "{}"
		else
		(
			local toReturn = "{"
			local pos = 1
			while pos <= words.count do
			(
				if pos == 1 then (append toReturn words[pos]; pos+=1)
				else
				(	
					if pos <= words.count-1 then (append toReturn (", "+words[pos]); pos+=1)
						else
						(
							append toReturn (" and " + words[pos])
							pos +=1
						)
				)
			)
			return (toReturn+"}")
		)
)

Output:

separate words:#()
"{}"
separate words:#("ABC")
"{ABC}"
separate words:#("ABC","DEF")
"{ABC and DEF}"
separate words:#("ABC","DEF","G","H")
"{ABC, DEF, G and H}"

Miranda

main :: [sys_message]
main = [Stdout (show test ++ ": {" ++ quibble test ++ "}\n") | test <- tests]

tests :: [[[char]]]
tests = [ [],
          ["ABC"],
          ["ABC","DEF"],
          ["ABC","DEF","G","H"] ]

quibble :: [[char]]->[char]
quibble []            = []
quibble [word]        = word
quibble [word1,word2] = word1 ++ " and " ++ word2
quibble (word:words)  = word ++ ", " ++ quibble words
Output:
[]: {}
["ABC"]: {ABC}
["ABC","DEF"]: {ABC and DEF}
["ABC","DEF","G","H"]: {ABC, DEF, G and H}

NetRexx

/* NetRexx */
options replace format comments java crossref symbols nobinary

runSample(arg)
return

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
method quibble(arg) public static
  parse arg '[' lst ']'
  lst = lst.changestr('"', '').space(1)
  lc = lst.lastpos(',')
  if lc > 0 then
    lst = lst.insert('and', lc).overlay(' ', lc)
  return '{'lst'}'

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
method runSample(arg) private static
  lists = ['[]', -                     -- {}
           '["ABC"]', -                -- {ABC}
           '["ABC", "DEF"]', -         -- {ABC and DEF}
           '["ABC", "DEF", "G", "H"]'] -- {ABC, DEF, G and H}
  loop lst over lists
    say lst.right(30) ':' quibble(lst)
    end lst
  return
Output:
                            [] : {}
                       ["ABC"] : {ABC}
                ["ABC", "DEF"] : {ABC and DEF}
      ["ABC", "DEF", "G", "H"] : {ABC, DEF, G and H}

Nim

proc commaQuibble(s: openArray[string]): string =
  result = ""
  for i, c in s:
    if i > 0: result.add (if i < s.high: ", " else: " and ")
    result.add c
  result = "{" & result & "}"

var s = @[@[], @["ABC"], @["ABC", "DEF"], @["ABC", "DEF", "G", "H"]]
for i in s:
  echo commaQuibble(i)

Nu

def quibble [] {
  if ($in | length) < 3 { $in } else [($in | drop | str join ', ') ($in | last)]
  | $'{($in | str join " and ")}'
}

# test
[
  []
  [ABC]
  [ABC DEF]
  [ABC DEF G H]
]
| each { quibble }
Output:
╭───┬─────────────────────╮
│ 0 │ {}                  │
│ 1 │ {ABC}               │
│ 2 │ {ABC and DEF}       │
│ 3 │ {ABC, DEF, G and H} │
╰───┴─────────────────────╯

Oberon-2

Works with: oo2c
MODULE CommaQuibbling;
IMPORT 
  NPCT:Args,
  Strings,
  Out;
  
VAR
  str: ARRAY 256 OF CHAR;

  PROCEDURE Do(VAR s: ARRAY OF CHAR);
  VAR
    aux: ARRAY 128 OF CHAR;
    i,params: LONGINT;
  BEGIN
    params := Args.Number() - 1;
    CASE params OF
       0: 
        COPY("{}",s)
      |1:
        Args.At(1,aux);
        Strings.Append("{",s);
        Strings.Append(aux,s);
        Strings.Append("}",s); 
      ELSE
        Strings.Append("{",s);
        FOR i := 1 TO params - 1 DO
          Args.At(i,aux);
          Strings.Append(aux,s);
          IF i # params - 1 THEN 
            Strings.Append(", ",s)
          ELSE 
            Strings.Append(" and ", s) 
          END
        END;
        Args.At(params,aux);
        Strings.Append(aux,s);
        Strings.Append("}",s)
    END;
    
  END Do;
  
BEGIN
  Do(str);
  Out.String(":> ");Out.String(str);Out.Ln
END CommaQuibbling.
Output:
$ bin/CommaQuibbling 
:> {}
$ bin/CommaQuibbling ABC
:> {ABC}
$ bin/CommaQuibbling ABC DEF
:> {ABC and DEF}
$ bin/CommaQuibbling ABC DEF G
:> {ABC, DEF and G}
$ bin/CommaQuibbling ABC DEF G H
:> {ABC, DEF, G and H}

Objeck

class Quibbler {
  function : Quibble(words : String[]) ~ String {
    text := "{";
    
    each(i : words) {
      text += words[i];
      if(i < words->Size() - 2) {
        text += ", ";
      }
      else if(i = words->Size() - 2) {
        text += " and ";
      };
    };
    text += "}";

    return text;
  }
 
  function : Main(args : String[]) ~ Nil {
    words := String->New[0];
    Quibble(words)->PrintLine();

    words := ["ABC"];
    Quibble(words)->PrintLine();

    words := ["ABC", "DEF"];
    Quibble(words)->PrintLine();

    words := ["ABC", "DEF", "G", "H"];
    Quibble(words)->PrintLine();
  }
}

Output:

{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

OCaml

open Printf

let quibble list =
  let rec aux = function
    | a :: b :: c :: d :: rest -> a ^ ", " ^ aux (b :: c :: d :: rest)
    | [a; b; c] -> sprintf "%s, %s and %s}" a b c
    | [a; b] -> sprintf "%s and %s}" a b
    | [a] -> sprintf "%s}" a
    | [] -> "}" in
  "{" ^ aux list

let test () =
  [[];
   ["ABC"];
   ["ABC"; "DEF"];
   ["ABC"; "DEF"; "G"; "H"]]
  |> List.iter (fun list -> print_endline (quibble list))
Works with: Core version v0.9.116.03+91
open Core

let quibble = function
  | [| |] -> "{}"
  | [| a |] -> sprintf "{%s}" a
  | array ->
    let last, rest = Array.last array, Array.slice array 0 (-1) in
    sprintf "{%s and %s}" (String.concat_array ~sep:", " rest) last

let test () =
  [[||];
   [|"ABC"|];
   [|"ABC"; "DEF"|];
   [|"ABC"; "DEF"; "G"; "H"|]]
  |> List.iter ~f:(fun list -> print_endline (quibble list))
Output:
# test ();;
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Oforth

: quibbing(l) -- string
| i s |
   StringBuffer new "{" <<
   l size dup 1- ->s loop: i [ 
      l at(i) <<
      i s < ifTrue: [ ", " << continue ]
      i s == ifTrue: [ " and " << ]
      ]
   "}" << dup freeze ;
Output:
[ [], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"] ] map(#quibbing) .
[{}, {ABC}, {ABC and DEF}, {ABC, DEF, G and H}]

Ol

(define (quibble . args)
   (display "{")
   (let loop ((args args))
      (unless (null? args) (begin
         (display (car args))
         (cond
            ((= 1 (length args)) #t)
            ((= 2 (length args))
               (display " and "))
            (else
               (display ", ")))
         (loop (cdr args)))))
   (print "}"))

; testing =>
(quibble)
(quibble "ABC")
(quibble "ABC" "DEF")
(quibble "ABC" "DEF" "G" "H")
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

PARI/GP

comma(v)={
  if(#v==0, return("{}"));
  if(#v==1, return(Str("{"v[1]"}")));
  my(s=Str("{",v[1]));
  for(i=2,#v-1,s=Str(s,", ",v[i]));
  Str(s," and ",v[#v],"}")
};
comma([])
comma(["ABC"])
comma(["ABC", "DEF"])
comma(["ABC", "DEF", "G", "H"])

Output:

%1 = "{}"
%2 = "{ABC}"
%3 = "{ABC and DEF}"
%4 = "{ABC, DEF, G and H}"

Pascal

program CommaQuibbling;

uses
  SysUtils,
  Classes,
  StrUtils;

const
  OuterBracket =['[', ']'];

type

{$IFNDEF FPC}
  SizeInt = LongInt;
{$ENDIF}



  { TCommaQuibble }

  TCommaQuibble = class(TStringList)
  private
    function GetCommaquibble: string;
    procedure SetCommaQuibble(AValue: string);
  public
    property CommaQuibble: string read GetCommaquibble write SetCommaQuibble;
  end;

{$IFNDEF FPC} // Delphi support

function WordPosition(const N: Integer; const S: string; const WordDelims:
  TSysCharSet): SizeInt;
var
  PS, P, PE: PChar;
  Count: Integer;
begin
  Result := 0;
  Count := 0;
  PS := PChar(pointer(S));
  PE := PS + Length(S);
  P := PS;
  while (P < PE) and (Count <> N) do
  begin
    while (P < PE) and (P^ in WordDelims) do
      inc(P);
    if (P < PE) then
      inc(Count);
    if (Count <> N) then
      while (P < PE) and not (P^ in WordDelims) do
        inc(P)
    else
      Result := (P - PS) + 1;
  end;
end;

function ExtractWordPos(N: Integer; const S: string; const WordDelims:
  TSysCharSet; out Pos: Integer): string;
var
  i, j, l: SizeInt;
begin
  j := 0;
  i := WordPosition(N, S, WordDelims);
  if (i > High(Integer)) then
  begin
    Result := '';
    Pos := -1;
    Exit;
  end;
  Pos := i;
  if (i <> 0) then
  begin
    j := i;
    l := Length(S);
    while (j <= l) and not (S[j] in WordDelims) do
      inc(j);
  end;
  SetLength(Result, j - i);
  if ((j - i) > 0) then
    Result := copy(S, i, j - i);
end;

function ExtractWord(N: Integer; const S: string; const WordDelims: TSysCharSet):
  string; inline;
var
  i: SizeInt;
begin
  Result := ExtractWordPos(N, S, WordDelims, i);
end;
{$ENDIF}

{ TCommaQuibble }

procedure TCommaQuibble.SetCommaQuibble(AValue: string);
begin
  AValue := ExtractWord(1, AValue, OuterBracket);
  commatext := AValue;
end;

function TCommaQuibble.GetCommaquibble: string;
var
  x: Integer;
  Del: string;
begin
  result := '';
  Del := ', ';
  for x := 0 to Count - 1 do
  begin
    result := result + Strings[x];
    if x = Count - 2 then
      Del := ' and '
    else if x = Count - 1 then
      Del := '';
    result := result + Del;
  end;
  result := '{' + result + '}';
end;

const
  TestData: array[0..7] of string = ('[]', '["ABC"]', '["ABC", "DEF"]',
    '["ABC", "DEF", "G", "H"]', '', '"ABC"', '"ABC", "DEF"', '"ABC", "DEF", "G", "H"');

var
  Quibble: TCommaQuibble;
  TestString: string;

begin
  Quibble := TCommaQuibble.Create;

  for TestString in TestData do
  begin
    Quibble.CommaQuibble := TestString;
    writeln(Quibble.CommaQuibble);
  end;
end.

Output:

{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Perl

Translation of: Raku
sub comma_quibbling :prototype(@) {
    return "{$_}" for
        @_ < 2 ? "@_" :
        join(', ', @_[0..@_-2]) . ' and ' . $_[-1];
}

print comma_quibbling(@$_), "\n" for
    [], [qw(ABC)], [qw(ABC DEF)], [qw(ABC DEF G H)];
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Perl 5.01 version and other approach:

use 5.01;
sub comma_quibbling {
  my $last = pop // '';
  return '{'. (@_ ? (join ', ', @_).' and '.$last : $last).'}';
}

say for map {comma_quibbling(@$_)}
  [], [qw(ABC)], [qw(ABC DEF)], [qw(ABC DEF G H)];
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Phix

function quibble(sequence words)
    if length(words)>=2 then
        words[-2..-1] = {words[-2]&" and "&words[-1]}
    end if
    return "{"&join(words,", ")&"}"
end function
 
constant tests = {{},
                  {"ABC"},
                  {"ABC","DEF"},
                  {"ABC","DEF","G","H"}}
 
for i=1 to length(tests) do
    ?quibble(tests[i])
end for
Output:
"{}"
"{ABC}"
"{ABC and DEF}"
"{ABC, DEF, G and H}"

PHP

<?php

function quibble($arr) {

    switch (count($arr)) {

        case 0:
            return '{}';

        case 1:
            return "{{$arr[0]}}";

        default:
            $left = implode(', ', array_slice($arr, 0, -1));
            $right = array_slice($arr, -1)[0];
            return "{{$left} and {$right}}";

    }

}


$tests = [
  [],
  ["ABC"],
  ["ABC", "DEF"],
  ["ABC", "DEF", "G", "H"] 
];

foreach ($tests as $test) {
  echo quibble($test) . PHP_EOL;
}
?>
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

PicoLisp

(for L '([] ["ABC"] ["ABC", "DEF"] ["ABC", "DEF", "G", "H"])
   (let H (head -1 L)
      (prinl
         "{"
         (glue ", " H)
         (and H " and ")
         (last L)
         "}" ) ) )

Output:

{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

PL/I

*process or(!);
 quib: Proc Options(main);
 /*********************************************************************
 * 06.10.2013 Walter Pachl
 *********************************************************************/
   put Edit*process or(!);
 quib: Proc Options(main);
 /*********************************************************************
 * 06.10.2013 Walter Pachl
 * 07.10.2013 -"- change "Oxford comma" to and
 *********************************************************************/
   put Edit(quibbling(''))(Skip,a);
   put Edit(quibbling('ABC'))(Skip,a);
   put Edit(quibbling('ABC DEF'))(Skip,a);
   put Edit(quibbling('ABC DEF G H'))(Skip,a);
   return;

 quibbling: proc(s) Returns(Char(100) Var);
   Dcl s Char(*);
   Dcl result   Char(100) Var Init('');
   Dcl word(10) Char(100) Var;
   Dcl (wi,p) Bin Fixed(31);
   If s='' Then result='';
   Else Do;
     Do wi=1 By 1 While(s^='');
       p=index(s,' ');
       if p=0 Then Do;
         word(wi)=s;
         s='';
         End;
       Else Do;
         word(wi)=left(s,p-1);
         s=substr(s,p+1);
         End;
       end;
     wn=wi-1;
     result=word(1);
     Do i=2 To wn-1;
       result=result!!', '!!word(i);
       End;
     If wn>1 Then
       result=result!!' and '!!word(wn);
     End;
   Return('{'!!result!!'}');
   End;
 End;
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}      

PL/M

100H:

/* COPY A STRING (MINUS TERMINATOR), RETURNS LENGTH (MINUS TERMINATOR) */
COPY$STR: PROCEDURE(SRC, DST) ADDRESS;
    DECLARE (SRC, DST) ADDRESS;
    DECLARE (SCH BASED SRC, DCH BASED DST) BYTE;
    DECLARE L ADDRESS;
    L = 0;
    DO WHILE SCH <> '$';
        DCH = SCH;
        SRC = SRC + 1;
        DST = DST + 1;
        L = L + 1;
    END;
    RETURN L;
END COPY$STR;

/* QUIBBLE GIVEN ARRAY OF $-TERMINATED STRINGS, STORE RESULT IN BUFR */
QUIBBLE: PROCEDURE(WORDS, BUFR) ADDRESS;
    DECLARE (WORDS, BUFR, ADR) ADDRESS;
    DECLARE (WORD BASED WORDS, WPTR) ADDRESS;
    DECLARE (WCHAR BASED WPTR, BCHAR BASED BUFR) BYTE;
    
    /* BRACES AND LOWERCASE LETTERS ARE NOT WITHIN PL/M CHARSET */
    DECLARE LBRACE LITERALLY '123', RBRACE LITERALLY '125';
    DECLARE ANDSTR DATA (32,97,110,100,32,'$');

    ADR = BUFR;
    BCHAR = LBRACE; 
    BUFR = BUFR + 1;
    DO WHILE WORD <> 0;
        BUFR = BUFR + COPY$STR(WORD, BUFR);
        WORDS = WORDS + 2;
        IF WORD <> 0 THEN
            IF WORD(1) <> 0 THEN
                BUFR = BUFR + COPY$STR(.', $', BUFR);
            ELSE
                BUFR = BUFR + COPY$STR(.ANDSTR, BUFR);
    END;
    BCHAR = RBRACE;
    BUFR = BUFR + 1;
    BCHAR = '$';
    RETURN ADR;
END QUIBBLE;

/* --- CP/M OUTPUT AND TESTING --- */
BDOS: PROCEDURE(FUNC, ARG); /* MAKE CP/M SYSTEM CALL */
    DECLARE FUNC BYTE, ARG ADDRESS;
    GO TO 5;
END BDOS;

DECLARE BDOS$EXIT LITERALLY '0',  /* EXIT TO CP/M */
        BDOS$PUTS LITERALLY '9';  /* PRINT STRING */

PUTS: PROCEDURE(S);
    DECLARE S ADDRESS;
    CALL BDOS(BDOS$PUTS, S);
    CALL BDOS(BDOS$PUTS, .(13,10,'$'));
END PUTS;

/* ARRAY WITH INITIALLY NO CONTENTS */
DECLARE ARR (5) ADDRESS INITIAL (0,0,0,0,0);

CALL PUTS(QUIBBLE(.ARR, .MEMORY)); /* NO STRINGS */
ARR(0) = .'ABC$';
CALL PUTS(QUIBBLE(.ARR, .MEMORY)); /* ABC */
ARR(1) = .'DEF$';
CALL PUTS(QUIBBLE(.ARR, .MEMORY)); /* ABC AND DEF */
ARR(2) = .'G$'; 
ARR(3) = .'H$';
CALL PUTS(QUIBBLE(.ARR, .MEMORY)); /* ABC, DEF, G AND H */
        
CALL BDOS(BDOS$EXIT, 0);
EOF
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Plain English

To quibble four words:
Add "ABC" to some string things.
Add "DEF" to the string things.
Add "G" to the string things.
Add "H" to the string things.
Quibble the string things.

To quibble one word:
Add "ABC" to some string things.
Quibble the string things.

To quibble some string things:
Quibble the string things giving a string.
Destroy the string things.
Write the string on the console.

To quibble some string things giving a string:
Append "{" to the string.
Put the string things' count into a count.
If the count is 0, append "}" to the string; exit.
Get a string thing from the string things.
If the count is 1, append the string thing's string then "}" to the string; exit.
Loop.
If a counter is past the count minus 2, append the string thing's string then " and " then the string thing's next's string then "}" to the string; exit.
Append the string thing's string then ", " to the string.
Put the string thing's next into the string thing.
Repeat.

To quibble two words:
Add "ABC" to some string things.
Add "DEF" to the string things.
Quibble the string things.

To quibble zero words:
Quibble some string things.

To run:
Start up.
Quibble zero words.
Quibble one word.
Quibble two words.
Quibble four words.
Wait for the escape key.
Shut down.
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

PowerShell

function Out-Quibble
{
    [OutputType([string])]
    Param
    (
        # Zero or more strings.
        [Parameter(Mandatory=$false, Position=0)]
        [AllowEmptyString()]
        [string[]]
        $Text = ""
    )

    # If not null or empty...
    if ($Text)
    {
        # Remove empty strings from the array.
        $text = "$Text".Split(" ", [StringSplitOptions]::RemoveEmptyEntries)
    }
    else
    {
        return "{}"
    }

    # Build a format string.
    $outStr = ""
    for ($i = 0; $i -lt $text.Count; $i++)
    { 
        $outStr += "{$i}, "
    }
    $outStr = $outStr.TrimEnd(", ")

    # If more than one word, insert " and" at last comma position.
    if ($text.Count -gt 1)
    {
        $cIndex = $outStr.LastIndexOf(",")
        $outStr = $outStr.Remove($cIndex,1).Insert($cIndex," and")
    }

    # Output the formatted string.
    "{" + $outStr -f $text + "}"
}
Out-Quibble
Out-Quibble "ABC"
Out-Quibble "ABC", "DEF"
Out-Quibble "ABC", "DEF", "G", "H"
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

What it might look like when working with a file:

$file = @'

ABC
ABC, DEF
ABC, DEF, G, H
'@ -split [Environment]::NewLine

foreach ($line in $file)
{
    Out-Quibble -Text ($line -split ", ")
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Prolog

Works with: SWI-Prolog version 7.1
words_series(Words, Bracketed) :-
    words_serialized(Words, Serialized),
    atomics_to_string(["{",Serialized,"}"], Bracketed).

words_serialized([], "").
words_serialized([Word], Word) :- !.
words_serialized(Words, Serialized) :-
    append(Rest, [Last], Words),                                  %% Splits the list of *Words* into the *Last* word and the *Rest* 
    atomics_to_string(Rest, ", ", WithCommas),                     
    atomics_to_string([WithCommas, " and ", Last], Serialized).



test :-
    forall( member(Words, [[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]]),
            ( words_series(Words, Series),
              format('~w ~15|=> ~w~n', [Words, Series]))
          ).
Output:
?- test.
[]             => {}
[ABC]          => {ABC}
[ABC,DEF]      => {ABC and DEF}
[ABC,DEF,G,H]  => {ABC, DEF, G and H}
true.

PureBasic

EnableExplicit

Procedure.s CommaQuibble(Input$)
  Protected i, count
  Protected result$, word$
  Input$ = RemoveString(Input$, "[")
  Input$ = RemoveString(Input$, "]")
  Input$ = RemoveString(Input$, #DQUOTE$)
  count = CountString(Input$, ",") + 1
  result$ = "{"
  For i = 1 To count
    word$ = StringField(Input$, i, ",")
    If i = 1
      result$ + word$
    ElseIf Count = i
      result$ + " and " + word$      
    Else
      result$ + ", " + word$
    EndIf
  Next
  ProcedureReturn result$ + "}"
EndProcedure

If OpenConsole()
  ; As 3 of the strings contain embedded quotes these need to be escaped with '\' and the whole string preceded by '~'
  PrintN(CommaQuibble("[]"))
  PrintN(CommaQuibble(~"[\"ABC\"]"))
  PrintN(CommaQuibble(~"[\"ABC\",\"DEF\"]"))
  PrintN(CommaQuibble(~"[\"ABC\",\"DEF\",\"G\",\"H\"]"))
  PrintN("")
  PrintN("Press any key to close the console")
  Repeat: Delay(10) : Until Inkey() <> ""
  CloseConsole()
EndIf
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Python

Python: Replace() whilst reversed

replace(..) can only replace the first X occurrences not the last hence the replace is done on the reverse of the intermediate string then reversed back.

>>> def strcat(sequence):
    return '{%s}' % ', '.join(sequence)[::-1].replace(',', 'dna ', 1)[::-1]

>>> for seq in ([], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]):
    print('Input: %-24r -> Output: %r' % (seq, strcat(seq)))

	
Input: []                       -> Output: '{}'
Input: ['ABC']                  -> Output: '{ABC}'
Input: ['ABC', 'DEF']           -> Output: '{ABC and DEF}'
Input: ['ABC', 'DEF', 'G', 'H'] -> Output: '{ABC, DEF, G and H}'
>>>

Python: Counted replacement

(Possible)

Translation of: Tcl

replace() will replace nothing if the count of items to replace is zero, (and negative integer counts act to replace all occurrences). This combines with the length of the input sequence to allow this to work:

def commaQuibble(s):
    return '{%s}' % ' and '.join(s).replace(' and ', ', ', len(s) - 2)

for seq in ([], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]):
	print('Input: %-24r -> Output: %r' % (seq, commaQuibble(seq)))
Output:
Input: []                       -> Output: '{}'
Input: ['ABC']                  -> Output: '{ABC}'
Input: ['ABC', 'DEF']           -> Output: '{ABC and DEF}'
Input: ['ABC', 'DEF', 'G', 'H'] -> Output: '{ABC, DEF, G and H}'

Python: Functional

>>> def quibble(s):
    return ('{' +
                (', '.join(s[:-1]) + ' and ' if len(s) > 1 else '') +
	        (s[-1] if s else '') +
	    '}')

>>> for seq in ([], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]):
	print('Input: %-24r -> Output: %r' % (seq, quibble(seq)))

	
Input: []                       -> Output: '{}'
Input: ['ABC']                  -> Output: '{ABC}'
Input: ['ABC', 'DEF']           -> Output: '{ABC and DEF}'
Input: ['ABC', 'DEF', 'G', 'H'] -> Output: '{ABC, DEF, G and H}'
>>>

Quackery

[ swap join join ]                    is glue     ( [ [ [ --> [ )

[ [ dup size
    dup 0 = iff
      [ 2drop [] ] done
    dup 1 = iff
      [ drop unpack ] done
    2 = iff
      [ unpack $ ' and ' glue ] done
    behead swap recurse $ ', ' glue ]
  $ '{' swap join $ '}' join ]         is quibble ( [     --> $ )

[] quibble echo$ cr
$ 'ABC' nest$ quibble echo$ cr
$ 'ABC DEF' nest$ quibble echo$ cr
$ 'ABC DEF G H' nest$ quibble echo$
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

R

quib <- function(vect)
{
  #The task does not consider empty strings to be words, so we remove them immediately.
  #We could also remove non-upper-case characters, but the tasks gives off the impression that the user will do that.
  vect <- vect[nchar(vect) != 0]
  len <- length(vect)
  allButLastWord <- if(len >= 2) paste0(vect[seq_len(len - 1)], collapse = ", ") else ""
  paste0("{", if(nchar(allButLastWord) == 0) vect else paste0(allButLastWord, " and ", vect[len]), "}")
}
quib(character(0)) #R has several types of empty string, e.g. character(0), "", and c("", "", "").
quib("")
quib(" ")
quib(c("", ""))
quib(rep("", 10))
quib("ABC")
quib(c("ABC", ""))
quib(c("ABC", "DEF"))
quib(c("ABC", "DEF", "G", "H"))
quib(c("ABC", "DEF", "G", "H", "I", "J", ""))
Output:
> quib(character(0))
[1] "{}"
> quib("")
[1] "{}"
> quib(" ")
[1] "{ }"
> quib(c("", ""))
[1] "{}"
> quib(rep("", 10))
[1] "{}"
> quib("ABC")
[1] "{ABC}"
> quib(c("ABC", ""))
[1] "{ABC}"
> quib(c("ABC", "DEF"))
[1] "{ABC and DEF}"
> quib(c("ABC", "DEF", "G", "H"))
[1] "{ABC, DEF, G and H}"
> quib(c("ABC", "DEF", "G", "H", "I", "J", ""))
[1] "{ABC, DEF, G, H, I and J}"

Racket

(define (quibbling words)
  (define (sub-quibbling words)
    (match words
      ['() ""]
      [(list a) a]
      [(list a b) (format "~a and ~a" a b)]
      [(list a b ___) (format "~a, ~a" a (sub-quibbling b))]))
  (format "{~a}" (sub-quibbling words)))

(for ((input '([] ["ABC"] ["ABC" "DEF"] ["ABC" "DEF" "G" "H"])))
  (printf "~s\t->\t~a~%" input (quibbling input)))
Output:
()	->	{}
("ABC")	->	{ABC}
("ABC" "DEF")	->	{ABC and DEF}
("ABC" "DEF" "G" "H")	->	{ABC, DEF, G and H}

Raku

(formerly Perl 6)

sub comma-quibbling(@A) {
    <{ }>.join: @A < 2 ?? @A !! "@A[0..*-2].join(', ') and @A[*-1]";
}

say comma-quibbling($_) for
    [], [<ABC>], [<ABC DEF>], [<ABC DEF G H>];
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

REBOL

Straightforward implementation

Rebol []

comma-quibbling: func [block] [
    rejoin [
        "^{"

        to-string use [s] [
            s: copy block
            s: next s
            forskip s 2 [insert s either tail? next s [" and "] [", "]]
            s: head s
        ]

        "^}"
    ]
]

foreach t [[] [ABC] [ABC DEF] [ABC DEF G H]] [print comma-quibbling t]
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Alternative (more efficient) version with oxford comma switch

Rebol []

; builds string instead of using an intermediate block

comma-quibbling: func [block /oxford /local s length] [
    length: length? block
    rejoin [
        "^{"

        either length < 2 [to-string block] [
            s: to-string block/1
            for n 2 (length - 1) 1 [repend s [", " pick block n]]
            if all [oxford (length > 2)] [append s ","]
            repend s [" and " last block]
        ]

        "^}"
    ]
]

test: [[] [ABC] [ABC DEF] [ABC DEF G H]]
foreach t test [print comma-quibbling t]
print "Now with Oxford comma"
foreach t test [print comma-quibbling/oxford t]
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
Now with Oxford comma
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G, and H}

Refal

$ENTRY Go {
    = <Prout <Quibble>>
      <Prout <Quibble ('ABC')>>
      <Prout <Quibble ('ABC') ('DEF')>>
      <Prout <Quibble ('ABC') ('DEF') ('G') ('H')>>;
};

Quibble {
    e.X = '{' <Quibble1 e.X> '}';
};

Quibble1 {
    = ;
    (e.Word) = e.Word;
    (e.Word1) (e.Word2) = e.Word1 ' and ' e.Word2;
    (e.Word) e.Words = e.Word ', ' <Quibble1 e.Words>;
};
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

REXX

version 1:

say quibbling('')
say quibbling('ABC')
say quibbling('ABC DEF')
say quibbling('ABC DEF G H')
exit

quibbling: procedure
    parse arg list
    Select
      When list='' Then result=''
      When words(list)=1 then result=word(list,1)
      Otherwise result=translate(strip(subword(list,1,words(list)-1)),',',' '),
        'and' word(list,words(list))
      End
    Return '{'result'}'
Output:
{}
{ABC}
{ABC and DEF}
{ABC,DEF,G and H}

version 2:

say quibbling('')
say quibbling('ABC')
say quibbling('ABC DEF')
say quibbling('ABC DEF G H')
exit
quibbling: 
  parse arg list
  If list='' Then result=''
  Else Do
    Do wi=1 By 1 while list<>''
      Parse Var list word.wi ' ' list
      End
    wn=wi-1
    result=word.1
    Do wi=2 To wn-1
      result=result', 'word.wi
      End
    If wn>1 Then
      result=result 'and' word.wn
    End
  Return '{'result'}'
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}    

version 3:

Translation of: NetRexx
/* Rexx */

i_ = 0
i_ = i_ + 1; lists.0 = i_; lists.i_ = '[]'
i_ = i_ + 1; lists.0 = i_; lists.i_ = '["ABC"]'
i_ = i_ + 1; lists.0 = i_; lists.i_ = '["ABC", ''DEF'']'
i_ = i_ + 1; lists.0 = i_; lists.i_ = '[ABC, DEF, G, H]'

say
do i_ = 1 to lists.0
  list = lists.i_
  say right(list, 30) ':' quibbling03(list)
  end i_
exit

quibbling03:
procedure
  parse arg '[' lst ']'
  lst = changestr('"', changestr("'", lst, ''), '') /* remove double & single quotes */
  lc = lastpos(',', lst)
  if lc > 0 then
    lst = overlay(' ', insert('and', lst, lc), lc)
  lst = space(lst, 1) -- remove extra spaces
  return '{'lst'}'
Output:
                            [] : {}
                       ["ABC"] : {ABC}
                ["ABC", 'DEF'] : {ABC and DEF}
              [ABC, DEF, G, H] : {ABC, DEF, G and H}

Ring

# Project : Comma Quibbling

text = list(4)
text[1] = "{}"
text[2] = "ABC"
text[3] = "ABC,DEF"
text[4] = "ABC,DEF,G,H"
comma(text)

func comma(text)
       listtext = []
       for n = 1 to 4
            listtext = str2list(substr(text[n], ",", nl))
            if n = 2
               see "{" + list2str(listtext) + "}" + nl
               loop
            ok
            if len(listtext) = 1
               see "{}" + nl
               loop
            ok
            str = "{"
            for m = 1 to len(listtext)-1
                if len(listtext) = 2
                   str = str + listtext[m] + " "
                else
                   str = str + listtext[m] + ", "
                ok
            next
            if len(listtext) = 2
               str = left(str, len(str)-1)
            else
               str = left(str, len(str)-2)
            ok
            if len(listtext) = 2
               str = str + " " + listtext[len(listtext)] + "}"
            else
               str = str + " and " + listtext[len(listtext)] + "}"
            ok
            see str + nl
     next

Output:

{}
{ABC}
{ABC DEF}
{ABC, DEF, G and H}

RPL

Whilst it is always important to ensure overall code clarity for maintenance by using explicit variable names and structured control flows, the spirit of reverse Polish notation programming is to optimize some key part of the algorithm in order to make it as compact - and possibly as fast - as possible, whatever the induced loss of readability. Here, two IFTE low-level instructions are nested to deliver the appropriate liaison substring between two words or before the first word.

Works with: Halcyon Calc version 4.2.7
≪ DUP SIZE → words n
  ≪ "{" 1
     WHILE DUP n ≤ REPEAT
        DUP 1 ≠ OVER n ≠ ", " " and " IFTE "" IFTE
        ROT SWAP + 
        words 3 PICK GET + 
        SWAP 1 +
     END
     DROP "}" +
≫ ≫ 'CMAQBL' STO
Input:
{ } CMAQBL
{ "ABC" } CMAQBL
{ "ABC", "DEF" } CMAQBL
{ "ABC", "DEF", "G", "H" } CMAQBL
Output:
4: "{}"
3: "{ABC}"
2: "{ABC and DEF}"
1: "{ABC, DEF, G and H}"

Ruby

Translation of: Raku
def comma_quibbling(a)
  %w<{ }>.join(a.length < 2 ? a.first :
               "#{a[0..-2].join(', ')} and #{a[-1]}")
end

[[], %w<ABC>, %w<ABC DEF>, %w<ABC DEF G H>].each do |a|
  puts comma_quibbling(a)
end
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Run BASIC

wrds$ = "[]
[""ABC""]
[""ABC"", ""DEF""]
[""ABC"", ""DEF"", ""G"", ""H""]
"
while  word$(wrds$,j+1,chr$(13)) <> ""
  a$ = word$(wrds$,j+1,chr$(13))
  print a$;" ==> ";
  a$ = "{"+mid$(a$,2,len(a$)-2)+"}"
  j = j + 1
  for i = len(a$) to 1 step -1
    if mid$(a$,i,1) = "," then 
       a$ =  left$(a$,i-1) + " and " + mid$(a$,i+2) 
       exit for
    end if
  next i
  print a$
WEND
Output:
[] ==> {}
["ABC"] ==> {"ABC"}
["ABC", "DEF"] ==> {"ABC" and  "DEF"}
["ABC", "DEF", "G", "H"] ==> {"ABC", "DEF", "G" and  "H"}

Rust

fn quibble(seq: &[&str]) -> String {
    match seq.len() {
        0 => "{}".to_string(),
        1 => format!("{{{}}}", seq[0]),
        _ => {
            format!("{{{} and {}}}",
                    seq[..seq.len() - 1].join(", "),
                    seq.last().unwrap())
        }
    }
}

fn main() {
    println!("{}", quibble(&[]));
    println!("{}", quibble(&["ABC"]));
    println!("{}", quibble(&["ABC", "DEF"]));
    println!("{}", quibble(&["ABC", "DEF", "G", "H"]));
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Scala

def quibble( s:List[String] ) = s match {
  case m if m.isEmpty => "{}"
  case m if m.length < 3 => m.mkString("{", " and ", "}")
  case m => "{" + m.init.mkString(", ") + " and " + m.last + "}"
}

// A little test...
{
  println( quibble( List() ) )
  println( quibble( List("ABC") ) )
  println( quibble( List("ABC","DEF") ) )
  println( quibble( List("ABC","DEF","G","H") ) )
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Scheme

(define (quibble . args)
  (display "{")
  (do ((rem args (cdr rem)))
    ((null? rem) (display "}\n"))
    (display (car rem))
    (cond ((= 1 (length rem)) )
          ((= 2 (length rem))
           (display " and "))
          (else
            (display ", ")))))

(quibble)
(quibble "ABC")
(quibble "ABC" "DEF")
(quibble "ABC" "DEF" "G" "H")
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Sed

script-file:

s/#.*$//g
y/[/{/
y/]/}/
s/"//g
s/ [A-Z][A-Z]*}/ and&/g
s/, and/ and/

test.txt:

[] # (No input words).
["ABC"]
["ABC", "DEF"]
["ABC", "DEF", "G", "H"]

sed -f script-file test.txt

Output:
{} 
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Seed7

$ include "seed7_05.s7i";

const func string: quibble (in array string: input) is func
  result
    var string: quibble is "{";
  begin
    case length(input) of
      when {0}:  quibble &:= "}";
      when {1}:  quibble &:= input[1] & "}";
      otherwise: quibble &:= join(input[.. pred(length(input))], ", ") &
                             " and " & input[length(input)] & "}";
    end case;
  end func;

const proc: main is func
  begin
    writeln(quibble(0 times ""));
    writeln(quibble([] ("ABC")));
    writeln(quibble([] ("ABC", "DEF")));
    writeln(quibble([] ("ABC", "DEF", "G", "H")));
  end func;
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

SenseTalk

Quibble [] // (No input words).
Quibble ["ABC"]
Quibble ["ABC", "DEF"]
Quibble ["ABC", "DEF", "G", "H"]

to Quibble with wordList
	if the number of items in wordList is ...
		... 0 then put "{}"
		... 1 then put "{" & item 1 of wordList & "}"
		... else put "{" & (items first to penultimate of wordList) joined by ", " & " and " & the last item of wordlist & "}"
	end if
end Quibble
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

SETL

program comma_quibbling;
    tests := [
        [],
        ["ABC"],
        ["ABC","DEF"],
        ["ABC","DEF","G","H"]
    ];

    loop for t in tests do
        print(t, "=", quibble(t));
    end loop;

    proc quibble(words);
        ret := '{';
        loop while words /= [] do
            word fromb words;
            ret +:= word;
            case of
            (#words = 1):
                ret +:= " and ";
            (#words > 1):
                ret +:= ", ";
            end case;
        end loop;
        return ret + '}';
    end proc;
end program;
Output:
[] = {}
[ABC] = {ABC}
[ABC DEF] = {ABC and DEF}
[ABC DEF G H] = {ABC, DEF, G and H}

Sidef

func comma_quibbling(words) {
    '{' + ([words.first(-1).join(', ')]-[''] + [words.last] -> join(' and ')) + '}'
}

[<>, <ABC>, <ABC DEF>, <ABC DEF G H>].each { |w|
    say comma_quibbling(w)
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Standard ML

local
  fun quib []      = ""
    | quib [x]     = x
    | quib [x0,x1] = x0 ^ " and " ^ x1
    | quib (x::xs) = x ^ ", " ^ quib xs
in
  fun quibble xs = "{" ^ quib xs ^ "}"
end

(* Tests: *)
val t_quibble_0 = quibble [] = "{}"
val t_quibble_1 = quibble ["ABC"] = "{ABC}"
val t_quibble_2 = quibble ["ABC", "DEF"] = "{ABC and DEF}"
val t_quibble_3 = quibble ["ABC", "DEF", "G", "H"] = "{ABC, DEF, G and H}"

Swift

let inputs = [[], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"]]

func quibbling(var words:[String]) {
    if words.count == 0 {
        println("{}")
    } else if words.count == 1 {
        println("{\(words[0])}")
    } else if words.count == 2 {
        println("{\(words[0]) and \(words[1])}")
    } else {
        var output = "{"
        while words.count != 2 {
            output += words.removeAtIndex(0) + ", "
        }
        output += "\(words.removeAtIndex(0)) and \(words.removeAtIndex(0))}"
        
        println(output)
    }
}

for word in inputs {
    quibbling(word)
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Tcl

proc commaQuibble {lst} {
    return \{[join [lreplace $lst end-1 end [join [lrange $lst end-1 end] " and "]] ", "]\}
}

foreach input { {} {"ABC"} {"ABC" "DEF"} {"ABC" "DEF" "G" "H"} } {
    puts [commaQuibble $input]
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

TXR

(defun quib (list)
  (tree-bind (: last . lead) (reverse list)
    `{@{(nreverse lead) ", "}@(if lead " and ")@last}`))

Uiua

Works with: Uiua version 0.10.0-dev.1
Tests ← {{}
         {"ABC"}
         {"ABC" "DEF"}
         {"ABC" "DEF" "G" "H"}}
Jam ← ◇⊂◇⊂:□
Quibble ← |1 ⟨""◌|°□⊢|Jam" and "°⊟|Quibble⊂⊃(□Jam", "°⊟↙2)(↘2)⟩↧3⧻.
Wrap ← ⊂⊂"{" : "}" Quibble
⍚(&pWrap) Tests

UNIX Shell

Works with: Bourne Again SHell
Works with: Korn Shell
Works with: Z Shell
quibble() {
    printf '{'
    while (( $# > 2 )); do
        printf '%s, ' "$1"
        shift
    done
    if (( $# )); then
        printf '%s' "$1"
        shift
    fi
    if (( $# )); then
        printf ' and %s' "$1"
    fi
    printf '%s\n' '}'
}

With a slight modification, it will work in any POSIX shell, or even older Bourne-compatible shells as long as they have functions and printf:

quibble() {
    printf '{'
    while [ $# -gt 2 ]; do
        printf '%s, ' "$1"
        shift
    done
    if [ $# -gt 0 ]; then
        printf '%s' "$1"
        shift
    fi
    if [ $# -gt 0 ]; then
        printf ' and %s' "$1"
    fi
    printf '%s\n' '}'
}

Going the other way, Zsh-specific code can be more compact:

Works with: Z Shell
quibble() {
    printf '{'
    if (( $# > 1 )) printf '%s and ' ${(j:, :)@[1,-2]}
    printf '%s}\n' $@[-1]
}

The test code is the same either way:

quibble
quibble ABC
quibble ABC DEF
quibble ABC DEF G H
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

VBA

Option Explicit

Sub Main()
   Debug.Print Quibbling("")
   Debug.Print Quibbling("ABC")
   Debug.Print Quibbling("ABC, DEF")
   Debug.Print Quibbling("ABC, DEF, G, H")
   Debug.Print Quibbling("ABC, DEF, G, H, IJKLM, NO, PQRSTUV")
End Sub

Private Function Quibbling(MyString As String) As String
Dim s As String, n As Integer
   s = "{" & MyString & "}": n = InStrRev(s, ",")
   If n > 0 Then s = Left(s, n - 1) & " and " & Right(s, Len(s) - (n + 1))
   Quibbling = s
End Function
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}
{ABC, DEF, G, H, IJKLM, NO and PQRSTUV}

VBScript

Function Quibble(s)
	arr = Split(s,",")
	If s = "" Then
		Quibble = "{}" 
	ElseIf UBound(arr) = 0 Then
		Quibble = "{" & arr(0) & "}"
	Else
		Quibble = "{"
		For i = 0 To UBound(arr)
			If i = UBound(arr) - 1 Then
				Quibble = Quibble & arr(i) & " and " & arr(i + 1) & "}"
				Exit For
			Else
				Quibble = Quibble & arr(i) & ", "
			End If
		Next
	End If
End Function

WScript.StdOut.Write Quibble("")
WScript.StdOut.WriteLine
WScript.StdOut.Write Quibble("ABC")
WScript.StdOut.WriteLine
WScript.StdOut.Write Quibble("ABC,DEF")
WScript.StdOut.WriteLine
WScript.StdOut.Write Quibble("ABC,DEF,G,H")
WScript.StdOut.WriteLine
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Visual Basic .NET

FormatEnumerable() accepts an IEnumerable(Of String), as per Lippert's original specification. FormatArray() contains an alternative implementation for String().

Option Explicit On
Option Infer On
Option Strict On

Module Program
    Function FormatEnumerable(source As IEnumerable(Of String)) As String
        Dim res As New Text.StringBuilder("{")

        Using en = source.GetEnumerator()
            Dim moreThanOne As Boolean = False
            Dim nxt = If(en.MoveNext(), en.Current, String.Empty)

            Do While en.MoveNext()
                If moreThanOne Then res.Append(", ")
                moreThanOne = True

                res.Append(nxt)
                nxt = en.Current
            Loop

            Dim lastItem = If(moreThanOne, " and ", "") & nxt
            Return res.ToString() & lastItem & "}"
        End Using
    End Function

    Function FormatArray(source As String()) As String
        Select Case source.Length
            Case 0 : Return "{}"
            Case 1 : Return "{" & source(0) & "}"
            Case Else : Return "{" & String.Join(", ", source.Take(source.Length - 1)) & " and " & source(source.Length - 1) & "}"
        End Select
    End Function

    Sub Main()
        Dim cases As String()() = {Array.Empty(Of String), New String() {"ABC"}, New String() {"ABC", "DEF"}, New String() {"ABC", "DEF", "G", "H"}}
        For Each c In cases
            Console.WriteLine(FormatArray(c))
            Console.WriteLine(FormatEnumerable(c))
        Next
    End Sub
End Module
Output:
{}
{}
{ABC}
{ABC}
{ABC and DEF}
{ABC and DEF}
{ABC, DEF, G and H}
{ABC, DEF, G and H}

V (Vlang)

Translation of: go
fn q(s []string) string {
    match s.len {
        0 {
            return '{}'
        }
        1 {
            return '{${s[0]}}'
        }
        2 {
            return '{${s[0]} and ${s[1]}}'
        }
        else{
            return '{${s[0..s.len-1].join(', ')} and ${s[s.len-1]}}'
        }
    }
}

fn main(){
    println(q([]))
    println(q(['ABC']))
    println(q(['ABC','DEF']))
    println(q(['ABC','DEF','G','H']))
}
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

Wren

var quibbling = Fn.new { |w|
    var c = w.count
    if (c == 0) return "{}"
    if (c == 1) return "{%(w[0])}"
    if (c == 2) return "{%(w[0]) and %(w[1])}"
    return "{%(w[0..-2].join(", ")) and %(w[-1])}"
}

var words = [ [], ["ABC"], ["ABC", "DEF"], ["ABC", "DEF", "G", "H"] ]
for (w in words) System.print(quibbling.call(w))
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

XBS

func task(a:array){
	set x:string="";
	foreach(k,v as a){
		set out:string="";
		if ((k==(?a-2))&((?a)>1)){out=" and "}elif(k!=(?a-1)){out=", "}
		x+=v+out;del out;
	}
	send "{"+x+"}";
}

log(task([]));
log(task(["ABC"]));
log(task(["ABC","DEF"]));
log(task(["ABC","DEF","GHI"]));
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF and GHI}

XLISP

I like the Oxford comma; but specifications are specifications. So this implementation produces the required output by default. It also, however, allows an optional OXFORD-COMMA parameter: pass a true value, and you won't find yourself saying things like "I want to thank my parents, Ayn Rand and God".

(defun quibble (inputs &optional oxford-comma)
    (define final
        (if (and (caddr inputs) oxford-comma)
            ", and "
            " and " ) )
    (defun comma-quibble (words)
        (cond
            ((null words) "")
            ((null (cdr words)) (car words))
            (t (begin
                (string-append (car words)
                    (if (caddr words)
                        (string-append ", " (comma-quibble (cdr words)))
                        (string-append final (cadr words))) ) ) ) ) )
    (string-append "{" (comma-quibble inputs) "}") )

; test cases:
(print (quibble '())) ; empty list
(print (quibble '("ABC")))
(print (quibble '("ABC" "DEF")))
(print (quibble '("ABC" "DEF" "G" "H")))

(newline)

; test cases using the Oxford comma:
(print (quibble '() t))
(print (quibble '("ABC") t))
(print (quibble '("ABC" "DEF") t))
(print (quibble '("ABC" "DEF" "G" "H") t))
Output:
"{}" 
"{ABC}" 
"{ABC and DEF}" 
"{ABC, DEF, G and H}" 

"{}" 
"{ABC}" 
"{ABC and DEF}" 
"{ABC, DEF, G, and H}"

XPL0

include c:\cxpl\codes;

proc Quibble(N, S);
int  N, S;
int  I;
[ChOut(0, ^{);
for I:= 0 to N-1 do
    [Text(0, S(I));
    if I<N-2 then Text(0, ", ");
    if I=N-2 then Text(0, " and ");
    ];
ChOut(0, ^});
];

int I;
for I:= 0 to 4 do
    if I#3 then [Quibble(I, ["ABC", "DEF", "G", "H"]);  CrLf(0)]
Output:
{}
{ABC}
{ABC and DEF}
{ABC, DEF, G and H}

zkl

This is a cheese ball solution that replies on no commas in the inputs

fcn quib(list){ text:=("{"+list.toString(*)[2,-1]+"}").replace("\"","");
   if(list.len()<2) text;
   else{
      z:=(text=text.replace(",",", ")).rfind(","); 
      String(text[0,z]," and ",text[z+2,*])
   }
}

List.toString("*") converts List(1,2,3) to "L(1,2,3)" with all elements; without the *, long lists are shortened to L(1,2,3,...)

Output:
quib(List)     //-->"{}"
quib(L("ABC")) //-->"{ABC}"
quib(L("ABC", "DEF")) //-->"{ABC and DEF}"
quib(L("ABC", "DEF", "G", "H")) //-->"{ABC, DEF, G and H}"

ZX Spectrum Basic

10 DATA 0
20 DATA 1,"ABC"
30 DATA 2,"ABC","DEF"
40 DATA 4,"ABC","DEF","G","H"
50 FOR n=10 TO 40 STEP 10
60 RESTORE n: GO SUB 1000
70 NEXT n
80 STOP 
1000 REM quibble
1010 LET s$=""
1020 READ j
1030 IF j=0 THEN GO TO 1100
1040 FOR i=1 TO j
1050 READ a$
1060 LET s$=s$+a$
1070 IF (i+1)=j THEN LET s$=s$+" and ": GO TO 1090
1080 IF (i+1)<j THEN LET s$=s$+", "
1090 NEXT i
1100 PRINT "{";s$;"}"
1110 RETURN
Cookies help us deliver our services. By using our services, you agree to our use of cookies.