Expressions
This page shows common expression forms. For exact typing, evaluation, and precedence rules, see Expressions and Operators.
DQ expressions are statically type-checked. Assignment is a statement, not an expression.
Arithmetic
DQ supports the usual arithmetic operators.
var a : int = 10 + 2 * 3
var b : int = a - 1
var f : float = 3 / 2
The / operator follows DQ arithmetic rules intended to make common mixed
numeric expressions behave naturally. Integer division, remainder, and modulo are written
with div, rem, and mod.
var a : int = 10 div 3
var b : int = 10 rem 3
var c : int = 10 mod 3
The / division operator always produces a floating point result.
Floating point values are not converted automatically to integers, explicit
conversion functions required: Round, Ceil, Floor, or Trunc
Integer calculations
DQ integer arithmetic is fixed-width and has no overflow checks. This keeps the generated code suitable for embedded targets: values use the selected integer width, and bits that do not fit are discarded.
Bare numeric values are contextual
Whole-number literals need no type suffix or conversion call. A declaration, assignment, function parameter, return type, or typed array element supplies the required storage type:
var retry_count : uint8 = 3
var threshold : float = 2 // no `2.0` required just to make it a float
var masks : [2]uint8 = [240, 15]
A bare whole-number literal has the ordinary int calculation type. Therefore
it also widens a mixed expression to int, before any result is stored:
var byte_value : uint8 = 250
var whole : int = byte_value + 10 // 260: `10` makes this an `int` calculation
byte_value = byte_value + 10 // 4: 260 is truncated when stored as `uint8`
The destination of an expression does not widen that expression. The widest
operand determines its calculation width. Two uint8 operands consequently
calculate at eight bits, even if their result is assigned to an int:
var left : uint8 = 250
var right : uint8 = 10
var sum : int = left + right // 4, not 260
Use an int operand or an explicitly wider intermediate when the full result
is needed.
Storage truncation and signed values
Converting an integer result to a narrower integer type keeps its low bits. For unsigned types those bits are the resulting unsigned value; for signed types they are interpreted as a two's-complement value. The rule is the same for constant and runtime expressions.
var unsigned_byte : uint8 = 250
var signed_byte : int8 = 120
var unsigned_wide : int = unsigned_byte + 10 // 260
var signed_wide : int = signed_byte + 10 // 130
unsigned_byte = unsigned_byte + 10 // 4
signed_byte = signed_byte + 10 // -126
This makes register fields, counters, and packed values efficient and predictable. If wrapping must happen at a particular point in a longer calculation, store the intermediate in the desired narrow type at that point.
Signed and unsigned operands
Integer operands are converted to a common calculation width. Subtraction has
an additional rule: if either operand is signed, its result is signed at the
widest operand width. A bare whole-number literal is signed int, so unsigned
subtraction with a literal can represent a negative result:
var available : uint = 3
var remaining : int = available - 5 // -2
When intentionally relying on unsigned wraparound, use an unsigned operand of the appropriate width, for example a typed variable, rather than relying on a literal hint.
Division and shifts
Use div, rem, and mod for integer division. rem keeps the dividend's
sign; mod is non-negative. / always performs floating-point division.
var quotient : int = 7 div 3 // 2
var remainder : int = -7 rem 3 // -1
var modulo : int = -7 mod 3 // 2
>> is an arithmetic right shift for signed values and a logical right shift
for unsigned values. Shift counts must be non-negative and smaller than the
calculation width. Division by zero, and signed minimum-value divided by -1,
have no defined result; code that can reach either case must guard it first.
Comparison
Comparison operators produce bool.
a == b
a <> b
a < b
a <= b
a > b
a >= b
<> tests inequality. The C-style != spelling is not accepted.
Ordering comparisons can be chained to test a range:
lower <= value < upper
high >= value >= low
The middle operand is evaluated once. The final operand is evaluated only if the first comparison succeeds.
Logical Operators
Logical operators use lowercase words.
if ready and not failed:
Run()
endif
The logical operators are:
andornot
Operands must be bool.
Bitwise Operators
Bitwise operators use symbols except for lowercase xor.
var masked : uint = value & 0xFF
var flags : uint = a | b
var flipped : uint = ~flags
The bitwise operators are:
&|xor~<<>>
Bitwise operators have higher precedence than logical operators and arithmetic
operators.
Symbolic bitwise modify-assignment uses &= and |=. The word operator keeps
DQ's leading-= form, =xor=:
var i : int = 0xFF00
i |= (1 << 3)
i &= ~(1 << 12)
i =xor= 1
Shift modify-assignment uses <<= and >>=.
i <<= 1
i >>= 1
Casts
Explicit casts use type-call syntax or expression as Type.
var p : pointer = %value
var ip : ^int = ^int(p)
var f : float64 = float64(value)
var ip2 : ^int = p as ^int
Both forms use the same conversion rules. An as cast binds at the comparison
level and is not chainable; parenthesize it before member access or another
comparison-level operation.
Inline If
DQ provides an inline conditional macro named iif.
var text : embstr = iif(ptr == nil, "nil", "not nil")
The first argument must be bool. The second and third arguments must be
compatible with the expected result type.
Optional Compile-Time Constants
FirstInt, FirstFloat, and FirstBool select the first declared DQ constant
from a list of optional identifiers. The final argument is a required constant
fallback expression.
const BUFFER_SIZE : int = FirstInt(BOARD_BUFFER_SIZE, DEFAULT_BUFFER_SIZE, 256)
const SCALE : float = FirstFloat(BOARD_SCALE, 1.0)
const LOGGING : bool = FirstBool(BOARD_LOGGING, false)
Optional identifiers are resolved from left to right in the lexical scope at the call. Missing identifiers are skipped. The first identifier that resolves must name a constant convertible to the intrinsic's result type; a variable or an incompatible constant is an error. The fallback must also be a convertible constant expression and is validated even when an earlier constant is found.
The result types are fixed: FirstInt returns int, FirstFloat returns
float, and FirstBool returns bool. These intrinsics are always evaluated
during compilation and can be used in ordinary expressions as well as #if
and #elif conditions. Optional identifiers use normal lexical lookup, whose
outer module scope includes preprocessor definitions. The optional-identifier
syntax itself does not accept qualified names such as @def.NAME.
Definition Test
Defined(NAME) is a compile-time intrinsic that returns true when NAME
exists in the merged current-module namespace and false otherwise. Like
#ifdef, this includes module declarations, symbols merged by use, and
preprocessor definitions, regardless of their values:
#if Defined(FEATURE_A) or Defined(FEATURE_B)
const FEATURE_AVAILABLE : bool = true
#endif
A namespace-qualified argument restricts the test to that named scope, for
example Defined(@def.FEATURE), Defined(@.MODULE_CONSTANT), or
Defined(@module.CONSTANT). The result is a constant bool, so Defined can
also be used in ordinary code.
Object Type Test
The is operator checks whether an object reference is compatible with an
object type.
if obj is OChild:
// obj is an OChild or derives from OChild
endif
is returns false for nil.
Address and Dereference
% takes the address of an addressable value.
var value : int = 10
var p : ^int = %value
^ dereferences a typed pointer.
p^ = 11
Pointers to structs are automatically dereferenced for member access.
point_ptr.x = 10
Indexing
Arrays, strings, Embedded Strings, and typed pointers support indexing.
var a : [3]int = [1, 2, 3]
var second : int = a[1]
For typed pointers, p[i] performs pointer indexing and returns a pointer value.
It does not dereference like C's p[i].
var p : ^char = text.pchar
var next : ^char = p[1]
var ch : char = p[1]^
Slicing
Arrays and strings support slicing.
var a : [*]int = [1, 2, 3, 4]
var middle : []int = a[1:3]
var all : []int = a[:]
var tail : []int = a[2:]
The special $end (= length) and $last (= length - 1) values can be used by
some indexing and mutation APIs.
arr.Insert($end, 99)
text.Insert($end, "!")
Operator Precedence
Precedence is listed from highest to lowest.
| Level | Operators and syntax | Meaning |
|---|---|---|
| 1 | literals, identifiers, @namespace.name, (...), [...], Type(expr), new, builtins such as Len(...), SizeOf(...), iif(...) |
Primary expressions, array literals, casts, allocation, builtin forms |
| 2 | expr(args...), expr.member, expr[index], expr[start:end], ptr[index], ptr^ |
Calls, member access, indexing, slicing, pointer indexing, pointer dereference |
| 3 | %expr, -expr, ~expr |
Address-of, unary minus, bitwise NOT |
| 4 | <<, >> |
Bit shifts |
| 5 | & |
Bitwise AND |
| 6 | |, xor |
Bitwise OR, bitwise XOR |
| 7 | /, div, rem, mod |
Division, integer division, remainder, integer modulo |
| 8 | * |
Multiplication |
| 9 | +, - |
Addition, subtraction |
| 10 | ==, <>, <, <=, >, >=, is, as |
Comparison, object type test, and explicit cast |
| 11 | not |
Logical NOT |
| 12 | and |
Logical AND |
| 13 | or |
Logical OR |
This order is intentionally different from C in the bitwise levels. For
example, value & mask <> 0 is parsed as (value & mask) <> 0.
Assignment operators are statements, not expressions, so they are outside the
precedence table. Supported modify-assignment forms include +=, -=, *=,
/=, <<=, >>=, &=, |=, =div=, =rem=, =mod=, and =xor=.
Member Access
. accesses struct fields, object fields, methods, properties, enum values,
and namespace members.
point.x = 1
object.Method()
box.property = 10
var c : NColor = NColor.red
Inside object methods, members can be used without self..
Function Calls
Functions and function references are called with parentheses.
var x : int = Add(1, 2)
var y : int = callback(x)
Object methods are called through object values.
obj.Update()
Object Allocation
new allocates an object on the heap and returns an object reference.
var obj : OThing = new OThing(1, "name")
Embedded object allocation uses <-.
var obj <- OThing(1, "stack or global storage")
See Objects and Memory and Pointers.