Variables and Types in BLISS
Learn how BLISS handles variables, storage classes, and its typeless machine word-oriented data model with practical Docker-ready examples
BLISS takes a radically different approach to variables compared to nearly every other language you may have encountered. There are no types like int, float, or string — all data in BLISS is a fullword: a value the size of the underlying machine’s native word (32 bits on a VAX, 64 bits on Alpha or x86-64). The programmer, not the compiler, is responsible for interpreting what that word means.
This typeless design was deliberate. BLISS was created for systems programming, where you frequently work with raw memory, hardware registers, and binary data. A type system would impose overhead and restrictions that conflict with systems-level control.
The second distinctive feature is BLISS’s address-value duality: a variable name alone evaluates to the variable’s address, not its contents. The dot (.) operator is the explicit “contents of” dereference. This means every read of a variable’s value must be written with a leading dot — there is no implicit dereferencing.
In this tutorial you will learn BLISS’s three storage classes (OWN, LOCAL, GLOBAL), how to declare and initialize variables, how constants work, and how the typeless model handles different kinds of data.
Storage Classes
BLISS has three storage classes for variables, each with a distinct scope and lifetime:
| Declaration | Scope | Lifetime | Equivalent in C |
|---|---|---|---|
OWN | Module (file) | Program lifetime | static at file scope |
LOCAL | Routine (function) | Stack frame | Local variable |
GLOBAL | Entire program | Program lifetime | extern / global |
OWN — Module-level Static Storage
OWN declares a variable that lives for the entire program and is visible within the module where it is declared. It is initialized to zero if no initializer is given.
LOCAL — Stack-allocated Variables
LOCAL declares a variable on the routine’s stack frame. It exists only while the routine is executing. It must appear inside a BEGIN...END block.
GLOBAL — Cross-module Variables
GLOBAL makes a variable visible to the linker, so other modules can access it. In our single-module examples the distinction between OWN and GLOBAL is not visible at runtime, but GLOBAL is the mechanism for sharing data across compiled units.
Example 1: OWN and LOCAL Variables
This example shows both OWN (module-level) and LOCAL (routine-level) variable declarations, assignments, and the dot operator for reading values. Because BLISS has no I/O primitives, we print results through an external C function.
Create a file named variables.bli:
MODULE variables =
BEGIN
EXTERNAL ROUTINE
print_int;
! OWN variables: module-level, initialized to 0 by default
OWN
counter,
total;
GLOBAL ROUTINE show_variables : NOVALUE =
BEGIN
! LOCAL variables: stack-allocated, exist only in this routine
LOCAL
a,
b,
result;
! Assignment: store a value at the address of the variable
a = 10;
b = 25;
! Reading values: .a means "contents of a"
result = .a + .b;
! Use OWN variables
counter = 1;
total = .a * .b;
! Print each value via C bridge
print_int(.a);
print_int(.b);
print_int(.result);
print_int(.counter);
print_int(.total)
END;
END
ELUDOM
Create a file named variables_wrapper.c:
| |
Example 2: Initializers and Constants
BLISS allows OWN variables to carry an initial value using the INITIAL attribute. Constants are declared with LITERAL, which binds a name to a compile-time integer value. Neither LITERAL nor INITIAL introduce types — they are still just word-sized values.
Create a file named constants.bli:
MODULE constants =
BEGIN
EXTERNAL ROUTINE
print_int;
! LITERAL declares a compile-time constant (no storage allocated)
LITERAL
max_count = 100,
buffer_size = 512,
flag_on = 1,
flag_off = 0;
! OWN with INITIAL: module-level variable with a non-zero starting value
OWN
limit INITIAL(max_count),
size INITIAL(buffer_size);
GLOBAL ROUTINE show_constants : NOVALUE =
BEGIN
LOCAL
x,
is_enabled;
! LITERAL values are substituted at compile time
x = max_count * 2;
is_enabled = flag_on;
print_int(max_count);
print_int(buffer_size);
print_int(.limit);
print_int(.size);
print_int(.x);
print_int(.is_enabled)
END;
END
ELUDOM
Create a file named constants_wrapper.c:
| |
Example 3: The Typeless Model — Integers, Addresses, and Bit Fields
Because all values are machine words, BLISS can treat the same word as an integer, an address, or a bit pattern depending on how you use it. This example demonstrates BLISS’s field extraction operators (<position, size>) which allow you to read or write sub-word bit ranges — a capability essential for systems programming with hardware registers and packed data.
Create a file named bitfields.bli:
MODULE bitfields =
BEGIN
EXTERNAL ROUTINE
print_int;
GLOBAL ROUTINE show_bitfields : NOVALUE =
BEGIN
LOCAL
word,
low_byte,
high_byte,
nibble;
! Store a 32-bit pattern: 0xABCD = 43981 decimal
word = %X'ABCD';
! Extract the low 8 bits (byte): position 0, size 8
low_byte = .word<0,8>;
! Extract bits 8-15 (next byte): position 8, size 8
high_byte = .word<8,8>;
! Extract a 4-bit nibble: position 4, size 4
nibble = .word<4,4>;
print_int(.word);
print_int(.low_byte);
print_int(.high_byte);
print_int(.nibble)
END;
END
ELUDOM
Create a file named bitfields_wrapper.c:
| |
Running with Docker
All three examples follow the same two-step compile-and-link pattern. The blissc compiler produces a native object file, and GCC links it with the C wrapper.
| |
Expected Output
Example 1 (variables):
10
25
35
1
250
Example 2 (constants):
100
512
100
512
200
1
Example 3 (bitfields) — 0xABCD = 43981:
43981
205
171
12
Explanation of Example 3:
0xABCD= 43981 in decimal- Low byte (
0xCD) = 205 - High byte (
0xAB) = 171 - Nibble at bits 4–7 (
0xC= 12)
Key Concepts
- All values are fullwords — BLISS has no int, float, or string types. Every variable holds a machine word. The programmer decides what that word represents.
- Variable names are addresses — Writing
xin an expression gives you the address ofx. Writing.xgives you the value stored at that address. Forgetting the dot is one of the most common BLISS bugs. - Three storage classes:
LOCAL(stack, exists per routine call),OWN(module-level static), andGLOBAL(program-wide, linker-visible). LITERALis a compile-time constant — It substitutes a value at compile time and allocates no storage. This is the BLISS equivalent of#definein C orconstin other languages.INITIALsets a starting value forOWNvariables. Without it,OWNvariables are zero-initialized.- Bit field extraction with
<position, size>makes working with hardware registers and packed data natural, without any type casting. This is a first-class language feature, not a library. - No type checking — The compiler trusts you. Treating an address as an integer or an integer as a boolean is entirely valid BLISS. This is power and responsibility combined.
%X'...'for hexadecimal literals — BLISS uses this notation instead of C’s0xprefix. Binary (%B) and octal (%O) literals follow the same pattern.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/bliss:latest
Comments
Loading comments...
Leave a Comment