Skip to main content

DATA blocks

Suppose we are making a trivia game, and we define a class QUIZ_CARD representing a question. Initializing an array of questions can be tedious:

CLASS QUIZ_CARD
VAR QUESTION: STRING
VAR ANSWERS: STRING[]
VAR POINTS: INT
END CLASS

MODULE MAIN
VAR CARDS: QUIZ_CARD[]

FUNC START()
VAR CARD: QUIZ_CARD, ANSWERS: STRING[]

NEW QUIZ_CARD[](2) -> MAIN::CARDS
MAIN::CARDS.RESIZE(2)

# INITIALIZE CARDS[0]
NEW QUIZ_CARD() -> CARD
"WHAT IS THE CAPITAL OF HAWAII?" -> CARD.QUESTION

NEW STRING[](3) -> ANSWERS
ANSWERS.RESIZE(3)
"TOPEKA" -> ANSWERS[0]
"HONOLULU" -> ANSWERS[1]
"DES MOINES" -> ANSWERS[2]

ANSWERS -> CARD.ANSWERS
5 -> CARD.POINTS
CARD -> MAIN::CARDS[0]

# INITIALIZE CARDS[1]
NEW QUIZ_CARD() -> CARD
"HOW MANY FEET IN A MILE?" -> CARD.QUESTION

NEW STRING[](3) -> ANSWERS
ANSWERS.RESIZE(3)
"5,280" -> ANSWERS[0]
"1,796" -> ANSWERS[1]
"2,545" -> ANSWERS[2]

ANSWERS -> CARD.ANSWERS
10 -> CARD.POINTS
CARD -> MAIN::CARDS[1]
END FUNC
END MODULE

Hybrix DATA blocks provide a very concise way to initialize data structures. The program below produces the same MAIN::CARDS data structures as the above example:

CLASS QUIZ_CARD
VAR QUESTION: STRING
VAR ANSWERS: STRING[]
VAR POINTS: INT
END CLASS

MODULE MAIN
VAR CARDS: QUIZ_CARD[]

FUNC START()
END FUNC
END MODULE

# INITIALIZE THE "CARDS" VARIABLE OF THE "MAIN" MODULE
DATA MAIN::CARDS
[
{
QUESTION: "WHAT IS THE CAPITAL OF HAWAII?",
ANSWERS: [ "TOPEKA", "HONOLULU", "DES MOINES"],
POINTS: 5
},
{
QUESTION: "HOW MANY FEET IN A MILE?",
ANSWERS: [ "5,280", "1,796", "2,545" ],
POINTS: 10
}
]
END DATA

Well, there is one important difference: Objects initialized with DATA go in ROM (read-only memory), whereas objects created with NEW go in RAM. Putting data in ROM memory has a few advantages: First, the Hybrix virtual machine provides lots of ROM memory, whereas RAM memory is limited. Second, the memory manager does not have to process ROM memory, so DATA objects won't affect the speed of garbage collections. Third, the class constructor function is not called at all, which otherwise would be expensive for big arrays.

Of course, ROM memory has a downside that it cannot be modified. The code below will fail:

MODULE MAIN
VAR CARDS: QUIZ_CARD[]

FUNC START()
# ⚠️ CHOMBIT CPU FAULT: CANNOT WRITE TO READ-ONLY ADDRESS C0_252A
6 -> MAIN::CARDS[0].POINTS
END FUNC
END MODULE

Important points:

  • Data blocks always start with DATA scope :: name and end with END DATA.
  • The scope must be a module name (not a class name), and name must be a variable name.
  • Inside the data block, arrays are initialized using [ element , element , ... ].
  • Inside the data block, objects (class instances) are initialized using className { variable : value , ... }. The className can usually be omitted, but see Polymorphic DATA objects below.
  • You aren't required to specify every variable of the class; omitted variables will get initialized to zero bytes (NULL, FALSE, etc).
  • The value initializer must match the type of variable.
  • The value can be another array, object, or primitive types such as strings ("HELLO"), integers (123 or $1234_ABCD), booleans (TRUE), NULL.
  • The value can also use a CLASS_ID operator, for example CLASS_ID(QUIZ_CARD).
  • The value can also be a function pointer, for example (NEW QUIZ_CARD).

