Functions
Hybrix functions are like reusable recipes. They allow you to perform an action multiple times. LOOP also provides a way to do something multiple times, but loop actions always happen one after another, and always in the same way. FUNC is much more flexible: You can do the action from any part of your program. And you can use parameters to change how the code behaves.
Math functions
Functions also appear in mathematics, but the concept is a bit different: Math functions represent relationships between inputs and outputs, whereas computer functions represent a step-by-step procedure that produces the output. Hybrix's functions are kept very simple to help you see how a
FUNCgets converted into Chombit CPU instructions. Other programming languages like TypeScript or Haskell offer more sophisticated functions with math-like qualities. These language features enable powerful abstractions, but if misused they can lead to inefficient or awkward programs. The reason is that mathematics focuses on the relationship between inputs and outputs, downplaying the calculation steps. In fact, math generally considers two functions to be equivalent if their outputs are the same, which is not true at all for computer programs. The calculation details directly determine the speed, memory usage, and maintainability of your code—the actual measures of success or failure in software engineering.
Here's a Hybrix function that calculates the maximum of two numbers. The maximum of 1 and 5 is 5, the maximum of 7 and -3 is 7, and so forth.
MODULE MAIN
# DEFINE A FUNCTION CALLED "GET_MAX":
FUNC GET_MAX(X: INT, Y: INT): INT
# IF X IS BIGGER, THEN X IS THE RESULT
IF X > Y
DO RETURN X
# OTHERWISE Y IS THE RESULT
RETURN Y
END FUNC
FUNC START()
VAR N: INT
# CALL THE FUNCTION GET_MAX() AND PUT ITS RETURN VALUE
# INTO THE VARIABLE "N":
MAIN::GET_MAX(1, 5) -> N
# (N IS NOW 5)
END FUNC
END MODULE
Important points about functions:
- Functions are always defined using
FUNC. - Hybrix functions always have a name like
GET_MAX. The name is made of letters, numbers, and underscore (_). The name must not start with a number. - Functions must be defined inside
MODULEorCLASS. - Other code can call a function (
MAIN::GET_MAX(1, 5)) - Hybrix programs always have a function
MAIN::START(). The system calls this function automatically to start your program. IfMAIN::START()returns, then your program is finished. - Functions can have parameters such as
XandYin the example above. - The caller's arguments
(1, 5)become the values of the parameters (1goes intoX,5goes intoY). - Function parameters can specify a data type. For example,
X: INTmeans thatXwill be a 4-byte integer (INT). - If the parameter type is omitted, then the compiler will assume that it is
INT. For example, we could have writtenFUNC GET_MAX(X, Y): INTinstead ofFUNC GET_MAX(X: INT, Y: INT): INT. - Functions can use
RETURNto produce a return value. - The return type must go after the parentheses. It specifies the type of the return value.
- If the return type is omitted, then the function does not return a value. Such functions cannot be used in expressions. For example, if we wrote
FUNC GET_MAX(X: INT, Y: INT), thenRETURN Xwould not be allowed. We could callMAIN::GET_MAX(1, 5)but we would not be allowed to do-> N.
Module functions
When referring to functions that belong to a module, you must always use :: to indicate the module name. In the above example, the module name is MAIN and the function name is GET_MAX, therefore we must write MAIN::GET_MAX. The same rule applies to variables defined inside a module. In this way, the same function name can appear in different modules (for example CONSOLE::THINK(), SOUND::THINK(), ENGINE::THINK()).
Here's another example, showing how to use GET_MAX() to compute the maximum of 3 numbers. GET_MAX3() works by computing the maximum of X and Y, then computing the maximum of that result and Z.
MODULE MAIN
FUNC GET_MAX(X: INT, Y: INT): INT
IF X > Y
DO RETURN X
RETURN Y
END FUNC
FUNC GET_MAX3(X: INT, Y: INT, Z: INT): INT
VAR RESULT: INT
MAIN::GET_MAX(MAIN::GET_MAX(X,Y), Z) -> RESULT
RETURN RESULT
END FUNC
FUNC START()
VAR N: INT
MAIN::GET_MAX3(7,11,4) -> N
# SHOW THE RESULT ON THE SCREEN: 11
CONSOLE::INIT()
CONSOLE::PRINT_INT(N)
END FUNC
END MODULE
Class functions
When referring to functions that belong to a class, you must always use . to indicate the object. In the example below, the object is the variable named P, so we write P.GREET():
CLASS PERSON
VAR NAME: STRING
FUNC GREET(COLOR: BOOL)
IF COLOR THEN
CONSOLE::PRINT("{6}")
ELSE
CONSOLE::PRINT("{1}")
END IF
CONSOLE::PRINT("HELLO, ")
CONSOLE::PRINT(.NAME)
CONSOLE::PRINT("!{N}")
END FUNC
END CLASS
MODULE MAIN
FUNC START()
VAR P: PERSON
CONSOLE::INIT()
NEW PERSON() -> P
"CAMERON" -> P.NAME
# PRINTS "HELLO, CAMERON!" WITH COLOR
PERSON.GREET(TRUE)
"TAYLOR" -> P.NAME
# PRINTS "HELLO, TAYLOR!" WITHOUT COLOR
P.GREET(FALSE)
END FUNC
END MODULE
Classes also support two other special kinds of member functions:
-
Virtual functions use the
HOOKkeyword instead ofFUNC, but otherwise look like regular functions. They are used with class inheritance. -
Class constructors use
NEWin place of the function name (FUNC NEW()). They are used to initialize newly created class instances.
Assembly functions
If you look at the system library code, you will find some FUNC definitions whose bodies say CHOMBIT or INTRINSIC.
CHOMBIT
Here is an example of a CHOMBIT function:
MODULE KERNEL
. . .
FUNC MEMCPY(TARGET: INT, SOURCE: INT, NUM_BYTES: INT)
CHOMBIT
END FUNC
. . .
END MODULE
CHOMBIT means that the function has been written using hand-coded assembly language, not the Hybrix language. The implementation of the KERNEL::MEMCPY() function is shown below (slightly simplified):
# -----------------------------------------------------------------------------
@KERNEL.KERNEL.MEMCPY:
PUSH FP
MOVE FP, SP
ADD SP, 16
# I:-20 ARG_TARGET
# I:-16 ARG_SOURCE
# I:-12 ARG_NUM_BYTES
# I:-8 RETURN IP
# I:-4 FP
# I:4 TARGET
# I:8 SOURCE
# I:12 SOURCE_END
MOVE I:4, I:-20
MOVE I:8, I:-16
MOVE I:12, I:8
ADD I:12, I:-12
# AVOID OVERSHOOTING THE END
ADD I:12, -15
# COPY BLOCKS OF 16 BYTES USING AN UNROLLED LOOP
@KERNEL.KERNEL.MEMCPY.L_0:
COMPARE I:8, I:12
IF NOT LESS
JUMP @KERNEL.KERNEL.MEMCPY.L_1
LOAD I:0, [I:8]
ADD I:8, 4
STORE [I:4], I:0
ADD I:4, 4
LOAD I:0, [I:8]
ADD I:8, 4
STORE [I:4], I:0
ADD I:4, 4
LOAD I:0, [I:8]
ADD I:8, 4
STORE [I:4], I:0
ADD I:4, 4
LOAD I:0, [I:8]
ADD I:8, 4
STORE [I:4], I:0
ADD I:4, 4
JUMP @KERNEL.KERNEL.MEMCPY.L_0
@KERNEL.KERNEL.MEMCPY.L_1:
# UNDO THE ADJUSTMENT
ADD I:12, 15
# COPY THE REMAINDER
@KERNEL.KERNEL.MEMCPY.L_2:
COMPARE I:8, I:12
IF NOT LESS
JUMP @KERNEL.KERNEL.MEMCPY.L_3
LOAD B:0, [I:8]
ADD I:8, 1
STORE [I:4], B:0
ADD I:4, 1
JUMP @KERNEL.KERNEL.MEMCPY.L_2
@KERNEL.KERNEL.MEMCPY.L_3:
ADD SP, -16
POP FP
POP IP
FILL 4
Hand-coded functions are often much more efficient than what the compiler produces. KERNEL::MEMCPY() is implemented this way because it is an important system function that needs to run as fast as possible.
You cannot write your own CHOMBIT functions yet, but in the future, Hybrix will support this.
INTRINSIC
Here is an example of an INTRINSIC function:
MODULE MATH
. . .
FUNC ABS(X: INT): INT
INTRINSIC
END FUNC
. . .
END MODULE
The MATH::ABS() function calculates the absolute value of a number, which converts negative numbers to positive numbers. For example, MATH::ABS(-6) is 6, MATH::ABS(1) is 1, and so forth.
INTRINSIC means that this function's implementation is generated directly by the Hybrix compiler. For example, when you write MATH::ABS(-3) --> X, the compiler generates assembly code that puts 3 directly into X. It does not even emit a function call, nor does it emit instructions to negate -3. For very small functions, this is a big savings.
You cannot implement your own INTRINSIC functions. They are an internal feature of the Hybrix compiler.