Arcadia Program Definition Language
The Arcadia Program Definition Language (Arcadia PDL) is the declaration and instruction language consumed by the current compiler implementation in PDLC. The command-line interface for that compiler lives in PDLC.CLI.
This specification records the language accepted by the current compiler implementation as far as it can be inferred from the scanner, parser, and checked-in .pdl inputs. Changes to accepted Arcadia PDL source syntax, lexical words, instruction forms, or primitive semantics in PDLC should be reflected here. It intentionally does not include the older HLL package, expression, or symbolic-operator grammar because those productions are not accepted by the current parser.
Notation
Grammar productions use single quotes for literal words and symbols. The suffix * means zero or more occurrences, ? means an optional occurrence, and alternatives are separated by |.
Translation
A program of the Arcadia PDL language is translated into declarations. This translation happens in three phases: The lexical translation translates Unicode code points to words. The syntactical translation filters the resulting sequence of words and then translates these words into a sentence. The semantical translation translates sentences into typed declarations.
Module Directories
A compiler invocation supplies one or more module directories. A module directory is the root directory of one Arcadia PDL module. If a supplied module directory path is relative, it is interpreted relative to the compiler invocation's working directory.
Each module directory contains a required compilation unit named Module.pdl. The compiler parses this file before the other compilation units of the module. If Module.pdl is not a regular file, the compiler reports an error.
The compiler discovers the remaining compilation units of a module by recursively enumerating the Library/Sources directory below the module directory and selecting regular files with the .pdl extension. These files are parsed as compilation units of the same module.
The current implementation therefore uses the following directory shape:
<module-directory>/
Module.pdl
Library/
Sources/
... *.pdl
Module.pdl must contain exactly one module definition. A missing module definition is an error. More than one module definition is an error. A module definition in any other compilation unit of the module is an error. The name of the module definition does not have to match the names of declarations in Library/Sources.
Module names and type names are looked up separately. A module and a class may both be named Arcadia.Engine; this does not constitute a duplicate definition. Two modules with the same name are still an error.
Lexical Translation
The input is read as Unicode code points from UTF-8 bytes. Arcadia PDL selects reusable productions and translation rules from Common Lexical Translations unless this section defines a more specific Arcadia PDL rule.
Arcadia PDL defines its own lexical word alternatives and start symbol using the selected common categories and the PDL-specific categories below. The common specification does not supply a universal word production. PDL's reserved-word and other disambiguation rules belong to this consuming language.
Arcadia PDL uses the common definitions for whitespace, line terminators, line continuations, single-line comments, multi-line comments, periods, parentheses, curly brackets, colons, commas, boolean literals, and the void literal. Whitespace and comments separate words and are otherwise ignored by the parser. Line terminators are significant as statement terminators unless a line continuation joins the statement with the following line.
whiteSpace : Lexical.Whitespace
lineTerminator : Lexical.LineTerminator
lineContinuation : Lexical.LineContinuation
singleLineComment : Lexical.SingleLineComment
multiLineComment : Lexical.MultiLineComment
period : Lexical.Period
leftParenthesis : Lexical.LeftParenthesis
rightParenthesis : Lexical.RightParenthesis
leftCurlyBracket : Lexical.LeftCurlyBracket
rightCurlyBracket : Lexical.RightCurlyBracket
colon : Lexical.Colon
comma : Lexical.Comma
booleanLiteral : Lexical.Boolean
voidLiteral : Lexical.Void
Line Continuations
A line continuation is a reverse solidus immediately followed by a line terminator. The scanner produces one line continuation word that spans the reverse solidus and the line terminator, and the parser ignores that word. A reverse solidus that is not followed by a line terminator is a lexical error.
Statements may therefore be written over several lines by ending every line but the last with a line continuation. Only the line terminator of the last line terminates the statement:
invoke $0 print \
c \
"done"
Names and Identifiers
Arcadia PDL names are similar to Lexical.Name, but the current scanner also accepts an underscore as the first character without requiring a following alphabetic character. A qualified identifier is one or more names separated by periods.
name : ('A'..'Z' | 'a'..'z' | '_') ('A'..'Z' | 'a'..'z' | '0'..'9' | '_')*
identifier : name (period name)*
Registers
A register starts with $ and is followed by either a decimal register number or a name.
register : '$' ('0' | ('1'..'9' '0'..'9'*) | name)
Literals
The current scanner recognizes the common boolean and void literals and Arcadia PDL-specific integer, real, and string literal forms. Arcadia PDL integer and real literals are narrower than Lexical.Number: they do not have signs, and a real literal must start with one or more decimal digits followed by a period. Arcadia PDL string literals are narrower than Lexical.String: they use double quotes only and currently recognize only the escapes listed below.
integerLiteral : digit+
realLiteral : digit+ '.' digit* exponent?
exponent : ('e' | 'E') ('+' | '-')? digit+
stringLiteral : '"' stringCharacter* '"'
stringCharacter : anyCodePointExceptBackslashQuoteOrEndOfInput | '\\"' | '\\\\' | '\\n' | '\\r'
Reserved Words
The following words are reserved and cannot be used as names unless the scanner is changed:
abstract add and call class concatenate construct constructor divide entry enumeration extends false implements interface invoke isEqualTo isGreaterThan isGreaterThanOrEqualTo isLowerThan isLowerThanOrEqualTo isNotEqualTo jump jumpIfFalse jumpIfTrue loadArgument loadField method module multiply native negate not or procedure raise return set storeArgument storeField subtract true variable void
Syntactical Translation
Compilation Units
A compilation unit contains zero or more top-level definitions.
compilationUnit : definition* endOfInput
definition : moduleDefinition | classDefinition | enumerationDefinition | interfaceDefinition | procedureDefinition
Modules
A module definition names a module. The current parser accepts an optional empty body.
moduleDefinition : 'module' identifier moduleBody?
moduleBody : '{' '}'
Classes
A class definition names a class, may name an extended class, and may contain fields, constructors, and methods.
classDefinition : 'abstract'? ('native' 'implementation'? stringLiteral? 'class' | 'class' stringLiteral?) identifier ('extends' identifier)? ('implements' interfaceNames)? classBody?
classBody : '{' classMemberDefinition* '}'
classMemberDefinition : fieldDefinition | constantDefinition | constructorDefinition | methodDefinition
fieldDefinition : 'variable' name ':' annotatedType
constantDefinition : 'constant' name ':' annotatedType '=' integerLiteral
annotatedType : ownership? 'output'? 'readonly'? 'reference'? (identifier | recordType)
ownership : 'borrowed' | 'owned' | 'retained'
recordType : 'record' '{' fieldDefinition* '}'
A class may implement one or more interface types. Every implemented interface type must be an interface type; the semantic analysis rejects any other symbol.
The annotation words borrowed, owned, retained, output, readonly, and reference, and the forms record, constant, and nonvirtual, are recognized contextually. They are not new globally reserved lexical words. A record field declares a value aggregate with its own fields; it does not declare a named nested type. An inherited base portion is implicit in extends, not an explicit PDL field.
The optional class abstract modifier precedes native, when both are present. An abstract class may contain ordinary constructors, fields, concrete methods, and explicit abstract method declarations. A method is declared abstract with method abstract name parameters? : ReturnType, without a body or native-target clause. Merely omitting an ordinary method body does not make that method abstract.
For a native class, an optional string literal after native or native implementation specifies its C header, for example native "Arcadia/Engine/Visuals/Resources/TextureResource.h" class Arcadia.Engine.Visuals.TextureResource extends Arcadia.Engine.Visuals.Resource { }. This class header clause is distinct from a callable's native-target clause. A native class without the string retains the default header path derived from its qualified type name.
native implementation distinguishes a class whose declarations belong to PDL but whose executable implementation is supplied natively. For example, native implementation "Arcadia/Engine/Visuals/Resource.h" class Arcadia.Engine.Visuals.Resource extends Arcadia.Object { ... } generates declarations at the mapped header path while retaining the native implementation. implementation is contextual here and is not accepted on enumeration declarations.
Not yet reflected by the C backend: the implements clause is parsed and semantically checked, but the current backend writer does not yet emit an implemented interface type set or the corresponding interface dispatch initializers. Classes are still written with Arcadia_defineObjectType.
Interfaces
An interface definition names an interface, may name the interface types it extends, and must contain operation definitions. The body of an interface definition is mandatory.
interfaceDefinition : 'interface' identifier ('extends' interfaceNames)? interfaceBody
interfaceBody : '{' interfaceMemberDefinition* '}'
interfaceMemberDefinition : operationDefinition
operationDefinition : 'method' 'abstract'? nativeClause? name parameters? ':' identifier methodBody?
interfaceNames : identifier (',' identifier)*
An interface type may extend one or more interface types. Every extended interface type must be an interface type; the semantic analysis rejects any other symbol. The inheritance graph of interface types is acyclic, and an interface type may extend the same interface type at most once, transitively. The semantic analysis rejects a cyclic interface inheritance graph.
Each interface type has its own operation table. An interface type does not inherit the operations of the interface types it extends.
Enumerations
An enumeration definition names an enumeration and may contain named constants. A constant may have an integer initializer.
enumerationDefinition : 'enumeration' stringLiteral? identifier enumerationBody
| 'native' stringLiteral? 'enumeration' identifier enumerationBody
enumerationBody : '{' enumerationConstantDefinition* '}'
enumerationConstantDefinition : name ('=' integerLiteral)?
Procedures, Constructors, and Methods
Procedures and methods declare a return type. Constructors do not. The declaration forms are:
procedureDefinition : 'procedure' 'entry'? nativeClause? identifier parameters? ':' identifier procedureBody?
constructorDefinition : 'constructor' nativeClause? parameters? constructorBody?
methodDefinition : 'method' ('abstract' | 'nonvirtual')? nativeClause? name parameters? ':' identifier methodBody?
nativeClause : 'native' stringLiteral
parameters : '(' (parameter (',' parameter)*)? ')'
parameter : 'variable' name ':' annotatedType
procedureBody : block
constructorBody : block
methodBody : block
block : '{' statement* '}'
Statements
A statement is terminated by a line terminator, a closing curly bracket, or the end of input. Empty lines are ignored.
statement : variableDefinitionStatement | labelDefinitionStatement | instructionStatement
variableDefinitionStatement : 'variable' name
labelDefinitionStatement : name ':'
instructionStatement : instruction
Operands
Arcadia PDL instruction operands are not general expressions. They are literals, variables, registers, or fully qualified enumeration constants.
operand : booleanLiteral | integerLiteral | realLiteral | stringLiteral | voidLiteral | identifier | register
A named operand denotes a local variable, parameter, receiver, or a fully qualified enumeration constant such as Arcadia.Media.SampleFormat.Integer8. Enumeration constants are read-only operands, not writable instruction targets. Unresolved names are errors; qualified names do not imply general field-access expressions.
Instructions
The parser recognizes the following instruction forms. Conditional jumps preserve their condition operand in the AST; unconditional jumps have no condition operand.
instruction : (binaryInstruction
| unaryInstruction
| invokeInstruction
| callInstruction
| constructInstruction
| jumpInstruction
| argumentInstruction
| fieldInstruction
| 'raise'
| returnInstruction) instructionTerminator
instructionTerminator : (Lexical.EndOfInput|Lexical.EndOfLine)
binaryInstruction : binaryOperator operand operand operand
binaryOperator : 'add'
| 'subtract'
| 'multiply'
| 'divide'
| 'and'
| 'or'
| 'concatenate'
| 'isEqualTo'
| 'isNotEqualTo'
| 'isLowerThan'
| 'isLowerThanOrEqualTo'
| 'isGreaterThan'
| 'isGreaterThanOrEqualTo'
unaryInstruction : unaryOperator operand operand
unaryOperator : 'negate'
| 'not'
| 'set'
invokeInstruction : 'invoke' operand identifier operand operand*
callInstruction : 'call' operand identifier operand*
constructInstruction : 'construct' operand identifier operand operand*
jumpInstruction : 'jump' name
| 'jumpIfTrue' operand name
| 'jumpIfFalse' operand name
argumentInstruction : 'loadArgument' operand operand
| 'storeArgument' operand operand operand
fieldInstruction : 'loadField' operand operand operand
| 'storeField' operand operand operand
returnInstruction : 'return' operand?
An instruction is terminated by a line terminator of the line of its opcode. The line continuation symbol may be used to continue an instruction in the next line.
call invokes a procedure and permits zero arguments. invoke retains its mandatory first argument; for a method invocation that argument is the receiver. Neither instruction parenthesizes its operands. The identifier in a construct instruction is the fully qualified class type name.
Semantical Translation
The semantical translation resolves the names of a sentence provided by the syntactical translation and binds every declaration to a type.
Module Names and Declaration Names
Module lookup is separate from type and procedure lookup. Module names must be unique among modules in a compilation; type and procedure declarations must be unique among top-level declarations, including built-in types. A module and a type may have the same fully qualified name, for example module Arcadia.Engine and class Arcadia.Engine. A module name alone never denotes a type or a callable, and does not reserve that name against declarations. Duplicate type declarations and duplicate module definitions remain errors in their respective name spaces.
A declaration belongs to its defining module independently of its name. Sharing a name with a module, or having a module name as a prefix, does not change declaration ownership or imply lexical nesting. Module membership determines generated-file placement and cross-module dependency boundaries; it does not add an implicit prefix during type lookup.
Type Names
Type names are fully qualified, and there is no relative resolution of type names. A type name is matched against the fully qualified names of the compilation as it is written, so Arcadia.Natural32 denotes a primitive type and MyModule.Greeter denotes the type declared with the qualified name MyModule.Greeter. A type name that does not spell out the fully qualified name of a type is not defined; for example, Greeter does not denote the type MyModule.Greeter. The same rule applies wherever the grammar expects a type name: a class extends or implements clause, an interface extends clause, a variable or parameter type, the return type of a procedure or method, and the class type named by construct. Constructors have no return type declaration.
Member Names
A member of a class type is looked up by its declared name, unqualified, in the scope of the class type that declares it. A member name, unlike a type name, is never fully qualified.
Abstract Classes
A class declared with abstract cannot be instantiated directly. It may declare fields, constructors, concrete implementations and explicit abstract methods, and may be extended by another abstract class or by a concrete class. A class is concrete unless explicitly declared abstract; class abstractness is not inherited. An abstract class need not contain abstract methods. A concrete non-native subclass must implement or inherit concrete implementations of every abstract class method.
A construct instruction naming an abstract class is a compilation error unless it is immediate-superclass initialization: the first statement of a derived constructor, naming its immediate parent and supplying self as the receiver. This operation initializes the existing derived object and never allocates an abstract object. Implicit zero-argument superclass initialization is also permitted for an abstract parent. Constructor argument and superclass-forwarding rules otherwise apply unchanged.
Built-in Types
The following types are built in to every compilation. They are always spelled with the fully qualified Arcadia. prefix:
Arcadia.BooleanArcadia.Integer8,Arcadia.Integer16,Arcadia.Integer32,Arcadia.Integer64Arcadia.Natural8,Arcadia.Natural16,Arcadia.Natural32,Arcadia.Natural64Arcadia.ObjectArcadia.Real32,Arcadia.Real64Arcadia.SizeArcadia.StringArcadia.ValueArcadia.Void
Arcadia.Object and Arcadia.String are class types; the other built-in types are primitive types. Arcadia.String denotes the runtime string object and maps to Arcadia_String* in C, distinct from the runtime UTF-8 string values used by string literals.
A built-in type is provided by the runtime rather than by a module, so it is not generated: the compiler emits no source or header file for a built-in type, and instead binds a reference to it to the corresponding runtime type.
Arcadia.Size is the runtime's unsigned, platform-sized size type. It is distinct from the fixed-width natural types, including on platforms where their widths happen to match. Integer literals retain their Arcadia.Natural64 representation; declaring a size parameter does not implicitly retag such a literal.
Native Definitions
A procedure, constructor, or method definition may carry an optional native clause. The clause takes a string literal naming the native target. A definition that carries the clause is a native definition; a definition that omits the clause is not native, even though it declares the same kind of member.
A native class may instead carry an optional header path after native. The path is nonempty and relative to the C compiler's include search roots, uses forward slashes, and contains no empty, ., or .. components. Control characters, reverse solidus, quotation marks, angle brackets, and colons are rejected with an invalid-path diagnostic. The header mapping does not change the PDL/runtime type name, derived C identifier, inheritance, or native implementation ownership. A mapped header need not reside in the PDL module's source tree; it is resolved by the consumer's C compiler.
A native enumeration uses the same optional header mapping, for example native "NativeGenerated/Type.h" enumeration Test.NativeDeclared.Kind { First = 0 Second = 7 } (one member per source line). This is a top-level type declaration: another declared type's name may be a prefix of its qualified name without implying lexical nesting. Its externally owned header is not emitted unless it shares a header explicitly owned by a native-implementation class, as described below.
A plain native class remains externally declared and is not emitted. A native implementation class instead owns its declaration output; its implementation remains native and native construction restrictions still apply. The mapped declaration path must end in .h and cannot be the module aggregate. Two output declarations cannot own the same header, and a generated header cannot replace an opaque native class's definition. Native enums explicitly mapped to the same owned header are emitted there as shared top-level declarations, not as nested types or runtime enum implementations.
A callable's native-target string names the target verbatim. It must be a nonempty C identifier. The rewriting that derives an implementation name from a fully qualified type name by replacing the . separator does not apply to a native target name.
A native callable definition must not have a PDL body: its implementation is provided by the named target. An ordinary, nonabstract definition is compiled from its PDL body. An omitted ordinary body is treated as an empty body. An empty void callable returns void; falling through a non-void callable raises a runtime error. An explicitly abstract method instead declares an implementation obligation and has no implementation body.
A built-in type may declare members, and such a member is native without a native clause appearing in any compilation unit: a member supplied by a built-in type is native by being supplied, and not by carrying the optional clause.
Qualified Declarations, Ownership, and Value Records
Fields and parameter annotations describe the program independently of a backend. reference T describes a reference to external storage for a scalar T; reference Arcadia.Void describes untyped external storage. readonly grants read-only access through an object view or external reference. output reference T describes caller-provided writable result storage. Output parameters cannot be readonly. These declarations do not prescribe a C pointer, a Java carrier, or any particular runtime value tag.
A readonly object view is not an immutable object type: it restricts access through that view, without making the object globally immutable or preventing other aliases from modifying it. It is also distinct from a constant declaration, which defines an immutable initialized class value. The language has no separate const keyword or general constant-object declaration; current class constants are Integer32 values.
Ordinary object fields are GC-managed references. A borrowed field does not acquire ownership; an owned reference field owns external storage; a retained object field follows its type's lifetime protocol. The current resource protocol provides nonvirtual zero-argument ref and unref operations returning void. Retained references are distinct from GC-traced references. Native implementations remain responsible for carrying out these declared contracts.
Record fields are value aggregates: their fields have declared types and order, and repeated field names are errors. They have no object identity or explicit base member. Class constants have immutable initialized values, not per-instance storage. The current implementation accepts class constants of unqualified Arcadia.Integer32 and diagnoses out-of-range initializers.
A nonvirtual method belongs to its declaring type and does not participate in override dispatch. A method cannot override a nonvirtual method, and a nonvirtual method cannot replace an inherited member. Overrides must preserve parameter types, reference/read-only qualifiers, and output modes. Interface operations are virtual.
| Declaration or annotation | Meaning |
|---|---|
readonly Arcadia.Math.Color4Real32 | A read-only object view for a native input, not a distinct immutable color object. |
readonly reference Arcadia.Void | A read-only view of borrowed, untyped external storage, as used by byte-buffer inputs. |
output reference Arcadia.Integer32 | Caller-provided writable scalar result storage, as used by frame-buffer size queries. |
borrowed Arcadia.Engine.Visuals.BackendContext | A non-owning object field. |
owned reference Arcadia.Void | An owning external-storage field, as used by vertex-buffer bytes. |
retained Arcadia.Engine.Visuals.MaterialResource | A field governed by the resource's explicit lifetime protocol. |
constant VertexDataDirty : Arcadia.Integer32 = 1 | An initialized class constant with no instance storage. |
For example, an aggregate field is written in PDL rather than as a quoted backend declaration:
variable canvasSize : record {
variable width : Arcadia.Real32
variable height : Arcadia.Real32
}
Procedures, Methods, and Constructors
A procedure belongs to its declaring module and may have a qualified declared identifier. A qualified procedure target is resolved by its exact declared name in the compilation; an unqualified target retains current-module lookup. There is no implicit namespace-prefix search. A method belongs to its declaring class and uses an unqualified member name. A constructor belongs to its declaring class and is not inherited. A class may declare at most one constructor and at most one member with a given declared name; overloading by parameter types or parameter count is not supported.
Parameters are ordered and passed by value. A callable accepts at most 255 parameters. At invocation, the argument count must match the parameter count and each argument must have the declared type. Primitive types require the corresponding runtime value tag; class and interface types accept instances of those types or void to denote an absent reference. The C backend maps that absent reference to NULL. Non-null class arguments must be instances of the declared class or a derived class; non-null interface arguments must implement the declared interface. No implicit scalar conversion is performed. A returned value is checked against the declared return type, with the same absent-reference rule for class and interface results. Locals and registers remain dynamically typed.
Parameter and local names are scoped to the complete callable body. Duplicate parameter or local names are errors. A method or constructor also binds self to its receiver; a parameter or local must not redeclare that name. Registers are separate from local names and are initialized to void, as are local variables. Labels are scoped to the complete body; duplicate labels and unresolved jump destinations are errors.
A virtual class method with the same declared name as an inherited virtual method overrides that method. Its parameter types, reference/read-only qualifiers, output modes, parameter count, and return type must agree with the inherited method. Invocation dispatches to the overriding implementation for the receiver's runtime class, including when invocation uses an ancestor's method entry point. Nonvirtual members cannot participate in this override relation.
An abstract method must be declared by an abstract class. It cannot be nonvirtual, have a native target, or have a body (including an empty body). Abstract intermediate classes may leave inherited methods unimplemented, or re-declare an inherited concrete method abstract. Every concrete non-native class must provide or inherit a concrete implementation of every abstract class method; implementing only some inherited obligations is an error. A concrete native binding is an implementation. A native class provider is responsible for inherited obligations fulfilled outside PDL, so opaque native and native-implementation classes are not rejected merely because an inherited implementation is absent from their PDL declarations.
Interface operations are implicitly abstract; explicitly writing method abstract is permitted. An explicitly abstract operation cannot also name a native target or have a body. This does not change the separate, currently limited class-interface implementation support.
A generated constructor initializes its own fields to their zero values. Unless explicitly forwarded, the immediate superclass constructor is invoked with zero arguments before the body. If that superclass constructor requires arguments, the first body statement must be construct target Fully.Qualified.Superclass self arguments.... This initializes the same object and does not allocate a second one. The superclass must be the immediate parent. A class without a declared constructor has a zero-argument constructor with implicit zero-argument superclass forwarding.
A field must not shadow an inherited member. A plain value field must not have type Arcadia.Void; reference Arcadia.Void is instead a permitted description of external storage. A class- or interface-valued field is initially null; reading an absent reference produces void, and storing void clears the reference. Non-null field values must match the declared type. Method invocation and field access still require a non-null object receiver; void cannot be used as that receiver. The receiver binding self is not a writable instruction target.
Interface Operations
Interface operations are method declarations. The syntactical translation accepts the same surface method form as class methods, but semantical translation rejects an interface operation that has a method body.
Instruction Semantics
Arcadia PDL is currently dynamically typed. The semantical translation does not bind an instruction operand to a type, so the descriptions below state which operand forms an instruction accepts and which effect it has on the values of its operands at run time. An instruction raises an error when it cannot be applied to the values of its operands.
In the instruction descriptions below, a register value may also be supplied by a declared local variable or parameter. A writable target must be a register or a declared local variable or parameter; a literal is never writable. Integer literal operands are represented as Arcadia.Natural64, real literal operands as Arcadia.Real64, and string literal operands as runtime UTF-8 strings. This operand representation is distinct from the width selection for enumeration initializers.
Arithmetic Instructions
add
The add instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation adds the value of the 3rd operand to the value of the 2nd operand and stores the result in the 1st operand.
If the values cannot be added, then an error is raised.
subtract
The subtract instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation subtracts the value of the 3rd operand from the value of the 2nd operand and stores the result in the 1st operand.
If the values cannot be subtracted, then an error is raised.
multiply
The multiply instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation multiplies the value of the 2nd operand with the value of the 3rd operand and stores the result in the 1st operand.
If the values cannot be multiplied, then an error is raised.
divide
The divide instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation divides the value of the 2nd operand by the value of the 3rd operand and stores the result in the 1st operand.
If the values cannot be divided, or if the value of the 3rd operand is zero, then an error is raised.
negate
The negate instruction expects two operands.
The 1st operand must be a register, then 2nd operand must be a register or a literal.
The operation negates the value of the 2nd operand and stores the result in the 1st operand.
If the value cannot be negated, then an error is raised.
Concatenation Instructions
concatenate
The concatenate instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation concatenates the value of the 3rd operand to the value of the 2nd operand and stores the result in the 1st operand.
If the values cannot be concatenated, then an error is raised.
Logic Instructions
and
The and instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation stores the logical conjunction of the values of the 2nd and the 3rd operand in the 1st operand.
If the values cannot be combined by a logical conjunction, then an error is raised.
or
The or instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation stores the logical disjunction of the values of the 2nd and the 3rd operand in the 1st operand.
If the values cannot be combined by a logical disjunction, then an error is raised.
not
The not instruction expects two operands.
The 1st operand must be a register, then 2nd operand must be a register or a literal.
The operation stores the logical negation of the value of the 2nd operand in the 1st operand.
If the value cannot be logically negated, then an error is raised.
Relational Instructions
isEqualTo
The isEqualTo instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation stores true in the 1st operand if the values of the 2nd and the 3rd operand are equal, and false otherwise.
If the values cannot be compared, then an error is raised.
isNotEqualTo
The isNotEqualTo instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation stores true in the 1st operand if the values of the 2nd and the 3rd operand are not equal, and false otherwise.
If the values cannot be compared, then an error is raised.
isLowerThan
The isLowerThan instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation stores true in the 1st operand if the value of the 2nd operand is lower than the value of the 3rd operand, and false otherwise.
If the values cannot be compared, then an error is raised.
isLowerThanOrEqualTo
The isLowerThanOrEqualTo instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation stores true in the 1st operand if the value of the 2nd operand is lower than or equal to the value of the 3rd operand, and false otherwise.
If the values cannot be compared, then an error is raised.
isGreaterThan
The isGreaterThan instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation stores true in the 1st operand if the value of the 2nd operand is greater than the value of the 3rd operand, and false otherwise.
If the values cannot be compared, then an error is raised.
isGreaterThanOrEqualTo
The isGreaterThanOrEqualTo instruction expects three operands.
The 1st operand must be a register, then 2nd and 3rd operands must be registers or literals.
The operation stores true in the 1st operand if the value of the 2nd operand is greater than or equal to the value of the 3rd operand, and false otherwise.
If the values cannot be compared, then an error is raised.
Data Movement Instructions
Data movement instructions transfer a value into a target operand, which is always given as the first operand. The operand denoted by name is a declared name, not a register.
set
The set instruction expects two operands.
The 1st operand must be a register or a variable declared in the enclosing procedure, constructor, or method, then 2nd operand must be a register, such a variable, or a literal.
The operation stores the value of the 2nd operand in the 1st operand.
loadArgument
The loadArgument instruction expects two operands.
The 1st operand must be a register, then 2nd operand must be a name denoting a parameter of the current procedure, constructor, or method.
The operation loads the value bound to that parameter into the 1st operand.
If the 2nd operand does not denote a parameter of the current procedure, constructor, or method, then an error is raised.
storeArgument
The storeArgument instruction expects three operands: a writable result target, a name denoting a parameter of the current callable, and a source value. It stores the source value into the current invocation's parameter slot and copies that value to the result target. It does not modify the caller's argument slot. An undefined parameter name is a semantic error.
loadField
The loadField instruction expects three operands.
The 1st operand must be a register, then 2nd operand must be a register holding an object, and the 3rd operand must be a name denoting a field of the class type of that object.
The operation reads that field into the 1st operand.
If the 3rd operand does not denote a field of that class type, then an error is raised.
storeField
The storeField instruction expects three operands.
The 1st operand must be a register holding an object, then 2nd operand must be a name denoting a field of the class type of that object, and the 3rd operand must be a register or a literal.
The operation stores the value of the 3rd operand into that field.
If the 2nd operand does not denote a field of that class type, then an error is raised.
Object Instructions
invoke
The invoke instruction writes a returned value to its first operand, a writable target. Its second operand is a callable identifier. A qualified identifier denotes a procedure by its exact declared name; an unqualified name first looks for a procedure in the current module. For a procedure, every following operand is an argument. Otherwise an unqualified name denotes a method: the first following operand is the receiver and the remaining operands are method arguments. The receiver must be an instance of a class declaring or inheriting the named method with the given argument count. Method dispatch uses its runtime class. A void method or procedure stores void in the target. At least one operand must follow the callable identifier.
call
call target identifier arguments... invokes a procedure and stores its return value in the writable target. Qualified identifiers resolve the exact declared procedure across the compilation; unqualified names retain current-module lookup. Unlike invoke, it permits zero arguments and never performs method lookup. Arguments are evaluated from left to right before invocation. Undefined procedure identifiers and statically known argument-count mismatches are semantic errors.
construct
construct target Fully.Qualified.Class receiver arguments... constructs an object and writes it to the writable target on success. A void receiver requests allocation of a new concrete object. An existing receiver is permitted only for immediate-superclass construction in a constructor and must be self. The remaining operands are constructor arguments, evaluated from left to right. A zero-argument allocation is written construct target Fully.Qualified.Class void. The C backend generates allocation entry points only for concrete, non-native classes. It rejects direct abstract-class construction even when the receiver operand is a variable. An immediate native superclass may be initialized by the first constructor statement using its declared constructor signature and native runtime callback; its native provider remains responsible for implementing that signature. Native-class allocation remains unsupported.
Control Flow Instructions
jump
The jump instruction expects one operand, which must be a name denoting a label declared in the body of the current procedure, constructor, or method.
The operation transfers control to that label.
If the operand does not denote a label declared in the current procedure, constructor, or method, then an error is raised.
jumpIfTrue
The jumpIfTrue instruction expects two operands.
The 1st operand must be a register or a literal, then 2nd operand must be a name denoting a label declared in the body of the current procedure, constructor, or method.
The operation transfers control to that label if the value of the 1st operand is true, and falls through otherwise.
If the 2nd operand does not denote a label declared in the current procedure, constructor, or method, then an error is raised.
jumpIfFalse
The jumpIfFalse instruction expects two operands.
The 1st operand must be a register or a literal, then 2nd operand must be a name denoting a label declared in the body of the current procedure, constructor, or method.
The operation transfers control to that label if the value of the 1st operand is false, and falls through otherwise.
If the 2nd operand does not denote a label declared in the current procedure, constructor, or method, then an error is raised.
raise
The raise instruction expects no operands.
The operation raises the current exception.
return
The return instruction returns from the current method / procedure / constructor.
It may have zero or one operand. It returns a void value with zero operands.
If an operand is specified, that operand must be a register, local variable, parameter, or literal whose value is returned. Constructors and void procedures or methods must not return an operand; non-void procedures and methods must return an operand. These form violations are semantic errors.
The target of a jump must be a bare name; a string literal is not accepted. Labels are introduced by a label definition statement, that is by name ':', in the body of the enclosing procedure, constructor, or method.
Execution Model
Each invocation has an independent execution frame containing its parameters, receiver when applicable, local variables, registers, and temporary values. Recursion and nested invocation create new frames. A return or propagated runtime error removes the frame's temporary state without changing the caller's live values.
The abstract machine supports string values, boolean values, signed integer values, unsigned natural values, real values, void, and procedure values. Integer and natural values may use 8, 16, 32, or 64 bit widths. Real values may use 32-bit or 64-bit IEEE 754 formats.
An interpreter may provide a stack. The stack is initially empty and supports querying its size, pushing a value onto the top of the stack, and popping the top value. Popping an empty stack is undefined.
An interpreter may provide a status variable with the states success and failure. The initial state is success.
Calling Convention
The C backend emits typed procedure and method entry points. The first parameter is Arcadia_Thread* thread. A method additionally receives a pointer to its declaring class as self, followed by its declared parameters in declaration order. A procedure has no receiver parameter. There is no public argument-count/argument-array ABI. The C return type is the representation of the declared PDL result type.
Arcadia.Void results map to C void, scalar types map to their corresponding Arcadia_*Value types, class types map to pointers to their generated C structs, interface types map to Arcadia_Object*, enumeration types map to their generated C enumeration types, and Arcadia.Value maps directly to Arcadia_Value. A void parameter maps to Arcadia_VoidValue.
Arcadia.Size maps to Arcadia_SizeValue (size_t) in fields, parameters, dispatch slots, factories, native calls, and results. Its boxed representation is the runtime Size value tag, not a fixed-width Natural tag. Conversions validate that tag and preserve the full platform-sized range.
Class and interface references are nullable at value boundaries: parameters (including constructor and native-target parameters), fields, and results. C NULL is boxed as Arcadia_Value_makeVoidValue(Arcadia_VoidValue_Void), never as an object-reference value containing a null pointer; PDL void converts back to a typed null pointer. This uses the existing void literal and requires no additional nullable-type syntax. A non-null reference remains type-checked. Scalars and enumerations do not accept void in place of their declared value types.
Receivers are not nullable: method dispatch and field access require an actual object. A void receiver is rejected rather than dispatched or dereferenced. Ordinary constructor allocation still uses its separate void-receiver convention. Nullable references in frames are void values, and object/interface visit callbacks skip null C fields; both representations are safe for GC.
Virtual class dispatch slots use the same typed signature as their declaring methods. An override preserves the inherited slot signature. When its implementation receives a pointer to a derived class, the backend emits a forwarding thunk with the exact inherited receiver type instead of assigning an incompatible function pointer. A public virtual-method wrapper dispatches through the slot and therefore also invokes overrides. A nonvirtual method has no slot and its wrapper calls its implementation directly.
An abstract method retains its typed public declaration and virtual slot, but has no generated private implementation body. Dispatch initialization sets an unimplemented abstract slot to null; a re-abstracting override explicitly clears an inherited concrete slot. Concrete overrides install their implementation or correctly typed forwarding thunk. Generated public virtual wrappers reject a null slot with Arcadia_Status_OperationInvalid and restore their value-stack frame on failure instead of invoking a null function pointer.
Generated class method wrappers also validate non-null managed class and interface arguments before entering a bound override, including a raw native override without a generated implementation body. A reference of the wrong runtime type raises Arcadia_Status_ArgumentTypeInvalid without invoking the callback. Nullable arguments remain accepted; external reference and output pointers are forwarded as C pointers rather than treated as managed objects. Receiver and argument roots are established before type metadata lookup, and failures restore the caller's stack depth.
Generated frames store live values in mutable slots of the runtime value stack, so reassignment in a loop does not accumulate roots. The receiver, parameters, local variables, registers, and temporary argument values remain GC roots while live. Generated visit callbacks trace object-valued fields. Jump-target cleanup restores the stack to its size at entry before propagating a failure.
Native targets use the same typed parameter and result mapping as generated procedures and methods. The first parameter is Arcadia_Thread*, followed by the typed receiver pointer for a method or constructor, then the declared parameters in order. A native constructor returns C void and initializes the supplied object. Native implementations must follow the runtime rooting and error-propagation conventions.
A method of a native class may bind to its existing conventional C entry-point name because that native class has no generated public wrapper. Calling such a method invokes the C target, which retains responsibility for any virtual dispatch or direct nonvirtual behavior. A native target must not collide with an actually generated public entry point or private method adapter.
When generated code in module B needs a declaration defined in module A, it includes A's master header, such as Example/A/Include.h, rather than the individual type header. Selection follows the symbol's defining module, not a prefix guessed from its type name, and must not bypass A's private-header guard. This applies to foreign superclass definitions, enum signatures, source-side conversions and calls, and interface inheritance. Dependencies resolving to the same master header are deduplicated; forward declarations may avoid unnecessary complete-type includes.
Within a module, generated dependencies on a native class use its explicit header path when supplied, including native calls and complete-type dependencies such as an embedded superclass. For a plain native class, the C backend does not generate or overwrite its externally owned header or implementation. Without an explicit mapping, the default qualified-name-derived header path remains in effect. Runtime built-ins are supplied through the runtime aggregate.
A generated class may preserve an established C output path with class "Path/To/Header.h" Qualified.Type, optionally preceded by abstract. The mapping is interoperability metadata: it changes neither the program's type name nor its fields or inheritance. It must be a safe relative forward-slash path ending in .h. The backend owns that header and the corresponding .c.g source beside it; include guards follow the mapped header path. Duplicate output ownership, replacing a module aggregate, and replacing an opaque native declaration remain errors.
For native implementation classes, normal C backend generation emits the actual .h declaration file under the owning module's Library/Sources tree. It emits no .c or .c.g implementation and no synthetic factory. Header guards follow the mapped file path, including legacy path components absent from the runtime type name. Native enums sharing that header are emitted once before class declarations; opaque native classes retain externally owned headers. This behavior is part of ordinary compiler generation, not a test-only preview or header-copying step.
A generated enumeration may likewise preserve its established C path with enumeration "Path/To/Header.h" Qualified.Type. The path obeys the same safe-relative-path and .h restrictions as a generated class mapping. The backend owns that header and its adjacent ordinary .c source (not .c.g); guards and source includes follow the mapped path while runtime and C type names remain unchanged. Mappings participate in output ownership validation: they cannot replace module aggregates, share another generated header, or overwrite opaque native declarations. Dependencies from another module still include that module's master header, not the mapped per-enum header.
Native enumeration declarations preserve their existing C type names and use their mapped native headers, including when a generated class stores such an enum in a field. They have no generated header/source output path. Executable value transport requires the native provider to export its Arcadia runtime enumeration descriptor through the conventional _<CType>_getType(thread) function, for example using Arcadia_declareEnumerationType and Arcadia_defineEnumerationType. The boxed representation is the existing Arcadia.EnumerationValue, carrying that exact descriptor and an Integer32 numeric payload. An integer or another enum type with the same numeric value is not interchangeable with the declared enum.
The native enum value ABI must be no wider than Arcadia_Integer32Value, enforced by a generated C static assertion whenever executable transport uses the type. Boxing checks the numeric value before narrowing; unboxing checks exact enum identity and that conversion to the native C type preserves the numeric value. Values outside the shared representable range raise ArgumentTypeInvalid. This also supports native scalar-alias/macro enum APIs that export an Arcadia enumeration descriptor. Native declarations alone neither manufacture descriptors nor regenerate the native enum's headers, values, aliases or implementation.
For native declaration compatibility, the C backend maps external scalar references to pointers to the scalar representation and untyped storage references to void*. Read-only views map to const-qualified referents. Output references remain writable pointers. A value record field becomes an embedded anonymous C struct. Inheritance supplies the C base members implicitly; virtual methods supply dispatch slots in declaration order, while nonvirtual methods have direct entry points and no slot. Class constants become prefixed macros. These are C-backend choices, not declarations of C layout in PDL.
The current C backend supports these record/ownership descriptions for native class fields and the qualified parameter declarations of native bindings. It does not yet transport external references or record values through its Arcadia_Value execution slots; such executable uses are diagnosed. Read-only object inputs to native bindings retain normal nullable-object boxing and GC rooting. Native fields with borrowed, owned, or retained lifetime contracts continue to use their existing native implementation, rather than silently acquiring generated ownership behavior.
Generated abstract-method C wrappers may forward qualified typed parameters directly to native overrides: external references remain C pointers and never enter value slots, while readonly object arguments retain their GC roots and const-qualified dispatch types. This permits compatible wrappers for byte inputs and scalar output parameters. Invoking an external-reference operation from an executable PDL body still requires a supported value representation and is diagnosed otherwise.
Planned native interoperability approach: external byte storage will be exposed through native class objects, for example Arcadia_ByteArray or Arcadia_ImmutableByteArray, rather than adding raw void* storage to Arcadia.Value. This is an implementation plan, not a currently available external-reference execution feature; it is deferred until generated implementation bodies need that transport.
Constructors are private initialization operations, not value-returning public functions. The runtime constructor callback returns void and takes thread and a typed self. It follows the runtime value-stack convention: callers push arguments in declaration order, then their Arcadia.Natural8 count; a successful callback consumes the count and arguments. A generated concrete non-native class exposes a typed Class_create(thread, parameters...) factory returning Class*. The factory adapts its typed parameters to the runtime constructor protocol. Abstract classes and native/native-implementation declarations have no synthesized factory.
At constructor entry, the top-relative offset 0 holds the count, the last argument is at 1, and the first of n arguments is at n. Thus a three-argument (timestamp, horizontal, vertical) callback reads offsets 3, 2, and 1. Forwarding a superclass call pushes a separate copied argument segment and count; successful superclass construction consumes that segment while the derived callback's original arguments remain available. Stack-depth snapshots and offsets are size values, distinct from the Natural8 argument-count value.
For enumeration parameters, including supported native enumerations, the runtime constructor callback accepts either an enumeration value carrying the exact declared enum type or a legacy C Arcadia.Integer32 value. The callback normalizes the latter to the declared enum type before invoking the typed initialization operation. This compatibility adaptation applies only to the runtime callback boundary: PDL construct arguments, including explicit superclass forwarding, still require the exact enumeration type and reject raw integers and values of other enum types.
A fully qualified enumeration constant used as an operand produces a boxed value carrying its declaring enumeration descriptor and Integer32 payload, not an untyped integer literal. This applies to generated and supported native enum declarations; native constants retain the existing native ABI/range checks. Constants may be assigned to registers or passed, compared, constructed with, and returned, subject to ordinary exact-type checks. Existing local/parameter lookup retains precedence, and a constant cannot be an instruction target. Foreign constants require their defining module's master header.
Equality instructions retain registered scalar comparison callbacks when present. If an enumeration on the left has no such callback, isEqualTo compares exact enumeration identity and payload; a non-enum right operand is unequal. isNotEqualTo is the inverse for that fallback. This does not add enumeration arithmetic or ordering, nor does it change object comparison dispatch.
Generated functions root incoming object/interface receivers and parameters and general-value parameters before any operation that can collect, including enum/type metadata resolution. Internal PDL registers and mutable parameters may remain value-stack slots. Calls convert those slots to typed arguments and box typed results as needed; this internal representation does not change the public C interface. Errors continue to propagate through the runtime status/jump mechanism, including from functions returning void.
The backend writes class and procedure headers directly as .h, just like interface and enumeration headers. Header dependencies refer directly to those files; no header-copying or installation step is required. Class and procedure sources retain the experimental .c.g suffix. Plain opaque native classes retain externally owned definitions; native-implementation classes regenerate their declaration headers but retain native executable bodies. Header declarations include or forward-declare their typed parameter/result dependencies.
Procedure output paths follow the declared identifier. For example, procedure M.N.p produces Library/Sources/M/N/p.h and Library/Sources/M/N/p.c.g within its module, with C entry point M_N_p. The enclosing module is not prefixed a second time. Existing short-name procedures retain their flat output paths and module-prefixed C names; qualified identifiers should be used for namespaced output. Native target names remain verbatim. Generated includes use the same output metadata, and foreign dependencies still use the owning module's master include. A procedure output that would replace a module aggregate or another declaration's header is rejected before files are written.
Abstract classes retain their object and dispatch layouts, type registration, visit callbacks, and constructor implementations, but have no generated _create declaration or definition. Superclass initialization calls the parent's constructor implementation on the existing receiver; it does not use an allocation entry point. This also applies to abstract parents.
A void parameter of a native target uses Arcadia_VoidValue; only a void result uses C void.
Generated Member Names
Generated fields and dispatch slots preserve their declared PDL names, except that one _pdl suffix is appended when the original name is a C/C++ keyword, is _parent, or already ends in _pdl. This rule is applied exactly once to the original name, not repeatedly to the result. The backend uses a fixed keyword set covering C23 and C++23. Names containing __ or beginning with an underscore followed by an uppercase ASCII letter are reserved by C/C++ and are diagnosed by the C backend.
For example, timestamp remains timestamp, switch becomes switch_pdl, switch_pdl becomes switch_pdl_pdl, _parent becomes _parent_pdl, and _parent_pdl becomes _parent_pdl_pdl. The generated base member itself is named _parent. Declarations, accesses, initialization, GC visits, and dispatch operations use the same mapping. Native code accessing generated members must use their mapped C names.
Parameter names in generated headers use the same escaping rule and additionally escape thread and self to avoid the generated calling-convention parameter names. Source implementations may use positional parameter names without changing the C function type.
Arithmetic
For unsigned natural values of width n and m, add, subtract, and multiply compute the mathematical result modulo 2k, where k = max(n,m). The result is represented as an unsigned natural value of width k.
For signed integer values of width n and m, operands are sign-extended to width k = max(n,m). Addition, subtraction, and multiplication use two's-complement arithmetic and keep the low-order k bits of the mathematical result.
For division, division by zero raises an arithmetic error. Otherwise the operation computes the quotient according to the selected integer or natural arithmetic model and represents it at width k = max(n,m).
The unary negate operation computes zero minus its operand in the operand's arithmetic domain. The unary not operation is the logical negation operation.
Examples
module Arcadia.Engine {
}
abstract class Arcadia.Engine.Event extends Arcadia.Object {
variable timestamp : Arcadia.Natural64
constructor(variable timestamp : Arcadia.Natural64) {
storeField self timestamp timestamp
}
}
class Arcadia.Engine.ConcreteEvent extends Arcadia.Engine.Event {
constructor(variable timestamp : Arcadia.Natural64) {
construct $0 Arcadia.Engine.Event self timestamp
}
}
procedure makeEvent(variable timestamp : Arcadia.Natural64) : Arcadia.Engine.Event {
construct $0 Arcadia.Engine.ConcreteEvent void timestamp
return $0
}
enumeration Arcadia.Engine.Visuals.WindingMode {
Clockwise = 0
CounterClockwise = 1
}
procedure sum(variable x : Arcadia.Integer32, variable y : Arcadia.Integer32) : Arcadia.Integer32 {
loadArgument $1 x
loadArgument $2 y
add $1 $1 $2
return $1
}
Event is abstract even though it has no abstract methods. ConcreteEvent forwards initialization to the abstract parent on the same receiver, and makeEvent allocates the concrete class. The following separate procedure is invalid because it attempts to instantiate Event directly:
procedure invalidEvent(variable timestamp : Arcadia.Natural64) : Arcadia.Engine.Event {
construct $0 Arcadia.Engine.Event void timestamp
return $0
}
Executable Procedures and Methods
module Demo
class Demo.Counter {
variable value : Arcadia.Natural64
constructor(variable initial : Arcadia.Natural64) {
storeField self value initial
}
method read : Arcadia.Natural64 {
loadField $0 self value
return $0
}
}
procedure initial : Arcadia.Natural64 {
return 42
}
procedure run : Arcadia.Natural64 {
call $0 initial
construct $1 Demo.Counter void $0
invoke $2 read $1
return $2
}
run calls a zero-argument procedure, allocates and initializes a counter, invokes its method, and returns 42. Instruction operands are adjacent; only declaration parameter lists use commas and parentheses.