Beginner

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:

DeclarationScopeLifetimeEquivalent in C
OWNModule (file)Program lifetimestatic at file scope
LOCALRoutine (function)Stack frameLocal variable
GLOBALEntire programProgram lifetimeextern / 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>

/* blissc uppercases all BLISS identifiers in the object file */
void SHOW_VARIABLES(void);

void PRINT_INT(long value) {
    printf("%ld\n", value);
}

int main(void) {
    SHOW_VARIABLES();
    return 0;
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>

void SHOW_CONSTANTS(void);

void PRINT_INT(long value) {
    printf("%ld\n", value);
}

int main(void) {
    SHOW_CONSTANTS();
    return 0;
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>

void SHOW_BITFIELDS(void);

void PRINT_INT(long value) {
    printf("%ld\n", value);
}

int main(void) {
    SHOW_BITFIELDS();
    return 0;
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the image if you haven't already
docker pull codearchaeology/bliss:latest

# Run Example 1: OWN and LOCAL variables
docker run --rm -v $(pwd):/app -w /app codearchaeology/bliss:latest \
  sh -c 'blissc -o variables.o variables.bli && gcc -o variables variables_wrapper.c variables.o && ./variables'

# Run Example 2: constants and LITERAL
docker run --rm -v $(pwd):/app -w /app codearchaeology/bliss:latest \
  sh -c 'blissc -o constants.o constants.bli && gcc -o constants constants_wrapper.c constants.o && ./constants'

# Run Example 3: bit field extraction
docker run --rm -v $(pwd):/app -w /app codearchaeology/bliss:latest \
  sh -c 'blissc -o bitfields.o bitfields.bli && gcc -o bitfields bitfields_wrapper.c bitfields.o && ./bitfields'

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 x in an expression gives you the address of x. Writing .x gives 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), and GLOBAL (program-wide, linker-visible).
  • LITERAL is a compile-time constant — It substitutes a value at compile time and allocates no storage. This is the BLISS equivalent of #define in C or const in other languages.
  • INITIAL sets a starting value for OWN variables. Without it, OWN variables 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’s 0x prefix. Binary (%B) and octal (%O) literals follow the same pattern.

Running Today

All examples can be run using Docker:

docker pull codearchaeology/bliss:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining