Everyone and their pet cats know about C, but relatively few people realize that C was indirectly derived from a language called BCPL, which stands for "Basic CPL"; CPL in turn stands for "Combined Programming Language".
I first encountered BCPL when I was at Cambridge University, the home of the language and its designer, Martin Richards. BCPL was the systems programming language of choice at Cambridge, because it generated code better than the IBM Fortran compilers while not requiring the agony of Assembler. In turn, Computer Science students were taught it as their first "serious" language (interestingly enough, not by Martin Richards). For many (myself not included) it was their first introduction to anything more advanced than Fortran 66.
After graduation, I went to work for a local manufacturer of Z80-based systems that did almost all of its systems programming in BCPL. Only after the move to Unix did we in turn move to C. As a result, I find myself with several years of practical BCPL programming experience.
When the topic came up once again on the net, I wrote this summary of the language for some readers who already know C. It is in no sense a comparison or a historical survey but simply a high-speed description. Feedback on this description is welcome; I have no doubt that there are parts which are still unduly terse and would benefit from expansion, but I'd like to know which !
BCPL implementations are available from Martin Richards, or from the old BCPL distribution.
Clive D.W. Feather
The memory of a BCPL program is divided into cells. All cells have the same size. A cell stores a single value which can be treated as any of:
Every cell has an address. Under certain circumstances, two or more cells are required to be adjacent in memory. In this case, their addresses, if treated as integers, are consecutive.
In addition, a group of adjacent cells may be treated as a character
buffer.  There are at least
BYTESPERWORD characters
for every cell (though possibly more).
Cells have a storage duration, which can be static, dynamic (associated with a scope), or heap.
	Integer constants are decimal; prefix # means octal,
	and #x hexadecimal.
	TRUE and FALSE are reserved words.
	? is a constant with an indeterminate value (which
    	may be different each time it is evaluated); any constant expression
    	containing an evaluated ? is itself a ? .
	Character constants are of the form 'a'; there are
	no multi-character constants.
	String constants are of
	the form "abc"; they contain between
	0 and 255 characters.
	Escape sequences in both string and character constants are
	*", *', **
	(representing the second character) and
	*N (newline),
	*T (tab),
	*S (space),
	*B (backspace), and
	*P (pagethrow).  
	In addition, a string can be extended over more than one line; the
	sequence *<white space>* is ignored within a string.
A string constant is replaced by the address of a large enough number of contiguous cells initialized so that:
    "abc" % 0 = 3
    "abc" % 1 = 'a'
    "abc" % 2 = 'b'
    "abc" % 3 = 'c'
	The cells have static storage duration.
A constant expression may use any operator, any constants other than string constants, and any identifier declared as a manifest.
Floating-point constants are of the form 1.2, 1.2e-3,
	or 1e+2.
! (dyadic) % :: highest precedence @ ! (monadic) * / REM #* #/ + - #+ #- (dyadic and monadic) = ~= < <= > >= #= #~= #< #<= #> #>= << >> ~ & | EQV NEQV -> TABLE VALOF lowest precedence SLCT precedence not described in R&W-S
Operators associate left-to-right except for -> and TABLE.
Except where stated, an identifier on the left of an assignment means that the result of the right hand side is to be stored in the cell named by that identifier, while an identifier in any other location represents the content of the cell it names. Statements like "a is an address" or "the integer a" mean "the value of the expression a, now taken as an address" or "the value of the expression a, now taken as an integer".
!b
!(a+b).
!a
%b
BITSPERBYTE
	  bits of the result of the right side.  In all
	  other contexts, the value is that of the bth character
	  padded to the left with zeroes (that is, characters are unsigned).
@a
@
	may only be used in this way or in the following two special ways).
@!a
@a!b
(a+b).
*b  and  
	a/b,  
	a REM b,  
	a+b,  
	+a,  
	a-b,  
	-a
#*b  and  
	a#/b,  
	a#+b,  
	#+a,  
	a#-b,  
	#-a
=b  etc.
TRUE
	or FALSE.
<b<=c  etc.
<b
	& b<=c.
#=b  etc.
<<b  and
	a>>b
~a 
&b
|b
NEQV b
EQV b
~(a NEQV
	b).
,c
SLCT a  and  
	SLCT a:b  and  
	SLCT a:b:c,
OF b
(SLCT x:y)
	OF b  
		selects a bitfield from within !b:
	!b to
	  the bottom x bits of the right hand
	  side.  The bits set are bits y to
	  y+x-1, where bit 0 is the rightmost bit;
	  if x is 0, they are all bits except the rightmost y.
	(!b >> y).
	(SLCT x:y:z)
		OF b  is equivalent to  
		(SLCT x:y)
		OF
		(b+z).
TABLE k1,k2,...kn, where all the ki are
	constant expressions,
VALOF c
RESULTIS command is reached, that controls the
        value of the expression.  Otherwise the value is
	unspecified.