Data labels

Class types often include variables that point back to other objects, forming data structures such as a doubly-linked list or directed graph. You can initialize such structures with DATA as well, by making use of data labels.

Consider this village map, where each place can contain pointers to neighboring places:

CLASS PLACE
VAR NAME: STRING

VAR NORTH: PLACE
VAR SOUTH: PLACE
VAR EAST: PLACE
VAR WEST: PLACE
END CLASS

MODULE MAIN
VAR PLACES: PLACE[]

FUNC START()
END FUNC
END MODULE

# VILLAGE MAP:
#
# HOME -- FOREST -- RIVER
# |
# FARM
#
DATA MAIN::PLACES
[
{
NAME: "HOME",
EAST: @PLACE_FOREST,
SOUTH: @PLACE_FARM
} AT @PLACE_HOME,
{
NAME: "FOREST",
WEST: @PLACE_HOME,
EAST: @PLACE_RIVER
} AT @PLACE_FOREST,
{
NAME: "FARM",
NORTH: @PLACE_HOME
} AT @PLACE_FARM,
{
NAME: "RIVER",
WEST: @PLACE_HOME
} AT @PLACE_RIVER,
]
END DATA

Important points:

  • You can use AT to add label after any array initializer or object initializer, for example:

    [1, 2, 3] AT @MY_ARRAY
    { NAME: "FARM" } AT @MY_OBJECT
  • The data label identifier always starts with @ and may contain letters, numbers, and underscores.

  • The label can then be used in place of an initializer, for example: EAST: @PLACE_FOREST instead of EAST: { NAME: "FOREST" }.

  • Data label identifiers are global, which means you can also reference them from other DATA blocks.

The Hybrix designer makes heavy usage of DATA and data labels to represent your artwork. For example, ART::TILEMAPS has the array of tilemaps:

MODULE ART
. . .
VAR TILEMAPS: TILEMAP[]
. . .
END MODULE

...whereas ID_TILEMAP will define a VAR for each tilemap:

MODULE ID_TILEMAP
. . .
VAR CITY: TILEMAP
VAR MOONBASE: TILEMAP
. . .
END MODULE

This is accomplished using data labels, something like this:

DATA ART::TILEMAPS
[
{ . . . } AS @ID_TILESET_CITY,
{ . . . } AS @ID_TILESET_MOONBASE
]
END DATA

DATA ID_TILEMAP::CITY
@ID_TILESET_CITY
END DATA

DATA ID_TILEMAP::MOONBASE
@ID_TILESET_MOONBASE
END DATA

Polymorphic DATA objects

Suppose we have "polymorphic" child classes that can be distinguished using the AS operator:

CLASS SHAPE EXTENDS CLASS_ID_BASE
VAR X: INT
VAR Y: INT
END CLASS

CLASS CIRCLE EXTENDS SHAPE
VAR RADIUS: INT
END CLASS

CLASS RECTANGLE EXTENDS SHAPE
VAR WIDTH: INT
VAR HEIGHT: INT
END CLASS

MODULE PICTURE
VAR SHAPES: SHAPE[]
END MODULE

Here the className becomes important, since if it is omitted, then the compiler will expect an actual SHAPE instead of its subclasses:

DATA PICTURE::SHAPES
[
CIRCLE { X: 10, Y: 10, RADIUS: 5 },
RECTANGLE { X: 20, Y: 20, WIDTH: 5, HEIGHT: 10 },
CIRCLE { X: 30, Y: 30, RADIUS: 5 },

# IF WE OMIT THE TYPE NAME, WE GET THE "SHAPE" BASE CLASS:
{ X: 40, Y: 40 }
]
END DATA