Whitespace

Appeared in:
01 Apr 2003
Paradigm:
Typing discipline:
File extensions:
.ws
Versions and implementations (Collapse all | Expand all):
Programming language

Whitespace is a popular esoteric language, widely known for using only whitespace characters for source code.

Whitespace was created in 2003 by Edwin Brady and Chris Morris. Its specification is quite simple, and it has no practical value, so its only standard is the authors’ description. It hasn’t spawned dialects, since it itself is perfect in its domain. It is not known whether it is Turing-complete (looks like it is, but nobody yet has taken the bother to prove it).

Whitespace was created it an attempt to fix the injustice done to whitespace characters. See, most modern languages ignore them, and it’s unjust to ignore something just because it’s invisible. So, a new language would ignore all characters except for whitespace ones. Other characters can be used as comments, however, one has to be careful not to slip an extra space or linefeed between comments. Any development in it is possible only in an editor which marks tabs and spaces visible, or alternatively in hex editor.

Each command of the language consists of three kinds of characters: space (ASCII 32), tab (ASCII 9) and line feed (ASCII 10). This way the commands can be rather lengthy, and they are quite numerous; they are systematized by using prefix (called Instruction Modification Parameter) which notes the type of the command: Stack Manipulation, Arithmetic, Heap Access, Flow Control, I/O.

The virtual machine of the language has a stack and a heap. Both of them can process arbitrary-width integers as their elements; the stack is used to perform all operations, and heap is used as a permanent store of data.

The only data type used in the language is integer numbers in their binary representation. A number starts with sign token (Space for positive and Tab for negative), than the binary notation follows (Space for 0 and Tab for 1), and finally the number ends with a new line.

Labels are arbitrary lists of spaces and tabs, terminated with line feed. Labels are used in flow control commands, and thus should be unique within the program.

The commands of the language are the following (grouped by type and followed with the type of parameter it takes, if any):

Stack Manipulation

  • Space-Space-Number : push the number on the stack
  • Space-LF-Space : duplicate the element on top of the stack
  • Space-LF-Tab : swap two top elements of the stack
  • Space-LF-LF : pop the top element of the stack and discard it
  • Space-Tab-Space-Number : copy the N-th item of the stack (index given by the argument) onto the top of the stack
  • Space-Tab-LF-Number : “slide” N items off the stack while keeping the top item

Two last commands are actually an extension, available since Whitespace 0.3, introduced to ease the usage of recursive functions.

Arithmetic

  • Tab-Space-Space-Space : addition
  • Tab-Space-Space-Tab : subtraction
  • Tab-Space-Space-LF : multiplication
  • Tab-Space-Tab-Space : division (integer)
  • Tab-Space-Tab-Tab : modulo remainder

The operation is applied to two topmost elements of the stack. Left operand of the operation is the deeper element of the stack (the one that was pushed first).

Heap Access

  • Tab-Tab-Space : store the top element of the stack at the memory cell, the address of which is given in second-top element
  • Tab-Tab-Tab : retrieve the contents of memory cell, the address of which is given with top element of the stack, and push this contents on the stack

Flow Control

  • LF-Space-Space-Label : put a label here
  • LF-Space-Tab-Label : call a subroutine
  • LF-Space-LF-Label : jump to the label
  • LF-Tab-Space-Label : if the top of the stack is zero, jump to the label
  • LF-Tab-Tab-Label : if the top of the stack is negative, jump to the label
  • LF-Tab-LF : end subroutine and pass control back to the caller
  • LF-LF-LF : end of program

I/O

  • Tab-LF-Space-Space/Tab : output the top element of the stack as a character/number
  • Tab-LF-Tab-Space/Tab : read a character/number from input stream and put it to memory cell, the address of which is given in top element of the stack

Examples:

Hello, World!:

Example for versions Whitespacers (Ruby)

This code is commented to simplify understanding it: letter means that next piece of whitespace pushes on the stack ASCII-code of this letter, and print means invoking the command of printing topmost element of the stack. The numbers that correspond to ASCII-codes are contained within brackets (except for the delimiting newline which is outside of the brackets for readability).

H  { 	  	   }
print	
  e  { 		  	 	}
print	
  l  { 		 		  }
print	
  l  { 		 		  }
print	
  o  { 		 				}
print	
  ,  { 	 		  }
print	
  space  { 	     }
print	
  W  { 	 	 			}
print	
  o  { 		 				}
print	
  r  { 			  	 }
print	
  l  { 		 		  }
print	
  d  { 		  	  }
print	
  !  { 	    	}
print	
  \n  { 	 	 }
print	
  


end

Factorial:

Example for versions Whitespacers (Ruby)

This example uses a slightly different method of commenting — each command is preceded with its literal description. Numbers and labels are enclosed in brackets.

