Inheritance
Object-oriented programming (OOP) allows a class A to extend from another class B. We say that A is a subclass or child class, and B is the base class or parent class. The subclass inherits all of the members of its base class, as if every member of B is now also a member of A. Inheritance is a powerful technique to avoid copy+pasting lots of code. Example:
CLASS SHAPE
VAR X: INT
VAR Y: INT
END CLASS
CLASS RECTANGLE EXTENDS SHAPE
VAR WIDTH: INT
VAR HEIGHT: INT
FUNC GET_AREA(): INT
VAR AREA: INT
.WIDTH * .HEIGHT -> AREA
RETURN AREA
END FUNC
END CLASS
CLASS CIRCLE EXTENDS SHAPE
VAR RADIUS: INT
END CLASS
# THIS CLASS EXTENDS "CIRCLE" WHICH EXTENDS "SHAPE". IT INHERITS
# ALL THEIR MEMBERS: X, Y, RADIUS
CLASS LABELED_CIRCLE EXTENDS CIRCLE
VAR LABEL: STRING
END CLASS
In the above example, SQUARE and CIRCLE both extend from SHAPE. This means they both have X and Y member variables. This also enables us to write abstract functions that can work on either rectangles or circles:
MODULE ANIMATION
# THIS FUNC CAN BE CALLED FOR SQUARES AND CIRCLES
FUNC MOVE_RIGHT(S: SHAPE)
SHAPE.X + 1 -> SHAPE.X
END FUNC
END MODULE
Virtual functions
You can use HOOK to introduce virtual functions, another well-known feature from object-oriented programming. The basic idea is that a child class can have a function with the same name as the base class's function. In this case, the functions are introduced using HOOK instead of FUNC:
CLASS SHAPE
VAR X: INT
VAR Y: INT
# "NEW HOOK" INTRODUCES A VIRTUAL FUNCTION
NEW HOOK GET_NAME(): STRING
VAR NAME: STRING
"SHAPE" -> NAME
RETURN NAME
END HOOK
END CLASS
CLASS RECTANGLE EXTENDS SHAPE
VAR WIDTH: INT
VAR HEIGHT: INT
FUNC GET_AREA(): INT
VAR AREA: INT
.WIDTH * .HEIGHT -> AREA
RETURN AREA
END FUNC
# "HOOK" OVERRIDES SHAPE'S GET_NAME()
HOOK GET_NAME(): STRING
VAR NAME: STRING
"RECTANGLE" -> NAME
RETURN NAME
END HOOK
END CLASS
CLASS CIRCLE EXTENDS SHAPE
VAR RADIUS: INT
# "HOOK" OVERRIDES SHAPE'S GET_NAME()
HOOK GET_NAME(): STRING
VAR NAME: STRING
"CIRCLE" -> NAME
RETURN NAME
END HOOK
END CLASS
MODULE MAIN
FUNC START()
VAR S: SHAPE
CONSOLE::INIT()
NEW CIRCLE() -> S
CONSOLE::PRINT(S.GET_NAME()) # PRINTS "CIRCLE"
NEW RECTANGLE() -> S
CONSOLE::PRINT(S.GET_NAME()) # PRINTS "RECTANGLE"
END FUNC
END MODULE
Even though VAR S: SHAPE does not know whether it is pointing to a circle or rectangle, calling S.GET_NAME() will magically find the right function based on the type of the object. This magic relies on a special vtable pointer that the compiler adds to each object whose type is SHAPE.
The Hybrix game engine's ACTOR class uses HOOK for its THINK() member, allowing different kinds of thinking for each subclass of ACTOR.
Important points:
HOOKfunctions have the same syntax as regularFUNCfunctions.- A child class's
FUNCmust NOT have the same name as aFUNCfrom a base class. - A child class's
HOOKMUST have the same name as a correspondingHOOKfrom the base class. - To introduce a new hook (not present in any base class), use
NEW HOOKinstead ofHOOK.
Calling the base class
A child class's HOOK completely replaces the base class's implementation. In the above example, SHAPE::GET_NAME() was never called, because S.GET_NAME() was redirected to RECTANGLE::GET_NAME() or CIRCLE::GET_NAME(). However, sometimes it is useful to do both behaviors. You can use BASE to invoke the HOOK block of the base class. For example:
CLASS CAT EXTENDS ACTOR
HOOK THINK()
# EVERY VIDEO FRAME, THE CAT MOVES TO THE RIGHT BY ONE PIXEL
.X + 1 -> .X
END HOOK
END CLASS
CLASS FALLING_CAT EXTENDS CAT
HOOK THINK()
# THE FALLING_CAT MOVES DOWN BY ONE PIXEL EVERY VIDEO FRAME...
.Y + 1 -> .Y
# ...AND IT ALSO MOVES TO THE RIGHT, BY CALLING THE "CAT" CLASS'S THINK()
BASE.THINK()
END HOOK
END CLASS
BASE is also useful with class constructors (even though they are not virtual functions).
CLASS SHAPE
VAR X: INT
VAR Y: INT
FUNC NEW(X: INT, Y: INT)
X -> .X
Y -> .Y
END FUNC
END CLASS
CLASS RECTANGLE EXTENDS SHAPE
VAR WIDTH: INT
VAR HEIGHT: INT
FUNC NEW(X: INT, Y: INT, WIDTH: INT, HEIGHT: INT)
BASE.NEW(X, Y)
WIDTH -> .WIDTH
HEIGHT -> .HEIGHT
END FUNC
FUNC GET_AREA(): INT
VAR AREA: INT
.WIDTH * .HEIGHT -> AREA
RETURN AREA
END FUNC
END CLASS
Important points:
- If there is a base class, the constructor
FUNCmust callBASE.NEW(). - When the constructor calls
BASE.NEW(), it must be the very first statement of theFUNC. - These rules do not apply to normal
FUNCorHOOKfunctions; they can useBASEto call any inheritedHOOK. It does not need to be the first statement. BASEmay not be used in any other kind of expression. For example, unlikeSELF, theBASEvalue cannot be assigned to a variable, and it cannot be used to access member variables.