CloudWriter is very similar to C and C++, but with a number of notable exceptions. This section begins by summarizing some interesting extensions followed by a complete language description.
Indexes for arrays and strings are 0-based as in C. This is
compatible with C, but users not comfortable with C may find
this unsettling.
Some valuable extensions:
Other extensions:
- The "%" operator for real numbers is equivalent to the fmod function.
- Unsigned integers are not supported. These types can be used, but they are treated as signed.
- The "private" class member access is not supported. CloudWriter supports "public", "protected", and "public_read".
In many real-world applications object "attributes" frequently require special processing and their raw values cannot be used directly. With C++, you must resort to using function calls to access such attributes. This works, but clutters up the caller's view of the object. A well designed class has a clean set of attributes and a clean set of functions. Every attempt should be made to avoid "cluttering" the external definition of a class.
CloudWriter supports the concept of "active" data members. To users of the class the data members are nice, simple, clean attributes. But inside the class definition member functions can be used to correct the "impedence mismatch" between the clean abstract world and the messy real world.
There are two kinds of active data members that both have the same implementation. The first is the "monitored" data member which means that the class needs to do some extra processing whenever the data member is accessed. This might included validation of new values, side effects of changing the value, reformatting the value, simplifying the raw value, or whatever other processing the designer feels is necessary. The second kind of active data member is the "simulated" data member which means that there really isn't an actual data member, but functions are written so that to the user of the class it will appear as if there was a simple, clean data member. This is commonly used when the actual data comes from somewhere "under" the class such as the operating system or a piece of hardware. Again, the goal is that the user of the class sees a simple, clean, attribute-oriented object without regard to the underlying implementation.
For example, the user might write:
(TBD)
// Get curent window title. old_title = window.title; // Set new window title. window.title = new_title; // Get the current font. font = window.font; // Set the font. window.font = new_font;
To the class user, the "window" class has two attributes: a title and a font. CloudWriter treats the above code as if it were written:
old_title = window.title (); window.title (new_title); font = window.font (); window.font (new_font);
If "title" were declared as a "public" data member then CloudWriter automatically generates the "title" function with zero and one arguments which directly access the actual data member. But since we need to do Windows SDK calls to get or modify the window title, the class definition includes two member functions do so:
string title ()
{
string the_title;
GetWindowText (...);
return the_title;
}
string title (string new_title)
{
SetWindowText (...);
return new_title);
}
That's an example of a "simulated" data member.
In the case of the font, it's okay for the user to directly read it, so we declare it as a "public_read" data member. But since we need to
ensure some side effects when the font is set, we also define a member
function to do that:
public_read:
font font;
font font (font font)
{
// Update the actual data member.
this::font = font;
// The side effects.
...
return font;
}
Unlike PASCAL, C has no provision for range checking, so here's how you could assure that the value of "year" was in the range 1900 to "this year":
public_read:
int year;
int year (int year)
{
if (year < 1900 || year > this_year) {
bad_year (year);
// Return current value.
return this::year;
} else
return this::year = year;
}
CloudWriter treats reading an array element as a call to the "get" member function and writing an array element as a call to the "put" member function. For example:
x = a [i]; b [j] = y;
are equivalent to:
x = x.get (i); b.put (j, y);
This allows the class developer to do extra processing when an array element is modified. Such as range checking.
Multiple subscripts are supported. In fact, a "matrix" class is included in the CloudWriter library:
m = new matrix; m [0, 0] = 1; x = m [i, j];
which is equivalent to:
m.put (0, 0) = 1; x = m.get (i, j);
CloudWriter variable argument functions are safe and easy to use. The variable arguments are passed as a dynamic array. For example:
f (x, values...)
{
for (int i = 0, int n = values.size; i < n; i++)
x += values [i];
return i;
}
...
a = f (100, 7, 2.1, "45", true, null);
The "size" member function is used to get the number of variable arguments. A simple array subscript access is used to fetch each
argument - in any order. Since the arguments carry their type and
types are converted as needed, there is no chance of running into the problems that C has with variable arguments.
This feature simplifies the "printf" functions and eliminates errors due to mismatch of parameters.
Another example is the "array" class which uses a constructor with a variable number of arguments to initialize the array. In the following example a five element array is created and initialized with elements of different types:
a = new array (pi, 2, "three", new four, 5.0);
This language definition uses a simple variation of BNF:
Text with double quotes is a "terminal" symbol which is literal text to be written as is.
"if"
"+"
Text within angle brackets is a "non-terminal" symbol which is defined by a syntax "rule" which is the non-terminal followed by
a colon followed on succeeding lines with an indented sequence of symbols the define the rule. The angle brackets are considered part of the symbol.
<function-decl>
Terminal symbols ending in "-name" represent an identifier.
Matched square brackets indicate that the enclosed symbols are optional.
["else" <stmt>]
The vertical bar separates alternatives.
"class" | "struct"
Parentheses can enclose any sequence of symbols or alternatives.
("class" | "struct")
Ellipses indcate that the preceding element (usually a symbol sequence enclosed in square brackets or parentheses) may be repeated zero or more times.
<expr> ["," <expr>]
The syntax of CloudWriter is very similar to C/C++. Differences will be noted in the language definition. There are a number of esoteric and complex features of C/C++ that are not included in CloudWriter. Unless there is a conflict with the goals of CloudWriter, where CloudWriter has a feature like C/C++ then every effort has been made to keep it compatible.
CloudWriter source code consists of a sequence of tokens which are keywords, names, constants, operators, or punctuation characters.
Tokens may be preceded, separated, and followed by any number of token separators which are white space, comments, and pragma directives. There are a few exceptions:
White space includes the space, tab, newline, formfeed, and carriage return characters.
As in C/C++, CloudWriter supports both // and /* comments. A comment beginning with // is terminated by the end of the line. A comment beginning with /* is terminated by */ (no nesting.)
As in C, names (identifiers) begin with a letter or underscore and may be followed by any number of letters, digits, or underscores.
Names must be unique in their first 32 characters. The remaining characters are ignored.
Names are case-sensitive: ABC, abc, Abc and aBc are distinct names.
Keywords (e.g., "if" and "while") may not be used as names. All keywords of C, C++, and the Microsoft and Borland compilers are reserved even if they currently have no meaning in CloudWriter.
Constants include the C constants of integers, real numbers, and string literals plus the null and boolean values.
The null value is the keyword "null" which is not equal to 0 but is treated as 0 in arithmetic expressions.
The boolean values are the keywords "true" and "false" which are not equal to 1 and 0 but are treated as 1 and 0 in arithmetic expressions.
A decimal constant begins with a non-zero digit. For example:
123 1 7890001
An octal constant begins with a 0 followed by zero or more octal (0-7) digits. For example:
0 0123 0777
A hex constant begins with 0x or 0X followed by one or more hex digits. For example:
0x123 0xffff 0xFF00abcd
For compatibility with C, an integer constant can be immediately followed by l or L, but that suffix is ignored.
CloudWriter supports only double-precision floating point.
A real number has the syntax:
[<integer>] "." <fraction> |
<integer> "." [<fraction>] |
[<integer>] "." <fraction> ("e" | "E") ["+" | "-"] <exponent>
<integer> ("e" | "E") <exponent>
<integer>, <fraction>, and <exponent>, are one or more decimal digits.
A real number may have a decimal precision specified by immediately following the number with the letter "p" optionally immediately followed by the precision. The precision can be blank, 0, negative, or positive (without the sign.) See the "Real Precision" section for details on the semantics of real precision.
The CloudWriter rules are similar to C, except the apostrophe can be used interchangibly with the double quote (since CloudWriter has no character constant.) The syntax is:
(<quote> (<non-quote> | <escape-sequence>)... <quote>)...
The start and end quotes must match and the <non-quote> includes the other type of quote. Note that a string can be written as a sequence of sub-string literals that are all pasted together (as in C.)
An escape sequence has the syntax:
"\" (<quote> | <newline> | "\" |
"a" | "b" | "f" | "n" | "r" | "t" | "v") |
"\" <octal-digit> [<octal-digit> [<octal-digit>]]
"\" "x" <hex-digit>...
Examples:
"\"" // One double quote. "\\" // One backslash. "\a" // The BEL (alarm) character. "\b" // The backspace character. "\f" // The formfeed character. "\n" // The newline character. "\r" // The carriage return character. "\t" // The horizontal tab character. "\v" // The vertical tab character.
A backslash at the end of a line is used to continue the string literal onto the next line but excludes the newline.
Examples of string literals:
"Hello World"
"" // Empty string
"Say \"Hello\"" // Say "Hello"
"line1\nline2" // Two lines with newline
"col1\tcol2\tcol3" // Three string with tabs between.
"Text\r\n" // Old-fashioned line of text.
The CloudWriter API operates on three syntax levels:
<macro>: [<stmt>] <source-file>: [<decl>]... <decl>: <const-decl> | <typedef-decl> | <global-var-decl> |
<function-decl> | <class-decl><const-decl>: "const" <const-entry ["," <const-entry>]... ";" <const-entry>: <const-name> "=" <const-value> <const-value>: <value> | (<const-name> "+" <value>) | <const-name> "-" <value>) <typedef-decl>: "typedef" <type> <typedef-name> ";" <global-var-decl>: [<type>] <global-var-name> ["," <global-var-name>]... ";"
- There are no "static" global (or local) variables.
<function-decl>: <func-hdr> (";" | <func-body> | <dll-spec>) <func-header>: [<type>] <func-name> ["(" [<func-arg-list>] ")"]
- Unlike C, types are optional. For example, the following are valid:
int f (int x) f (x) int f f (int x) g () any g (void)
<func-arg-list>: "void" | (<func-parm> ["," <func-parm> ]... "...")
- Unlike C, the ellipses for variable length argument lists must follow a parameter name. That last parameter will be a dynamic array containing the variable arguments.
<func-parm>: [<type>] <parm-name> <type>: "bool" |
"int" |
"real" |
"num" |
"string" |
"array" |
"memory" |
"any" |
"signed" |
("signed" "int") |
("signed" "long") |
("signed" "long" "int") |
"unsigned" |
("unsigned" "int") |
("unsigned" "long") |
("unsigned" "long" "int") |
"char" |
("unsigned" "char") |
"double" |
"float"
"char" "*" |
<type-name>
- Unlike C, CloudWriter does not require that the types of variables be specified. Since CloudWriter is a "typeless" language, the type information is carried around with the value. But the type can be specified for compatibility with C and to improve readability.
- An exception is the declaration of DLL entry points where the type is needed to properly encode the argument and return values.
- The special type "any" indicates that any type of value is acceptable.
| <dll-spec>: |
A single literal whose string value has the following syntax:
|
- <library-name> is a file path which may included a drive, directory, file name, and extension. Since it is used within a string, any backslashes must be doubled or replaced with forward slashes.
- If <library-name> is not specified, the CloudWriter runtime DLL (shared library) is assumed.
| <func-body>: | <compound-stmt> |
- The "return" statement is optional, even if the function header specifies a return value. The "null" value is returned if there is no return statement.
| <stmt>: |
<compound-stmt> | [<expr>] ";" | "if" "(" <expr> ")" <stmt> ["else" <stmt>] | "while" "(" <expr> ")" <stmt> "do" <stmt> "while" "(" <expr> ")" ";" "for" "(" [<expr>] ";" [<expr>] ";" [<expr>] ")" <stmt> "switch" "(" <expr> ")" <stmt> "case" <expr> ":" <stmt> "default" ":" <stmt> "break" ";" "continue" ";" "return" [<expr>] ";" "goto" <label-name> ";" <label-name> ":" <stmt> |
- Unlike C, the switch expression is not limited to integers and case labels are not restricted to constant integers. For example, you can switch on a string expression and use string literals for case labels.
| <compound-stmt>: | "{" [<variable-decl>]... [<stmt>]... "}" |
| <variable-decl>: | <type> <var-decl-entry> ["," <var-decl-entry>]... ";" |
| <var-decl-entry>: | <local-variable-name> ["=" <expr>] ";" |
| <expr>: |
<term> | <expr> <binary-op> <expr> | <expr> "?" <expr> ":" <expr> | <expr> ("," <expr>)... |
| <term>: |
<primary-expr> | <unary-op> <term> | <term> "++" | <term> "--" | "new" (<class-name> | <string-constant> | "(" <expr> ")") ["(" [<expr> ["," <expr>]... ")"] |
| <primary-expr>: |
[<type>] <local-variable-name> | <variable-name> | [<class-name> | "this"] "::" <variable-name> | <constant> | "this" | "(" <expr> ")" | <primary-expr> "[" <expr> ["," <expr>]... "]" | <primary-expr> "(" <expr> ["," <expr>]... ")" | <primary-expr> "." <member-name> | <primary-expr> "->" <member-name> <type> "(" <expr> ")" |
- The points-to operator (->) is provided for compatibility with C and bahaves the same as the member-reference (.) operator.
- Local variables can be declared in expressions, typically on the left side of an assignment. Simply precede the variable name with a type.
- Suscript values for arrays and strings are 0-based as in C. In other words, if a string or array has length n, the last element is accessed with a subscript of n-1.
| <binary-op>: |
"*" | "/" | "%" |"+" | "-" | "<<" | ">>" | "<" | "<=" | ">" | ">=" | "==" | "!=" | "&" | "^" | "|" | "&&" | "||" | "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "^=" | "&=" | "|=" | ">>=" | "<<=" |
- Listed in order of decreasing precedence.
- The only C operator not supported is casting.
| <unary-op>: | "++" | "--" | "+" | "-" | "~" | "!" |
- Note that unary operators have higher precedence than binary operators.
| <class-decl>: |
("class" | "struct") <class-name> [":" <base-class-name> ] "{" [<member>]... "}" ";" |
- Multiple inheritance is not supported.
| <member>: |
<member-var-decl>[<access>] <variable-decl> | <function-decl> |
| <member-var-decl>: |
[<access> [":"]] ["static"] [<type>] <member-var-name> ["," <member-var-name>]... ";" |
| <access>: |
"public" | "private" | "protected" | "public_read" |
- "public" means unrestricted access ala C "struct". CloudWriter allows C-like structs which default member access to "public" unless you add more restricted access.
- "public_read" allows other non-derived classes to directly read, but not modify the member.
- "protected" means no direct access is allowed from other non-derived classes. This is the default for a "class".
- "private" is not supported. Derived classes always have unrestricted direct access to members of ancester classes.
- All members of base classes are accessible in derived classes.
CloudWriter is a bit simpler than C/C++. They both have global and local variables and data members.
CloudWriter differences:
Global variables are initialized to the null value rather than 0.
Class data members are also initialized to the null value.
Unlike C, all local variables are initialized, to the null value, when the function is called.
Although CloudWriter is only a subset of C/C++, all of the ANSI C/C++ reserved words as well as Microsoft and Borland extensions are reserved:
any
asm
_asm
auto
_based
bool
break
case
catch
cdecl
_cdecl
char
class
const
continue
_cs
default
delete
do
double
_ds
else
_emit
enum
_es
_export
extern
false
far
_far
float
for
_fortran
friend
goto
huge
_huge
if
in
inline
int
interrupt
_interrupt
_loadds
long
near
_near
new
null
num
operator
pascal
_pascal
private
protected
public
public_read
real
register
_regparam
return
_saveregs
_seg
_segment
_segname
_self
short
signed
sizeof
_ss
static
string
struct
switch
template
this
throw
true
try
typedef
union
unsigned
virtual
void
volatile
while
CloudWriter does not support C-style casting, but does offer conversion
functions that force a value to a basic type:
a = any (a)
No conversion. This construct is not normally used but is provided for completeness.
b = bool (a)
Convert to boolean (true/false.) Numeric values are converted to integer and compared to zero. Null, converts to false. Any string value is converted to true. The null value is converted to false.
s = char (a)
Converts a numeric value to a one-character string using the
number as a character code.
For a string, takes the first character and returns it as
a string.
For other values, returns an empty string.
i = int (a) i = long (a) i = unsigned (a)
Converts to 32-bit integer.
Booleans convert to 0, 1.
For null, converts to 0.
For strings, converts the optional sign and leading digits
to an integer.
For other values, returns 0.
i = short (a)
Converts to 16-bit integer.
Booleans convert to 0, 1.
For strings, converts the optional sign and leading digits
to a 16-bit integer.
For integers, truncates the high 16-bits and sign-extends
the low 16-bits.
For other values, returns 0.
r = real (a) r = double (a) r = float (a)
Converts to floating point.
For booleans, converts to 0.0 and 1.0.
For null, converts to 0.0.
For strings, the leading characters of the string are
converted to floating point.
For other values, returns 0.
n = num (a)
Converts to number, 32-bit integer or floating point.
For booleans, converts to 0 and 1.
For null, converts to 0.
For strings, checks to see if the string contains a
decomal point. If so, converts to floating point,
otherwise converts to 32-bit integer.
For other values, returns 0.
s = string (a)
Converts to string.
The null value converts to an empty string.
Boolean values convert to "true" and "false".
Numbers are converted to text.
Non-basic objects are converted to the text:
object of class xxx
where "xxx" is the object's class name.
Real numbers are represented in IEEE double precision floating point. This format can represent a maximum of 16 decimal digits of precision. By default, CloudWriter arithmetic operations for real operands maintain this precision.
In addition, CloudWriter allows the application to specify that a smaller number of decimal digits of precision are to be maintained for the fraction. The lowest digit to be maintained is incremented if the value to be stored has a value of 5 or higher in the next lower digit.
Precision may also be zero or negative. A precision of zero means the real will always be an integer. A negative precision means that the specified number of decimal digits (actually the negative of the negative precision) to the left of the decimal will be kept as zeros.
There are two ways to control precision:
1. At the source level you may append the letter "p" followed by the number of decimal digits to maintain. For example:
x = 1.23p2; // Two digits y = 3.0p6; // Six digits a = 5.0p0 / 2; // Result is 3.0 (2.5 rounded up) b = 100p-2 + 50; // Result is 200.0 (150 rounded up)
You can also specify the number of digits implicitly by writing
"p" without a number after it:
x = 1.234p; // Three digits y = 1200.p; // Zero digits z = 1200p; // Minus 2 digits (keep hundreds only)
2. Runtime conversion using the "real" conversion function with
a second argument which is the number of decimal digits to
maintain. For example:
x = real (abc, 2); // Two digits y = real (def, 6); // Six digits
In arithmetic operations the maximum precision of the operands is used for the precision of the result. DLL functions and the trig functions yield results with the maximum precision. For example:
z = x * y; // Six digits
You can determine the precision at runtime by calling the "precision" member function:
prec = x.precision;
The actual maximum number of digits of precision is kept in the
"max_real_precision" global variable.
John Elkins (john@vermontdatabase.com) 20 Nov 2001