The example works as follows: heap is used to store variables (1 — index of the first factorial which doesn’t need to be calculated, 2..5 — ASCII-codes of special characters used in printing, 6 and 7 — current number and its factorial), and stack is used to run commands). Factorial is calculated iteratively, on each iteration previously calculated values are printed, and new ones are calculated and stored to memory. After this, the newly calculated number is compared with contents of cell 1: if it is less, the loop continues, otherwise it halts.

A curious thing to note is that in Whitespace numeric system zero is “negative”: a number must have at least one Tab in its notation, and binary notation of zero has no 1s, so it has to be written as Tab (only sign bit).

push_1  { 	}
push_17  { 	   	}
store		 push_2  { 	 }
push_33  { 	    	}
store		 push_3  { 		}
push_32  { 	     }
store		 push_4  { 	  }
push_61  { 				 	}
store		 push_5  { 	 	}
push_10  { 	 	 }
store		 push_6  { 		 }
push_0  {	 }
store		 push_7  { 			}
push_1  { 	}
store		 label
  { }
printing_block_push_6  { 		 }
retrieve			print_as_number	
 	push_2  { 	 }
retrieve			print_as_char	
  push_3  { 		}
retrieve			print_as_char	
  push_4  { 	  }
retrieve			print_as_char	
  push_3  { 		}
retrieve			print_as_char	
  push_7  { 			}
retrieve			print_as_number	
 	push_5  { 	 	}
retrieve			print_as_char	
  increase_counter_block_push_6  { 		 }
push_6  { 		 }
retrieve			push_1  { 	}
add	   store		 calculate_next_factorial_block_push_7  { 			}
push_7  { 			}
retrieve			push_6  { 		 }
retrieve			multiply	  
store		 conditional_return_block_push_6  { 		 }
retrieve			push_1  { 	}
retrieve			subtract	  	jump_if_negative
		{ }
quit


end

Fibonacci numbers:

Example for versions Whitespacers (Ruby)

This example is similar to factorial one, except for that it makes more use of stack data storage and duplicate command to avoid extra readings from memory cells. Also, in this case the counter is negative and increased at each iteration, as opposed to positive and compared to fixed number of iterations.

push_1  { 	}
push_-16  {		    }
store		 push_2  { 	 }
push_44  { 	 		  }
store		 push_3  { 		}
push_32  { 	     }
store		 push_4  { 	  }
push_0  {	 }
store		 push_5  { 	 	}
push_1  { 	}
store		 label
  { }
start_loop_push_5  { 	 	}
push_4  { 	  }
retrieve			push_4  { 	  }
duplicate 
 push_5  { 	 	}
retrieve			duplicate 
 print_as_number	
 	push_2  { 	 }
retrieve			print_as_char	
  push_3  { 		}
retrieve			print_as_char	
  store		 retrieve			add	   store		 push_1  { 	}
duplicate 
 duplicate 
 duplicate 
 retrieve			add	   store		 retrieve			jump_if_negative
		{ }
push_10  { 	 	 }
push_46  { 	 			 }
duplicate 
 duplicate 
 print_as_char	
  print_as_char	
  print_as_char	
  print_as_char	
  quit


end

CamelCase:

Example for versions Whitespacers (Ruby)
push-1  { 	}
push-1  { 	}
save		 LOOP-START.label-0
  { }
push-2  { 	 }
readchar	
	 push-2  { 	 }
load			CHECK-WHETHER-IS-EOL.duplicate 
 push-10  { 	 	 }
subtract	  	if-0-goto-1
	 {	}
CONVERT-TO-LOWERCASE.duplicate 
 push-A  { 	     	}
subtract	  	if-neg-goto-2
		{	 }
duplicate 
 push-Z  { 	 		 	 }
swap 
	subtract	  	if-neg-goto-2
		{	 }
push-32  { 	     }
add	   label-2
  {	 }
CHECK-WHETHER-IS-LETTER.duplicate 
 push-a  { 		    	}
subtract	  	if-neg-goto-3
		{		}
duplicate 
 push-z  { 				 	 }
swap 
	subtract	  	if-neg-goto-3
		{		}
ACTION-IF-LETTER.CHECK-WHETHER-LAST-WAS-SPACE.push-1  { 	}
load			if-0-goto-4
	 {	  }
push-32  { 	     }
subtract	  	label-4
  {	  }
print	
  push-1  { 	}
push-0  {	 }
save		 goto-0
 
{ }
label-3
  {		}
ACTION-IF-NOT-LETTER.push-1  { 	}
push-1  { 	}
save		 goto-0
 
{ }
label-1
  {	}
push-10  { 	 	 }
print	
  


end.memory:1-was-last-space,2-currentchar