Logical operations are evaluated according to one of two rules. If the result of the operator is in "truth context", then "truth rules" are used. Otherwise the operator takes bit-patterns as argument(s) and operates on each bit separately. Truth context is:
->;
TRUE or FALSE.  For dyadic
operators, the left operand is evaluated.  If and only if this does
not determine the result, then the right operand is evaluated.
TRUE and FALSE have values such that logical
operations not using truth rules evaluate correctly to TRUE
or FALSE.
    e1 ()
    e1 (e2, e3, ..., ex)
and associates from left to right.  e1 to ex are all
evaluated, and e1 must
result in a procedure designator; the procedure is called
with the appropriate arguments.  
If the procedure is a function, the value of the call is that of the
evaluated function body; if it is a routine, the value is
?.  
The number of arguments does not have to match the number
of parameters of the procedure.  
Arguments are passed strictly by value.
$( and $) optionally followed by a
	tag - which is a sequence of letters, digits, and dots (not
	necessarily beginning with a letter) - without an intervening
	space.  
	If the tag of the closing bracket does not match that of the
	opening one, it also closes any intermediate open section.  
	For example, the following is valid BCPL:
    $(1
        some code
        $(1.1
            some code
        $)1.1
        $(1.2
            some code
            $(
                some code
    $)1 // Also closes 1.2 and the untagged block
    c REPEAT
    c REPEATWHILE e
    c REPEATUNTIL e
    IF e THEN c
    UNLESS e DO c
    TEST e THEN c1 ELSE c2
    WHILE e DO c
    UNTIL e DO c
    FOR i = e1 TO e2 BY e3 DO c
    RESULTIS e
    SWITCHON e INTO c
    GOTO e
    FINISH
    RETURN
    BREAK
    LOOP
    ENDCASE
Assignments have the form:
    a1, a2, ..., an := e1, e2, ..., en
	where the lists on the two sides must have the same length,
	and ai must be valid on the left side.  The order
	of the assignments is unspecified; they may be
	interleaved.  Most recent implementations
	allow the assignment symbol to be prefixed with any dyadic
	operator, as in:
    a1, a2, a3 <<:= e1, e2, e3
This is equivalent to:
    a1, a2, a3 := a1 << e1, a2 << e2, a3 << e3
	except that the expressions a1, a2,
	a3 are evaluated only once, and the <<
	has lower precedence than any operators in the six expressions.
A call has the same form as in an expression, except that any result is ignored.
	The contained command of a REPEAT command is the smallest
	possible; in particular, in:
    I := VALOF F () REPEATWHILE J
	the repeated command is the call to F.  The
	unconditional REPEAT command repeats forever.  
	REPEAT commands always execute the contained
	command at least once.
	In the FOR statement,
	the BY e3 part is optional (equivalent
	to BY 1); e3 must be a
	constant expression.  
	The identifier i is declared as a new dynamic cell
	whose scope is the contained command.  The command
	is executed zero or more times with the cell taking values
	e1, e1+e3,
	e1+2*e3, ...  so long as
	the value is less than (greater if e3 is negative)
	e2.  The expressions are evaluated before the
	first iteration, and not re-evaluated each time.
In the SWITCHON command, the contained command must be a
block which cannot contain declarations, but can contain case
labels.  These have the form
    CASE e:
    DEFAULT:
where e is a constant expression.  Note that case labels
can only be in that block, and not in inner blocks (except for nested
SWITCHONs).
FINISH terminates the program; RETURN terminates
the current procedure (with an undefined result if it is a function); 
BREAK terminates, and LOOP ends the
current iteration of, the textually surrounding WHILE,
UNTIL, REPEAT, or FOR.
ENDCASE similarly exits the textually
surrounding SWITCHON.  RESULTIS cannot
terminate the current procedure.
Each declaration that creates a cell with dynamic storage duration causes a new cell to be created each time the declaration is executed; the cell is (notionally) destroyed when the block containing the declaration terminates. The order that the cells declared in the same simultaneous declaration are initialized is undefined; they may be interleaved.
There are three types of section declarations. In each case, vi stands for an identifier not declared at the current level, and ki for a constant expression.
MANIFEST $(
	   v1 = k1;
	   v2 = k2;
	   v3 = k3;
	   ... ;
	   vn = kn $)
STATIC $(
	v1 = k1;
	v2 = k2;
	v3 = k3;
	... ;
	vn = kn $) 
GLOBAL $(
	v1 : k1;
	v2 : k2;
	v3 : k3;
	... ;
	vn : kn $)
Simultaneous declarations take the form:
    LET d1 AND d2 AND ... AND dn
There are four types of declaration that can be used; they are shown
here with LET, but of course can also occur with
AND.  The types may be freely mixed within one
simultaneous declaration.
LET v1, v2, v3, ..., vn = e1, e2, e3, ..., en
LET v = VEC k
LET v (v1,
	v2,
	...,
	vn) BE commandLET v (v1,
	v2,
	...,
	vn) = e
GLOBAL declaration for
    v, then it continues to designate the cell of the global
    vector.  Otherwise it designates a cell with static storage
    duration.  Before the program starts, the cell
    is initialized to a procedure designator that invokes
    the appropriate body.
    The first form generates a routine (which has no result), and the second a function (which does). In effect, the following are equivalent:
    LET v (v1, v2, ..., vn) BE command
    LET v (v1,
	v2,
	...,
	vn) = 
VALOF $( command ;
    RESULTIS ? $)
	The first two forms may only occur within blocks.  
	This isn't a formal constraint written in the
	BCPL book by Richards and
	Whitby-Strevens, but I've never
	seen file-scope LET declarations of variables.  In
	particular, if I wanted a static or global array, I would always
	declare the array within START, and then assign it to
	the static or global variable.
The parameters of a function or routine designate contiguous cells, with adjacent parameters being adjacent. It is implementation defined whether the first parameter has the lowest or highest address. It is implementation-defined whether the values of arguments in excess of the number of parameters are stored in further contiguous cells or are discarded.
Functions and routines may be declared inside blocks. A function or routine may not refer to an identifier which designates a cell with dynamic storage duration and is not declared in that function or routine.
GLOBAL declaration of
	the identifier is in scope, that cell is used) which is
	initialized, before the program starts, to a jump target
	referring to the location of the label.  A label is
	in scope within the smallest of:
	A jump closure is a value which refers to
	a particular invocation of a function or routine.  The closure
	of the current invocation can be determined by calling the library
	routine LEVEL(); this value is no longer valid when
	the invocation terminates.
	There are two ways to jump to a label: GOTO t,
	and calling the library routine
	LONGJUMP(c,t);
	t is an expression which evaluates to a jump target,
	and c is an expression evaluating to a jump closure.  
	The jump must be made either from the scope of the target label,
	or from a function or routine called (possibly indirectly) from
	within that scope.  
	In the latter case, c must evaluate to the closure of
	an invocation of the function containing the label and which is
	still active.
GLOBAL declarations in
	the header LIBHDR.  The allocation of
	routines and functions to particular cells is implementation
	defined, but only cells less than FIRSTFREEGLOBAL
	are used.
	Note that input and output goes via the designators held in
	the global vector cells designated by RDCH and WRCH.  
	Altering those cells will alter the behaviour of all
	I/O operations.
LIBHDR contains the manifests:
    BITSPERBYTE
    BYTESPERWORD
    ENDSTREAMCH
    FIRSTFREEGLOBAL
START (implementation-defined parameters)
character
	= RDCH ()
ENDSTREAMCH at end of file.
WRCH (character)
	SELECTINPUT (stream.designator)
	SELECTOUTPUT (stream.designator)
	stream.designator = FINDINPUT (string)
	stream.designator = FINDOUTPUT (string)
stream.designator = FINDFILE (string)
stream.designator = CURRENTINPUT
	()
stream.designator = CURRENTOUTPUT
	()
integer = READN ()RDCH and returns it.
WRITES (string)WRITEN (integer)WRITED (integer, minimum.width)WRITEOCT (integer, minimum.width)WRITEHEX (integer, minimum.width)WRCH.
Padding is done with spaces on the left.
WRITEF (format, args ...)
WRCH.
The format is a string which can contain:
    %%  call WRCH('%')
    %S  call WRITES   with the next argument
    %C  call WRCH     with the next argument
    %O1 call WRITEOCT with the next argument
    %X1 call WRITEHEX with the next argument
    %I1 call WRITED   with the next argument
    %N  call WRITEN   with the next argument
where 1 can be any single base-36 digit (i.e.  A
means 10, B means 11, and so on), and is passed as the
second argument of the call.
Other formats are implementation-defined.  Note that these calls go
via the global vector (normally twice, since each
then calls WRCH).
character = GETBYTE (s, i)s%i; used when % is not supported.
PUTBYTE (s, i, c)
s%i:=c; used when % is not supported.
integer = PACKSTRING (v, s)
FOR i = 0 TO v!0 DO s%i = v!i  except that it returns the
offset from v of the cell holding s%(v!0).
UNPACKSTRING (s, v)
FOR i = 0 TO s%0 DO v!i = s%i.
integer = MULDIV (x, y, z)
(x*y/z) evaluated without overflow if the final
result fits in a cell.
integer = RANDOM (seed)
closure = LEVEL ()LONGJUMP (closure, target)
value = APTOVEC (function, n)
address = GETBLK (n)
FREEBLK (address)
// or ||
	or \\ to end of line, or from /* or
	|* or \* to the corresponding close symbol
  	(the two characters reversed);
  	comments do not nest, and all comment symbols other than the
	required close symbol are ignored in a comment.
THEN and DO are
	interchangeable in all circumstances, as are OR
	and ELSE.  
	THEN/DO may be omitted if followed
	by another keyword or a block.
GET followed by a string constant causes the
	file indicated by the string to be textually included at
	this point.  The keyword and string (possibly
	followed by a comment) should form a source line of their own.
The following parts of the language are not supported by all compilers:
% operator
	(but string constants are
	part of the core language)
SLCT and :: operators
//
+:=