C headers are full of enum definitions. To the C compiler, these are just integers. To the programmer, they have semantic meaning. When binding these functions to Perl, passing magic numbers (like 0, 1, 2) makes your code unreadable. Affix solves this by allowing you to define Enums that generate Perl constants and return dualvars.
The Recipe
Let's wrap a hypothetical state machine library.
use v5.40;
use Affix qw[:all];
use Affix::Build;
# 1. Compile C
my $c = Affix::Build->new();
$c->add( \<<~'END', lang => 'c' );
typedef enum {
STATE_IDLE = 0,
STATE_RUNNING,
STATE_PAUSED,
STATE_ERROR = 99,
// Bitmasks
FLAG_READ = 1 << 0, // 1
FLAG_WRITE = 1 << 1, // 2
FLAG_RDWR = FLAG_READ | FLAG_WRITE // 3
} MachineState;
int set_state(MachineState s) {
return s; // Returns the new state
}
END
my $lib = $c->link;
# 2. Define the Enum
# We can use:
# 'NAME' -> Auto-increments
# [ NAME => Value ] -> Explicit value
# [ NAME => 'Expr'] -> C-style expression strings
typedef State => Enum [
[ STATE_IDLE => 0 ], # 0
'STATE_RUNNING', # Auto-set to 1
'STATE_PAUSED', # Auto-set to 2
[ STATE_ERROR => 99 ], # Manually set to 99
[ FLAG_READ => '1 << 0' ], # Calculated value is 1
[ FLAG_WRITE => '1 << 1' ], # Calculated value is 2
[ FLAG_RDWR => 'FLAG_READ | FLAG_WRITE' ] # Calculated value is 3
];
# 3. Bind
# We use the typedef alias '@State'
affix $lib, 'set_state', [ State() ] => State();
# 4. Use Constants (Input)
# typedef installs these constants into your package.
set_state( STATE_RUNNING() );
set_state( FLAG_RDWR() );
# 5. Use Dualvars (Output)
# The return value behaves like a number AND a string.
my $current = set_state( STATE_PAUSED() );
if ( $current == 2 ) {
say 'Numeric check passed.';
}
if ( $current eq 'STATE_PAUSED' ) {
say 'String check passed.';
}
# Debugging is free
say sprintf 'Current State: %s (%d)', $current, $current; # Prints "Current State: STATE_PAUSED (2)"
How It Works
-
1. Constants Generation
When you call
typedef ... Enum [...], Affix calculates the integer value of every element in the list. When you pass that totypedef, Affix installs a constant subroutine (e.g.,sub STATE_IDLE () { 0 }) into your current package. This allows you to use the names directly in your Perl code. -
2. Expression parsing
Affix includes a small expression parser (see shunting yard algorithm). This allows you to copy and paste definitions from C headers like bit shifts (
1 << 4) or combinations (FLAG_A | FLAG_B) directly into your Affix definition without manually calculating the result. -
3. Dualvars
When a C function returns a value defined as an
Enumtype, Affix looks up the integer in the definition map. If found, it returns a Scalar with both slots filled:- IV (Integer Value): 2
- PV (String Value): "STATE_PAUSED"
This allows you to write readable code (
eq 'STATE_...') while maintaining high performance for numeric comparisons.
Kitchen Reminders
-
Unknown values
If the C library returns an integer that you didn't define in your Enum list (e.g., a new error code added in a library update), Affix simply returns the integer.
-
Scope
The constants are installed into the package where
typedefis called. If you put your Affix setup in a utility module (e.g.,Acme::LibSample), callers will need to import those constants or refer to them fully qualified (Acme::LibSample::STATE_IDLE).