This page describes the semantics of the ivory language.
Rules Concerning Declarations in General
- The named objects created by a declaration are called "program elements". Each program element must have a unique name in any given namespace.
- Modules, signatures, functions, collections, compound statements, catch statements and templates define name-subspaces.
- The global scope the static context in which all base declarations are defined. Additionally, functions have a separate scope in which their data is held during function execution. Collections also treat their data as a separate scope (held wherever the collection is held).
- A program element in a given name-subspace may override an element from a lower-tiered namespace, even if the types are different.
- The extern specifier may only be given for a variable declaration in the topmost scope (outside of a module or signature). No declaration with the keyword may have an initializer. It may not appear more than once.
- The shared specifier may only be given for a declaration inside a signature. It may not appear more than once.
- The global may not appear more than once, or in combination with static.
- The static may appear up to as many times necessary as it is to move the declaration to the global scope. As such, it may not appear in combination with the global specifier.
- Any declaration with an initializer must match the type of the initializer with the declaration. Additionally, specific rules apply to some types.
- In the case of modules, the actual module must implement a superset of the signatures that the empty declaration implements.
- In the case of collections, the actual value must be of the same class (struct or union) as the empty declaration.
- In the case of variables, the initializer must be assignment-compatible with the variable's type.
- Only empty declarations may exist within a signature.
- A module implementing a signature must define or declare all the elements declared by the signature.
- Two signatures being implemented by a module may not contain a declaration of the same name unless both declarations have the shared specifier, and have the same type.
- In a template definition, all optional (initialized) declarations in the argument list must come last in the list.
- When calling a template, the effect is the same as if the declaration inside had been processed, and assigned a name, and the name is used in place of the template call (meaning a new program element is created each time the template is called).
- Arguments to a template must match the type of the argument declaration.
Rules Concerning Global Declarations
- An empty declaration (one with no initializer) may coexist with either a declaration with an initializer, or a definition of a same-typed program element provided the element resulting from the initializer or the definition would be a valid initializer in the empty declaration, and that some additional rules are satisfied based on the type.
- In the case of variables, an empty declaration with the extern specifier may be replaced by an empty or non-empty variable declaration of the same type.
- In the case of function definitions replacing empty variable declarations, the types of the definition and variable must be of a compatible type.
- All initializers for variables must be free of side-effects, and must have a constant value. They may, however, refer to other initialized variables.
- All declarations must have a fixed size, or else have an initializer with a fixed size.
Rules Concerning Declarations Within Functions
- Any type of declaration may exist within a function.
- Any initializer may have a side-effect.
- Declarations with initializers are treated exactly like a statement with an assignment expression. Side effects are processed in the order in which they occur.
- A function's scope is destroyed when the function returns. Any pointer to data in this scope, or any inner function that references data in the scope becomes invalid at that time.
- Declarations with extern are not allowed.
- Only one local variable may have an unfixed size.
Rules Concerning Declarations Within Collections
- The following are not allowed: control statements, modules, temlpates, and signatures
- Functions declared in a collection (unless moved out of the scope by the static keyword take an implicit argument named "this", whose type is a pointer to the collection being defined. The "this" pointer points to the structure scope.
- Alignment of structures is guaranteed to be the same within the scope of a single program for a given structure. However, it is otherwise unspecified.
- Structures bearing the strict qualifier are aligned exactly as they are specified.
- Only one local variable declaration may have an unfixed size.
Rules Concerning Types
- Each type qualifier may be applied to a given type only once. The signed and unsigned qualifiers may not appear together.
- A declaration of type void may not exist. However, functions may return void, and void pointers and arrays may exist.
- An unspecified-size array has an unfixed size. Also, any collection type containing an element of unfixed size also has an unfixed size.
- The base type of an array must have a fixed size
- All exceptions must have a fixed size.
- The following qualifiers may apply to any type: volatile, const, additionally, other qualifiers may apply based on the type.
- The following qualifiers may apply to the byte, char, short, int, and long types: strict, signed, unsigned
- The strict qualifier may also apply to the float and double types.
- The strict qualifier, when applied to arithmetic (integer and floating point) types causes an zerodiv, overflow, and nan exceptions to be thrown when appropriate.
- The strict qualifier may apply to an enumeration type. It causes the mismatch exception to be thrown if an attempt is made to assign a value not in the enumeration to the variable.
- The strict qualifier may apply to a pointer, which makes pointer arithmetic on that pointer illegal.
- Arrays may also take the strict qualifier, which causes them to perform bounds checks. A bounds exception is thrown in the event of an out-of-bounds index.
- Functions may have the pure and strict qualifiers. The pure qualifier means that the function has the properties of a mathematically pure function (meaning no side effects). A strict function rigidly observes the calling conventions of the architecture. All extern functions, all higher-order functions which may be returned or passed to an external source, and all functions visible at the top level are implicity strict.
- A non-strict enumeration type is treated like an integer type in all ways.
- There are two "pseudotypes", which do not exist outside of certain areas of the language. In essence, these act as a combination of several types.
- The exception pseudotype is the type of the exception caught in a catchall block.
- The list pseudotype is the type given to a list literal.
Rules Concerning Type Compatibility
- There are four general classes of types: boolean-compatible, integer, arithmetic, and throwable.
- Enumeration types, as well as byte, short, int, long, and char are integer types.
- Any integer type is also an arithmetic type. Additionally, float and double types are arithmetic.
- Any arithmetic type is boolean-compatible. It is converted to a boolean by testing for equivalence to 0 and inverting the result.
- Pointers and functions are also boolean-compatible, and are converted to a boolean by testing for equivalence to NULL and inverting the result.
- Exceptions, and the special exception pseudotype found in catchall blocks are throwable.
- Additionally, there is a test for assignment-compatibility.
- Any primitive type is assignment-compatible with any other primitive type.
- Any primitive is also assignment-compatible with an enumeration type.
- An unnamed collection is assignment-compatible with any other collection as long as the fields match in name and type. This permits assignment from a non-strict unnamed structure to a strict one. This also permits assignment of a named structure from an unnamed structure, and reverse.
- An named collection is assignment-compatible only with itself, or an unnamed collection whose fields match. However, as with unnamed structures, assignment from a non-strict named structure to a strict one is permitted.
- A struct or exception is assignment-compatible with the list pseudotype used by list literals, assuming all the elements of the list literal are assignment-compatible with the fields of the struct or exception.
- A list literal may be assigned from a struct or exception, presuming that the elements of the list literal are assignment-compatible with the fields of the struct or exception.
- Two functions are assignment-compatible if and only if their return types and their argument types are identical. In this test, the optional argument's initializers are disregarded, effectively treating them as regular arguments. Variable arguments in either function allows the other function's argument list to have any number of additional arguments.
- Incomplete types are assignment-compatible only with themselves.
- The exception pseudotype may recieve any exception type in an assignment. However, no exception may be assigned from it.
- Arrays with the strict qualifier are compatible only with other strict arrays, additionally, an array with a specified size may only recieve an array with a larger size, or an unspecified size. Aside from these constraints, arrays behave exactly like non-strict pointers.
- Strict pointers may be assigned from any pointer or non-strict array, however no non-strict pointer or array may be assigned from them.
- Pointers may recieve other pointers or non-strict arrays, and arrays may recieve pointers or other array, provided the earlier-stated constraints are satisfied. Additionally, the base types are subject to further constraints.
- All pointer and array base types must match qualifiers on the following constraints: strictness must be the same, signedness must be the same, and for pure, const, volatile, and restrict, if the recipient has the given qualifier, then the source must as well.
- Pointers to void and void arrays are compatible with anything.
- In the case of pointers to and arrays of other pointers and arrays, the constraints apply recursively to the base types.
- In all other cases, the base types must be equal (not merely assignment-compatible).
Rules Concerning Statements
- A catch statement may have only one catch-all handler, and may have only one handler per exception.
- A catch statement's handlers must only catch an exception type.
- The test in an if statement must be a boolean-compatible type.
- The value given to a switch statement must be an integer type.
- The cases in a switch statement must be unique: two cases cannot address the same value.
- The test in an while- or a do-loop statement must be a boolean-compatible type.
- The test in an if statement must be a boolean-compatible type.
- The test in a for-loop (middle expression) must be a boolean-compatible type.
- A continue statement must occur inside a loop.
- A break statement must occur inside a switch statement or a loop.
- A return statement must return a value that is assignment-compatible with the return value of the function.
- A return statement in a void-returning function must be empty.
- A throw statement must only throw an exception.
Rules Concerning Expressions
- A conditional expression's test must be boolean-compatible, and both cases' types must match exactly.
- An assignment uses the assignment-compatibility test. Additionally, the recipient expression must be a field, variable, subscript, or dereferencing expression or a list literal, all of whose expressions satisfy this test.
- The arguments in a +, -, *, /, <, >, <=, >=, binary operator expression must have an arithmetic type.
- The arguments in a %, |, &, ^, <<, or >> binary operator expression must have an integer type.
- In the case of binary operators and integer or arithmetic types, types are propogated in this order of preference: char, byte, short, int, long, float, double,
- The arguments in a || or && must be boolean-compatible.
- The arguments in a ==, != expression must be assignment-compatible.
- The + operator may be used on a non-strict pointer array, with a right-hand value of integer type. It propogates a same-type pointer.
- The - operator may be used with two non-strict pointers or arrays. It propogates an integer.
- The <, >, <= or >= operators may be used with non-strict pointers or arrays.
- The + and - unary operators must be used with signed arithmetic types.
- The ! unary operator must be used with a boolean type.
- The ~ unary operator must be used with an integer type.
- The & unarp operator must be used with a variable, field, subscript, or dereference expression.
- The * unary operator must be used with a pointer or non-strict array type.
- The |< and >| unary operators must be used with an integer type.
- A size expression may be an expression or a type. However, it cannot be an unspecified size array.
- A subscript must operate on a non-strict or an array of any kind. The value in the subscript must be an integer type.
- A field expression must operate on a collection type. The specified field must exist.
- The -> field expression must operate on a pointer to a collection. The specified field must exist.
- A call must operate on a function type. The arguments must be assignment compatible with those in the function's argument list. A variable argument list may take any number of arguments of any type after the specified arguments are given. Optional arguments do not need to be given. If a function has both optional and variable arguments, it is assumed that the optional arguments are given completely before any are passed as variable arguments.