program caseTest; #include( "stdlib.hhf" ); const _maxCases_ := 256; type _caseRecord_: record value:uns32; label:uns32; endrecord; // _SortCases_ // // This routine does a bubble sort on an array // of _caseRecord_ objects. It sorts in ascending // order using the "value" field as the key. // // This is a good old fashioned bubble sort which // turns out to be very efficient because: // // (1) The list of cases is usually quite small, and // (2) The data is usually already sorted (or mostly sorted). macro _SortCases_( ary, size ):i, bnd, didswap, temp; ?bnd := size - 1; ?didswap := true; #while( didswap ) ?didswap := false; ?i := 0; #while( i < bnd ) #if( ary[i].value > ary[i+1].value ) ?temp := ary[i]; ?ary[i] := ary[i+1]; ?ary[i+1] := temp; ?didswap := true; #endif ?i := i + 1; #endwhile ?bnd := bnd - 1; #endwhile; endmacro; // HLA Macro to implement a C/C++ Switch Statement. // Note that the switch parameter must be a register. macro switch( reg ): _minval_, _maxval_, _otherwise_, _endcase_, _jmptbl_, _cases_, _caseIndex_, _doCase_, _hasOtherwise_; // Verify that we have a register operand. #if( !@isreg( reg ) ) #error( "Switch operand must be a register" ) #endif // Create the _cases_ array. Allow, at most, 256 cases. ?_cases_:_caseRecord_[ _maxCases_ ]; // General initialization for processing cases. ?_caseIndex_ := 0; // Index into _cases_ array. ?_minval_ := $FFFF_FFFF; // Minimum case value. ?_maxval_ := 0; // Maximum case value. ?_hasOtherwise_ := false; // Determines if DEFAULT section present. // We need to process the cases to collect information like // _minval_ prior to emitting the indirect jump. So move the // indirect jump to the bottom of the case statement. jmp _doCase_; // "case" keyword macro handles each of the cases in the // case statement. keyword case( _constant_ ); // If we have at least one case already, terminate // the previous case by transfering control to the // first statement after the endcase macro. Note // that the semantics here differs from C/C++ // (C/C++ falls through to the next case). #if( _caseIndex_ <> 0 ) jmp _endcase_; #endif // Update minimum and maximum values based on the // current case value. #if( _constant_ < _minval_ ) ?_minval_ := _constant_; #endif #if( _constant_ > _maxval_ ) ?_maxval_ := _constant_; #endif // Emit a unique label to the source code for this case: @text( @string:_cases_ + "_" + string( _caseIndex_ )): // Save away the case label and the case value so we // can build the jump table later on. ?_cases_[ _caseIndex_ ].value := _constant_; ?_cases_[ _caseIndex_ ].label := _caseIndex_; // Bump _caseIndex_ value because we've just processed // another case. ?_caseIndex_ := _caseIndex_ + 1; #if( _caseIndex_ >= _maxCases_ ) #error( "Too many cases in statement" ); #endif // Handle the default keyword/macro here. keyword default; // If there was not a preceding case, this is an error. // If so, emit a jmp instruction to skip over the // default case. #if( _caseIndex_ < 1 ) #error( "Must have at least one case" ); #endif jmp _endcase_; // Emit the label for this default case and set the // _hasOtherwise_ flag to true. _otherwise_: ?_hasOtherwise_ := true; // The endswitch terminator/macro checks to see if // this is a reasonable switch statement and emits // the jump table code if it is. terminator endswitch:_i_, _j_, _curCase_, _adrsList_; #if( (_maxval_ - _minval_) > 256 ) #if( (_maxval_ - _minval_) > 1024 ) // Perhaps in the future, this macro could // switch to generating an if..elseif..elseif... // chain if the range between the values is // too great. #error( "Range of cases is too great" ); #else #print( "Warning: Range of cases is large" ); #endif #endif // Table emission algorithm requires that the _cases_ // array be sorted by the case values. _SortCases_( _cases_, _caseIndex_ ); // Build a string of the form: // // _jmptbl_:dword[ xx ] := [&case1, &case2, &case3...&casen]; // // so we can output the jump table. static _jmptbl_:dword[ _maxval_ - _minval_ + 1] := [ ?_i_ := 0; #while( _i_ < _caseIndex_ ) ?_curCase_ := _cases_[ _i_ ].value; // Emit the label associated with the current case: @text ( "&" + @string:_cases_ + "_" + string( _cases_[ _i_ ].label ) + "," ) // Emit "&_otherwise_" table entries for any gaps present // in the table: ?_j_ := _cases_[ _i_ + 1 ].value; ?_curCase_ := _curCase_ + 1; #while( _curCase_ < _j_ ) &_otherwise_, ?_curCase_ := _curCase_ + 1; #endwhile ?_i_ := _i_ + 1; #endwhile // Emit a dummy entry to terminate the table: &_otherwise_]; endstatic; #if( _caseIndex_ < 1 ) #error( "Must have at least one case" ); #endif // After the default case, or after the last // case entry, jump over the code that does // the conditional jump. jmp _endcase_; // Okay, here's the code that does the conditional jump. _doCase_: #if( _minval_ <> 0 ) cmp( reg, _minval_ ); jb _otherwise_; #endif cmp( reg, _maxval_ ); ja _otherwise_; jmp( _jmptbl_[ reg*4 - _minval_*4 ] ); // If there was no default case, transfer control // to the first statement after the "endcase" clause. #if( !_hasOtherwise_ ) _otherwise_: #endif // When each of the cases complete execution, // transfer control down here. _endcase_: // The following statement deallocates the storage // assocated with the _cases_ array. ?_cases_ := 0; endmacro; begin caseTest; // Simple demo program that demonstrates // the switch/case/default/endcase macro: mov( 0, ebx ); while( ebx < 10 ) do switch( ebx ) case( 8 ) stdout.put( "case 8" nl ); case( 2 ) stdout.put( "case 2" nl ); case( 3 ) stdout.put( "case 3" nl ); case( 5 ) stdout.put( "case 5" nl ); case( 1 ) stdout.put( "case 1" nl ); default stdout.put( "default, ebx=", (type uns32 ebx), nl ); endswitch; inc( ebx ); endwhile; end caseTest;