HLA Reference Manual

1 HLA Overview 1

1.1.1 What is a "High Level Assembler"? 1

1.1.2 What is an "Assembler" 4

1.1.3 Is HLA a True Assembly Language? 4

1.1.4 HLA Design Goals 5

1.1.5 How to Learn Assembly Programming Using HLA 7

1.1.6 Legal Notice 7

1.1.7 Teaching Assembly Language using HLA 8

2 The Quick Guide to HLA 25

2.2.1 Overview 25

2.2.2 Running HLA 25

2.2.3 HLA Language Elements 26

2.2.3.1 Comments 26

2.2.3.2 Special Symbols 26

2.2.3.3 Reserved Words 27

2.2.3.4 External Symbols and Assembler Reserved Words 27

2.2.3.5 HLA Identifiers 27

2.2.3.6 External Identifiers 27

2.2.4 Data Types in HLA 27

2.2.4.1 Native (Primitive) Data Types in HLA 27

2.2.4.2 Composite Data Types 28

2.2.4.3 Array Data Types 28

2.2.4.4 Record Data Types 28

2.2.5 Literal Constants 29

2.2.5.1 Numeric Constants 29

2.2.5.1.1 Decimal Constants 29

2.2.5.1.2 Hexadecimal Constants 29

2.2.5.1.3 Binary Constants 29

2.2.5.1.4 Real (Floating Point) Constants 29

2.2.5.1.5 Boolean Constants 29

2.2.5.1.6 Character Constants 29

2.2.5.1.7 String Constants 30

2.2.5.1.8 Pointer Constants 30

2.2.5.1.9 Structured Constants 30

2.2.6 Constant Expressions in HLA 30

2.2.7 Program Structure 31

2.2.8 Procedure Declarations 31

2.2.8.1 Declarations 32

2.2.8.2 Type Section 32

2.2.8.3 Const Section 33

2.2.8.4 Static Section 33

2.2.8.4.1 The @NOSTORAGE Option 33

2.2.8.4.2 The EXTERNAL Option 33

2.2.8.5 Macros 34

2.2.9 The #Include Directive 35

2.2.10 The Conditional Compilation Statements (#if) 35

2.2.11 The 80x86 Instruction Set in HLA 36

2.2.11.1 Zero Operand Instructions (Null Operand Instructions) 36

2.2.11.2 General Arithmetic and Logical Instructions 36

2.2.11.3 The XCHG Instruction 37

2.2.11.4 The CMP Instruction 37

2.2.11.5 The Multiply Instructions 37

2.2.11.6 The Divide Instructions 38

2.2.11.7 Single Operand Arithmetic and Logical Instructions 38

2.2.11.8 Shift and Rotate Instructions 38

2.2.11.9 The Double Precision Shift Instructions 38

2.2.11.10 The Lea Instruction 39

2.2.11.11 The Sign and Zero Extension Instructions 39

2.2.11.12 The Push and Pop Instructions 39

2.2.11.13 Procedure Calls 39

2.2.11.14 The Ret Instruction 40

2.2.11.15 The Jmp Instructions 40

2.2.11.16 The Conditional Jump Instructions 40

2.2.11.17 The Conditional Set Instructions 40

2.2.11.18 The Input and Output Instructions 41

2.2.11.19 The Interrupt Instruction 41

2.2.11.20 Bound Instruction 41

2.2.11.21 The Enter Instruction 41

2.2.11.22 CMPXCHG Instruction 41

2.2.11.23 The XADD Instruction 42

2.2.11.24 BSF and BSR Instructions 42

2.2.11.25 The BSWAP Instruction 42

2.2.11.26 Bit Test Instructions 42

2.2.11.27 Floating Point Instructions 42

2.2.11.28 MMX and SSE Instructions 42

2.2.12 Memory Addressing Modes in HLA 42

2.2.13 Type Coercion in HLA 44

3 Installing HLA 45

3.3.1 Installing HLA Under Windows 45

3.3.1.1 New Easy Installation: 45

3.3.1.2 Manual Installation under Windows 45

3.3.1.2.1 What You’ve Just Done 46

3.3.1.2.2 Running HLA 49

3.3.1.3 Standard Configurations Under Windows 52

3.3.2 Installing HLA Under Linux, Mac OSX, or FreeBSD (*NIX) 54

3.3.2.1 Standard Configurations under Linux/FreeBSD/Mac OSX 57

3.3.3 Non-Standard Configurations under Windows and Linux 57

3.3.4 Customizing HLA 57

3.3.4.1 Changing the Location of HLA 58

3.3.4.2 Setting Auxiliary Paths 59

3.3.4.3 Setting the Default Back-End Assembler 59

4 Using HLA with the HIDE Integrated Development Environment 1

4.4.1 The HLA Integrated Development Environment (HIDE) 1

4.4.1.1 Description 1

4.4.1.2 Operation 1

4.4.1.3 First Execution 1

4.4.1.4 The Windows 1

4.4.1.4.1 Editor 2

4.4.1.4.2 Output 2

4.4.1.4.3 Tool Bar 2

4.4.1.4.4 Tab Bar 2

4.4.1.4.5 Status Bar 2

4.4.1.4.6 Panel 2

4.4.1.4.7 Project Panel 3

4.4.1.4.8 Properties 4

4.4.1.5 Compiling Simple Programs 4

4.4.1.6 Menus 4

4.4.1.6.1 Edit 4

4.4.1.6.2 View 5

4.4.1.6.3 Project 6

4.4.1.6.4 Make 6

4.4.1.6.5 Tools 7

4.4.1.6.6 Options 9

4.4.1.6.7 HIDE Settings 10

4.4.1.6.8 SetPaths 12

4.4.1.6.9 User 13

4.4.1.6.10 Help 14

4.4.1.7 HIDE Macros 14

4.4.1.8 Project Manager 14

4.4.1.9 Auto Completion 17

4.4.1.10 CommandLine Tools 18

4.4.1.10.1 kMake 18

4.4.1.11 Project File Format 18

4.4.1.12 Licences 22

4.4.1.12.1 HIDE 22

4.4.1.12.2 PellesC 23

4.4.1.12.3 HLA 23

4.4.2 The RadASM/HLA Integrated Development Environment 24

4.4.2.1 Integrated Development Environments 24

4.4.2.2 HLA Project Organization 24

4.4.2.3 Using Makefiles 25

4.4.2.4 Installing RadASM 31

4.4.2.5 Running RadASM 31

4.4.2.6 The RadASM Project Management Window 32

4.4.2.7 Compiling and Executing an Existing RadASM Project 38

4.4.2.8 Creating a New Project in RadASM 41

4.4.2.9 Working With RadASM Projects 48

4.4.2.10 Build Options with RadASM/HLA 50

4.4.2.11 Editing HLA Source Files Within RadASM 55

4.4.2.12 Managing Complex Projects with RadASM 59

4.4.2.13 Project Maintenance with Batch Files 60

4.4.2.14 Project Maintenance with Make Files 61

4.4.2.15 RadASM Menus 64

4.4.2.15.1 The RadASM File Menu 64

4.4.2.15.2 Edit Menu Items 67

4.4.2.15.3 The View Menu 67

4.4.2.15.4 Format Menu 68

4.4.2.15.5 The Project Menu 68

4.4.2.15.6 Make Menu 72

4.4.2.15.7 The Tools Menu 72

4.4.2.15.8 The Window Menu 72

4.4.2.15.9 The Option Menu 72

4.4.2.16 Customizing RadASM 74

4.4.2.16.1 The RADASM.INI Initialization File 74

4.4.2.16.2 The HLA.INI Initialization File 77

5 HLA Internal Operation 84

6 Using the HLA Command-Line Compiler 86

7 HLA v2.x Language Reference Manual 93

7.7.1 HLA Language Elements 93

7.7.2 Comments 93

7.7.3 Special Symbols 93

7.7.4 Reserved Words 93

7.7.5 External Symbols and Assembler Reserved Words 100

7.7.6 HLA Identifiers 100

7.7.7 External Identifiers 100

7.7.8 HLA Literal Constants 101

8 HLA Data Types 102

8.8.1 Data Types in HLA 102

8.8.2 Native (Primitive) Data Types in HLA 102

8.8.2.1 Enumerated Data Types 103

8.8.2.2 HLA Type Compatibility 104

8.8.3 Composite Data Types 105

8.8.4 Array Data Types 105

8.8.5 Union Data Types 105

8.8.6 Record Data Types 106

8.8.7 Pointer Types 111

8.8.8 Thunks 112

8.8.9 Class Types 114

8.8.10 Regular Expression Types 114

9 HLA Literal Constants and Constant Expressions 115

9.9.1 HLA Literal Constants 115

9.9.1.1 Numeric Constants 115

9.9.1.1.1 Decimal Constants 115

9.9.1.1.2 Hexadecimal Constants 115

9.9.1.1.3 Binary Constants 116

9.9.1.1.4 Numeric Set Constants 116

9.9.1.1.5 Real (Floating-Point) Constants 116

9.9.1.2 Boolean Constants 117

9.9.1.3 Character Constants 117

9.9.1.4 Unicode Character Constants 117

9.9.1.5 String Constants 117

9.9.1.6 Unicode String Constants 117

9.9.1.7 Character Set Constants 118

9.9.2 Structured Constants 118

9.9.2.1 Array Constants 118

9.9.2.2 Record Constants 119

9.9.2.3 Union Constants 120

9.9.2.4 Pointer Constants 123

9.9.2.5 Regular Expression Constants 123

9.9.3 Constant Expressions in HLA 124

9.9.3.1 Type Checking and Type Promotion 124

9.9.3.2 Type Coercion in HLA 125

9.9.3.3 !expr 126

9.9.3.4 - expr (unary negation operator) 127

9.9.3.5 expr1 * expr2 128

9.9.3.6 expr1 div expr2 129

9.9.3.7 expr1 mod expr2 129

9.9.3.8 expr1 / expr2 129

9.9.3.9 expr1 << expr2 130

9.9.3.10 expr1 >> expr2 130

9.9.3.11 expr1 + expr2 130

9.9.3.12 expr1 - expr2 130

9.9.3.13 Comparisons (=, ==, <>, !=, <, <=, >, and >=) 131

9.9.3.14 expr1 & expr2 131

9.9.3.15 expr1 in expr2 131

9.9.3.16 expr1 | expr2 131

9.9.3.17 expr1 ^ expr2 131

9.9.3.18 ( expr ) 132

9.9.3.19 [ comma_separated_list_of_expressions ] 132

9.9.3.20 record_type_name : [ comma separated list of field expressions ] 132

9.9.3.21 identifier 132

9.9.3.22 identifier1.identifier2 {...} 132

9.9.3.23 identifier [ index_list ] 133

10 HLA Program Structure and Organization 134

10.10.1 HLA Program Structure 134

10.10.2 The HLA Declaration Section 135

10.10.2.1 The HLA LABEL Declaration Section 135

10.10.2.2 The HLA CONST Declaration Section 142

10.10.2.3 The HLA VAL Declaration Section and the Compile-Time "?" Statement 146

10.10.2.4 The HLA TYPE Declaration Section 150

10.10.2.4.1 typeID 151

10.10.2.4.2 newTypeID : typeID; 152

10.10.2.4.3 newTypeID : typeID [ list_of_array_bounds ]; 152

10.10.2.4.4 newTypeID : procedure (<<optional_parameter_list>>); 153

10.10.2.4.5 newTypeID : record <<record_field_declarations>> endrecord; 153

10.10.2.4.6 newTypeID : union <<union_field_declarations>> endunion; 153

10.10.2.4.7 newTypeID : class <<class_field_declarations>> endclass; 153

10.10.2.4.8 newTypeID : pointer to typeID; 153

10.10.2.4.9 newTypeID : enum{ <<list_of_enumeration_identifiers>> }; 153

10.10.2.5 The HLA VAR Declaration Section 153

10.10.2.6 The HLA STATIC Declaration Section 160

10.10.2.7 The HLA STORAGE Declaration Section 164

10.10.2.8 The HLA READONLY Declaration Section 165

10.10.2.9 The HLA PROC Declaration Section 167

10.10.2.10 THE HLA NAMESPACE Declaration Section 167

11 HLA Procedure Declarations and Procedure Calls 171

11.11.1 Procedure Declarations 171

11.11.1.1 Original Style Procedure Declarations 171

11.11.1.2 "New Style" Procedure Declarations 175

11.11.2 Overloaded Procedure/Iterator/Method Declarations 177

11.11.3 The _vars_ and _parms_ Constants and the _display_ Array 182

11.11.4 External Procedure Declarations 183

11.11.5 Forward Procedure Declarations 184

11.11.6 Setting Default Procedure Options 185

11.11.7 Disabling HLA’s Automatic Code Generation for Procedures 186

11.11.8 Procedure Calls and Parameters in HLA 191

11.11.9 Calling HLA Procedures 192

11.11.10 Parameter Passing in HLA, Value Parameters 193

11.11.10.1 Passing Byte-Sized Parameters by Value 194

11.11.10.2 Passing Word-Sized Parameters by Value 198

11.11.10.3 Passing Double-Word-Sized Parameters by Value 200

11.11.10.4 Passing Quad-Word-Sized Parameters by Value 200

11.11.10.5 Passing Tbyte-Sized Parameters by Value 201

11.11.10.6 Passing Lword-Sized Parameters by Value 201

11.11.10.7 Passing Large Parameters by Value 202

11.11.11 Parameter Passing in HLA, Reference, Value/Result, and Result Parameters 203

11.11.12 Untyped Reference Parameters 207

11.11.13 Pass by Value/Result and Pass by Result Parameters 208

11.11.14 Parameter Passing in HLA, Name and Lazy Evaluation Parameters 213

11.11.15 Hybrid Parameter Passing in HLA 215

11.11.16 Parameter Passing in HLA, Register Parameters 216

11.11.17 Instruction Composition and Parameter Passing in HLA 216

11.11.18 Lexical Scope 218

12 HLA Classes and Object-Oriented Programming 222

12.12.1 Class Data Types 222

12.12.2 Classes, Objects, and Object-Oriented Programming in HLA 222

12.12.3 The THIS and SUPER Reserved Words 223

12.12.4 Class Procedure and Method Prototypes 225

12.12.5 Inheritance 228

12.12.6 Abstract Methods 232

12.12.7 Classes versus Objects 232

12.12.8 Initializing the Virtual Method Table Pointer 233

12.12.9 Creating the Virtual Method Table 234

12.12.10 Calling Methods and Class Procedures 234

12.12.11 Non-object Calls of Class Procedures 236

12.12.12 Static Class Fields 237

12.12.13 Taking the Address of Class Procedures, Iterators, and Methods 239

12.12.14 Program Unit Initializers and Finalizers 240

13 The HLA Compile-Time Language 245

13.13.1 HLA Compile-Time Language, Macros, and Pragmas 245

13.13.2 Viewing the Output of the HLA Compile-Time Language 245

13.13.3 #linker Directive 246

13.13.4 The #Include Directive 246

13.13.5 The #IncludeOnce Directive 247

13.13.6 Macros 248

13.13.6.1 Standard Macros 248

13.13.6.2 Where You Declare a Macro Affects its Visibility 251

13.13.6.3 Multi-part (Context Free) Macro Invocations: 252

13.13.6.4 Macro Invocations and Macro Parameters: 256

13.13.6.5 Processing Macro Parameters 257

13.13.7 Built-in Functions: 259

13.13.8 Constant Type Conversion Functions 260

13.13.8.1 Bitwise Type Transfer Functions 261

13.13.8.2 General functions 261

13.13.8.3 String functions: 265

13.13.8.4 String/Pattern matching functions 266

13.13.8.5 Symbol and constant related functions and assembler control functions 272

13.13.8.6 Pseudo-Variables 277

13.13.8.7 Text emission functions 280

13.13.8.8 Miscellaneous Functions 280

13.13.9 #Text and #endtext Text Collection Directives 281

13.13.10 #String and #endstring Text Collection Directives 281

13.13.11 Regular Expression Macros and the @match/@match2 Functions 281

13.13.11.1 #regex..#endregex 283

13.13.11.2 The #return Clause 283

13.13.11.3 Regular Expression Elements 284

13.13.11.4 Kleene Star, Plus, and Numeric Range Specifications 284

13.13.11.5 Matching Characters in a Regular Expression 285

13.13.11.6 Case-insensitive Character Matching in a Regular Expression 286

13.13.11.7 Negated Character Matching 286

13.13.11.8 String Matching in Regular Expressions 286

13.13.11.9 Case-insenstive String Matching in Regular Expressions 287

13.13.11.10 Negated String Matching 287

13.13.11.11 String List Matching 288

13.13.11.12 Character Set Matching in a Regular Expression 288

13.13.11.13 Negated Character Set Matching 289

13.13.11.14 Matching Arbitrary Characters 289

13.13.11.15 Sequences (Concatenation) - The ‘,’ Operator 289

13.13.11.16 Alternation - The "|" Operator 289

13.13.11.17 Subexpressions - The "()" operator 290

13.13.11.18 Extracting Substrings - The Extraction Operator "<>:" 291

13.13.11.19 Invoking Other #regex Macros in a Regular Expression 291

13.13.11.20 Lookahead (peeking) 292

13.13.11.21 Utility Matching Functions 292

13.13.11.22 Backtracking 294

13.13.11.23 Lazy Versus Greedy Evaluation 295

13.13.11.24 The @match and @match2 Functions 296

13.13.11.25 Compiling and Precompiling Regular Expressions 297

13.13.11.26 The #match..#endmatch Block 298

13.13.11.27 Using Regular Expressions in Your Assembly Programs 299

13.13.12 The #asm..#endasm and #emit Directives 299

13.13.13 The #system Directive 300

13.13.14 The #print and #error Directives 301

13.13.15 Compile-Time File Output (#openwrite, #append, #write, #closewrite) 301

13.13.16 Compile-time File Input (#openread, @read, #closeread) 302

13.13.17 The Conditional Compilation Statements (#if) 302

13.13.18 The Compile-Time Loop Statements (#while and #for) 303

13.13.19 Compile-Time Functions (macros) 305

13.13.20 Sample Macro: A Modified IF..ELSE..ENDIF Statement 306

13.13.21 Text Processing, Lexical Analysis and the #text..#endtext Block 309

14 HLA Language Reference and User Manual 321

14.14.1 High Level Language Statements 321

14.14.2 Exception Handling in HLA:try..exception..endtry 321

14.14.3 Exception Handling in HLA:try..always..endtry 326

14.14.4 Exception Handling in HLA:raise 327

14.14.5 IF..THEN..ELSEIF..ELSE..ENDIF Statement in HLA 328

14.14.6 Boolean Expressions for High-Level Language Statements 329

14.14.7 WHILE..WELSE..ENDWHILE Statement in HLA 333

14.14.8 REPEAT..UNTIL Statement in HLA 334

14.14.9 The FOR..ENDFOR Statement in HLA 334

14.14.10 The FOREVER..ENDFOR Statement in HLA 336

14.14.11 The BREAK and BREAKIF Statements in HLA 336

14.14.12 The CONTINUE and CONTINUEIF Statements in HLA 336

14.14.13 The BEGIN..END, EXIT, and EXITIF Statements in HLA 337

14.14.14 The SWITCH/CASE/DEFAULT/ENDSWITCH Statement in HLA 339

14.14.15 The JT and JF Medium Level Instructions in HLA 341

14.14.16 Iterators and the HLA Foreach Loop 342

15 HLA Units and External Compilation 345

15.15.1 HLA Units and External Compilation 345

15.15.2 External Declarations 345

15.15.3 HLA Naming Conventions and Other Languages 347

15.15.4 HLA Calling Conventions and Other Languages 348

15.15.5 Calling Procedures Written in a Different Language 349

15.15.6 Calling HLA Procedures From Another Language 349

15.15.7 Linking in Code Written in Other Languages 349

15.15.8 Calling HLA Code From Other Languages 349

15.15.9 Exercising Complete Control with HLA 356

15.15.9.1 Overhead Present in an HLA Program 357

15.15.9.1.1 The "empty" Program 357

15.15.9.2 The empty Program, Part II 362

15.15.9.3 Overhead Associated With Exceptions 364

15.15.9.4 Overhead Associated with Procedures, Iterators, and Methods 371

15.15.9.5 Overhead Associated with Procedure Calls 379

15.15.9.6 Bloat in the HLA Standard Library 384

15.15.9.7 Taking Control with HLA Units 384

15.15.9.8 Hello World, Revisited 387

16 The HLA Memory Model and Memory Addressing Modes 390

16.16.1 The HLA Memory Model 390

16.16.2 Memory Addressing Modes in HLA 390

16.16.3 Type Coercion in HLA 394

17 HLA v2.x Language Reference Manual 397

17.17.1 The 80x86 Instruction Set in HLA 397

17.17.2 Zero Operand Instructions (Null Operand Instructions) 398

17.17.3 General Arithmetic and Logical Instructions 402

17.17.4 The XCHG Instruction 403

17.17.5 The CMP Instruction 404

17.17.6 The Multiply Instructions 404

17.17.7 The Divide Instructions 406

17.17.8 Single Operand Arithmetic and Logical Instructions 408

17.17.9 Shift and Rotate Instructions 409

17.17.10 The Double Precision Shift Instructions 409

17.17.11 The Lea Instruction 410

17.17.12 The Sign and Zero Extension Instructions 411

17.17.13 The Push and Pop Instructions 411

17.17.14 Procedure Calls 412

17.17.15 The Ret Instruction 414

17.17.16 The Jmp Instructions 414

17.17.17 The Conditional Jump Instructions 415

17.17.18 The Conditional Set Instructions 415

17.17.19 The Conditional Move Instructions 415

17.17.20 The Input and Output Instructions 416

17.17.21 The Interrupt Instruction 416

17.17.22 Bound Instruction 416

17.17.23 The Enter Instruction 417

17.17.24 CMPXCHG Instruction 417

17.17.25 CMPXCHG8B Instruction 418

17.17.26 The XADD Instruction 418

17.17.27 BSF and BSR Instructions 419

17.17.28 The BSWAP Instruction 419

17.17.29 Bit Test Instructions 419

17.17.30 Floating Point Instructions 420

17.17.31 Additional Floating-Point Instructions for Pentium Pro and Later Processors 423

17.17.32 MMX Instructions 423

17.17.33 SSE Instructions 425

17.17.34 OS/Priviledged Mode Instructions 429

17.17.35 Other Instructions and features 431

18 Advanced HLA Programming 433

18.18.1 Writing a DLL in HLA 433

18.18.1.1 Creating a Dynamic Link Library 433

18.18.1.2 Linking and Calling Procedures in a Dynamic Link Library 436

18.18.1.3 Going Farther 437

18.18.2 Compiling HLA 438

18.18.3 Code Generation for HLA HLL Control Structures 440

18.18.3.1 The HLA Standard Library 440

18.18.3.2 Compiling to MASM Code -- The Final Word 441

18.18.3.3 The HLA if..then..endif Statement, Part I 446

18.18.3.4 Boolean Expressions in HLA Control Structures 447

18.18.3.5 The JT/JF Pseudo-Instructions 453

18.18.3.6 The HLA if..then..elseif..else..endif Statement, Part II 453

18.18.3.7 The While Statement 457

18.18.3.8 repeat..until 459

18.18.3.9 for..endfor 459

18.18.3.10 forever..endfor 459

18.18.3.11 break, breakif 459

18.18.3.12 continue, continueif 460

18.18.3.13 begin..end, exit, exitif 460

18.18.3.14 foreach..endfor 460

18.18.3.15 try..unprotect..exception..anyexception..endtry, raise 460

18.18.4 A Modified IF..ELSE..ENDIF Statement 461

18.18.5 Object Oriented Programming in Assembly 468

18.18.5.1 Hoopla and Hyperbole 468

18.18.5.2 Some Basic Definitions 468

18.18.5.3 OOP Language Facilities 469

18.18.5.4 Classes in HLA 469

18.18.5.5 Objects 471

18.18.5.6 Inheritance 473

18.18.5.7 Overriding 473

18.18.5.8 Virtual Methods vs. Static Procedures 474

18.18.5.9 Writing Class Methods, Iterators, and Procedures 476

18.18.5.10 Object Implementation 479

18.18.5.10.1 Virtual Method Tables 482

18.18.5.10.2 Object Representation with Inheritance 484

18.18.5.11 Constructors and Object Initialization 487

18.18.5.12 Dynamic Object Allocation Within the Constructor 488

18.18.6 Compiling Resource Scripts Using HLA 491

18.18.6.1 The Motivation 491

18.18.6.2 The HLA Solution 491

18.18.6.3 The Resource..Endresource Declaration Section 492

18.18.7 Structures in Assembly Language Programs 493

18.18.7.1 What is a Record (Structure)? 493

18.18.7.2 Record Constants 494

18.18.7.3 Arrays of Records 495

18.18.7.4 Arrays and Records as Record Fields 495

18.18.7.5 Controlling Field Offsets Within a Record 496

18.18.7.6 Aligning Fields Within a Record 497

18.18.7.7 Using Records/Structures in an Assembly Language Program 499

18.18.7.8 Implementing Structures in an Assembler 500

HLA Overview

HLA, the High Level Assembler, is a vast improvement over traditional assembly languages. With HLA, programmers can learn assembly language faster than ever before and they can write assembly code faster than ever before. John Levine, comp.compilers moderator, makes the case for HLA when describing the PL/360 machine specific language:

1999/07/11 19:36:51, the moderator wrote:

"There's no reason that assemblers have to have awful syntax. About 30 years ago I used Niklaus Wirth's PL360, which was basically a S/360 assembler with Algol syntax and a a little syntactic sugar like while loops that turned into the obvious branches. It really was an assembler, e.g., you had to write out your expressions with explicit assignments of values to registers, but it was nice. Wirth used it to write Algol W, a small fast Algol subset, which was a predecessor to Pascal. ... -John"

PL/360, and variants that followed like PL/M, PL/M-86, and PL/68K, were true "mid-level languages" that let you work down at the machine level while using more modern control structures (i.e., those loosely based on the PL/I language). Although many refer to "C" as a "medium-level language", C truly is high level when compared with languages like PL/*. The PL/* languages were very popular with those who needed the power of assembly language in the early days of the microcomputer revolution. While it’s stretching the point to say that PL/M is "really an assembler," the basic idea is sound. There really is no reason that assemblers have to have an awful syntax.

HLA bridges the gap between very low level languages and very high level languages. Unlike the PL/* languages, HLA really is an assembly language. You can do just about anything with HLA that you can do with a traditional assembler like MASM, TASM, NASM, or Gas. If you want to write low-level assembly code using x86 machine instructions, HLA does not get in your way; if you want to use compares and conditional branches rather than structured control statements, you can. On the other hand, if you prefer to use more readable high-level control structures, HLA allows this, as well. HLA lets you work at the level you are most comfortable with and at the level that is most appropriate for the task at hand.

Beyond supplying a "non-awful" syntax, HLA has one other important feature -- it’s extensible. HLA provides special features that let you add new statements to the language. So if HLA is not "high level" (or "low level") enough for your tastes, you can extend it. This document will expend considerable effort describing exactly how to do this in a later section.

In addition to the HLA language itself, the HLA system provides one other very important component - the HLA Standard Library. This is a collection of hundreds of functions that you can use to write assembly language programs as quickly and easily as you would write C programs.

What is a "High Level Assembler"?

The name "High Level Assembler" and its abbreviation "HLA" is certainly not new1. Nor is the concept of a high level assembler. David Salomon in his 1992 text "Assemblers and Loaders" (Ellis Horwood, ISBN 0-13-052564-2) uses these terms to describe various assembly languages dating back to 1966. Furthermore, both IBM and Motorola have assembler products with very similar names (e.g., IBM’s HLAsm, though it’s somewhat debatable whether HLAsm is truly a high level assembler).

Salomon offers the following definitions for a High Level Assembler (or HLA):

A high-level assembler language (HLA) is a programming language where each instruction is translated into a few machine instructions. The translator is somewhat more complex than an assembler, but much simpler than a compiler. Such a language should not have features like the if, for, and case control structures, complex arithmetic, logical expressions, and multi-dimensional arrays. It should consist of simple instructions, closely resembling traditional assembler instructions, and of a few simple data types.

Since Salomon describes a couple of high level assemblers that exceed this definition, he offers a second definition for high level assemblers that is a bit higher-level:

A high-level assembler language (HLA) is a language that combines most of the features of higher-level languages (easy to use control structures, variables, scope, data types, block structure) with one important feature of assembler languages namely, machine dependence.

Neither definition is particularly useful for describing HLA/86 and other HLAs like Terse, MASM and TASM. Of course the term "High Level Assembler" is very nebulous and offers a fair amount of latitude. Almost any macro assembler could pass as an HLA on the basis that a macro-instruction expands into a few machine instructions.

David Salomon describes several different high level assemblers in his text. The examples he describes are PL/360, NEAT/3, PL516, and BABBAGE.

PL/360 and PL516 are products that conform to the second definition above. They allow simple arithmetic expressions and assignment statements, the use of high level control structures (if, for, while, etc.), high level data declarations, and block structure (among other things). These languages expose the underlying machine’s registers and allow the use of machine instructions using a "functional" syntax.

The NEAT/3 language is a much lower-level language; basically it is an assembly language for the NCR Century computers that provide COBOL-style data declarations. Most of its "instructions" translate one-for-one into Century machine instructions, though it does automatically insert code to convert data types from one format two another if the data types of an instruction’s operands are incompatible.

The BABBAGE assembly language is an expression-based assembly language (very similar to Terse). It allows simplified high level control structures like if and while. The interesting thing about this assembler is that it was the only assembler for the GEC4000 family of computers.

In addition to the HLAs that Salomon describes, there have been several other high level assemblers created over the years. PL/M and PL/M-86 was designed by Intel for their 8080 and 8086 CPU families. This was an obvious adaptation of the PL/360 style HLA for Intel’s CPUs. PL/68 was also available for the Motorola 680x0 family. SL/65 was a similar adaptation of PL/360 for the 6502 family. At one point there was a product named "High Level Assembler" for the Atari ST system (68K based). Jim Neil has also created an expression-based high level assembler (similar in principle to Babbage) for Intel’s x86 family. MASM and TASM (for the x86) also fall into the category of a high level assembler due to their inclusion of high level control structures and logical expressions.

So where does HLA/86 fit into these definitions? In truth, the definition of HLA/86 falls somewhere between these two definitions. So the following paragraphs will define the term "High Level Assembler" as it should apply to HLA/86 and similar high level assemblers.

The first definition above is overly restrictive. It implies that any language that exceeds these limits is a high level language, not a high level assembly or traditional assembly language. Obviously, this definition is too restrictive in the sense that by this definition many traditional assemblers would have to be considered as high level languages (even beyond a high level assembler). Furthermore, it elevates many traditional assemblers to the status of an HLA even though we wouldn’t normally think of them as high level assemblers; i.e., most macro assemblers provide the ability to create instructions that translate into a few machine instructions. Macro facilities, however, are something we expect out of a modern assembly language; their presence doesn’t make the language a "high level" assembly language in most people’s mind. Furthermore, most modern assemblers provide a mechanism for declaring multi-dimensional arrays (even though you still have to use some sequence of instructions to index into said arrays).

The second definition David Salomon provides hits the other extreme. Arguably, languages like C could be called HLAs under this definition (yes, there are some machine dependent features in C, though probably not enough to satisfy David Salomon’s original intent).

The definition of high level assemblers like Terse, MASM, TASM, and HLA/86 fall somewhere between these extremes. Therefore, this document will define a high level assembler as follows:

A "high level assembly language" (HLAL) is a language that provides a set of statements or instructions that practically map one-to-one to machine instructions of the underlying architecture. The HLAL exposes the underlying machine architecture including access to machine registers, flags, memory, I/O, and addressing modes. Any operation that is possible with a traditional assembler should be possible within the HLAL. In addition to providing access to the underlying architecture, the HLAL must provide some abstractions that are not normally found in traditional assemblers and that are typically found in traditional high level languages; this could include structured control statements (e.g., if, for, and while), high level data types and data structuring facilities, extensive compile-time language facilities, run-time expression evaluation, and standard library support. A "High Level Assembler" is a translator that converts a high level assembly language to machine code.

There is a very important difference between this definition and the ones that David Salomon provides. Specifically, a high-level assembly language must provide access to the underlying machine architecture. Within the HLAL you must be able to specify any (reasonable) machine instruction that is available on the CPU. The HLAL may provide other statements that do not directly map to machine instructions (e.g., an if statement), but it must, at least, provide a set of statements that practically map one-to-one with the machine instructions. The "practically" modifier appears here for two reasons. First of all, some assembly source statements may map to two or more different, but equivalent, machine instructions. A good example is the x86 "mov reg, reg" which can map to two different (though equivalent) opcodes depending on the setting of the direction bit in the opcode. Most assemblers will map the source statement to only one of these opcodes, hence there is not truly a one-to-one mapping (since there exist some opcodes that do not map back to some source instruction). Another allowable restriction is that the HLAL may limit the programmer to a subset of the complete machine instruction set if it makes sense to do so (e.g., many modern x86 assemblers do not support 16-bit mode on the 80x86).

In addition to supporting the underlying machine architecture (which almost any traditional assembler will do), the HLAL must also provide support for some features normally found in a high level language. The definition does not require that a HLAL support all the features listed above, nor is it restricted to just the features listed, but a HLAL must support some of the features traditionally found in a high level language. The number and type of features the HLAL supports determines how "high level" the assembly language is. Like HLLs, we can have "low-level" HLALs, "medium-level" HLALs, "high-level" HLALs, and even "very high-level" HLALs. NEAT/3, for example, would be a low-level HLAL since it provides higher-level data types, conversions, and not much else.

MASM and TASM are probably best considered medium-to-high-level HLALs since they provide high level data structuring facilities, structured control statements, high level procedure definitions and invocations, a limited block structure, powerful compile-time language (macro) facilities, standard library support (e.g., the UCR Standard Library and many other available library modules), and other high level language features. In actual use, the programmer is expected to normally use standard machine instructions and rise up to the high level statements only as necessary.

The Terse language is a good example of a medium level HLAL since it uses an expression syntax but otherwise maps statements fairly closely to the assembly counterparts. It does provide some higher-level data structuring capabilities, though this is inherited from the underlying assembler(s) on which Terse is based.

PL/360 and PL516 are definitely high-level HLALs because they fully support simplified arithmetic expressions, control structures, high-level data types, and other features. These languages provide access to the underlying architecture, but the emphasis is to use these languages as a high level language and drop down to the machine instructions only as necessary.

HLA/86 probably falls in the high-level-to-very-high-level range because it provides high level data types and data structuring abilities, high level and very high level control structures, extensive parameter passing facilities (more than most high level languages), a very extensive compile time language, a very extensive standard library, built-in parsing facilities for language extension, and many other features. Generally, HLA/86 has a larger feature set than the other HLALs described above. There are a few design goals that limit the "high-levelness" of HLA/86:

(1) With one exception, HLA never emits any code behind the programmer’s back that modifies registers or flags (the one exception is object method invocation, and this is well documented), and

(2) HLA doesn’t support arithmetic expressions (it does support a limited form of logical/boolean expressions).

One interesting aspect of HLA/86 is that it is extensible. Using features built into the language, you can extend HLA/86’s syntax by adding new statements and other features. This feature gives you the ability to make HLA/86 as high level as you desire (though it may take some effort to achieve certain language features). The bottom line is this: in some ways, HLA/86 is lower level than languages like PL/360 and PL516; in other ways, it’s higher level than these HLALs. However, as the definition requires, almost anything you can do with a traditional assembler is possible in HLA/86.

What is an "Assembler"

Because high-level assemblers are clearly different that traditional assemblers, one might question whether a high level assembly language is truly an assembly language and whether translators for high-level assembly languages can be properly called an assembler. Unfortunately, there is a considerable range of opinions as to exactly what constitutes an "assembler" versus other translators. This document will not attempt to get involved in this debate. Instead, this section provides a set of definitions that are useful for describing assemblers at various levels of abstraction.

Pure Assembler:

A "pure assembler" is a program that processes an assembly language source file and translates the source code using a direct mapping from source code instructions to individual machine instructions (each source instruction is mapped to exactly one machine instruction). The assembler only provides machine-primitive data types like bytes, words, double words, etc. A pure assembler does not provide macro facilities. A pure assembler always produces machine code as output.

Traditional Assembler:

A "traditional assembler" is a pure assembler plus macro facilities. The assembler may provide some "built-in macros" and instruction synonyms, but in general, the built-in statements should still map to individual machine instructions (note that the programmer may extend this by writing macros). There is no support by the assembler for run-time arithmetic or boolean expressions. A traditional assembler may also provide some simple data typing facilities (such as the ability to rename primitive data types as something else, e.g., byte->char). A traditional assembler always emits machine code as output.

High Level Assembler:

A high-level assembler is a macro assembler plus some additional high-level language-like facilities, such as high-level control constructs or high-level-like procedure calls. If a programmer elects to ignore these additional facilities, they still have all the capabilities of a macro assembler at their disposal.

Is HLA a True Assembly Language?

Some people are confused by HLA. On the one hand, it looks like a High Level Language, employing syntax similar to Pascal and C/C++. On the other hand, it does support the machine instructions found in a typical assembly language. Many people accuse HLA of being a compiler rather than an assembler. What’s the truth?

The truth is, assembly languages have evolved, just as high-level languages have evolved, and we can no longer use a definition for an assembler that made sense in the 1950s when describing modern assemblers such as MASM, TASM, and HLA. Today, the best definition we can use is that an assembler is a compiler for an assembly language. An assembler accepts a source file written in some sort of assembly language and produces an object file as its output.

The real question, then, is not whether HLA is an assembler, but whether the HLA language is an assembly language. Some people argue that any compiler that includes any sort of statement that compiles into more than one machine instruction cannot be called an "assembler." However, such an argument immediately eliminates macro assemblers. Eliminating macro assemblers is unsatisfactory because almost every modern assembler provides, at the very least, some simple macro facilities. Whether you implement an "IF" statement with a macro (generally supplied by the assembler’s author, as is the case, for example, with FASM) that you have to include into your source file, or via a ‘macro’ that the assembler’s author has provided as part of the assembler is really a matter of implementation. To the end user of the assembler, the "IF" statement is just as much a part of the language that they can use regardless of the implementation. The fact that assemblers such as MASM, TASM, and HLA provide these high-level-like control structures in assembly language does not imply that the languages these products implement are not assembly languages.

Some people argue that "high-level assemblers" such as MASM, TASM, and HLA are not assemblers any more than C/C++ compilers could be considered assemblers if those C/C++ compilers support an in-line assembly capability. However, their arguments strengthen the case for calling a product like HLA an "assembler." After all, if we’re going to continue to call C/C++ a high-level language even though it provides support for machine instructions, then there is no reason we cannot call a product like MASM, TASM, or HLA "assemblers" even though they provide a modicum of support for high-level-like control structures. Ultimately, language's focus determines its type. C/C++’s focus is on writing high-level language programs, with a few machine instructions thrown in now and then when the high-level language doesn’t quite handle everything. High-level assemblers, such as HLA, MASM, and TASM are focused on writing assembly language modules. They have some high-level control structures thrown in to simplify some tasks (e.g., in the case of HLA, the high-level control structures exists as a bridge between HLLs and assembly during the learning process), but the focus is mainly on writing assembly language code.

Some people feel that if you learn HLA (or some other high level assembler), then you’re not really learning "assembly language." This is utter nonsense. If you thoroughly learn HLA, you’ll know assembly language programming inside and out. Switching to a different assembler from HLA would be no different, say, than switching from Gnu’s Gas assembler to MASM (or vice versa). One might bemoan the features lost in such a translation, but when going from HLA to some other assembler you’re typically giving up features rather than gaining anything.

Still there is a pervasive argument that high-level control structures like IF/WHILE/FOR/etc. don’t belong in a true assembler. Well, HLA, MASM, and TASM users can elect to ignore these statements (as many old-time MASM programmers do; with HLA you can even disable these statements). As long as the rest of the assembler supports a language that allows one to write "pure" assembly language code, why would anyone question the validity of the title "assembly language" for the code? (Unless, of course, they have an ax to grind.) For those who are diametrically opposed to allowing any language that contains IF/WHILE/FOR/etc. statements to be called assembly language, well, that’s why we call these things high level assembly languages: to note the fact that they are a little more powerful than traditional assembly languages.

The bottom line is this: if you learn HLA, you will learn assembly language programming. As long as you understand how to write the low-level code (within HLA) and don’t rely exclusively on the high-level control statements in your programs, no one can truthfully question your assembly language programming knowledge.

HLA Design Goals

HLA was originally conceived as a tool to teach assembly language programming. In early 1996 I decided to do a Windows version of my electronic text "the Art of Assembly Language Programming" (AoA). After an attempt to develop a new version of the " UCR Standard Library for 80x86 Programmers" (a mainstay of AoA), I came to the conclusion that MASM just wasn’t powerful enough to make learning assembly language really easy. I decided to develop an assembler with sufficient power, providing the tools for a good standard library as well as satisfy some other requirements. Therefore, HLA has two important goals: provide a system that is powerful enough to develop code and macros to make learning assembly language, which simultaneously providing a system that is easy for beginners to learn.

The principle goal of HLA was to leverage student’s existing programming knowledge. For example, a good Pascal programmer can get their first C/C++ program operational in a few minutes. All they have to do is note the similarities between the two programming languages, make the appropriate syntactical changes, and they’re up and running. Take that same Pascal programmer and expect them to learn LISP or Prolog the same way, and you’ll not meet with the same success. LISP and Prolog are completely different, they use a different "programming paradigm," so the student has to "start over from scratch" when learning these languages. Although assembly language is an imperative language (like Pascal and C/C++), there is a considerable "paradigm shift" when moving from one of these high level languages to assembly. In HLA, I wanted to create a language with high level control structures and declarations that made it possible for someone familiar with an imperative language like Pascal or C/C++ to get their first HLA program running in a matter of minutes (or, at worst, a matter of hours). Of course, to achieve this goal, I needed to add high-level data declarations and high-level control constructs to the HLA language.

The astute reader will quickly point out that high level control structures are not assembly language and letting the students use these types of statements is not really teaching them assembly language. This is quite true; since the purpose of teaching an assembly language course is to teach the students assembly language programming it is quite clear that HLA would fail if it only provided these high level control structures (e.g., like the PL/M language does). Fortunately, this is not the case. HLA supports all standard assembly language instructions including CMP and Jcc instructions, so you can still write "pure" assembly language programs without using those high-level language control structures. However, it does take time to learn the several hundred different machine instructions. Traditionally, it’s taken my students (using only MASM) about five weeks before they could really write any meaningful programs in assembly language (you have to cover things like numeric representation, basic CPU architecture, addressing modes, data types, and introduce the instruction set before any real programs can be written).

HLA lets students write meaningful programs within about a week of its introduction (e.g., the first assignment I give in a typical quarter is to write an "addition table" program that computes the outer product [addition table] of the two vectors 0..15 and 0..15, printing the table formatted nicely). They achieve this by using statements they already know (like IF and WHILE) with the injection of just a few assembly language concepts (registers, and the MOV and ADD instructions) plus an introduction to the HLA Standard Library. Over the next several weeks, these students write increasingly complex programs as they are introduced to new assembly language and HLA concepts (e.g., data representation, basic architecture, addressing modes, data types, and additional instructions). At about the sixth week, I begin "weaning" these students off the high-level language statements and force them to use the low-level machine instructions. It turns out that they learn how to simulate an IF statement at roughly the same point in the quarter as they did when they used only MASM, but the big difference is that they’ve written a lot more code up to that point proving out other concepts in machine organization and assembly language programming. In my limited experience with classroom testing, I’ve found that students spend less time on the class, cover more material, and retain the knowledge better (by the time of the final exam) than they did when I only used MASM.

The general goal of reducing the learning curve for students is achieved several ways.

(1) As noted above, HLA allows a gradual transition from high-level languages into pure assembly language. My favorite analogy here is the Nicoderm CQ smoking cessation system ("gradual steps are better."). Like the Nicoderm system, HLA allows students learn assembly language in gradual steps rather than throwing them into the water and shouting "sink or swim!"

(2) In addition to letting the students employ high level language statements in their assembly language programs, HLA contains several other familiar concepts and syntactical items that ease the transition from high-level language programming to assembly language. For example, HLA uses the familiar (to C/C++ programmers) "/*" and "*/" comment delimiters (as well as the "//" comment delimiter). Statements generally end with a semicolon (just as in high level languages). Machine instructions use a functional notation rather than "mnemonic-operand" notation. Constant, type, and variable declarations should look very familiar to Pascal programmers. HLA’s standard library should look comfortable to anyone who has used the C/C++ standard library.

In addition to syntactical similarities, well-written HLA programs share a similar programming style with modern high-level languages. Therefore, a student who has learned how to write readable Pascal, C/C++, or Java programs will be able to write readable HLA programs with almost no additional study. Contrast this with the style guide I’ve written for (MASM) assembly language programmers that is quite a bit different than high level languages and takes a while to master.

Another factor many people don’t consider is the evaluation of a programming project. At UC Riverside instructors are given about 1.5-2 hours per student per quarter of reader (student grader) time to grade projects. Experienced readers who can grade (or want to grade) assembly language projects are few and far in-between. Most readers are "stuck" with grading the assembly class rather than volunteer for the job. The fact that most student assembly language projects have a horrible programming style and are hard to read only exacerbates this situation. HLA helps solve this problem. Since good HLA programming style is very similar to good C/C++ style, UC Riverside’s readers have a much easier time reading the projects and evaluating their programming style. In addition, since the students have (presumably) learned good programming style in the prerequisite course(s), they tend to write easier to read HLA programs than MASM programs. This lets the instructor assign more projects without fear of exceeding my reader budget each quarter.

HLA’s advantages are easily summed up by a complaint I had from a student once. She said "HLA drives me nuts. It’s so similar to C++ that I often get confused and try out something that would work in C++ only to have the HLA compiler reject it." I agreed with this student that this was a bit of a problem, but I also mentioned, "what about all the times you’ve tried something from C++ and it HAS worked?" She thought about it for a moment and walked away agreeing with my assessment of her complaint. Had this student been learning assembly the traditional way, she wouldn’t have bothered to try anything. She would had to have spent extra time learning how to achieve what she wanted by reading an assembly text or she would have missed out on the opportunity to actually learn something new. HLA’s similarity to C++ encouraged her to try something out on her own. The experiments weren’t always successful, but in those cases where they were, she benefited greatly from this. This anecdote, more than any other, sums up what my goals with HLA were and describes the success I believe I have achieved with it.

How to Learn Assembly Programming Using HLA

Of course, a compiler without a language reference manual and tutorial is useless. This document will provide a reference to the HLA programming language. It is not, however, appropriate pedagogy for beginners (it’s more suitable for those who already know assembly language programming and wish to learn HLA’s syntax). A better text for beginners is "The Art of Assembly Language Programming, Second Edition" available from No Starch Press. This provides a complete college level textbook that teaches assembly language programming from the ground up using HLA. You can also find an electronic copy of "AoA" on Webster at http://webster.cs.ucr.edu. Webster also contains the latest version of HLA as well as tons of HLA sample source code. That’s the first place you should go for information on learning HLA.

Legal Notice

The HLA v2.x implementation is a prototype intended to test language design and implementation features. I (Randall Hyde) have placed this code and language design in the public domain so others may benefit from this work. However, keep in mind that, as a prototype, HLA is not up to contemporary commercial standards for software quality. It is your responsibility to evaluate whether HLA is suitable for whatever purpose you have.

At any given time, there are several known and unknown defects in this software. Some may be corrected in later releases of HLA v2.x; some may never be corrected in the v2.x series. I (Randall Hyde) do not warrant or guarantee this software in any way. In particular, you cannot expect corrections of any given defect in the system. Obviously, I try to fix known problems (if possible), but I refuse to be held legally responsible for such defects in the software.

The purpose of developing a prototype implementation of the HLA language was to try out language design and implementation ideas. The prototype phase of HLA development is rapidly coming to an end and an "official" HLA language design will be forthcoming. HLA v3.0 will implement this new language. The only guarantees I make about compatibility between HLA v2.x and HLA v3.0 is that there will be some incompatibilities. The exact nature and magnitude of those incompatibilities is unknown at this point, but it is safe to assume that no HLA v2.x program will compile under HLA v3.0 without at least some minor source code changes. So please don’t get the idea that any investment you make in HLA source code will be protected in v3.0 (note: after the release of v3.0 this is a relatively safe assumption to make, though there will still be no guarantees).

Because HLA is constantly changing (typical of a prototype), it is very difficult to keep the documentation in phase with the language. You can expect this documentation (and all HLA documentation) to contain omissions (e.g., of new features that have yet to be documented), discussion of features removed from HLA, and incorrect descriptions of HLA features. Every attempt will be made to keep the documentation in phase with the software, but like so many free software projects, lack of time and motivation prevents perfection2.

This software is not fit for use in mission-critical or life-support software systems. This software is principally intended for evaluation and educational (i.e., learning assembly language) purposes only. It has been successfully used to develop commercial and industrial applications (including a nuclear reactor control system) and it has been successfully used in educational environments, but again, you are personally responsible for determining the fitness of this software and documentation for your particular application and you must take responsibility for that choice.

HLA’s current design makes use of other software tools that I (Randall Hyde) did not write. These tools include the Microsoft Linker, the Microsoft Librarian, the Pelles C linker, the Pelles C librarian, and the Free Software Foundations ld and as programs. It can optionally make use of programs such as MASM, FASM, TASM, and NASM. Because some of these tools are commercial products and are covered by various license agreements, not all of these tools come with the HLA distribution. For example, if you want to use the Microsoft or Borland tools, you’ll have to obtain copies of them from some other source. Note that using HLA does not require the Microsoft or Borland tools; HLA is simply compatible with these tools if you already own them and would prefer to use them. HLA does ship with all the tools you need to effectively use HLA; the use of these non-free tools is optional.

Teaching Assembly Language using HLA

I first began teaching assembly language programming at Cal Poly Pomona in the Winter Quarter of 1987. I quickly discovered that good pedagogical material was difficult to come by; even the textbooks available for the course left something to be desired. As a result, my students were learning very little assembly language in the ten weeks available to the course. After about two quarters, I decided to do something about the textbook problem, so I began writing a text I entitled "How to Program the IBM PC Using 8088 Assembly Language" (obviously, this was back in the days when schools still used PCs made by IBM and the main CPU you could always count on was the 8088). "How to Program..." became the epitome of a "work in progress." Each quarter I would get feedback from the students, update the text, and give it to Kinko's (and the UCR Printing and Reprographics Department) to run off copies for my students the very next quarter.

The original "How to Program..." text provided a basic set of library routines to print strings, input characters and lines of text, and a few other basic functions. This allowed the students to quickly begin writing programs without having to learn about the INT instruction, DOS, or BIOS. However, I discovered that students were spending a significant time each quarter writing their own numeric conversion routines, string manipulation routines, etc. One student commented on "how much easier it was to program in 'C' than assembly language since all those conversions and string operations were built into the language." I replied that the real savings were due more to the 'C' standard library than the language itself and that a comparable library for assembly language programmers would make assembly language programming almost as easy as 'C' programming. At that moment a little light when on in my head and I sat down and wrote the first few routines of what ultimately became the "UCR Standard Library for 80x86 Assembly Language Programmers" (You can still get a copy of the UCR stdlib from webster at the URL given above). As I finished each group of routines in the standard library, I incorporated them into my courses. This reaped immediate benefits as students spent less time writing numeric conversion routines and spent more time learning assembly language. My students were getting into far more advanced topics than was possible before the advent of the UCR Stdlib.

In the early 1990's, the 8088 CPU finally died off and IBM was no longer the major supplier of PCs. Not only was it time to change the title of my text, but I also needed to update references to the 8088 (that were specific to that chip) and bring the text into the world of the 80386 and 80486 processors. DOS was still King and 16-bit code was still what everyone was writing, but issues of optimization and the like were a little outdated in the text. In addition to the changes reflecting the new Intel CPUs, I also incorporated the UCR Standard Library into the text since it dramatically improved the speed at which students progressed beyond the basic assembly programming skills. I entitled the new version of the text "The Art of Assembly Language Programming," an obvious knock-off of Knuth's series ("The Art of Computer Programming").

In early 1996 it became obvious to me that DOS was finally dying and I needed to modify "The Art of Assembly Language Programming" (AoA) to use Windows as the development platform. I wasn't interested in having students write Windows GUI applications in assembly language. (The time spent teaching event-oriented programming would interfere with the teaching of basic machine organization and assembly language programming.) However, it was clear that the days of writing code that arbitrarily pokes around in memory and accesses I/O addresses directly (things that AoA taught) were over. Therefore, I decided to get started on a new version of AoA that used Windows as the basic development environment with the emphasis on writing console applications.

The UCR Standard Library was the single most important pedagogical tool I'd discovered that dramatically improved my students' progress. As I began work on a new version of AoA for Windows 3.1 my first task was to improve upon the UCR Standard Library to make it even easier to use, more flexible, more efficient, and more "high level." After six months of part time work, I eventually gave up on the UCR Stdlib v2.0. The idea was right; unfortunately, the tools at my disposal (specifically, MASM 6.11) weren't quite up to the task. I was writing some tricky macros, obviously exploiting code inside MASM that Microsoft's engineers had never run (i.e., I discovered lots of bugs). I would code in some workarounds to the defects only to have the macro package break at the next minor patch of MASM (e.g., from MASM 6.11a to MASM 6.11b).

There was also a robustness issue. Although MASM's macro capabilities are quite powerful and it almost let me do everything I wanted, it was very easy to confuse the macro package. This would cause MASM would generate some totally weird (but absolutely correct) diagnostic messages that correctly described what was going wrong in the macro but made absolutely no sense whatsoever at all. As it became clear that the UCR Stdlib v2.0 would never be robust enough for student use, I decide to take a different approach.

About this time, I was talking with my Department Chair about the assembly language course. We were identifying some of the problems that students had learning assembly language. One problem, of course, was the paradigm shift- learning to solve problems using machine language rather than a high level language. The second problem we identified is that students get to apply very little of what they've learned from other courses to the assembly language class. A third problem was the primitive tools available to assembly language programmers. Energized by this discussion, I decided to see how I could solve these problems and improve the educational process.

Problem 1, the paradigm shift, had to be handled carefully. After all, the whole purpose of having students take an assembly language programming course in the first place is to acquaint them with the low-level operation of the machine. However, I felt it was certainly possible to redefine parts of assembly language so that would be more familiar to students. For example, one might test the carry flag after an addition to determine if an unsigned overflow has occurred using code like the following:

add eax, 5

jnc NoOverflow

<< code to execute if overflow occurs >>

NoOverflow:

Although this code is straightforward, you would be surprised how many students cannot visualize this code. On the other hand, if you feed them some pseudo code like:

add eax, 5

if( the carry flag is set ) then

<< code to execute if overflow occurs >>

endif

Those same students won't have any problems understanding this code. To take advantage of this difference in perspective, I decided to explore changing the definition of assembly language to allow the use of the "if condition then do something" paradigm rather than the "if a condition is false them skip over something" paradigm. Fundamentally, this does not change the material the student has to learn; it just presents it from a different point of view to which they're already accustomed. This certainly wasn't a gigantic leap away from assembly language as it existed in 1996. After all, MASM and other assemblers were already allowing statements like ".if" and ".endif" in the code. Therefore, I tried these statements out on a few of my students. What I discovered is that the students picked up the basic "high level" syntax very rapidly. Once they mastered the high level syntax, they were able to learn the low-level syntax (i.e., using conditional jumps) faster than ever before.

The second problem, students not being able to leverage their programming skills from other classes, is largely linked to the syntax of Intel x86 assembly language. Many skills students pick up, such as programming style, indentation, appropriate programming construct selection, etc., are useless in a typical assembly language class. Even skills like commenting and choosing good variable names are slightly different in assembly language programs. As a result, students spend considerable (unproductive) time learning the new "rules of the game" when writing assembly language programs. This directly equates to less progress over the ten-week quarter. Ideally, students should be able to applying knowledge like program style, commenting style, algorithm organization, and control construct selection they learned in a C/C++ or Pascal course to their assembly language programs. If they could, they'd be "up and writing" in assembly language much faster than before.

The third problem with teaching assembly language is the primitive state of the tools. While MASM provides a wonderful set of high level language control constructs, very little else about MASM supports this "brave new world" of assembly language I want to teach. For example, MASM's variable declarations leave a lot to be desired (the syntax is straight out of the 1960's). As I noted earlier, as powerful as MASM's macro facilities are, they weren't sufficient to develop a robust library package for my students. I briefly looked at TASM, but it's "ideal" mode fared little better than MASM. Likewise, while development environments for high-level languages have been improving by leaps and bounds (e.g., Delphi and C++ Builder), assembly language programmers are still using the same crude command line tools popularized in the early 1970's. Codeview, which is practically useless under Windows, was the most advanced tool Microsoft provided specifically for assembly language programmers.

Faced with these problems, I decided the first order of business was to create a new x86 assembly language and write a compiler for it. I decided to give this language the somewhat-less-than-original name of "the High Level Assembler," or HLA (IBM and Motorola both already have assemblers that use a variant of this name). It took three years, but the first version of HLA was ready for public consumption in September of 1999.

I began using HLA in my CS 61 course (machine organization and assembly language programming) at UCR in the Fall Quarter, 1999. With no pedagogical material other than a roughly written reference guide to the language, I was expecting a complete disaster. It turns out that I was pleasantly surprised. Although the students did have major problems, the course went far more smoothly than I anticipated and we managed to cover about the same material I normally covered when using MASM.

Although things were going far better than I expected, this is not to say that things were going great, or even as smoothly as I would have liked. The major problem, of course, was the lack of a textbook. The only material the students had to study from was their lecture notes. Clearly, something needed to be done about this. Of course, the whole reason for spending three years writing HLA was to allow me to write a new version of AoA. Therefore, in November 1999 I began work on the new edition of the text. By the start of the Winter Quarter in January 2000, I had roughed together five chapters, about 50% of the material was brand new and the other 50% was cut, pasted, and updated from the older version of the text. During the quarter, I rushed out two more chapters bringing the total to seven. The Winter Quarter went far more smoothly than the Fall Quarter. Student projects were much better and the progress of the class outstripped any assembly language course I'd taught prior to that point. Clearly, the class was benefiting from the use of HLA.

By the start of the Spring Quarter in April 2000, I'd managed to make one proofreading pass over the first six chapters and I'd written the first draft of the eighth chapter. By the middle of 2002, The Art of Assembly Language was on-line and receiving rave reviews across the internet. In 2003, No Starch Press published an edited and revised edition in "treeware" form.

Well, this has been a long-winded report of HLA's justification. You're probably wondering what HLA is and whether it is applicable to you (especially if you're a programmer rather than an educator). Fair enough, the rest of this article will discuss the HLA system and how you would use it.

HLA (under Windows) is a Win32 console application and it generates Win32 applications. By default, it generates console applications although it does not restrict you to writing console applications under Windows. There is absolutely no support for DOS applications. HLA v2.0 also supports Mac OS X, Linux, and FreeBSD. Applications written in HLA that use the HLA Standard Library can run under all four operating systems with nothing more than a recompile. This allows a student, for example, to work under Windows at home and submit projects under Linux (or any of the other OSes) at school.

When designing the HLA language, I chose a syntax that is very similar to common imperative high-level languages such as Pascal/Delphi, Ada, Modula-2, FORTRAN77, C/C++, and Java. That is not to say that HLA compiles Pascal programs, but rather, a Pascal programmer will note many similarities between Pascal and HLA (and ditto for the other languages). HLA stole many of the ideas for data declarations from the Algol-based languages (Pascal, Modula-2, and Ada), it grabbed the ideas for many of its control structures from FORTRAN77, Ada, and C/C++/Java, and the structure of the HLA Standard Library is based on the C Standard Library. So regardless of which high level language you're most comfortable with in this set, you'll certainly recognize some elements of your favorite HLL in HLA.

A carefully written HLA program will look almost like a high-level language program. Consider the following sample program:

program SampleHLApgm;

#include( "stdlib.hhf" )

const

HelloWorld := "Hello World";

begin SampleHLApgm;

stdout.put( "The classical 'Hello World' program: ", HelloWorld, nl );

end SampleHLApgm;

This program does the obvious thing. Anyone with any high-level language background can probably figure out everything except the purpose of "nl" (which is the newline string imported by the standard library). This certainly doesn't look like an assembly language program; there isn't even a real machine instruction in sight. Of course, this is a trivial example; nonetheless, I've managed to write reasonable HLA programs that were just over 1,000 lines of code that contained only one or two identifiable machine language instructions. If it's possible to do this, how can I get away with calling HLA an assembly language?

The truth is, you can actually write a very similar looking program with MASM. Here's an example I trot out for unbelievers. This code is compilable with MASM (assuming you include the UCR Standard Library v2.0 and some additional code I've cut out for brevity:

var

enum colors,<red,green,blue>

colors c1, c2

endvar

Main proc

mov ax, dseg

mov ds, ax

mov es, ax

MemInit

InitExcept

EnableExcept

finit

try

cout "Enter two colors:"

cin c1, c2

cout "You entered ",c1," and ",c2,nl

.if c1 == red

cout "c1 was red"

.endif

except $Conversion

cout "Conversion error occured",nl

except $Overflow

cout "Overflow error occured",nl

endtry

CleanUpEx

ExitPgm ;DOS macro to quit program.

Main endp

As you can see, the only identifiable machine instructions here are the ones that initialize the segment registers at the beginning of the program (which is unnecessary in a Win32 environment). So allow me to blunt criticism from "die-hard" assembly fans right at the start: HLA doesn't open up all kinds of new programming paradigms that weren't possible before. With some clever macros (e.g., enum, cout, and cin in the MASM code), it is quite possible to do some amazing things. If you're wondering why you should bother with HLA if MASM is so wonderful, don't forget my comments about the robustness of these macros. Both HLA and MASM (with the UCR Standard Library v2.0) work great as long as you write perfect code and don't make any mistakes. However, if you do make mistakes, the MASM macro scheme rapidly gets ugly.

The "die-hard" assembly fan will probably observe that they would never write code like the MASM code I've presented above; they would write traditional assembly code. They want to write traditional code. They don't want this high level syntax forced upon them. Well, HLA doesn't force you to use high-level control structures rather than machine instructions. You can always write the low level code if you prefer it that way. Here is the original HLA program rewritten to use familiar machine instructions:

program SampleHLApgm2;

#include( "stdlib.hhf" )

static

dword 37, 37;

TcHWpStr: dword; @nostorage;

byte "The classical 'Hello World' program: ",0,0,0;

dword 11, 11;

HWstr: dword; @nostorage;

byte "Hello World",0;

begin SampleHLApgm2;

lea( eax, TcHWpStr );

push( eax );

call stdout.puts;

lea( eax, HWstr );

push( eax );

call stdout.puts;

call stdout.newln;

end SampleHLApgm2;

The stdout.puts and stdout.newln procedures come from the HLA Standard Library. I will leave it up to the interested reader to translate these into Win API Write calls if this code isn't sufficiently low level to satisfy. Note that HLA strings are not simple zero terminated strings like C/C++. This explains the extra zeros and dword values in the STATIC section (the dword values hold the string lengths; I offer these without further explanation, see the HLA documentation for more details on HLA's string format).

One thing you've probably noticed from this second example is that HLA uses a functional notation for assembly language statements. That is, the instruction mnemonics look like function calls in a high level language and the operands look like parameters to those functions. The neat thing about this notation is that it easily allows the use of "instruction composition." Instruction composition, like functional composition, means that you get to use one instruction as the operand of another. For example, an instruction like "mov( mov( 0, eax ), ebx );" is perfectly legal in HLA. The HLA compiler will compile the innermost instruction first and then substitute the destination operand of the innermost instruction for the operand position occupied by the instruction. HLA's MOV instruction takes the generic form "MOV( source, destination );" so the former instruction translates to the following two instruction sequence:

mov( 0, eax ); // intel syntax: mov eax, 0

mov( eax, ebx ); // intel syntax: mov ebx, eax

By and of itself, instruction composition is somewhat interesting, but programmers striving to write readable code need to exercise caution when using instruction composition. It is very easy to write some unreadable code if you abuse instruction composition. E.g., consider:

mov( add( mov( 0, eax ), sub( ebx, ecx)), edx ), mov( i, esi ));

Egads! What does this mess do? Some might consider the inclusion of instruction composition in HLA to be a fault of the language if it allows you to write such unreadable code. However, I've never felt it was the language syntax's job to enforce good programming style. If there's really a reason for writing such messy code, the compiler shouldn't prevent it.

Although you can produce some truly unreadable messes with instruction composition, if you use it properly it can enhance the readability of your programs. For example, HLA lets you associate an arbitrary string with a procedure that HLA will substitute for that procedure name when the procedure call appears as an operand of another instruction. Most functions that return a value in a register specify that register name as their "returns" string (the string HLA substitutes for the procedure call). For example, the "str.eq( str1, str2)" function compares the two string operands and returns true or false in AL depending on the result of the comparison. This allows you to write code like the following:

if( str.eq( str1, "Hello" )) then

stdout.put( "str1 = 'Hello'" nl );

endif;

HLA directly translates the IF statement into the following sequence:

str.eq( str1, "Hello" );

if( @c ) then

stdout.put( "str1= 'Hello'" nl );

endif;

Arguably, the former version is a little more readable than the latter version. Instruction composition, when you use it in this fashion, lets you write code that "looks" a little more high level without the compiler having to generate lots of extra code (as it would if HLA supported a generalized arithmetic expression parser).

Like MASM, HLA supports a wide variety of high level control structures. HLA's set is both higher level and lower level at the same time. There is a good reason HLA's control structures aren't always as powerful as MASM's. First, with the sole exception of object method invocations, I made a rule that HLA's high level control structures would not modify any general purpose registers behind the programmer's back. MASM, for example, may modify the value in EAX for certain boolean expressions or parameter values it must compute.

Although I designed HLA as a tool to teach assembly language programming, this is also a tool that I intend to use so I included many goodies for advanced assembly language programmers. For example, HLA's macro facilities are more powerful than I've seen in any programming-language-based macro processor. One unique feature of HLA's macro preprocessor is the ability to create "context free" control structures using macros. For example, suppose that you decide that you need a new type of looping construct that HLA doesn't provide; let's say, a loop that will repeat once for each character in a string supplied as a parameter to the loop. Let's call this loop "OnceForEachChar" and decide on the following syntax:

OnceForEachChar( SomeString )

<< Loop Body >>

endOnceForEachChar;

On each iteration of this loop, the AL register will contain the corresponding character from the string specified as the OnceForEachChar operand. You can easily implement this loop using the following HLA macro:

#macro OnceForEachChar( SomeString ): TopOfLoop, LoopExit;

pushd( -1 ); // index into string.

TopOfLoop:

inc( (type dword [esp] )); // Bump up index into string.

#if( @IsConst( SomeString ))

// Load address of string constant into EAX.

lea( eax, SomeString );

#else

mov( SomeString, eax ); // Get ptr to string.

#endif

add( [esp], eax ); // Point at next available character

mov( [eax], al ); // Get the next available character

cmp( al, 0 ); // See if we're at the end of the string

je LoopExit;

#terminator endOnceForEachChar;

jmp TopOfLoop; // Return to the top of the loop and repeat.

LoopExit:

add( 4, esp ); // Remove index into string from stack.

#endmacro

Anyone familiar with MASM's macro processor should be able to figure out most of this code. Note that the symbols "TopOfLoop" and "LoopExit" are local symbols to this macro. Hence, if you repeat this macro several times in the code, HLA will emit different actual labels for these symbols to the MASM output file. The "@IsConst" is an HLA compile-time function that returns true if its operand is a constant. Obtaining the address for a constant is fundamentally different than obtaining the address of a string variable (since HLA string variables are actually pointers to the string data). The most interesting feature of this macro definition is the "terminator" line. This actually defines a second macro that is active only after HLA encounters the "OnceForEachChar" macro and control returns to the first statement after the OnceForEachChar invocation. Invocations of "context free" macros always occur in pairs; that is, for every "OnceForEachChar" invocation there must be a matching "endOnceForEachChar" invocation. The following program demonstrates this macro in use; it also demonstrates that you can nest this newly created control structure in your program:

program SampleHLApgm3;

#include( "stdlib.hhf" )

#macro OnceForEachChar( SomeString ): TopOfLoop, LoopExit;

pushd( -1 ); // index into string.

TopOfLoop:

inc( (type dword [esp] ));

#if( @IsConst( SomeString ))

lea( eax, SomeString );

#else

mov( SomeString, eax );

#endif

add( [esp], eax );

mov( [eax], al );

cmp( al, 0 );

je LoopExit;

#terminator endOnceForEachChar;

jmp TopOfLoop;

LoopExit:

add( 4, esp );

#endmacro

static

strVar: string := ":" nl;

begin SampleHLApgm3;

OnceForEachChar( "Hello" )

stdout.putc( al );

OnceForEachChar( strVar )

stdout.putc( al );

endOnceForEachChar;

endOnceForEachChar;

end SampleHLApgm3;

This program produces the output:

H:

e:

l:

l:

o:

Here's some sample MASM code, similar to what the HLA compiler emits (when using the -masm and -source command-line options) for the sequence above:

strings segment page public 'data'

align 4

?635_len dword 5

dword 5

?635_str byte "Hello",0,0,0

strings ends

pushd -1

?634__0278_:

inc dword ptr [esp+0] ;(type dword [esp])

lea eax, ?635_str

add eax, [esp+0] ;[esp]

mov al, [eax+0] ;[eax]

cmp al, 0

je ?636__0279_

push eax

call stdio_putc ;putc

pushd -1

?639__027d_:

inc dword ptr [esp+0] ;(type dword [esp])

mov eax, dword ptr ?630_strVar[0] ;strVar

add eax, [esp+0] ;[esp]

mov al, [eax+0] ;[eax]

cmp al, 0

je ?640__027e_

push eax

call stdio_putc ;putc

jmp ?639__027d_

?640__027e_:

add esp, 4

jmp ?634__0278_

?636__0279_:

add esp, 4

In addition to the "terminator" clause, HLA macros also support a "keyword" clause that let you bury reserved words within a context-free language construct. For example, although the HLA language provides a SWITCH/CASE statement, you can create a new one with slightly different semantics. I implemented the SWITCH .. CASE .. DEFAULT .. ENDCASE statement using HLA's macro facilities (as a demonstration of HLA's power). An HLA SWITCH statement (using this macro) takes the following form:

switch( reg32 )

case( constantList1 )

<< statements >>

case (constantList2 )

<< statements >>

.

.

.

default // This is optional

<< statements >>

endswitch;

The switch macro implements the "switch" and "endswitch" reserved words using the macro and terminator clauses in the macro declaration. It implements the "case" and "default" reserved words using the HLA "keyword" clause in a macro definition. The "keyword" clause is similar to the "terminator" clause except it doesn't force the end of the macro expansion in the invoking code. The actual code for the HLA SWITCH statement is a little too complex to present here, so I will extend the example of the OnceForEachChar macro to demonstrate how you code use the "keyword" clause in a macro.

Let's suppose you wanted to add a "_break" clause to the "OnceForEachChar" loop ( I'm using "_break" with an underscore because "break" is an HLA reserved word). You could easily modify the "OnceForEachChar" macro to achieve this by using the following code:

#macro OnceForEachChar( SomeString ): TopOfLoop, LoopExit;

pushd( -1 ); // index into string.

TopOfLoop:

inc( (type dword [esp] ));

#if( @IsConst( SomeString ))

lea( eax, SomeString );

#else

mov( SomeString, eax );

#endif

add( [esp], eax );

mov( [eax], al );

cmp( al, 0 );

je LoopExit;

#keyword _break;

jmp LoopExit;

#terminator endOnceForEachChar;

jmp TopOfLoop;

LoopExit:

add( 4, esp );

#endmacro

The "#keyword" clause defines a macro ("_break") that is active between the "OnceForEachChar" and "endOnceForEachChar" invocations. This macro simply expands to a jmp instruction that exits the loop. Note that if you have nested "OnceForEachChar" loops and you "_break" out of the innermost loop, the code only jumps out of the innermost loop, exactly as you would expect.

HLA's macro facilities are part of a larger feature I refer to as the "HLA Compile-Time Language." HLA actually contains a built-in interpreter than executes while it is compiling your program. The compile-time language provides conditional compilation (the #IF..#ELSE..#ENDIF statements in the previous example), interpreted procedure calls (macros), looping constructs (#WHILE..#ENDWHILE), a very powerful constant expression evaluator, compile-time I/O facilities (#PRINT, #ERROR, #INCLUDE, and #TEXT..#ENDTEXT), and dozens of built-in compile time functions (like the @IsConst function above).

The HLA built-in string functions (not to be confused with the HLA Standard Library's string functions) are actually powerful enough to let you write a compiler for a high level language completely within HLA. I mentioned earlier that it is possible to write an expression compiler within HLA; I was serious. The HLA compile-time language will let you write a sophisticated recursive descent parser for arithmetic expressions (and other context-free language constructs, for that matter).

HLA is a great tool for creating low-level Domain Specific Embedded Languages (DSELs). DSELs are mini-languages that you create on a project-by-project basis to help reduce development time. HLA's compile time language lets you create some very high level constructs. For example, HLA implements a very powerful string pattern matching language in the "patterns" module found in the HLA Standard Library. This module lets you write pattern-matching programs that use techniques found in language like SNOBOL4 and Icon. As a final example, consider the following HLA program that translate RPN (reverse polish notation) expressions into their equivalent assembly language (HLA) statements and displays the results to the standard output:

// This program translates user RPN input into an

// equivalent sequence of assembly language instrs (HLA fmt).

program RPNtoASM;

#include( "stdlib.hhf" );

static

s: string;

operand: string;

StartOperand: dword;

#macro mark;

mov( esi, StartOperand );

#endmacro

#macro delete;

mov( StartOperand, eax );

sub( eax, esi );

inc( esi );

sub( s, eax );

str.delete( s, eax, esi );

#endmacro

procedure length( s:string ); @returns( "eax" ); @nodisplay;

begin length;

push( ebx );

mov( s, ebx );

mov( (type str.strRec [ebx]).length, eax );

pop( ebx );

end length;

begin RPNtoASM;

stdout.put( "-- RPN to assembly --" nl );

forever

stdout.put( nl nl "Enter RPN sequence (empty line to quit): " );

stdin.a_gets();

mov( eax, s );

breakif( length( s ) = 0 );

while( length( s ) <> 0 ) do

pat.match( s );

// Match identifiers and numeric constants

mark;

pat.zeroOrMoreWS();

pat.oneOrMoreCset( {'a'..'z', 'A'..'Z', '0'..'9', '_'} );

pat.a_extract( operand );

stdout.put( " pushd( ", operand, " );" nl );

strfree( operand );

delete;

pat.alternate;

// Handle the "+" operator.

mark;

pat.zeroOrMoreWS();

pat.oneChar( '+' );

stdout.put

(

" pop( eax );" nl

" add( eax, [esp] );" nl

);

delete;

pat.alternate;

// Handle the '-' operator.

mark;

pat.zeroOrMoreWS();

pat.oneChar( '-' );

stdout.put

(

" pop( eax );" nl

" pop( ebx );" nl

" sub( eax, ebx );" nl

" push( ebx );" nl

);

delete;

pat.alternate;

// Handle the '*' operator.

mark;

pat.zeroOrMoreWS();

pat.oneChar( '*' );

stdout.put

(

" pop( eax );" nl

" imul( eax, [esp] );" nl

);

delete;

pat.alternate;

// handle the '/' operator.

mark;

pat.zeroOrMoreWS();

pat.oneChar( '/' );

stdout.put

(

" pop( ebx );" nl

" pop( eax );" nl

" cdq(); " nl

" idiv( ebx, edx:eax );" nl

" push( ebx );" nl

);

delete;

pat.if_failure

// If none of the above, it must be an error.

stdout.put( nl "Illegal RPN Expression" nl );

mov( s, ebx );

mov( 0, (type str.strRec [ebx]).length );

pat.endmatch;

endwhile;

endfor;

end RPNtoASM;

Consider for a moment the code that matches an identifier or an integer constant:

mark;

pat.zeroOrMoreWS();

pat.oneOrMoreCset( {'a'..'z', 'A'..'Z', '0'..'9', '_'} );

pat.a_extract( operand );

stdout.put( " pushd( ", operand, " );" nl );

strfree( operand );

delete;

The "mark;" invocation saves a pointer into the "s" string where the current identifier starts. The pat.ZeroOrMoreWS pattern matching function skips over zero or more whitespace characters. The pat.OneOrMoreCset pattern match function matches one or more alphanumeric and underscore characters (a crude approximation for identifiers and integer constants). The pat.a_extract function makes a copy of the string between the "mark" and the "a_extract" calls (this corresponds to the whitespace and identifier/constant). The stdout.put statement emits the HLA machine instruction that will push this operand on to the x86 stack for later computations. The remaining statements clean up allocated string storage space and delete the matched string from "s".

Although the "pat.xxxxx" statements look like simple function calls, there's actually a whole lot more going on here. HLA's pattern matching facilities, like SNOBOL4 and Icon, support success, failure, and backtracking. For example, if the pat.oneOrMoreChar function fails to match at least one character from the set, control does not flow down to the pat.a_extract function. Instead, control flows to the next "pat.alternate" or "pat.if_failure" clause. Some calls to HLA pattern matching routines may even cause the program to back up in the code and reexecute previously called functions in an attempt to match a difficult pattern (i.e., the backtracking component). This article is not the place to get into the theory of pattern matching; however, these few examples should be sufficient to show you that something really special is going on here. And all these facilities were developed using the HLA compile-time language. This should give you a small indication of what is possible when using the HLA compile-time language facilities.

 

The Quick Guide to HLA

Overview

This guide is designed to help those who are already familiar with x86 assembly language programming to get up to speed with HLA as rapidly as possible. HLA was designed as a tool for teaching assembly language programming to University/College students who have no prior experience with assembly language but have some high level language programming experience (C/C++, Pascal, Java, etc.). The documentation that exists for HLA comes in two forms: the HLA reference manuals and the "Art of Assembly Language Programming/32-bit Edition." The "Art of Assembly" text is suitable for students and beginners to assembly language programming; it starts from square one and teaches assembly language programming using HLA. Unfortunately, this text is not particularly suitable for those programmers who already know assembly language. The HLA reference manuals are great when you need to look up some particular feature. They do fully explain the HLA language, however, the HLA language is rather large so the assembly programmer who is new to HLA is faced with reading a tremendous amount of material just to get started with HLA. Most individuals won’t bother. The purpose of this guide is to present a very small subset of HLA to the advanced x86 assembly language programmer in as few pages as possible. This guide does not attempt to teach any of HLA’s special features; it assumes the reader is using an assembler such as MASM, TASM, NASM, Gas, etc., and is interested in learning how to write assembly code using HLA in a fashion comparable to those assemblers. Of course, the whole reason for such a person to learn HLA is to be able to take advantage of HLA’s advanced features. However, one has to learn to walk before they run, this is the guide that will get that person walking. Once the reader is comfortable using HLA in a "traditional assembly" sense, then that reader can refer to the HLA reference manuals in order to learn the more advanced features of the language.

Running HLA

HLA is a command line tool that you run from the Win32, Mac OSX, Linux, or FreeBSD Command Prompt. This document assumes that you are familiar with basic command prompt syntax and you’re familiar with various commands like "DIR" and "RENAME" (under Windows) or "ls" and "mv" (under *NIX). To run HLA from the command line prompt, you use a command like the following:

hla optional_command_line_parameters Filename_list

The filename list consists of one or more unambiguous filenames having the extension: HLA, ASM, or OBJ. HLA will first run the HLAPARSE program on all files with the HLA extension (producing files with the same base name and an .obj/.o extension). Finally, HLA runs the linker to combine all the object files together. The ultimate result, assuming there were no errors along the way, is an executable file .

HLA supports the following command line parameters:

options:

-@ Do not generate linker response file.

-@@ Always generate a linker response file.

-thread Enable thread-safe code generation and linkage.

-axxxxx Pass xxxxx as command line parameter to assembler.

-dxx Define VAL symbol xx to have type BOOLEAN and value TRUE.

-dxx=yy Define VAL symbol xx to have type STRING and value "yy".

-e:name Executable output filename (appends ".exe" under Windows).

-x:name Executable output filename (does not append ".exe").

-b:name Binary object file output name (only when using HLABE).

-i:path Specifies path to HLA include file directory.

-lib:path Specifies path to the HLALIB.LIB file.

-license Displays copyright and license info for the HLA system.

-lxxxxx Pass xxxxx as command line parameter to linker.

-m Create a map file during link

-p:path Specifies path to hold temporary working files.

-r:name <name> is a text file containing cmd line options.

-obj:path Specifies path to place object files.

-main:name Use ‘name’ as the name of the HLA main program.

-source Compile to human readable source file format.

-s Compile to .ASM files only.

-c Compile and assemble to object files only.

-fasm Use FASM as back-end assembler (applies to -s and -c)

-gas Use GAS as back-end (Linux/BSD, applies to -s and -c)

-gasx Use Gas as back-end (Mac OSX, --s and -c only)

-hla Produce a pseudo-HLA source file as output (implies -s).

-hlabe (Default) Produce object code using the HLA Back Engine.

-masm Use MASM as back-end assembler (applies to -s and -c)

-nasm Use NASM as back-end assembler (applies to -s and -c)

-tasm Use TASM as back-end assembler (applies to -s and -c)

-sym Dump symbol table after compile.

-win32 Generate code for Win32 OS.

-linux Generate code for Linux OS.

-freebsd Generate code for FreeBSD OS.

-macos Generate code for Mac OSX.

-test Send diagnostic info to stdout rather than stderr (This

option is intended for HLA test/debug purposes).

-v Verbose compile.

-level=h High-level assembly language

-level=m Medium-level assembly language

-level=l Low-level assembly language

-level=v Machine-level assembly language (very low level).

-w Compile as windows app (default is console app).

-? Display this help message.

Please see the appropriate chapter in the HLA Reference Manual chapter Using the HLA Command-Line Compiler for an explanation of each of these options. Most of the time, you will not use any of these options when compiling typical HLA programs. The "-c" and "-s" options are the ones you will use most commonly (and this document assumes that you understand their purpose).

HLA Language Elements

Starting with this section we being discussing the HLA source language. HLA source files must contain only seven-bit ASCII characters. These are Windows text files with each source line record containing a carriage return/line feed termination sequence or *NIX (Mac OSX, Linux, and FreeBSD) source files with a line feed terminating each line. White space consists of spaces, tabs, and newline sequences. Generally, HLA does not appreciate other control characters in the file and may generate an error if they appear in the source file.

Comments

HLA uses "//" to lead off single line comments. It uses "/*" to begin an indefinite length comments and it uses "*/" to end an indefinite length comment. C/C++, Java, and Delphi users will be quite comfortable with this notation.

Special Symbols

The following characters are HLA lexical elements and have special meaning to HLA:

* / + - ( ) [ ] { } < > : ; , . = ? & | ^ ! @

&& || <= >= <> != == := .. << >>

## #( )# #{ }#

This document will not explain the meaning of all these symbols, only the minimum necessary to write simple HLA programs. See the HLA Reference Manual for more details.

Reserved Words

HLA supports a large number of reserved words (mostly, they are machine instructions). For brevity, that list does not appear here; please see the HLA reference manual chapter HLA Language Elements for a complete and up-to-date list. Note that HLA does not allow you to use a reserved word as a program identifier, so you should scan over the list at least once to familiarize yourself with reserved words that you might be used to using as identifiers in your assembly language programs. HLA reserved words are case insensitive. That is, "MOV" and "mov" (as well as any permutation with respect to case) both represent the HLA "mov" reserved word.

External Symbols and Assembler Reserved Words

HLA v2.0 produces an option to produce an assembly language file during compilation and can invoke an assembler such as MASM, FASM, NASM, or Gas to complete the compilation process. HLA automatically translates normal identifiers you declare in your program to benign identifiers in the assembly language program. However, HLA does not translate EXTERNAL symbols, but preserves these names in the assembly language file it produces. Therefore, you must take care not to use external names that conflict with the underlying assembler’s set of reserved words or that assembler will generate an error when it attempts to process HLA’s output.

For a list of the back-end assembler's reserved words, please see the documentation for the assembler you are using to process HLA’s output (i.e., MASM, NASM, FASM, or Gas).

HLA Identifiers

HLA identifiers must begin with an alphabetic character or an underscore. After the first character, the identifier may contain alphanumeric and underscore symbols. There is no technical limit on identifier length in HLA, but you should avoid external symbols greater than about 32 characters in length since the assemblers and linkers that process HLA output may not be able to handle such symbols.

HLA identifiers are always case neutral. This means that identifiers are case sensitive insofar as you must always spell an identifier exactly the same (with respect to alphabetic case). However, you are not allowed to declare two identifiers whose only difference is alphabetic case.

External Identifiers

HLA lets you explicitly provide a string for external identifiers. External identifiers are not limited to the format for HLA identifiers. HLA allows any string constant to be used for an external identifier. It is your responsibility to use only those characters that are legal in the back-end assembler (if you are using one). Note that this feature lets you use symbols that are not legal in HLA but are legal in external code (e.g., Win32 APIs use the ’@’ character in identifiers). See the discussion of the external option for more details.

Data Types in HLA
Native (Primitive) Data Types in HLA

HLA provides the following basic primitive types:

One-byte types: byte, boolean, enum, uns8, int8, and char.

Two-byte types: word, uns16, int16.

Four-byte types: dword, uns32, int32, real32, string, pointer

Eight-byte types: uns64, int64, qword, thunk, and real64.

Ten-Byte types: tbyte, and real80.

Sixteen-byte types: uns128, int128, lword, and cset

For details on these particular types, please consult the HLA Reference Manual chapter HLA Data Types. This document will make use of the following types:

byte, word, dword, string, real32, qword, real64, and real80

These are the typical types assembly language programmers use.

BYTE variables and objects may hold integer numeric values in the range -128..+255, any ASCII character constant, and the two predefined boolean values true (1) and false (0). Normally, HLA does a small amount of type checking; however, you can associate any value that can fit into eight bits with a byte-sized variable (or other object).

WORD variables and object may hold integer numeric values in the range -32768..+65535. Generally, HLA does not allow the association of other values with a WORD object.

DWORD variables and objects may hold integer numeric values in the range -2147483647..+4294967295, or the address of an object (using the "&" address-of operator).

STRING variables are also DWORD objects. STRING objects hold the address of a sequence of zero or more ASCII characters that end with a zero byte. In the four bytes immediately preceding the location contained in the string pointer is the current length of the string. In the four bytes preceding the current length is the maximum allowable length of the string. Note that HLA strings are "read-only" compatible with ASCIIZ strings used by Windows and C/C++ (read-only meaning that you can pass an HLA string to a Windows API or C/C++ function but that function should not modify the string).

QWORD, UNS64, and INT64 objects consume eight bytes of memory. TBYTE objects consume ten bytes (80 bits). LWORD, UNS128, and INT128 values are also legal and support 128-bit hexadecimal, unsigned, or signed constants.

REAL32, REAL64, and REAL80 types in HLA support the three different IEEE floating-point formats.

Composite Data Types

In addition to the primitive types above, HLA supports arrays, records (structures), unions, classes, and pointers of the above types (except for text objects).

Array Data Types

HLA allows you to create an array data type by specifying the number of array elements after a type name. Consider the following HLA type declaration that defines intArray to be an array of dword objects:

type intArray : dword[ 16 ];

The "[ 16 ]" component tells HLA that this type has 16 four-byte double words. HLA arrays use a zero-based index, so the first element is always element zero. The index of the last element, in this example, is 15 (total of 16 elements with indices 0..15).

HLA also supports multidimensional arrays. You can specify multidimensional arrays by providing a list of indices inside the square brackets, e.g.,

type intArray4x4 : dword[ 4, 4 ];
type intArray2x2x4 : dword[ 2,2,4 ];

Record Data Types3

HLA’s records allow programmers to create data types whose fields can be different types. The following HLA static variable declaration defines a simple record with four fields:

static Planet:

record

x: dword;
y: dword;
z: dword;
density: real64;

endrecord;

Objects of type Planet will consume 20 bytes of storage at run-time.

The fields of a record may be of any legal HLA data type including other composite data types. You use dot-notation to access fields of a record object, e.g.,

mov( Planet.x, eax );

Literal Constants

Literal constants are those language elements that we normally think of as non-symbolic constant objects. HLA supports a wide variety of literal constants. The following sections describe those constants.

Numeric Constants

HLA lets you specify several different types of numeric constants.

Decimal Constants

The first and last characters of a decimal integer constant must be decimal digits (0..9). Interior positions may contain decimal digits and underscores. The purpose of the underscore is to provide a better presentation for large decimal values (i.e., use the underscore in place of a comma in large values). Example: 1_234_265.

Hexadecimal Constants

Hexadecimal literal constants must begin with a dollar sign ("$") followed by a hexadecimal digit and must end with a hexadecimal digit (0..9, A..F, or a..f). Interior positions may contain hexadecimal digits or underscores. Hexadecimal constants are easiest to read if each group of four digits (starting from the least significant digit) is separated from the others by an underscore. E.g., $1A_2F34_5438.

Binary Constants

Binary literal constants begin with a percent sign ("%") followed by at least one binary digit (0/1) and they must end with a binary digit. Interior positions may contain binary digits or underscore characters. Binary constants are easiest to read if each group of four digits (starting from the least significant digit) is separated from the others by an underscore. E.g., %10_1111_1010.

Real (Floating Point) Constants

Floating point (real) literal constants always begin with a decimal digit (never just a decimal point). A string of one or more decimal digits may be optionally followed by a decimal point and zero or more decimal digits (the fractional part). After the optional fractional part, a floating point number may be followed by "e" or "E", a sign ("+" or "-"), and a string of one or more decimal digits (the exponent part). Underscores may appear between two adjacent digits in the floating point number; their presence is intended to substitute for commas found in real-world decimal numbers.

Boolean Constants

Boolean constants consist of the two predefined identifiers true and false. Note that your program may redefine these identifiers, but doing so is incredibly bad programming style.

Character Constants

Character literals generally consist of a single (graphic) character surrounded by apostrophes. To represent the apostrophe character, you use four apostrophes, e.g., ‘’’’.

Another way to specify a character constant is by typing the "#" symbol followed by a numeric literal constant (decimal, hexadecimal, or binary). Examples: #13, #$D, #%1101.

String Constants

String literal constants consist of a sequence of (graphic) characters surrounded by quotes. To embed a quote within a string, insert a pair of quotes into the string, e.g., "He said ""This"" to me."

If two string literal constants are adjacent in a source file (with nothing but whitespace between them), then HLA will concatenate the two strings and present them to the parser as a single string. Furthermore, if a character constant is adjacent to a string, HLA will concatenate the character and string to form a single string object. This is useful, for example, when you need to embed control characters into a string, e.g.,

"This is the first line" #$d #$a "This is the second line" #$d #$a

HLA treats the above as a single string with a newline sequence (CR/LF) at the end of each of the two lines of text.

Pointer Constants

HLA allows a very limited form of a pointer constant. If you place an ampersand in front of a static object’s name (i.e., the name of a static variable, readonly variable, uninitialized (storage) variable, procedure, method, or iterator), HLA will compute the run-time offset of that variable. Pointer constants may not be used in arbitrary constant expressions. You may only use pointer constants in expressions used to initialize static or readonly variables or as constant expressions in 80x86 instructions.

Structured Constants

HLA also supports certain structured constants including character set constants, array constants, union constants and record constants. Please see the HLA Reference Manual chapter HLA Constants for more details.

Constant Expressions in HLA

HLA provides a rich expression evaluator to process assembly-time expressions. HLA supports the following operators (sorting by decreasing precedence):

! (unary not), - (unary negation)

*, div, mod, /, <<, >>

+, -

=, = =, <>, !=, <=, >=, <, >

&, |, &, in

!expr

The expression must be either boolean or a number. For boolean values, not ("!") computes the standard logical not operation. For numbers, not ("!") computes the bitwise not operation on the bits of the number.

- expr (unary negation operator)

expr1 * expr2 (multiplication operator)

expr1 div expr2 (integer division operator)

expr1 mod expr2 (integer remainder operator)

expr1 / expr2 (real division operator)

expr1 << expr2 (integer shift left operator)

expr1 >> expr2 (integer shift right operator)

expr1 + expr2 (addition operator)

expr1 - expr2 (subtraction operator)

expr1 = expr2 (equality comparison operator)

expr1 <> expr2 (inequality comparison operator)

expr1 < expr2 (less than comparison operator)

expr1 <= expr2 (less than or equal comparison operator)

expr1 > expr2 (greater than comparison operator)

expr1 >= expr2 (greater or equal comparison operator)

expr1 & expr2 (logical/boolean AND operator)

expr1 | expr2 (logical/boolean OR operator)

expr1 ^ expr2 (logical/boolean XOR operator)

( expr ) (override operator precedence)

HLA supports several other constant operators. Furthermore, many of the above operators are overloaded depending on the operand types. Note that for numeric (integer) operands, HLA fully support 128-bit arithmetic. Please see the HLA Reference Manual chapter HLA Constants for more details.

Program Structure

An HLA program uses the following general syntax:

program identifier ;

declarations

begin identifier;

statements

end identifier;

The three identifiers above must all match. The declaration section (declarations) consists of type, const, val, var, static, storage, readonly, procedure, iterator, and method definitions. Any number of these sections may appear and they may appear in any order; more than one of each section may appear in the declaration section.

If you wish to write a library module that contains only procedures and no main program, you would use an HLA unit. Units have a syntax that is nearly identical to programs, there isn’t a begin associated with the unit, e.g.,

unit TestPgm;

procedure LibraryRoutine;
begin LibraryRoutine;
<< etc. >>
end LibraryRoutine;

end TestPgm;

Procedure Declarations

Procedure declarations are nearly identical to program declarations.

procedure identifier; @noframe;

begin identifier;

statements

end identifier;

Note that HLA procedures provide a very rich set of syntactical options. The template above corresponds to the syntax that creates procedures most closely resembling those that other assemblers use. HLA’s procedures allow parameters, local variable declarations, and many other features this document won’t describe. For more details, please see the HLA Reference Manual chapter on HLA Procedures.

Note, and this is very important, that the procedure option @noframe must appear in the procedure declaration. Without this declaration, HLA inserts some additional code into your procedure and it will probably fail to work as you intend (indeed, it’s likely the inserted code will crash when it runs).

Example of a procedure:

procedure ProcDemo; @noframe;

begin ProcDemo;

add( 5, eax );

ret();

end ProcDemo;

Declarations

Programs, units, procedures, methods, and iterators all have a declaration section. Classes and namespaces also have a declaration section, though it is somewhat limited. A declaration section can contain one or more of the following components (among other things this document doesn’t cover):

  • A type section.
  • A const section.
  • A static section.
  • A procedure.

The order of these sections is irrelevant as long as you ensure that all identifiers used in a program are defined before their first use. Furthermore, as noted above, you may have multiple sections within the same set of declarations. For example, the two const sections in the following procedure declaration are legal:

program TwoConsts;

const MaxVal := 5;

type Limits: dword[ MaxVal ];

const MinVal := 0;

begin TwoConsts;

//...

end TwoConsts;

Type Section

You can declare user-defined data types in the type section. The type section appears in a declaration section and begins with the reserved word type. It continues until encountering another declaration reserved word (e.g., const, var, or val) or the reserved word begin. A typical type definition begins with an identifier followed by a colon and a type definition. The following paragraphs demonstrate some of the legal forms of type definitions. See the HLA Reference Manual chapter on HLA Program Structure for more examples.

id1 : id2; // Defines id1 to be the same as type id2.

id1 : id2 [ dim_list ]; // Defines id1 to be an array of type id2.

id1 : record // Defines id1 as a record type.

field_declarations

endrecord;

Const Section

You may declare manifest constants in the const section of an HLA program. It is illegal to attempt to change the value of a constant at some later point during assembly. Of course, at run-time the constant always has a fixed value.

The constant declaration section begins with the reserved word const and is followed by a sequence of constant definitions. The constant declaration section ends when HLA encounters a keyword such as const, type, var, val, etc. Actual constant definitions take the forms specified in the following paragraphs.

id := expr; // Assigns the value and type of expr to id

id1 : id2 := expr; // Creates constant id1 of type id2 of value expr.

Note that HLA supports several types of constants this section doesn’t discuss (e.g., array and record constants and well as compile-time variables). See the HLA Reference Manual chapter on HLA Program Structure for more details.

Static Section

The static section lets you declare static variables you can reference at run-time by your code. The following paragraphs list some of the forms that are legal in the static section. As usual, see the HLA Reference Manual chapter on HLA Program Structure for lots of additional features that HLA supports in the static section.

static

id1 : id2; // Declares variable id1 of type id2

id1 : id2 := expr; // Declares variable id1 of type id2, init’d with expr

id1 : id2[ expr ]; // Declares array id1 of type id2 with expr elements

The @NOSTORAGE Option

The @ nostorage option tells HLA to associate the current offset in the segment with the specified variable, but don’t actually allocate any storage for the object. This option effectively creates an alias of the current variable with the next object you declare in one of the static sections. Consider the following example:

static

b: byte; @nostorage;

w: word; @nostorage;

d: dword;

Because the b and w variables both have the @ nostorage option associated with them, HLA does not reserve any storage for these variables. The d variable does not have the @ nostorage option, so HLA does reserve four bytes for this variable. The b and w variables, since they don’t have storage associated with them, share the same address in memory with the d variable.

The EXTERNAL Option

The external option gives you the ability to reference variables that you declare in other files. Like the external clause for procedures, there are two different syntaxes for the external clause appearing after a variable declaration:

varName: varType; external;

varName: varType; external( "external_Name" );

The first form above uses the variable’s name for both the internal and external names. The second form uses varName as the internal name that HLA uses and it associates this variable with external_Name in the external modules. The external option is always the last option associated with a variable declaration.

If the actual variable definition for an external object appears in a source file after an external declaration, this tells HLA that the definition is a public variable that other modules may access (the default is local to the current source file). This is the only way to declare a variable public so that other modules can use it. Usually, you would put the external declaration in a header file that all modules (wanting to access the variable) include; you also include this header file in the source file containing the actual variable declaration.

Macros

HLA has one of the most powerful macro expansion facilities of any programming language. HLA’s macros are the key to extending the HLA language. If you’re a big user of macros then you will want to read the HLA Reference Manual chapter The HLA Compile-Time Language to learn all about HLA’s powerful macro facilities. This section will describe HLA’s limited "Standard Macro" facility that is comparable to the macro facilities other assemblers provide.

You can declare macros in the declaration section of a program using the following syntax:

#macro identifier ( optional_parameter_list ) ;

statements

#endmacro;

Example:

#macro MyMacro;

?i = i + 1;

#endmacro;

The optional parameter list must be a list of one or more identifiers separated by commas. HLA automatically associates the type "text" with all macro parameters (except for one special case noted below). Example:

#macro MacroWParms( a, b, c );

?a = b + c;

#endmacro;

If the macro does not allow any parameters, then you follow the identifier with a semicolon (i.e., no parentheses or parameter identifiers). See the first example in this section for a macro without any parameters.

Occasionally you may need to define some symbols that are local to a particular macro invocation (that is, each invocation of the macro generates a unique symbol for a given identifier). The local identifier list allows you to do this. To declare a list of local identifiers, simply following the parameter list (after the parenthesis but before the semicolon) with a colon (":") and a comma separated list of identifiers, e.g.,

#macro ThisMacro(parm1):id1,id2;

...

HLA automatically renames each symbol appearing in the local identifier list so that the new name is unique throughout the program. HLA creates unique symbols of the form " _XXXX_ " where XXXX is some hexadecimal numeric value. To guarantee that HLA can generate unique symbols, you should avoid defining symbols of this form in your own programs (in general, symbols that begin and end with an underscore are reserved for use by the compiler and the HLA standard library). Example:

#macro LocalSym : i,j;

j: cmp(ax, 0)

jne( i )

dec( ax )

jmp( j )

i:

#endmacro;

To invoke a macro, you simply supply its name and an appropriate set of parameters. Unless you specify a variable number of parameters (using the array syntax) then the number of actual parameters must exactly match the number of formal parameters. If you specify a variable number of parameters, then the number of actual parameters must be greater than or equal to the number of formal parameters (not counting the array parameter).

Actual macro parameters consist of a string of characters up to, but not including a separate comma or the closing parentheses, e.g.,

example( v1, x+2*y )

" v1 " is the text for parameter #1, " x+2*y " is the text for parameter #2. Note that HLA strips all leading whitespace and control characters before and after the actual parameter when expanding the code in-line. The example immediately above would expand do the following:

?v1 := x+2*y;

If (balanced) parentheses appear in some macro’s actual parameter list, HLA does not count the closing parenthesis as the end of the macro parameter. That is, the following is legal:

example( v1, ((x+2)*y) )

This expands to:

?v1 := ((x+2)*y);

The #Include Directive

Like most languages, HLA provides a source inclusion directive that inserts some other file into the middle of a source file during compilation. HLA’s #INCLUDE directive is very similar to the pragma of the same name in C/C++ and you primarily use them both for the same purpose: including library header files into your programs.

HLA’s include directive has the following syntax:

#include( string_expression );

The Conditional Compilation Statements (#if)

The conditional compilation statements in HLA use the following syntax:

#if( constant_boolean_expression )

<< Statements to compile if the >>

<< expression above is true. >>

#elseif( constant_boolean_expression )

<< Statements to compile if the >>

<< expression immediately above >>

<< is true and the first expres->>

<< sion above is false. >>

#else

<< Statements to compile if both >>

<< the expressions above are false. >>

#endif

The #elseif and #else clauses are optional. As you would expect, there may be more than one #elseif clause in the same conditional if sequence.

Unlike some other assemblers and high-level languages, HLA’s conditional compilation directives are legal anywhere whitespace is legal. You could even embed them in the middle of an instruction! While directly embedding these directives in an instruction isn’t recommended (because it would make your code very hard to read), it’s nice to know that you can place these directives in a macro and then replace an instruction operand with a macro invocation.

An important thing to note about this directive is that the constant expression in the #if and #elseif clauses must be of type boolean or HLA will emit an error. Any legal constant expression that produces a boolean result is legal here.

Keep in mind that conditional compilation directives are executed at compile-time, not at run-time. You would not use these directives to (attempt to) make decisions while your program is actually running.

The 80x86 Instruction Set in HLA

One of the most obvious differences between HLA and standard 80x86 assembly language is the syntax for the machine instructions. The two primary differences are the fact that HLA uses a functional notation for machine instructions and HLA arranges the operands in a (source, dest) format rather than the (dest, source) format used by Intel.

Zero Operand Instructions (Null Operand Instructions)

The following instructions do not require any operands. There are two sytactically allowable forms for each instruction:

instr;

instr();

The zero-operand instruction mnemonics are

aaa, aad, aam, aas, cbw, cdq, clc, cld, cli, cmc, cmpsb, cmpsd, cmpsw, cpuid, cwd, cwde, daa, das,

insb, insd, insw, into, iret, iretd, lahf, leave, lodsb, lodsd, lodsw, movsb, movsd, movsw, nop, outsb,

outsd, outsw, popad, popa, popf, popfd, pusha, pushad, pushf, pushfd, rdtsc, rep.insb, rep.insd,

rep.insw, rep.movsb, rep.movsd, rep.movsw, rep.outsb, rep.outsd, rep.outsw, rep.stosb, rep.stosd,

rep.stosw, repe.cmpsb, repe.cmpsd, repe.cmpsw, repe.scasb, repe.scasd, repe.scasw, repne.cmpsb,

repne.cmpsd, repne.cmpsw, repne.scasb, repne.scasd, repne.scasw, sahf, scasb, scasd, scasw,

stc, std, sti, stosb, stosd, stosw, wait, xlat

General Arithmetic and Logical Instructions

These instructions include adc, add, and, mov, or, sbb, sub, test, and xor. They all take the same basic form:

Generic Form:

adc( source, dest );
add( source, dest );
and( source, dest );
mov( source, dest );
sbb( source, dest );
sub( source, dest );
test( source, dest );
xor( source, dest );

The XCHG Instruction

The xchg instruction allows the following syntactical forms:

Generic Form:



xchg( source, dest );

The CMP Instruction

The "cmp" instruction uses the following general forms:

Generic:

cmp( LeftOperand, RightOperand );

Note that the CMP instruction’s operands are ordered "dest, source" rather than the usual "source,dest" format (that is, the operands are in the same order as MASM expects them). This is to allow an intuitive use of the instruction mnemonic (that is, CMP normally reads as "compare dest to source."). We will avoid this confusion by simply referring to the operands as the "left operand" and the "right operand". Left vs. right signifies the placement of the operands around a comparison operator like "<=" (e.g., "left <= right").

The Multiply Instructions

HLA supports several variations on the 80x86 "MUL" and IMUL instructions. Some of the supported forms are:

Standard Syntax:

mul( src )
imul( src )


intmul( const, Reg )
intmul( const, Reg, Reg )
intmul( Reg, Reg )
intmul( mem, Reg )

The first, and probably most important, thing to note about HLA’s multiply instructions is that HLA uses a different mnemonic for the extended-precision integer multiply versus the single-precision integer multiply (i.e., imul vs. intmul).

Note that the forms listed above correspond to the standard mul and imul instructions most assemblers provide. HLA actually provides several additional forms, please see the HLA documentation on "The 80x86 Instruction Set in HLA" for more details.

The Divide Instructions

HLA support several variations on the 80x86 DIV and IDIV instructions. The supported forms are:

Generic Forms:


div( source );
idiv( source );

Note that the forms listed above correspond to the standard div and idiv instructions most assemblers provide. HLA actually provides several additional forms; please see the HLA Reference manual chapter on The 80x86 Instruction Set in HLA for more details.

Single Operand Arithmetic and Logical Instructions

These instructions include dec, inc, neg, and not. They take the following general forms (substituting the specific mnemonic as appropriate):

Generic Form:


dec( dest );
inc( dest );
neg( dest );
not( dest );

Shift and Rotate Instructions

These instructions include rcl, rcr, rol, ror, sal, sar, shl, and shr. These instructions support the following generic syntax, making the appropriate mnemonic substitution.

Generic Form:


shl( count, dest );
shr( count, dest );
sar( count, dest );
sal( count, dest );
rcl( count, dest );
rcr( count, dest );
rol( count, dest );
ror( count, dest );


The Double Precision Shift Instructions

These instruction use the following general form:

Generic Form:



shld( count, source, dest )
shrd( count, source, dest )


The Lea Instruction

These instructions use the following syntax:

lea( Reg32, memory )
lea( Reg32, ProcID )

lea( Reg32, LabelID )

Note: HLA does not support an lea instruction that loads a 16-bit address into a 16-bit register. That form of the lea instruction is not useful in 32-bit programs running on 32-bit operating systems.

The Sign and Zero Extension Instructions

The HLA movsx and movzx instructions use the following syntax:

Generic Forms:


movsx( source, dest );
movzx( source, dest );

The Push and Pop Instructions

These instructions take the following general forms:

pop( reg );
pop( mem );
pushw( Reg16 )
pushw( memory )
pushw( Const )

pushd( Reg32 )
pushd( memory )
pushd( Const )

These instructions push or pop their specified operand.

Procedure Calls

Given a procedure or a DWORD variable (containing the address of a procedure) named "MyProc" you can call this procedure as follows:

call( MyProc );

HLA actually supports several other syntaxes for calling procedures, including a syntax that will automatically push parameters on the stack for you. See the HLA Reference Manual chapter on HLA Procedures for more details.

The Ret Instruction

The ret statement allows two syntactical forms:

ret();
ret( integer_constant_expression );

The Jmp Instructions

The HLA jmp instruction supports the following syntax:

jmp Label;
jmp ProcedureName;
jmp( dwordMemPtr );
jmp( anonMemPtr );
jmp( reg32 );

The Conditional Jump Instructions

These instructions include ja, jae, jb, jbe, jc, je, jg, jge, jl, jle, jo, jp, jpe, jpo, js, jz, jna, jnae, jnb, jnbe, jnc, jne, jng, jnge, jnl, jnle, jno, jnp, jns, jnz, jcxz, jecxz, loop, loope, loopz, loopne, and loopnz. They all take the following generic form (substituting the appropriate instruction for ja).

ja LocalLabel;

The Conditional Set Instructions

These instructions include: seta, setae, setb, setbe, setc, sete, setg, setge, setl, setle, seto, setp, setpe, setpo, sets, setz, setna, setnae, setnb, setnbe, setnc, setne, setng, setnge, setnl, setnle, setno, setnp, setns, and setnz. They take the following generic forms (substituting the appropriate mnemonic for seta):

seta( Reg8 );

seta( mem );

The Conditional Move Instructions

These instructions include cmova, cmovae, cmovb, cmovbe, cmovc, cmove, cmovg, cmovge, cmovl, cmovle, cmovo, cmovp, cmovpe, cmovpo, cmovs, cmovz, cmovna, cmovnae, cmovnb, cmovnbe, cmovnc, cmovne, cmovng, cmovnge, cmovnl, cmovnle, cmovno, cmovnp, cmovns, and cmovnz. They use the following general syntax:

CMOVcc( src, dest );

Allowable operands:

CMOVcc( reg16, reg16 );
CMOVcc( reg32, reg32 );
CMOVcc( mem16, reg16 );
CMOVcc( mem32, reg32 );

These instructions move the data if the specified condition is true (specified by the cc condition). If the condition is false, these instructions behave like a no-operation.

The Input and Output Instructions

The in and out instructions use the following syntax:

in( port, al )

in( port, ax )

in( port, eax )


in( dx, al )

in( dx, ax )

in( dx, eax )


out( al, port )

out( ax, port )

out( eax, port )


out( al, dx )

out( ax, dx )

out( eax, dx )

The "port" parameter must be an unsigned integer constant in the range 0..255. Note that these instructions may be privileged instructions when running under 32-bit operating systems. Their use may generate a fault in certain instances or when accessing certain ports.

The Interrupt Instruction

This instruction uses the syntax int( constant ); where the constant operand is an unsigned integer value in the range 0..255.

Bound Instruction

This instruction takes the following form:

bound( Reg16/32, mem )

The Enter Instruction

The enter instruction uses the syntax:

 

enter( const, const );

The first constant operand is the number of bytes of local variables in a procedure; the second constant operand is the lex level of the procedure. As a rule, you should not use this instruction and the corresponding leave instruction. HLA procedures automatically construct the display and activation record for you (more efficiently than when using enter). See the HLA Reference Manual chapter on HLA Procedures for more details on building procedure activation records.

CMPXCHG Instruction

This instruction uses the following syntax:

 

cmpxchg( reg/mem, reg )

The XADD Instruction

The XADD instruction uses the following syntax:


xadd( source, dest );

BSF and BSR Instructions

The bit scan instructions use the following syntax:



bsr( source, dest );
bsf( source, dest );


The BSWAP Instruction

This instruction takes the form:

 

bswap( reg32 );

 

It converts between little endian and big endian data formats in the specified 32-bit register.

Bit Test Instructions

This group of instructions includes BT, BTC, BTR, and BTS. They allow the following generic forms:

bt( BitNumber, Dest );

Floating Point Instructions

See the HLA Reference Manual chapter The 80x86 Instruction Set in HLA for a complete list of the floating-point instructions and their syntax.

MMX and SSE Instructions

See the HLA Reference Manual chapter The 80x86 Instruction Set in HLA for a complete list of the MMX and SSE instructions and their syntax.

Memory Addressing Modes in HLA

HLA supports all the 32-bit addressing modes of the Intel 80x86 instruction set4. A memory address on the 80x86 may consist of one to three different components: a displacement (also called an offset), a base pointer, and a scaled index value. The following are the legal combinations of these components:

displacement

basePointer

displacement + basePointer

displacement + scaledIndex

basePointer + scaledIndex

displacement + basePointer + scaledIndex

Note that a scaled index value cannot exist by itself.

HLA’s syntax for memory addressing modes takes the following forms:

staticVarName


staticVarName [ constant ]


staticVarName[ breg32 ]

staticVarName[ ireg32 ]

staticVarName[ ireg32*index ]



staticVarName[ breg32 + ireg32 ]

staticVarName[ breg32 + ireg32*index ]


staticVarName[ breg32 + constant ]

staticVarName[ ireg32 + constant ]

staticVarName[ ireg32*index + constant ]


staticVarName[ breg32 + ireg32 + constant ]

staticVarName[ breg32 + ireg32*index + constant ]


staticVarName[ breg32 - constant ]

staticVarName[ ireg32 - constant ]

staticVarName[ ireg32*index - constant ]


staticVarName[ breg32 + ireg32 - constant ]

staticVarName[ breg32 + ireg32*index - constant ]



[ breg32 ]


[ breg32 + ireg32 ]

[ breg32 + ireg32*index ]


[ breg32 + constant ]


[ breg32 + ireg32 + constant ]

[ breg32 + ireg32*index + constant ]


[ breg32 - constant ]


[ breg32 + ireg32 - constant ]

[ breg32 + ireg32*index - constant ]

" staticVarName " denotes any static variable currently in scope (local or global).

" basereg " denotes any general purpose 32-bit register.

" breg32 " denotes a base register and can be any general purpose 32-bit register.

" ireg32 " denotes an index register and may also be any general purpose register, even the same register as the base register in the address expression.

" index " denotes one of the four constants "1", "2", "4", or "8". In those address expression that have an index register without an index constant, "*1" is the default index.

Those memory addressing modes that do not have a variable name preceding them are known as "anonymous memory locations." Anonymous memory locations do not have a data type associated with them and in many instances you must use the type coercion operator in order to keep HLA happy.

Those memory addressing modes that do have a variable name attached to them inherit the base type of the variable. Read the next section for more details on data typing in HLA.

HLA allows another way to specify addition of the various addressing mode components in an address expression - by putting the components in separate brackets and concatenating them together. The following examples demonstrate the standard syntax and the alternate syntax:

[ebx+2] [ebx][2]

[ebx+ecx*4+8] [ebx][ecx][8]

lbl[ebp-2] lbl[ebp][-2]

The reason for allowing the extended syntax is because you might want to construct these addressing modes inside a macro from the individual pieces and it’s much easier to concatenate two operands already surrounded by brackets than it is to pick the expressions apart and construct the standard addressing mode.

Type Coercion in HLA

While an assembly language can never really be a strongly typed language, HLA is much more strongly typed than most other assembly languages.

Strong typing in an assembly language can be very frustrating. Therefore, HLA makes certain concessions to prevent the type system from interfering with the typical assembly language programmer. Within an 80x86 machine instruction, the only checking that takes place is a verification that the sizes of the operands are compatible.

Despite HLA playing fast and loose with machine instructions, there are many times when you will need to coerce the type of some operand. HLA uses the following syntax to coerce the type of a memory location or register operand:

(type typeID memOrRegOperand)

There are two instances where type coercion is especially important: (1) when you need to assign a type other than byte, word, or dword to a register5; (2) when you need to assign an anonymous memory location a type.

Installing HLA

Installing HLA Under Windows
New Easy Installation:

You can find a program titled "hlasetup.exe" on Webster. Running this application automatically installs HLA on your system. That’s all there is to it. Those who wish to exercise complete control over the placement of the HLA executables can still choose to manually install HLA, but this is not recommended for first-time users.

Manual Installation under Windows

HLA can operate in one of several modes. In the standard mode, it converts an HLA source file directly into an object file like most assemblers. In other modes, it has the ability to translate HLA source code into another source form that is compatible with several other assemblers, such as MASM, TASM, FASM, NASM, and Gas. A separate assembler, such as MASM, can compile that low-level intermediate source code to produce an object code file. Strictly speaking, this step (converting to a low-level assembler format and assembling via MASM/FASM/GASM/Gas is not necessary, but there are some times when it’s advantageous to work in this manner. Finally, you must link the object code output from the assembler using a linker program. Typically, you will link the object code produced by one or more HLA source files with the HLA Standard Library (hlalib.lib or hlalib_safe.lib) and, possibly, several operating system specific library files (e.g., kernel32.lib under Win32). Most of this activity takes place transparently whenever you ask HLA to compile your HLA source file(s). However, for the whole process to run smoothly, you must have installed HLA and all the support files correctly. This section will discuss how to set up HLA on your system.

First, you will need an HLA distribution for your particular Operating System. These instructions describe installation under Windows; see the next section if you’re using Linux, FreeBSD, or Mac OSX. The latest version of HLA is always available on Webster at http://webster.cs.ucr.edu. You should go there and download the latest version if you do not already possess it.

The HLA package contains the HLA compiler, the HLA Standard Library, and a set of include files for the HLA Standard Library. It also includes copies of the Pelles C librarian and linker, and some other tools. These tools will let you produce executable files under Windows. However, it’s still a good idea for Windows’ users to grab a copy of the Microsoft linker. The easiest way to get all the Microsoft files you need is to download the Visual C++ Express Edition package from Microsoft's web site. As I was writing this, this package could be found at http://www.microsoft.com/Express/vc/.

path=c:\hla;%path%

set hlalib=c:\hla\hlalib

set hlainc=c:\hla\include

What You’ve Just Done

Before describing how to write, compile, and run your very first HLA program, it's probably worthwhile to take a quick step back and carefully consider what we've accomplished in the previous section so it's easier to troubleshoot problems that may come up. This section describes how the steps you went through in the previous section affect the execution of HLA.

Note: This section is of interest to all HLA users; whether you've installed HLA via the HLASETUP.EXE program, or you've manually installed HLA.

In order to execute HLA.EXE, HLAPARSE.EXE, and various other programs needed to compile and run an HLA program, the operating system needs to be able to find all of the executable files that HLA needs. In theory, the executable files that HLA needs could be spread out all over your system as long as you tell the OS where to find every file. In practice, however, this makes troubleshooting the setup a lot more difficult if something goes wrong. Therefore, it's best to put all the necessary executables in the same directory.

Note that Windows doesn't know anything at all about the "C:\HLA" subdirectory; it's not going to automatically look for the HLA executables in this subdirectory, you have to tell Windows about this directory. This is done using the "PATH" environment variable. Whenever you tell Windows to run a program from a command prompt window, the OS first looks for a program with the given name in the current directory. If it finds the program (with a ".EXE", ".COM", or ".BAT" suffix), it will run that program from the current directory. If it does not find a program with an allowable suffix in the current directory, then the OS will use the PATH environment variable to determine which subdirectories to search through in order to find the executable program. The PATH environment variable is a (possibly empty) string that takes the following form:

pathToDirectory; pathToDirectory; pathToDirectory;...

Each pathToDirectory item in the list above generally represents a full path to some directory in the system. Examples include "c:\hla", "c:\windows", and "c:\bin"; these are always paths to directories, not to individual files. A PATH environment string may contain zero or more such paths; if there are two or more paths, each subdirectory path is separated from the others by a semicolon (the ellipses ["..."] above signify that you may have additional paths in the string, you don't actually place three periods at the end of the list).

When Windows fails to find an executable program you specify on the command line in the current directory, it tries searching for the executable file in each of the directory paths found in the PATH environment variable. Windows searches for the executable file in the subdirectories in the order that they appear in the environment string. That is, it searches for the executable in the first directory path first; if it doesn't find the executable in that directory, it tries the second path in the environment string; then the third, then the fourth, etc. If Windows exhausts the list of directory paths without finding the executable file, it displays an error message. For example, suppose your PATH environment variable contains the following:

c:\hla; c:\windows; c:\bin

If you type the command "xyz file1" at the command line prompt, Windows will first search for program "xyz.exe", "xyz.com", or "xyz.bat" in the current directory. Failing to find the program there, Windows will search for "xyz" in the "C:\HLA" subdirectory. If it's not there, then Windows tries the "C:\WINDOWS" subdirectory. If this still fails, Windows tries the "C:\BIN" subdirectory. If that fails, then Windows prints an error message and returns control to the command line prompt. If Windows finds the "xyz" program somewhere along the way, then Windows runs the program and the process stops at the first subdirectory containing the "xyz" program.

The search order through the PATH environment string is very important. Windows will execute the first program whose name matches the command you supply on the command line prompt. This is why the previous section had you put "c:\hla;" at the beginning of the environment string; this causes Windows to run programs like HLA.EXE, HLAPARSE.EXE, and LINK.EXE from the "C:\HLA" subdirectory rather than some other directory. This is very important! For example, there are many versions of the "LINK.EXE" program and not all of them work with HLA (and chances are pretty good that you might find an incompatible version of LINK.EXE on your system). Were you to place the "C:\HLA" directory path at the end of the PATH environment string, the system might execute an incompatible version of the linker when attempting to compile and link and HLA program. This generally causes the HLA compilation process to abort with an error. Placing the "C:\HLA" directory path first in the PATH environment variable helps avoid this problem1.

By specifying the PATH environment variable, you tell Windows where it can find the executable files that HLA needs in order to compile your HLA programs. However, HLA and LINK also need to be able to find certain files. You may specify the location of these files explicitly when you compile a program, but this is a lot of work. Fortunately, both HLA.EXE and LINK.EXE also look at some environment variable strings in order to find their files, so you can specify these paths just once and not have to reenter them every time you run HLA.

Most HLA programs are not stand-alone projects. Generally, an HLA program will make use of routines found in the HLA Standard Library. The HLA Standard Library contains many conversion routines, input/output routines, string functions, and so on. Calling HLA Standard Library routines saves a considerable amount of effort when writing assembly language code. However, the HLA compiler isn't automatically aware of all the routines you can call in the HLA Standard Library. You have to explicitly tell the compiler to make these routines available to your programs by including one or more header files during the compilation process. This is accomplished using the HLA #include and #includeonce directives. For example, the following statement tells the HLA compiler to include all the definitions found in the "stdlib.hhf" header file (and all the header files that "stdlib.hhf" includes):

#include( "stdlib.hhf" )

When HLA encounters a #include or #includeonce directive in the source file, it substitutes the content of the specified file in place of the #include or #includeonce . The question is "where does this file come from?" If the string you specify is a full pathname, HLA will attempt to include the file from the location you specify; if it cannot find the file in the specified directory, the HLA will report an error. E.g.,

#include( "c:\myproject\myheader.hhf" )

In this example, HLA will look for the file "myheader.hhf" in the "c:\myproject" directory. If HLA fails to find the file, it will generate an error during compilation.

If you specify a plain filename as the #include or #includeonce argument, then HLA will first attempt to find the file in the current directory (the one you're in when you issued the HLA command at the command line prompt). If HLA finds the file, it substitutes the file's contents for the include directive and compilation continues. If HLA does not find the file, then it checks out the "HLAINC" environment variable, whose definition takes the following form:

hlainc=c:\hla\include

Unlike the PATH environment variable, the HLAINC environment variable allows only a single directory path as an operand. This is the path to the HLA include files directory which is "c:\hla\include" (assuming you've followed the directions in the previous section). Since HLA header files usually come in two varieties: headers associated with library routines and header files associated with the current project, the fact that HLA only provides one include path is not much of a limitation. You should keep all project-related header files in the same directory as your other source files for the project; the HLA library header files (and header files for any generic library modules you write) belong in the "C:\HLA\INCLUDE" directory. Note that if you want to place header files in some directory other than the current directory or the directory that the HLAINC environment variable specifies, you will have to specify the path to the include file in the #include or #includeonce statement.

If HLA cannot find the specified header file in the current directory or in the directory specified by the HLAINC environment variable, then you'll get an error to the effect that HLA cannot find the specified include file. Needless to say, most assemblies will fail if HLA cannot find the appropriate header files.

Since most HLA programs will use one or more of the HLA Standard Library header files, chances are good that the assembly won't be successful if the HLAINC environment string is not set up properly. Conversely, if HLAINC does contain the appropriate string, then HLA will be able to successfully compile your code to assembly and produce an object file. The last step, converting the object file to an executable, introduces another possible source of problems. The LINK program combines the object file associated with your code with HLA Standard Library and Windows library modules. Even the most trivial HLA program will need to link with (at least) one or more Windows module. (The most trivial HLA program is probably the one that immediately returns to the OS; such a program needs to call the Windows ExitProcess API in order to return to the OS, so at the very least you'll need to link with the Windows' kernel32.lib module to be able to call ExitProcess.) Once again, however, the library modules that your program needs could be anywhere on the disk. You'll have to use some environment variables to tell HLA and the linker where it can find the library modules. You accomplish this using two environment variables: one for HLA and one for the linker. We'll discuss the HLALIB environment variable first, since it's the easiest to understand.

The HLA Standard Library contains thousands of small little routines that have been combined into a single file: either "hlalib.lib" or "hlalib_safe.lib" ("hlalib_safe.lib" is a thread-safe version of the library). Whenever you call a particular standard library routine, the linker extracts the specific routine you call from the "hlalib.lib" library module and links combines this code with your program. Once again, the linker program and HLA don't know where to find these files, you have to tell them where to find it. This is done using the HLALIB environment variable; this environment variable contains a single pathname to the directory containing the HLALIB.LIB and HLALIB_SAFE.LIB files. I.e.,

set hlalib=c:\hla\hlalib

The important thing to note in this example is that the path is the directory containing the HLALIB.LIB and HLALIB_SAFE.LIB files. This is in direct contrast to the PATH and HLAINC environment objects where you specify only the subdirectory containing the desired files.

In addition to the "hlalib.lib" or "hlalib_safe.lib" library file, you'll also need to link your HLA programs against various Windows library modules. Basic HLA programs will need to link against the Windows' kernel32.lib, user32.lib, and gdi32.lib library modules. These library modules (plus several other Windows related library modules) are found in the Visual C++ Express Edition package; though in the previous section you should have copied these three library modules to the "c:\hla\hlalib" subdirectory. You'll need to tell the LINK.EXE program where it can find these files; this is done with the LIB environment variable. The LIB environment variable's syntax is very similar to that for the PATH environment variable. You get to specify a list of directories in the LIB environment string and LINK.EXE will automatically search for missing library files in each of the paths you specify, in the order you specify, until it finds a matching filename. By prepending "c:\hla\hlalib;" to this environment string, you tell LINK.EXE to search for library modules like KERNEL32.LIB, USER32.LIB, and GDI32.LIB in the "C:\hla\hlalib" subdirectory if it doesn't find them in the current subdirectory.

Note: in the future, if you make other Win32 API calls you may need to copy additional .LIB files from the Visual C++ Express Edition package to the "c:\hla\hlalib" directory. However, for most basic HLA programs (and certainly, all console mode programs) you won't need to do this. Another alternative would be to add the path to the Visual C++ Express Edition's libraries directory to the LIB environment string (generally, installing the Visual C++ Express Edition package does this for you).

Okay, with this explanation out of the way, it's time to write, compile, and run, our first HLA program!

Running HLA

Now it’s time to try your hand at writing an honest to goodness HLA program and verify that the whole system is working. A long running convention is to write a "Hello World" programs as the very first program in any language. This document will continue that cherished convention.

When writing HLA programs, the best approach is to create a single directory for each project you write. I'd suggest creating a subdirectory "c:\hla\projects" and then create a new subdirectory inside "c:\hla\projects" for each HLA project you write. For the example in this section, you might create the directory "c:\hla\projects\hw" (for "Hello World").

There are many different ways to create project directories. The most common way to do this under Windows is in the Windows Explorer (right click on a window and select "NewFolder"). However, since HLA is a command-line based tool, it's probably best to describe how to do this from the command line just to introduce some (possibly) new command line commands.

The first step is to bring up a command prompt window. Generally, this is done by selecting "StartProgramsAccessoriesCommand Prompt" from the Windows Start menu. You may also select "StartRun" and type "cmd" to bring up a command prompt shell. If you wind up writing a lot of HLA code, you'll want a faster and easier way to run the command prompt, so I recommend creating a shortcut to the command prompt program on your desktop. To do this, click on Start (and release the mouse button) and then move the mouse cursor to select "ProgramsAccessoriesCommand Prompt". Now right-click on the "Command Prompt" menu item and drag it onto your desktop. Select "Create Shortcut(s) Here" from the resulting pop-up menu.

If you're using Visual C++ Express Edition, then select StartProgramsMicrosoft Visual C++ Express EditionVisual Studio Tools and right-click on that menu item. You should now have a shortcut to the command prompt shell program on your desktop and you can easily run the shell by double-clicking on its icon.

After creating a shortcut, there are some things you can do to make HLA development a little easier. Right click on the shortcut you've just created and select properties. In the window that pops up, you'll probably want to change the "Start In:" string to "c:\hla\projects". This tells the command prompt program to make the specified path the current directory whenever you run the command prompt program by double clicking on this icon. By starting off inside the c:\hla\projects" subdirectory, you'll find that you save some typing (assuming, of course, you wind up putting most of your projects in the "c:\hla\projects" directory; use a different path here if this is not the case). Next, select the "layout" tab in the Command Prompt Properties window. Under "Screen Buffer Size" you'll probably want to make the value larger (the default is 300 on my system). I've found that 3000 is a good number here. This number specifies the number of lines of text that the system will save when data scrolls off the top of the screen. This lets you view up to 3,000 lines of text from program execution (including your program's output, HLA error messages, etc.). If you have a large monitor, you might also want to change the Window Size values as well. The default of 80 columns is probably fine, though you may want to expand the height to 50 lines (or whatever your monitor allows). Once you've set up the command window properties, double-click on the command prompt icon to start it running.

The first step, before doing anything else, is to verify that you've properly set up the environment variables. To check out their values, simply type "set" followed by ENTER. The "set" command without any parameters tells the command shell to dump the current values of all the environment variables active in the command prompt window. Scan through this list (scrolling back using the scroll bar if there are too many definitions to all fit in the window at one time) and search for the PATH, LIB, HLAINC, and HLALIB environment variables. Verify that they are present and have the correct values (that you've entered in the previous sections). If they're not present or the values are incorrect, HLA will not execute properly and you need to fix this problem first (Win95/98 users, did you remember to reboot after changing the autoexec.bat file?). If the environment variables are present and correct, then you're ready to try writing and running your first HLA program.

Switch to the "C:\HLA" subdirectory by using the following DOS (command line prompt) command5:

 

cd c:\hla

"cd" stands for "Change Directory". This command expects a single command line parameter that is the path of the directory that you want to make the "current directory." If you ever want to know what the current directory is, simply type "cd" without any parameters and the Command Shell will display the current directory6

If you haven't done so already, it's time to create the "projects" directory where you will place the HLA projects you create. Do this with the following command:

 

mkdir projects

"mkdir" stands for "make directory" and this command requires a single argument - the name of the directory you want to create. If you do no specify a full pathname (as is the case in this example), the command shell creates the directory within the current directory. Verify that you've properly created the "projects" subdirectory by issuing the following DOS command:

 

dir

"dir" stands for "directory" and tells the command shell to display all the files in the current directory. This should list the projects directory you just created (plus all the other files and directories in the "c:\hla" directory). Note that you can also use the Windows Explorer to create directories and view the contents of directories. However, since HLA is a command prompt based application, it's useful to learn a few commands that will prove useful.

Now, switch to the projects subdirectory by using the following command:

 

cd projects

At this point, it's a good idea to create a new subdirectory for the "Hello World" project. Do this by using the following command:

 

mkdir hw

CD into this directory once you've created it. Now you're ready to begin work on the "Hello World" program.

Leaving the command prompt Window open for the time being, run the editor of your choice (e.g., NOTEPAD.EXE, if you don't have any other preferences). Enter the following HLA program into the editor:

program HelloWorld;

#include( "stdlib.hhf" )

begin HelloWorld;

stdout.put( "Hello, World of Assembly Language", nl );

end HelloWorld;

Scan over the program you've entered and verify that you've entered it exactly as written above. Save the file from your editor as hw.hla in the c:\hla\projects\hw subdirectory (generally using the File>Save or File>Save As menu item in the editor). Switch over to the command prompt window and verify that the file is present by issuing a "DIR" command; this should list the hw.hla file. If it's not present, try saving the file again and be sure to browse to the "c:\hla\projects\hw" before actually saving the file.

WARNING: NOTEPAD.EXE has a habit of tacking a ".txt" to the end of filenames you save to disk. Default installations of Windows do not display file suffixes of known file types (and ".txt" is a known type). Therefore, a directory window may show a program name like "hw.hla" when, in fact, the filename is "hw.hla.txt". Probably the number one problem people have when testing out their HLA installation is that the compiler claims it cannot find the file "hw.hla" when the user first attempts to compile the file. This is because it's really named "hla.hw.txt". You must change the filename to "hw.hla" before HLA will accept it. This is the number one HLA installation problem. Watch out for this!

Make sure you’re in the same directory containing the HW.HLA file and type the following command at the "C:>" prompt: "HLA -v HW". The "-v" option tells HLA to produce VERBOSE output during compilation. This is helpful for determining what went wrong if the system fails somewhere along the line. This command should output similar to the following (this changes all the time, so don’t expect it to be exact):

HLA (High Level Assembler)

Use '-license' to see licensing information.

Version 2.0 build 407 (prototype)

Win32 COFF output

OBJ output using HLA Back Engine

-test active

HLA Lib Path: g:\hla\hlalib\hlalib.lib

HLA include path: g:\hla\hlalibsrc\trunk\hlainc

HLA temp path:

Linker Lib Path: C:\Program Files\Microsoft Visual Studio 9.0\VC\LIB;C:\Program Files\Microsoft SDK

s\Windows\v6.0A\lib;g:\hla\hlalib;g:\hla\hlalib

Files:

1: hw.hla

Compiling 'hw.hla' to 'hw.obj'

using command line:

[hlaparse -WIN32 -level=high -v -ccoff -test "hw.hla"]

----------------------

HLA (High Level Assembler) Parser

use '-license' to view license information

Version 2.0 build 406 (prototype)

-t active

File: hw.hla

Output Path: ""

hlainc Path: "g:\hla\hlalibsrc\trunk\hlainc"

hlaauxinc Path: "(null)"

Compiler running under Windows OS

Back-end assembler: HLABE

Language Level: high

Compiling "hw.hla" to "hw.obj"

Compilation complete, 18758 lines, 0.454 seconds, 41317 lines/second

------------

HLA Back Engine Object code formatter

HLABE compiling 'hw.hla' to 'hw.obj'

Optimization passes: 3+2

----------------------

Linking via [link @"hw.link._.link"]

Microsoft (R) Incremental Linker Version 9.00.30729.01

Copyright (C) Microsoft Corporation. All rights reserved.

-heap:0x1000000,0x1000000

-stack:0x1000000,0x1000000

-base:0x4000000

-entry:HLAMain

-section:.text,ER

-section:.data,RW

-section:.bss,RW

kernel32.lib

user32.lib

gdi32.lib

-subsystem:console

-out:hw.exe

g:\hla\hlalib\hlalib.lib

hw.obj ctl32.dll

If you get output that is similar to the above, you’re in business.

Manually installing HLA is a complex and slightly involved process. Fortunately, the hlasetup.exe program automates almost everything so that you don’t have to worry about changing registry settings and things like that. If you’re a first-time HLA user, you definitely want to use this method to install HLA. Manual installation is really intended for upgrades as new versions of HLA appear. You do not have to change the environment variables to install a new version of HLA, simply extract the executable files over the top of your existing installation and everything will work fine.

The most common problems people have running HLA involve the location of the Win32 library files, the choice of linker, and appropriately setting the hlalib, halib_safe, and hlainc environment variables. During the linking phase, HLA (well, link.exe actually) requires the kernel32.lib, user32.lib, and gdi32.lib library files. These must be present in the pathname(s) specified by the LIB environment variable. If, during the linker phase, HLA complains about missing object modules, make sure that the LIB path specifies the directory containing these files. If you’re a Microsoft Visual C++ user, installation of VC++ should have set up the LIB path for you. If not, then locate these files and copy them to the HLA\HLALIB directory. If these files are not present on your system, you should download the Microsoft Visual C++ Express edition to obtain them.

Another common problem with running HLA is the use of the wrong link.exe program. Microsoft has distributed several different versions of link.exe; in particular, there are 16-bit linkers and 32-bit linkers. You must use a 32-bit segmented linker with HLA. If you get complaints about "stack size exceeded" or other errors during the linker phase, this is a good indication that you’re using a 16-bit version of the linker. Obtain and use a 32-bit version and things will work. Don’t forget that the 32-bit linker must appear in the execution path (specified by the PATH environment variable) before the 16-bit linker. HLA ships with a copy of the Pelles linker (polink.exe) that you can use if you've not downloaded Microsoft's Visual C++ Express edition.

Standard Configurations Under Windows

The "standard" HLA configuration under Windows consists of HLA.EXE, HLAPARSE.EXE, PORC.EXE, and POLINK.EXE. This standard configuration generates object files directly, compiles any resource files using the Pelles C PORC.EXE resource compiler, and links the object modules together using the Pelles C linker (POLINK). The Pelles C tools were chosen for the standard configuration under Windows because they are freely distributable (unlike the Microsoft tools). For those who care about such things, HLAPARSE.EXE produces object modules directly. HLA's object code generator produces slightly more optimal output code than FASM, MASM, or TASM.

HLA also provides the ability to produce assembly language source files, in a MASM, TASM, FASM, NASM, or Gas format that can be assembled to object code using one of these assemblers. So why would anyone want to have HLA produce assembly language output to be run through a different assembler (much like GCC does)? For common applications, there is no need to do this. However, in some specialized situations having this facility is quite useful. For example, rather than using the HLA back-engine native code generator, you may elect to have HLA generate FASM source code to be processed by the FASM assembler. There are three reasons for doing this:

  • You want to see how HLA would translate the HLA program (in HLA syntax) to a lower-level assembly language (in FASM syntax); this is great, for example, for seeing how macros expand or how HLA processes high-level control constructs.
  • If there is a defect in the internal HLA back engine that prevents HLA from directly generating an object code file, you can probably produce a source file and successfully compile your program using the external version of FASM.

 

Another configuration is to have HLA produce a MASM compatible output file and use Microsoft’s MASM to translate that output source file into an object file. There are several reasons why you might want to use MASM:

  • MASM can inject additional symbolic debugging information (usable by Visual Studio’s debugger) into the object file, making it easier to debug HLA applications using Visual Studio.
  • FASM and HLABE may have some code generation defect that you can’t work around.
  • The HLA back engine’s output might not be completely compatible with some other object module tool you’re using.
  • You want to take HLA output and merge it with some MASM projects you have.

Although MASM is not a freely distributable program (and, therefore, is not included in the HLA download), you may download a free copy from the Microsoft Web site or obtain a copy as part of the Visual C++ Express Edition package.

 

Another configuration is to have HLA produce a NASM compatible output file and use the Netwide Assembler (NASM) to translate that output source file into an object file. There are a couple of reasons why you might want to use NASM:

  • HLA's back engine may have some code generation defect that you can’t work around.
  • HLA's back engine output might not be completely compatible with some other object module tool you’re using and NASM's output is compatible.
  • You want to take HLA output and merge it with some NASM projects you have.
  • You want to compile the code on an operating system that supports NASM but doesn't directly support HLA.

One last assembler choice under Windows is Borland’s Turbo Assembler (TASM). There is one main reason why you would want to use TASM to process HLA output - you want to link HLA output with a Borland Delphi project. Delphi is very particular about the object files it will link against. Effectively, you can only use TASM-generated output files when linking with Delphi code. Therefore, if you want to link your HLA modules into a Delphi application, you’ll need to use the TASM output mode. Like MASM, TASM is not a freely distributable product and cannot be included as part of the HLA download. However, Borland will provide a free copy as part of their free C++ download on their website (registration required). Note that TASM support in HLA has been deprecated and stop functioning as time passes.

Under Windows, you may use either the freely distributable Pelle’s C linker (Polink) or the Microsoft linker to process the object code output from the HLA system. Polink is provided with the HLA download (subject, of course, to the Pelles C license agreement). Microsoft’s linker is a commercial product (and as such, it is not included as part of the HLA download), but it is available as a free download from Microsoft’s web site and as part of the Visual C++ express edition package. HLA will use either linker as the final stage in producing an executable. The Microsoft linker has been around longer and has, arguably, fewer bugs than Polink, but the choice is yours. Another possible linker option is the Borland Turbo linker (TLINK). Just note that HLA.EXE will not automatically run TLINK; you will have to run it manually after producing an OMF object file with HLA. Note that only MASM and TASM are capable of producing OMF files. FASM and HLA’s internal code generator do not generate OMF object code files, so you cannot use TLINK with their output.

To produce libraries, you may optionally employ a librarian such as Microsoft’s LIB.EXE, the Pelle’s C POLIB.EXE, or Borland’s Turbo Librarian (TLIB.EXE). The HLA.EXE program does not automatically run these programs; you will have to run them manually to create a .LIB file from your object files. Please see the documentation for these products for details on their use. The HLA download includes the POLIB.EXE program and the HLA standard library source code includes a make file option that will use any of these three librarians to produce the HLA hlalib.lib library file.

Note that it is possible to mix and match modules in the HLA system, within certain reasonable limitations. For example, you could use the FASM assembler and the Microsoft linker, the TASM assembler and the POLINK linker, or even the MASM assembler the TLINK linker. In general, FASM output works fine with the Microsoft linker and librarian or the Pelle’s C linker and librarian, MASM output works best with Microsoft’s linker and librarian, and Turbo assembler works best with the Borland tools or the Microsoft tools.

Under Windows, the default configuration is to generate an MSCOFF object file directly and use the POLINK linker to process the resulting object file(s). See the section on "Customizing HLA" for details on changing the default configuration.

Installing HLA Under Linux, Mac OSX, or FreeBSD (*NIX)

HLA is a compiler that translates source code into either object code or a lower-level assembly language that Gas (GNU’s as assembler) must process. After compilation, you must link the object code output using a linker program such as the GNU ld linker. Typically, you will link the object code produced by one or more HLA source files with the HLA Standard Library (hlalib.a). Most of this activity takes place transparently whenever you ask HLA to compile your HLA source file(s). However, for the whole process to run smoothly, you must have installed HLA and all the support files correctly. This section will discuss how to set up HLA on your system.

These instructions assume that you are using the BASH command-line shell program. If you are using a different command-line shell interpreter, you may need to adjust some of the following instructions accordingly. Note that you can run the BASH interpreter from just about any command-line shell by typing "bash" at the command line.

Mac OSX users note: the terminal window, by default, does not run the BASH shell command interpreter. You should explicitly run BASH by typing "bash" at the command-line prompt when you open up a terminal window.

First, you will need an HLA distribution for Linux, Mac OSX, or FreeBSD. Please see Webster or the previous section if you’re attempting to install HLA on a different OS such as Windows. The latest version of HLA is always available on Webster at http://webster.cs.ucr.edu. You should go there and download the latest version if you do not already possess it.

Under Linux, Mac OSX, and FreeBSD, HLA will produce a low-level assembly language output file that you can assemble using the Free Software Foundation’s Gas assembler. The HLA package contains the HLA compiler, the HLA Standard Library, and a set of include files for the HLA Standard Library. If you write an HLA program want Gas to process it, you’ll need to make sure you have a reasonable version of Gas available (Gas is available on most *NIX distributions, so this shouldn’t be a problem). Note that the HLA Gas output can only be assembled by Gas v2.10 or later (so you will need the 2.10 or later binutils distribution).

Here’s the steps I went through to install HLA on my Linux, Mac OSX, and FreeBSD systems:

  • First, if you haven’t already done so, download the HLA executables file (for Linux, Mac OSX, or FreeBSD) from Webster at http://webster.cs.ucr.edu. On Webster you can download several different tar.gz files associated with HLA from the HLA download page. The "Linux Executables", "Mac Executables", or "FreeBSD executables" is the only one you’ll absolutely need; however, you’ll probably want to grab the documentation and examples files as well. If you’re curious, or you want some more example code, you can download the source listings to the HLA Standard Library. If you’re really curious (or masochistic), you can download the HLA compiler source listings to (this is not for casual browsing!).
  • I downloaded the linux.hla.tar.gz (for Linux), mac.hla.tar.gz (for Mac OSX), or bsd.hla.tar.gz (for FreeBSD) file for HLA v2.2 while writing this. Most likely, there is a much later version available as you’re reading this. Be sure to get the latest version. I chose to download this file to my "/usr/hla" directory; you can put the file wherever you like, though this documentation assumes that all HLA files wind up in the "/usr/hla/..." directory tree. Note: the .tar.gz file downloads into /usr/hla. If you want the files placed somewhere else, unpack them to this directory and then move them.
  • After downloading linux.hla.tar.gz, mac.hla.tar.gz, or bsd.hla.tar.gz to my root directory, I executed the following shell command: "gzip -d linux.hla.tar.gz" ("gzip -d bsd.hla.tar.gz" under FreeBSD; "gzip -d mac.hla.tar.gz" for Mac OSX). Once decompression was complete, I extracted the individual files using the command "tar xvf linux.hla.tar" ("tar xvf bsd.hla.tar" under FreeBSD, "tar xvf mac.hla.tar" under Mac OSX). This extracted several executable files (e.g., "hla" and "hlaparse") along with three subdirectories (include, hlalib, and hlalibsrc). The HLA program is a "shell" program that runs the HLA compiler (hlaparse), gas (as), the linker (ld), and other programs. You can think of hla as the "HLA Compiler". It would be a real good idea, at this point, to set the permissions on "hla" and "hlaparse" so that everyone can read and execute them. You should also set read and execute permissions on the two subdirectories and read permissions on all the files within the directories (if this isn’t the default state). Do a "man chmod" from the Linux/Mac OSX/FreeBSD command-line if you don’t know how to change permissions.
  • If you prefer a more "Unix-like" environment, you could copy the hla and hlaparse (and other executable) files to the "/usr/bin" or "/usr/local/bin" subdirectory. This step, however, is optional
  • Next, (logged in as a plain user rather than root or the super-user), I edited the ".bashrc" file in my home directory ("/home/rhyde" in my particular case, this will probably be different for you). I found the line that defined the "path" variable, it originally looked like this on my system:

 


PATH=$DBROOT/bin:$DBROOT/pgm:$PATH

I edited this line to add the path to the HLA directory, producing the following:



PATH=$DBROOT/bin:$DBROOT/pgm:/usr/hla:$PATH

Without this modification, *NIX will probably not find HLA when you attempt to execute it unless you type a full path (e.g., "/usr/hla/hla") when running the program. Since this is a pain, you’ll definitely want to add "/usr/hla" to your path. Of course, if you’ve chosen to copy hla and hlaparse to the "/usr/bin" or "/usr/local/bin" directory, chances are good you won’t have to change the path as it already contains these directories.


hlalib=/usr/hla/hlalib
export hlalib

hlainc=/usr/hla/include
export hlainc


These four lines define (and export) environment variables that HLA needs during compilation. Without these environment variables, HLA will probably complain about not being able to find include files, or the linker (ld) will complain about strange undefined symbols when you attempt to compile your programs. Note that this step is optional if you leave the library and include files installed in the /usr/hla directory subtree.



hlatemp=/tmp
export hlatemp

After saving the ".bashrc" shell, you can tell *NIX to make the changes to the system by using the command:


source .bashrc

Note: this discussion only applies to users who run the BASH shell. If you are using a different shell (like the C-Shell or the Korn Shell), then the directions for setting the path and environment variables differs slightly. Please see the documentation for your particular shell if you don’t know how to do this.

program HelloWorld;

#include( "stdlib.hhf" )

begin HelloWorld;

stdout.put( "Hello, World of Assembly Language", nl );

end HelloWorld;

HLA (High Level Assembler)

Use '-license' to see licensing information.

Version 2.0 build 411 (prototype)

ELF output

GAS output

-test active

HLA Lib Path: /usr/hla/hlalib/hlalib.a

HLA include path: /usr/hla/include

HLA temp path:

Files:

1: hw.hla

Compiling 'hw.hla' to 'hw.asm'

using command line:

[hlaparse -LINUX -level=high -v -sg -test "hw.hla"]

----------------------

HLA (High Level Assembler) Parser

use '-license' to view license information

Version 2.0 build 411 (prototype)

-t active

File: hw.hla

Output Path: ""

hlainc Path: "/usr/hla/include"

hlaauxinc Path: "(null)"

Compiler running under Linux OS

Back-end assembler: GAS

Language Level: high

Compiling "hw.hla" to "hw.asm"

Compilation complete, 25444 lines, 0.122 seconds, 208557 lines/second

----------------------

Assembling "hw.asm" to "hw.o" via [as --32 -o hw.o "hw.asm"]

Linking via [ld -o "hw" "hw.o" "/usr/hla/hlalib/hlalib.a"]

Versions of HLA may appear for other Operating Systems (beyond Windows, Linux, FreeBSD, and Mac OSX) as well. Check out Webster to see if any progress has been made in this direction. Note a unique thing about HLA: Carefully written (console) applications will compile and run on all supported operating systems without change. This is unheard of for assembly language! Therefore, if you are using multiple operating systems supported by HLA, you’ll probably want to download files for all supported OSes.

Standard Configurations under Linux/FreeBSD/Mac OSX

HLA supports fewer configurations under Linux, FreeBSD, and Mac OSX than under Windows but this is primarily because the main tools available for *NIX (Linux/FreeBSD/MacOSX) are all freely distributable and there is no need to support commercial tools. There are three different ways to generate object code files and only one linker and one librarian option available under Linux. There is no resource compiler (that HLA would automatically use).

HLA can generate object files in one of two different ways under *NIX:

  • The hlaparse program can generate an ELF object file directly.
  • The hlaparse program can generate a Gas-compatible source file that the FSF Gas assembler can convert to an ELF file.

As this was being written, HLA under Mac OS X only generates Gas-compatible source files that the Gas assembler converta to Mach-o object files. Direct output of mach-o object files should appear in HLA v2.3.

Under *NIX you don’t get a choice of linkers. Everyone uses the FSF/GNU ld (load) program as the standard system linker. The HLA package also uses ld. In a similar vein, your only librarian choice is the FSF/GNU ar (archive) program. These tools work great and they’re freely distributable, so they’re the perfect back ends to the HLA system.

Non-Standard Configurations under Windows and Linux

It is possible, though uncommon, to use HLA in ways that aren’t 100% compatible with the underlying operating system. For example, under Windows you can use HLA to produce a Gas-compatible assembly language source file. Likewise, you can use HLA under Linux to produce a MASM or TASM compatible assembly language source file. However, note that when HLA produces a Gas file, it includes certain start-up code that is only appropriate for Linux; this is true even if you do this under Windows. Similarly, producing a MASM or TASM source file includes start-up code that is only appropriate for Windows, even if the file is produced under Linux. So even if it were possible to run these products under the "wrong" operating system (e.g., MASM under Linux), the resulting object files would not be in a format acceptable to the OS and the code emitted by the HLA compiler wouldn’t run properly. Nevertheless, if you just want to view the assembly language file that HLA produces, it doesn’t really matter what operating system you’re running under, so you may as well pick an output format with which you are most comfortable.

Customizing HLA

With environment variables you can create a customized version of HLA that suits your particular needs. The following subsections describe different ways you can optimize HLA for your personal use.

Changing the Location of HLA

To simplify installation and reduce installation problems, this manual suggests that you install HLA under Windows in the C:\hla subdirectory and install HLA under *NIX in the /usr/hla subdirectory. If you would prefer to put the HLA system somewhere else, it’s easy to do as long as you tell the system what you’re doing. This is typically accomplished by setting up a couple of environment variables.

First, to be able to run the HLA compiler and associated tools, the hla.exe/hla, hlaparse.exe/hlaparse, back-end assembler (if applicable), and linker all have to be in directories in the execution path. You may either move the HLA executables to some existing directory in the OS’ execution path, or you can tell the OS to include the directory containing these files in the execution path. The standard HLA installation instructions, for example, opt for this latter case.

Under *NIX, for example, it’s not uncommon for someone to put executables in the /usr/bin or /usr/local/bin directories. These directories are always in the execution path, so placing all the HLA executables in one of these directories under *NIX would spare you having to add the /usr/hla subdirectory to your execution path.

Under Windows, there is no special directory where everyone dumps their little executable files (like /usr/local/bin under *NIX). You could find an existing directory that’s in the execution path and dump the HLA executables in there, however it’s almost always a better idea to simply change the path environment variable so that it includes the HLA directory that contains the executables. If you’ve installed HLA via the HLA installation program, the install program automatically sets this up for you. However, if you want to move HLA to a different directory in the future, you will need to remove the old path to HLA from your PATH environment variable and add the path to the new HLA executables to the PATH.

Changing the execution path isn’t your only concern if you decide to move HLA around. The HLA compiler will also need to know where it can find the HLA include files and the hlalib.lib standard library files. Under Windows, the linker might also want to know where the hlalib.lib file can be found. If you haven’t told it otherwise, HLA under *NIX assumes that the include subdirectory and the hlalib subdirectory can be found in the /usr/hla subdirectory. Under Windows, HLA will first look in the same directory containing the HLA executables and, failing to find the include and hlalib directories there, it will then look in the C:\HLA subdirectory. If you’ve moved the HLA include and hlalib directories somewhere else, then you will need to set up environment variables to tell HLA where it can find these directories (technically, you could specify the paths to these directories on the HLA command-line, but that’s so painful that you would never consider it for anything other than a temporary solution). The "hlainc" and "hlalib" environment variables serve this purpose.

Windows:

set hlainc=path_to_include_directory

Linux/FreeBSD/Mac OS (using BASH shell interpreter):

set hlainc=path_to_include_directory

export hlainc

Under Windows or *NIX you can use the set command to set the hlainc environment variable to the path where HLA can find the HLA include subdirectory. For example, if you’re using Windows and you’ve moved the HLA include files to the C:\tools\hla\hlainc subdirectory, you could use the following command to tell HLA where it can find the include file:

set hlainc=c:\tools\hla\hlainc

The hlalib and hlalib_safe environment variables specifie the complete path to the hlalib.lib/hlalib.a and hlalib_safe.lib/hlalib_safe.a files. Unlike the hlainc environment variable, this is not the path to the directory containing the library file, but the full path to the file itself. The reason this is a path to the library file rather than a path to the subdirectory containing the file is very simple: it’s possible to have two or more library modules (in the same directory) and you might want to choose the most appropriate one for the job at hand. For example, you might have a debugging version of the library, an OMF version of the library, and a standard version of the library all in one directory. In any case, suppose the hlalib.a and hlalib_safe.a files (archive files) under *NIX are located at /usr/home/rhyde/hla/hlalib/hlalib.a and /usr/home/rhyde/hla/hlalib/hlalib_safe.a; you could tell Linux/FreeBSD/Mac OSX about this using BASH commands like the following:

set hlalib=/usr/home/rhyde/hla/hlalib/hlalib.a

export hlalib

set hlalib_safe=/usr/home/rhyde/hla/hlalib/hlalib_safe.a

export hlalib_safe

(export is a bash command that tells it to make the environment variable available to the invoking shell.)

Setting Auxiliary Paths

When assembling HLA source files using a back-end assembler such as MASM, FASM, Gas, or NASM, it emits a couple intermediate files for use by these back-end assemblers and the linkers. Specifically, the compilation process produces a ".asm" file for the assembler and a ".link" file for the linker. Some HLA users feel that these auxiliary files clutter up their project directory and would prefer not to see them. Fortunately, there are a couple of different ways to tell HLA to put these files in some other location besides the current project directory.

The first way to do this (which isn’t really the subject of this section) is to use the "-p:<path>" command-line option to provide a temporary path for HLA to use. The advantage to using this command-line parameter is that you can set a different temporary path for each compilation. The disadvantage to this approach is that it can be a real pain to constantly set the path (if you’re typing command lines manually).

A more comprehensive solution is to define the hlatmp environment variable. When HLA runs, it checks this environment variable and, if defined, uses its value to determine the path to the directory where HLA will store all temporary files. This spares you from having to place an explicit path on each command line. For example, the following command line will tell HLA to use the C:\temp (under Windows) subdirectory to hold all temporary files:

set hlatmp=c:\temp

Do take care when using the hlatmp environment variable. If you compile multiple source files with the same name (presumably from different directories), then the intermediate files they produce may create conflicts. In other words, don’t use the hlatmp environment variable to specify a temporary path when doing several compilations in a batch operation. Use an explicit "-p:<path>" command-line option in those cases.

Setting the Default Back-End Assembler

By default, the "HLA.EXE" (Windows) or "hla" (*NIX) programs use the HLA back engine to directly produce an object file from the translation of the input HLA source file. For reasons explained earlier, you might want to override this default selection and use one of the back-end assemblers that HLA supports (MASM, TASM, NASM, or FASM under Windows, or GAS under *NIX). There are two ways to do this: via command-line parameters or by the "hlaopt" environment variable.

As described earlier, the -hlabe, -masm, -fasm, -nasm, -tasm, -gas, and -gasx command-line options let you specify which assembly language syntax and back-end assembler HLA will use to produce an object code file. The default is "-hlabe" which uses the internal HLA back engine to directly produce an object code file without using an intermediate assembly language file. The other options all produce an intermediate assembly language source file and use the associated assembler (if possible under the current operating system) to translate that assembly language source file into an object code file.

If you would like to change the default so you don’t have to specify a command-line option all the time, you can use the "hlaopt" environment variable to automatically supply that command-line parameter for you. For example:

set hlaopt=-masm

or

set hlaopt=-gas

export hlaopt

Using HLA with the HIDE Integrated Development Environment

This chapter describes two IDEs (Integrated Development Environments) for HLA: HIDE and RadASM.

The HLA Integrated Development Environment (HIDE)

Sevag has written a nice HLA-specified integrated development environment for HLA called HIDE (HLA IDE). This one is a bit easier to install, set up, and use than RadASM (at the cost of being a little less flexible). HIDE is great for beginners who want to get up and running with a minimal amount of fuss. You can find HIDE at the HIDE home page:

http://sites.google.com/site/highlevelassembly/downloads/hide

Contact: sevag.krikorian@gmail.com

Note: the following documentation was provided by Sevag. Thanks Sevag!

 

Description

HIDE is an integrated development environment for use with Randall Hyde's HLA (High Level Assembler). The HIDE package contains various 3rd party programs and tools to provide for a complete environment that requires no files external to the package. Designed for a system-friendly interface, HIDE makes no changes to your system registry and requires no global environment variables to function. The only exception is ResEd (a 3rd party Resource Editor written by Ketil.O) which saves its window position into the registry.

Operation

HIDE is an integrated development environment for use with Randall Hyde's HLA (High Level Assembler). The HIDE package contains various 3rd party programs and tools to provide for a complete environment that requires no files external to the package. Designed for a system-friendly interface, HIDE makes no changes to your system registry and requires no global environment variables to function. The only exception is ResEd (a 3rd party Resource Editor written by Ketil.O) which saves its window position into the registry.

First Execution

The first time you run HIDE you may see 1 to 4 windows open, depending on the initial setup of your current version. At the very least, the main window (the one with the menu-bar) will be open. The visibility of other windows may be altered by selections in the View menu. The windows may be in either floating or docked mode. Floating windows reside outside the main window, while docked windows will be within the main window itself.

Whatever changes you make to the window positions and status will be saved and restored the next time you start HIDE.

The Windows

HIDE is subdevided into several Windows. A collapsable side panel contains several tool windows and an output window below displays execution details.

Editor

The HIDE editor uses KetilO's powerful RAEDIT.dll This window contains 3 buttons along the horizontal scroll bar: From left to right, Show/Hide Line Numbers, Expand All, Collapse All. Expand and Collapse work on folding text which makes navigating large documents easier. HIDE is setup to fold procedures and declaration sections.

There will be a '+' and '-' buttons in the margin to indicate text blocks that can be folded/expanded.

Near the bottom scrollbar, there may be up to three extra buttons, one expands the margin to show line numbers, the other two expand/collapse all blocks in the current window.

When a bookmark is set, you will see a blue square in the margin. Clicking on the square removes the bookmark.

The editor window also has a Splitting Bar along the top of the vertical scroll bar. This allows you to split the view into 2 windows.

Output

This window has two modes selectable by two buttons on the title bar; Notes and Output

While in Output mode, the window displays information on programs that are launched from HIDE. This includes error reports from HLA.

If the Output window is hidden when information needs to be displayed, it is opened temporarily and then closed. To cancel the automatic closing timer, click anywhere in the Output window.

While in Note mode, the window displays an edit control used for saving project notes. These notes are saved directly in the project file (.hpr)

Tool Bar

Contains various buttons for quick access. All of these also have menu-bar counterparts.

Tab Bar

When more than 1 file is open, The Tab Bar contains the filenames of all the open files allowing quick navigation.

Status Bar

Status bar contains various information on HIDE modes and files.

From the left:

Line number, number of lines

INS/OVR, insert/overwrite mode

No Project/Project, indicates if a project is currently active

Release/Debug, inidcates if "Debug" or "Release" is selected in Compiler Settings

Info, if the pointer/cursor hovers over a recognized property, the property information is displayed here.

Panel

The collapsable panel on the right side hosts several windows.

Each window has a Pin button and a Close button.

To undock a window from the Panel, simply click on the title bar and drag the window out.

To dock a window back into the Panel, click its tab on the Panel.

If you wish to anchor an undocked window so that it does not dock when the its Panel tab is clicked, click on the Pin button. The icon will change to indicate window is pinned.

Click the Pin button againt to deactivate anchor.

The close button hides the window and docks it back to the Panel.

Project Panel

If a project is open, this window will contain a treeview display of all the Jobs and files in the project. Double-click on a file to open it in the editor or Hex editor (depending on the file-type).

There are also other options available if you right-click the name. A menu will open offering different options are available depending on the file-type.

Also, when a file has been altered by an external program, an asterisc '*' will appear before the name.

Different file/job types have special icons for easy recognition.

A target/ghosted target indicate a target job type.

A hammer/ghosted hammer indicates a build-type job.

Ghosted indicates a held job.

A circle with a 'P' indicates a Program (or entry) source.

A square with an 'U' indicates a unit source.

A bule square with an 'RC' indicates a resource-type file.

A square with line-dashes indicates a misc or include type file.

A green circle with a 'B' indicates a binary-type file.

Items that may appear in the Right-click menu:

-Project Manager

Opens the Project Manager. This option is available with all types.

-Dedicate to wscan

Selecting this file will reserve it for being used with "Consolidate Windows Header" Project menu.

This option is available with all include file-types.

-Set Auto Open

Selects this file to open automatically when the project is loaded. This option is available with all text file-types.

-Reopen File

Reloads the file from disc. Especially useful it the file has been altered externally. This option is available with all text file-types.

-Open With Resed

Opens the file with ResEd. This option is available with resource file-types.

-Rebuild File

Forces a rebuild of the selected file. This option is available with Source and Resource file-types

-Rebuild Job

Forces a rebuild of the selected job. This option is available with Build Job-types.

-Run, Run With Debug

Runs any output for the selected job. These options are available with Program Job-types.

-Execute Script

Executes the selected kMake script. This option available with Target job-types.

-Open With kHelp

Opens the selected file with kHelp. This option available with kHelp file-types.

-View Dependents

Only available if Autodependencies is selected in HIDE Setting and the current source has dependents. This item lists all the dependents in the Output window.

Properties

If a project is open, this window will contain information on the sections of a project, including procedures, and variables. A tool bar allows selection of display mode: all project files or current open file.

Compiling Simple Programs

When no projects are loaded, HIDE will operate on No-Project mode, this mode is displayed on the status bar. In this mode, you may compile simple programs. This mode does not allow use of units, linking additional libraries or resources.

To use Non-Project mode, simply load an HLA sample program or write one. Use menu item File -> Open to open a new editor window. When the program is typed, you may save it then compile. By default, HIDE allows you to compile and run a sample program without saving it first, HIDE will automatically save it in the HIDE/TEMP folder as “temp.hla” The executable will also end up in the temp folder.

This default mode may be removed from the HIDE Settings dialog (in Options menu). To compile, use menu item Make -> Build, Build & Run or Rebuild.

Menus

When no projects are loaded, HIDE will operate on No-Project mode, this mode is displayed on the status bar. In this mode, you may compile simple programs. This mode does not allow use of units, linking additional libraries or resources.

To use Non-Project mode, simply load an HLA sample program or write one. Use menu item File -> Open to open a new editor window. When the program is typed, you may save it then compile. By default, HIDE allows you to compile and run a sample program without saving it first, HIDE will automatically save it in the HIDE/TEMP folder as “temp.hla” The executable will also end up in the temp folder.

This default mode may be removed from the HIDE Settings dialog (in Options menu). To compile, use menu item Make -> Build, Build & Run or Rebuild.

Edit

Standard: Standard cut, copy, paste options available.

Indentation:

Indent adds the tab character at the beginning of each selected line, while Outdent works the inverse.

Commentation:

Comment and Uncomment have 2 modes. With HLA files (sources ending with .hla or .hhf), the comment adds a double slash at the beginning of each selected line. Uncomment works the inverse.

Bookmarks:

Toggle bookmark adds a bookmark at the current line. The margin displays a filled circle at the location. Next/Previous Bookmarks cycle between saved bookmarks. Clear All Bookmarks removes all saved bookmarks.

These bookmarks are only good for the current session, they will not be remembered if you exit HIDE.

Source Bookmarks:

Regular bookmarks only last one session, to have persistant bookmarks, use a source bookmark. These will add a commented bookmark into the source which the HIDE properties scanner will pick up and display in the "bookmarks" view of the Properties panel. Remove a bookmark by deleting the comment.

"Insert Source Bookmark" will open a dialog asking for a label. Enter a single word to describe this bookmark. HIDE will add a comment at the current cursor position that looks like:

//bm=label

This can be entered manually as well. Next property update, the bookmark will be displayed in the "bookmarks" view and the bookmark will be saved in the source.

Block Selection:

Mark Set/End sets begin and end points for block selection. This is an alternative to shift-scrolling to select large blocks of text.

Quick Navigation to Labels:

Find/Return Declare tries to find the location in the sources where the current label under the carot is declared. The middle mouse button does the same thing except for the word currently under the carot position. There are up to 5 saved position, 4 through menus and 1 with middle mouse button. Selecting a menu again returns to the original spot.

Note: When the cursor hovers over a declared label, the property information of the label (if any) is displayed on the status bar.

Goto Program Begin jumps to the main program begin location of the Entry file.

View

Shows or hides, docks or undocks various HIDE windows:

Treeview: contains a file-list of the current project.

Toolbar: some of the commonly used menuitems are on the toolbar

Statusbar: contains some useful at-a-glance information about mode.

Output: compiling output is displayed here.

Toggle Windows: closes open Panels, opens closed Panels

HIDE Windows: hides all open Panels

Panel: Hides/shows side panel

Output: Hides/shows output panel

Dock Output: docks/undocks Output panel from main window

Cycle Panel: changes views in side panel

Project

New Project:

Opens a dialog to get a file name for creating a new project.

Enter a project name, options include using a template or starting with the Project Manager.

There are some quick templates included for convinience.

Projects will be saved in their own folder located at HIDE(default), or at the current user-specified projects folder (Options -> Set Paths). The current projects folder is displayed in the dialog.

Once a project is created, any currently open project is closed. If no templates are used, the Project Manager opens. Otherwise the template project is created and opened.

See the description of Project Manager on how to add new jobs and files to the project.

Open Project:

Opens a previously created project. If there is an active project, it is closed.

Close Project:

Closes current project.

Project Manager:

Opens the Project Manager dialog. See the Project Manager topic for more information.

Consolidate Windows Header

Selecting this option activates wscan.exe which scans your project sources for any Window header labels ( namespace w ). It then scans the main w.hhf header files and creates a smaller sub-set of this file using only the labels (direct and indirect) declared in your sources. This automatically creates a "win_inc.hhf" header file and adds it to your project. To use this file, you must #include ("win_inc.hhf") with any unit that needs them (see the wscan documents for more information).

Empty Temp Folder

This will delete the contents of the current temp folder. The location of the temp folder will vary depending on project settings. See Help->Show Envrionment menu for current temp folder.

Make

Build Active Job:

If the current file belongs to a job, that job is built. If it's a

non-project file, the file is built.

Build Project:

This option builds all the jobs in the project, in the order displayed

in the Project Panel.

ReBuild Project:

Forces a rebuild of all the jobs without checking file-dates.

Rebuild Active File:

Forces a rebuild of the current file without checking file-dates.

Rebuild Active Job:

Forces a rebuild of the current active job without checking file-dates.

Run: Runs the executable of the current active job, if any.

Build Project & Run:

First Builds the project then runs the produced executable of the active job

Run With Debug:

Runs a previously built program of the active job with a specified Debugger.

See Options menu Set Paths for more info.

Clean:

Deletes compiled object and resource files

Test Build -Commands only:

Shows the commandlines HIDE will execute under normal execution of Make->Build Project

No actual execution will take place.

Source HLABE:

Converts the file in the active window into hla's internal HLABE format, opens a new

window to display the contents.

This format is useful for seeing the high level functionally of hla unrolled into low level and debugging macros.

Source -> MASM, FASM, NASM, GAS

Converts the file in the active window into hla's translation of the various supported languages.

This format is useful for seeing how hla code appears in other assembly languages.

Some of these options are also available in the right-click menu of the

Project Panel.

Tools

These tool selections are hard coded into HIDE, included in the bin folder or operating system and always available

Debug Window

A simple window that can display run-time output sent by your programs. Operatation of Debug Window requires several steps.

1. Include the dbgwin header file (located in the hlaincfolder).

#includeonce ("hide.hhf")

2. Link with debug.lib This is done automatically by setting the Output Version (in Compiler Settings dialog, see Options) to Debug (debug mode).

3. Add a global compile time variable called debug to your source. This is done automatically by setting the Output Version to Debug (debug mode).

Displaying text to the debug window is done by a series of macros all of which use the "dbg" namespace.

Current functions:

dbg.put(arg1,[arg2],[arg...]);

Used somewhat like stdout.put, but less powerful.

Eg: dbg.put("The variable MyVar contains the value: ",MyVar);

dbg.cls:

Clears the debug window display of any text.

Eg: dbg.cls;

dbg.putz (addr);

Displays a zero-terminated string.

dbg.separator;

Draws a separating dashed line.

Eg: dbg.separator;

dbg.dumpmem(address,length);

Displays an arbitrary memory location at address passed in address parameter and of length passed in length parameter.

dbg.dumpregs;

Displays the contents of the registers.

dbg.trace;

Starts HLA trace, displayse current file/linenumber for most instructions.

dbg.endtrace;

Ends the trace

dbg.timer;

Starts a timer for measuring performance of code.

dbg.endtimer;

Ends the timer and displays the time.

Resource Editor

Launches Ketil.O's ResEd.

If there is a project open, it checks the current job and tries to find

a resource file. It will open that file in ResEd.

To make sure ResEd opens a particular file, right-click the resource

in the Project Panel and select "Open With ResEd"

Note: HIDE loads all files into memory. ResEd modifies external files. Although HIDE reopens the current resource file after exiting from ResEd, at times a user will have ResEd produce an additional file (such as rsrc.hhf) which will need to be manually reopened. An asterisk beside the file name in the Project view indicates a file has been modified externally and needs to be reopened, do so by right-clicking on the filename and selecting "Reopen File".

ASCII Table

Runs asciitbl.exe which outputs a basic text Ascii Table to the output window.

Calculator

Launches system calculator, calc.exe

Color Picker

Opens a color selection dialog. Clicking OK on the dialgo will send the current color to the main edit window at the carot postion as an HLA hex number.

Open Console

Runs the program attached to the "ComSpec" environment variable.

Run Program

Opens a dialog for input. Attempts to execute the input. You may use HIDE macros.

The dialog shows the last text entered.

Options

Control all cutsomizable aspects of HIDE, from font/color settings to output types.

Code Editor Font

Customize the font used in the editor and output windows.

Line Number Font

Customize the font used in the margin.

Colors & Keywords

Allows customization of highlite colors and highlite key-words. It contains 10 user key-lists which come predefined with HLA directive and assembler opcodes. There are also 4 HIDE specific lists, two of which contain keywords for HLA standard library routines and two contain keywords for Windows API structures, constants and functions.

Here you will also be able to use and save Themes. Several themes are included and your own themes may be saved. Saved themes are stored in data.ini. Only themes you save may be deleted.

HIDE Settings

This dialog allows you to change the editor behavior settings.

Tabs:

Tab Size: If a project is open, the new tab settings will only apply to the project. Otherwise, it will apply to default and all new projects.

Expand tabs: change tabs to spaces.

Auto indent: pressing enter will automatically tab to the previous indent.

Backups:

Instructs HIDE to make backups of every project file modified, up to the number specified. Once the limit is reached, the backups cycle back through the lower numbers. The files are saved either in the BAK folder of your project (if one was created during project creation), or in the HIDEfolder.

The backup naming convention is thus:

[n]<filename> where n is the backup number

If Backups is set to 0, no backups will be created.

Options

Hilite lines: Hilites the current cursor line

Hilite Comments: hilites commented code

Hide Divider Lines: does not show divider lines

Show Line #s: Automatically opens the line number panel for every open file

Auto HLA Structures: automatically completes or assists when HLA keywords are used

Auto Parenthesis:

Automatically completes parenthesis or '[', ''

and '('. Also if the closing parenthesis is used

at the end of a word, the opening parenthesis is

automatically inserted at the beginning of the word

Keep Temp Files:

Saves temporary files in either project/temp folder

or HIDE/temp folder

Use Debug Lib: links in debug.lib and sends -ddebug ctl variable

Verbose Output: more verbose information during compiling

PE GUI: links output as Windows PE GUI

Send Console output to Output Window:

Redirects standard out of console programs executed to the output window. Note: this does not allow input.

Auto Dependencies:

This activates the dependency checking feature of HIDE. Dependencies are considered only if the file is included and exists as a project file. To determine which dependencies HIDE will consider, you may right click on a file name in the Project View. If there are dependents, a menu item "View Dependents" will be visible. Clicking this will list dependents in Output window.

HIDE Global Settings

Open Last Project: Attempts to open the last project worked on

Autosave [Untitled]:

Automatically save [Untitled] as HIDE.hla *

When on No Project mode, this allows you to compile

a program without saving it first. The program is

saved in the HIDEfolder with the name temp.hla,

the executable after a build will be in that folder

as well.

*Note, if there is a project open and it has 'Use Temp Folder'

active, the file will be saved in the projects's temp folder.

Use Standard Templates:

Everytime a new file is opened, depending on what kind

of file, HIDE will fill in code according to the files

located at HIDE. If you do

not like these, feel free to update them to your taste,

but create backups before upgrading HIDE, as the files

may be overwritten.

Scan for Properties:

Properties scanning is optional. Turn this off on low end systems that

exhibit slowdowns during symbol scanning.

Top Window: Makes HIDE a top level window.

HLA Level:

This determines the level at which HLA will attempt to compile sources.

4 options available from left to right:

high, medium, low, machine

Global Link Settings

Allows you to edit link settings for non-project files.

There must not be a project open for this to edit global

settings.

SetPaths

Opens a dialog where certain HIDE and HLA paths can be set.

The fields can accept legal physical or relative paths. All HIDE macros are available for use in the fields and it is suggested that HIDE-relative macros be used for changing environment paths.

User Paths

Use this to extend the system search path used by HIDE

Project Folder

The default project folder is located at HIDE. Use this field to change the location to another folder.

Debugger

In this field, enter the path and executable of the debugger that will be launched when the user selects "Run With Debug"

Help (F1)

In this field, enter the path and name of a help file that will be used for Windows API documentation

hlalib

Use this field to change the "hlalib" environment path. This path must always point to a valid hlalib library.

hlainc

Use this field to change the "hlainc" environment path.

hlaopt

Use this field to change the default compiling options set by HIDE. Possible uses could be to use an external assembler.

Beware, changing this could make HIDE inoperable so only change it if you know what you're doing! You can revert to the default by deleting the "hlaopt" line in data.ini

User

The user menu is not shown unless there are user menus defined in the Data.ini file.

To define a user menu, add a [User Menu] section to HIDE.ini

Each line under "[User Menu]" has the format:

menu id, command to execute w/arguments

the command execution line may also contain HIDE macros (see below).

Eg:

[User Menu]

calc, calc.exe

Alpha, alpha.exe %s

See HIDE Macros section for more details on macros.

HIDE will also use appropriate programs to launch for opening documents from the user menu. It will determine this based on the document extension provided.

One can use this feature to open files using HIDE.

eg:

[User Menu]

My Help, "c:documents.pdf"

The above will attempt to open mypdf.pdf with

default pdf viewer, if one is found.

To open files using the text/hex editor in HIDE,

place the '<' (less than) char right before

the filename

eg:

[User Menu]

HIDE.ini,<%h.ini

This will open "hide.ini" located in the HIDE

folder using the current instance of HIDE.

Use the '>' (greater than) char right before

the filename to open as HEX.

The '<' and '>' chars must appear before quotes, if

quotes are used.

Help

Win32 API

Before this feature can be used, it has to be configured with the location of your Windows help file.

To do so, use Option -> Set Paths menu and add the location of your win32.hlp (or other) file to the "Help F1" field.

Once configured, selecting this menu will open win32.hlp (or other selected program). Selecting it while cursor is on a word will try to locate the keyword in win32.hlp and open that topic.

The Tutorials and manuals may need to be downloaded separately. These files are the kHelp versions of the documents. When downloaded, the tutorials go in HIDEfolder, the rest go in HIDE

When selected, kHelp is launched to display the document.

Show Environment:

Displays the current HIDE environment and macros associated with the paths.

Show Files List:

Dumps a list of all the project files, their Job segment and their locations in the Output window.

About

Opens a dialog with credits, info and other...

HIDE Macros

Here is a list of HIDE Macros that may be used in HIDE control boxes for paths and output redirection and in Target scripts.

%$ current directory, usually folder of current open project

%h HIDE homepath

%p projects folder path (not current open project)

%c folder for current open project

%i hlainc folder path

%l lib folder path

%t temp folder path

%s active edit source filename

%x template folder path

See Help-> Show Environment for the actual paths that will be substituted

for some of these macros.

Project Manager

The Project Manager is a new feature added in HIDE 1.23.00+

From here, the details of the project are handled; adding/removing/moving/renaming jobs, files, folders, linked libraries; selecting compiling options; selecting linker options; selecting auto-maintained folder operation...

The main operation of the Project Manager is broken down into 4 panels from left to right:

1. Jobs Build/Held Queue + combobox (refered to as Jobs Combo)

2. Folders

3. Files + combobox (refered to as Files Combo)

4. Linked Objects

Most of the buttons are only active when the are useful.

On the extereme left, the up/down buttons are only active if there are two or more jobs in the project. You may select a Job and move them up or down the build Queue using these arrows.

1. Jobs List. This first listbox has two modes, Build Queue and Held. Select modes from the two buttons on top of the list.

To begin adding jobs to the project, select a job type from the Jobs Combo. There are several to choose from.

HLA Program

For small, mono-source HLA programs.

Modular Program

For large unit-based programs.

A Build Target

For kMake scripts

DLL

For modular Dynamic Linked Libraries

Library

For modular Libraries

Misc

Special type. This does not build anything.

Buttons:

Add Job - activated when a Job type is selected in the Jobs Combo.

Opens a dialog for getting the Job name.

Delete Job - deletes selected job. Only works if there are no files in the job.

Rename Job - renames selected job.

Hold Job - sends selected job into the Held list.

2. Folders List

When a project is created, this will already have the project folder listed here.

Add Folder - adds a new folder to the project

Delete Folder - deletes selected folder. Only works if the folder is empty. Does not delete the project folder.

Rename Folder - renames selected folder.

Move File - only active if there is also a file selected in the Files List. Moves the file to the folder.

3. Files List

Files Combo - only active when a job is selected. Allows you to add file-types to the job.

Note: some file-types are only available in certain jobs.

Select a file type to activate add button.

Entry File - only available in Program and DLL type jobs. Only one entry file may be created per job. The entry file is usually the one that contains the "Program.." heading.

Include File - for includes/headers

RC File - for resources

kHelp File - for kHelp documents/manuals

Binary File - for opening with Hex editor

Misc File - for general non-source text files

Unit File - only available in modular type jobs. For separately linked units.

Definition File - only available in DLLs, only one may be created.

kMake Script - only available in Target type jobs. Adds a kMake script. Only one per target.

Buttons:

Add File - opens a dialog to get name. Creates a new file. If "Use Standard Templates" is selected, the file is pre-filled with code found in the Datafolder based on type of file created.

To create a file in a folder different from the Project root, select a folder before clicking "Add File"

Delete File - removes a file from project and deletes it from disc. File is gone forever.

Rename File - give a file a new name.

Import File - Import an existing file into the project.

To move a file, select the file in the Files list, select a new folder in the Folders list and the "Move File" button will activate. Click to move.

4. Linked Objects List

This will contain a list of files linked in with the final executable/library. You may fine some default libraries already listed. These are added when a new Job is created. To see the list of default libraries, or to make changes to the default list, see Data.ini

Buttons:

Add Library - add a library for linking.

Remove Library - remove a library from linking - does not delete library.

Project Options

Several buttons available:

Rename Project - renames the folder and project file.

Use Standard Templates - if selected, new files created will contain initial code taken from Data, depending on the type of file.

Use Temp Folder - creates a "temp" folder and uses that to store all temporary files.

Use Units Folder - creates a "units" folder and stores all the object files there.

Use Bak Folder - creates a "bak" folder and stores all backup files there.

Use Debug Lib - linkes debug.lib with all compiled jobs.

Keep Temp Files - does not delete temporary files. These files will be saved either in the current project temp folder or in the HIDE/temp folder, depending on the use of temp folder.

Verbose Compile - shows more compiling data.

Job Options:

Only available when significant jobs types are selected.

Link As GUI - links with -w option.

Link Options - opens a dialog allowing the editing of finder linker details. For advanced users only.

Output:

Shows how the final output file (if any) will be named and in which directory it will be created. You may use standard HIDE macros here for output redirection.

Done:

Closes the dialog and saves changes to project file.

Auto Completion

Bernd Kastenholz has written an autocompletion module for HIDE.

Autocompletion listbox is opened by pressing ctrl-space

Activation in HIDE-editor: Key Ctrl-Space

Opens a dialog beneath the cursor. The listbox contains all

strings of file 'HideHomepath.txt'.

Settings: Key F1 (does only work when autocompletion dialog is visible)

Opens the settings dialog. Only the sort property of

the listbox can be changed.

The settings are stored in file 'HIDE.ini'

Controlling the listbox:

Arrow up: Scroll up in the listbox

Arrow down: Scroll down

Arrow left: Increase the selection to the left

Arrow right: Decrease the selection to the right

Backspace: Increase the selection to the left

Return: Inserts the selected word and closes the dialog

DblClk with left MB in listbox: Inserts the selected word and closes the dialog

Scrolling with mousewheel.

ESC: Just closing the dialog and deletes the selection

HOME: Scroll to first item

END: Scroll to last item

PGDN: Scroll down one page

PGUP: Scroll up one page

Updates:

8/2/2006

-fixed a bug (while inserting chars)

-now the word list will be destroyed when changing the sort property

8/1/2006

v1.0.1

-added controlling the listbox with HOME, END, PGDN and PGUP

-fixed deleting of text

-fixed decreasing/increasing bug

-fixed backspace bug. Deletes now the selection

-cleaning the source code

7/31/2006

v1.0.0

first release of autocompletion

CommandLine Tools

For more in-depth documentation of these tools, see the HIDE Tools manual.

kMake

kMake is used to build target jobs. To edit a target job write in the optional [BUILD] section to avoid warnings from kMake.

eg:

[BUILD]

; commands to run

For those unfamiliary with kMake, the manual is included in Help -> HIDE Tools

For a quick summary, a script may launch any program or MS-DOS command that are available on your path. If you need to add more paths to HIDE, use Option -> Set Paths menu and add as many paths as you like to "User Paths"

The script may also contain HIDE macros (see Help -> Show Environment for an available list of macros and what they expand to).

Project File Format

Note: this format has changed significantly in HIDE 1.23.00 +

The original format is no longer supported and an automatic converter

is provided to help in converting older projects to the newer format.

Here is a sample HPR file. I'll walk through the sections one at a time.

[HPR Settings]

mainfile=Src.hla

tab=6

backups=0

options=199

Project Version=10

usetemp=false

useunits=true

useback=false

findscope=1

findflags=0

[HPR Jobs]

cCalc

[HPR Folders]

units

res

src

[cCalc]

console=false

output=cCalc.exe

type=modular

main=cCalc.hla

[cCalc.link]

-heap:0xF4240,0xF4240

-stack:0xF4240,0xF4240

-base:0x4000000

-entry:HLAMain

-section:.data,RW

-section:.text,ER

-machine:ix86

[cCalc.files]

kernel32.lib,,extlinked

user32.lib,,extlinked

hlalib.lib,,extlinked

hidelib.lib,,extlinked

cCalc.hla,src,hlaprogram

cCalc.rc,res,resource

cCalc.hhf,src,include

cCalc.txt,,misc

==============================================================

[HPR Settings]

mainfile=Src.hla

tab=6

backups=0

options=199

Project Version=10

usetemp=false

useunits=true

useback=false

findscope=1

findflags=0

This section gives HIDE some detailes on how to treat this project

mainfile

HIDE opens ths file when the project is first loaded. Any file

can be set as mainfile by right-clickig the name in the Project

Panel and selecting "Set Auto Open"

tab

Number of spaces in a tab

backups

Maximum number of backups reserved for this project

options

Maintains a bit-map of options

Project Version

Used for automatically updating changes to the project file format.

A version of <10 is the older project format which is no longer supported.

usetemp

useunits

useback

Some folders are auto-maintained. These show true or false wether this project makes use of these facilities.

usetemp = temp folder "temp"

useunits= units folder "units"

useback = backups folder "bak"

findscope

findflags

These maintain the find options for this project.

[HPR Jobs]

cCalc

This section lists the jobs queue. Every item here is executed in

order from first to last. There may also be a [Held Jobs] section

which are jobs removed from the build queue.

[HPR Folders]

units

res

src

This section lists all the folders recognized by the project.

[cCalc]

console=false

output=cCalc.exe

type=modular

main=cCalc.hla

Each job listed in [HPR Jobs] and [Held Jobs] will have its own

corresponding section which describes what the job is all about.

console

Indicates if this job will build as a console or PE GUI

output

Indicates the path/name of the output produced by this job

type

Corresponds to the type of job as selected when job was created

main

If this job has an entry (ie: it's an executable or DLL) then

this will indicate which file is the entry.

[cCalc.link]

-heap:0xF4240,0xF4240

-stack:0xF4240,0xF4240

-base:0x4000000

-entry:HLAMain

-section:.data,RW

-section:.text,ER

-machine:ix86

Each job will have its own link section. This passes information to

the linker. These options may be edited from the Project Manager

[cCalc.files]

kernel32.lib,,extlinked

user32.lib,,extlinked

hlalib.lib,,extlinked

hidelib.lib,,extlinked

cCalc.hla,src,hlaprogram

cCalc.rc,res,resource

cCalc.hhf,src,include

cCalc.txt,,misc

Each job will have its own files section. Every file is listed,

along with its folder and type.

Licences

HIDE package contains tools released in various licences.

HIDE

HIDE

Copyright (c)2006 Sevag Krikorian

This licence applies to all versions of HIDE including any

future released version, unless a new licence is applied.

HIDE consists of HIDE.exe as well as the package "HIDE." The

package consists of other 3rd party tools and any licences

associated with these tools are provided separately.

This program is free for commercial or private use as long as

the following conditions are respected.

1. Redistribution

Redistribution in source or binary form is permitted.

The copyright notice, disclaimer and this licence

(as well as the lincences of all 3rd party software that is

redistributed) must be retained.

2. Modification

The source may be modified and redistributed.

Redistribution of modified code also falls under the conditions

of redistribution (see #1).

Modified source cannot be copied and placed under a

different licence, including the GNU Public Licence.

3. Disclaimer

This software is provided "as is" and any express or

implied warranties, including merchantability and fitness for

a particular purpose are disclaimed.

The author and contributors will in no case be liable

for the use or misuse of this software, including, but not

limited to direct, indirect, incidental, special, exemplary or

consequential damages.

PellesC

This software is provided 'as-is', without any expressed or implied warranty. In no event

will the author be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial

applications, and to redistribute it freely, subject to the following restrictions:

1.The origin of this software must not be misrepresented; you must not claim that you wrote the original software.

2.Including Pelles C in a CDROM or in other products sold for profit needs explicit agreement from the author.

3.This notice may not be removed or altered from any distribution.

Pelle Orinius

Stockholm, Sweden

HLA

Use the -license command-line parameter to see the current license for the HLA system.

 

The RadASM/HLA Integrated Development Environment

Note: the HIDE integrated development environment is standard IDE for HLA. However, RadAsm, an older system that is compatible with several different assemblers, also supports HLA. I wrote this documentation for an older version of RadAsm (before HIDE appeared). As many HLA users prefer RadAsm (because they grew up with it or because they use several different assemblers and prefer using a single IDE) I’ve included this documentation. You may need to make appropriate mental adjustments if you’re using a newer version of RadAsm than this document describes.

Last I checked, the official RadAsm development site was dead, but if you Google RadAsm you can find mirror sites to download it from. Last time I did this, found found the following sites:

http://www.oby.ro/rad_asm/

http://radasm.cherrytree.at/radasm/

 

Integrated Development Environments

An integrated development environment (IDE) traditionally incorporates a text editor, a compiler/assembler, a linker, a debugger, a project manager, and other development tools under the control of a single main application. Integrated doesn’t necessarily mean that a single program provides all these functions. However, the IDE does automatically run each of these applications as needed. An application developer sees a single “user interface” to all these tools and doesn’t have to learn different sets of commands for each of the components needed to build an application.

The central component of most IDEs is the editor and project manager. A project in a typical IDE is a related collection of files that contain information needed to build a complete application. This could, for example, include assembly language source files, header files, object files, libraries, resource files, and binary data files. The point of an IDE project is to collect and manage these files to make it easy to keep track of them.

Most IDEs manage the files specific to a given project by placing those files in a single subdirectory. Shared files (such as library and shared object code files) may appear elsewhere but the files that are only used for the project generally appear within the project directory. This makes manipulation of the project as a whole a bit easier.

RadASM, created by Ketil Olsen, is a relatively generic integrated development environment. Many IDEs only work with a single language or a single compiler. RadASM, though designed specifically for assembly language development, works with a fair number of different assemblers. The nice thing about this approach is that you may continue to use RadASM when you switch from one assembler to another. This spares you the effort of having to learn a completely new IDE should you want to switch from one assembler to another (e.g., switching between FASM, MASM, NASM, TASM, and HLA is relatively painless because RadASM supports all of these assemblers. RadASM is extremely customizable, allowing you to easily set it up with different assemblers/compilers or even modify it according to your own personal tastes. Although the version of RadASM that ships with HLA has been specifically set up to work seamlessly with RadAsm, it’s nice to know that you can customize RadASM however you choose..

HLA Project Organization

A RadASM/HLA project is a collection of all the files specific to a given executable program. This includes project-specific source files, resource files, object files, header files, makefiles, and so on. Share library, object, and header files are logically a part of an HLA project, though they generally are not physically present in the set of files that comprise a project (i.e., you don’t make copies of these files for each project you produce). Whether a given file is physically a part of the project or just logically a part of a project, a RadASM/HLA project cannot compile correctly without all the files that make up the project.

This document will adopt the (reasonable) convention of placing each HLA project in its own subdirectory. A given project directory will contain the following files and directories:

  • All source files specific to the project (this includes make files, .hla, .hhf, .rc, .rap [RadASM project] and other files created specifically for this project, but does not include any standard library header or generic library files that all projects use).
  • A “makefile” file to be processed by a “make” program or a batch file to compile and combine all the files in a project.
  • A “Tmp” subdirectory where HLA can place temporary files it creates during compilation (normally these files wind up in the same directory as the HLA source files; placing them in the “Tmp” directory prevents clutter of the main project directory).
  • A “Bak” subdirectory where backup files can be kept.

The RadASM IDE provides the ability to maintain projects directly. However, the combination of RadASM/HLA and a “make” program provides a superior solution to the standard RadASM project paradigm. Therefore, this document will assume that you’re using makefiles in your RadASM projects (the next section describes the “make” program, so if you’re not familiar with it, keep on reading...).

The drawback to using makefiles to maintain the project is that you’ve got to manually create the makefile; RadASM won’t do this for you automatically (as it does with its own projects). Fortunately, 90% of your makefile creations will simply be copying an existing makefile to your project’s directory, editing the file, and changing the filenames from the previous project to the current project (indeed, this operation is so common that you’ll find a generic makefile in the “sniplets” RadASM directory accompanying the HLA download. You can easily create a copy of this generic makefile from RadASM’s “Tools > Sniplets” menu, as you’ll see soon enough).

Using Makefiles

Although RadASM provides a true IDE for HLA that supports projects, browsing, and other nice features, the best way to manage your Win32 assembly projects (even within RadASM) is via a makefile. Since the use of make is going to be a fundamental assumption in this book (e.g., most examples will include a makefile), it’s probably wise to discuss the use of make here for those who may be unfamiliar with this program.

The main purpose of a program like make (or nmake, if you’re using Microsoft’s version of the program) is to automatically manage the compilation and linking of a multi-module project. Although it is theoretically possible to write a single, self-contained, assembly language source file that assembles directly to an executable file, in practice this is rarely done. Instead, programs are usually broken up into separate source files by logical function. In order to save time during development, you don’t always have to recompile every source file that makes up the application. Instead, you need only recompile those source files that have been changed (or depend upon changes in other source files). This can save a considerable amount of time during development if your project consists of many different source files that you’re compiling and linking together and you make a single change to one of these source files (because you will only have to recompile the file you’ve changed rather than all files in the system).

Note that you will have to obtain a make utility program in order to use make files. If you’ve got any Microsoft development tools, then you’ve probably got a copy of Microsoft’s nmake.exe program lying around. Ditto for Borland tools. The Free Software Foundation (FSF - the GNU folks) have their own version of make as well. If you don’t have a copy of a make utility, you can download Borland’s version as part of their C++ command line compiler package that they distribute free on their website (though you do have to register with Borland to receive this). Check out the C++Builder Downloads page at

http://www.borland.com/products/downloads/download_cbuilder.html

Click on the “compiler” link in order to download Borland’s command line C++ compiler (that includes the make.exe utility). If this link is broken, just visit http://www.borland.com and follow the downloads link.

Although separate compilation reduces assembly time and promotes code reuse and modularity, it is not without its own drawbacks. Suppose you have a program that consists of two modules: pgma.hla and pgmb.hla. Also suppose that you’ve already compiled both modules so that the files pgma.obj and pgmb.obj exist. Finally, you make changes to pgma.hla and pgmb.hla and compile the pgma.hla file but forget to compile the pgmb.hla file. Therefore, the pgmb.obj file will be out of date since this object file does not reflect the changes made to the pgmb.hla file. If you link the program’s modules together, the resulting executable file will only contain the changes to the pgma.hla file, it will not have the updated object code associated with pgmb.hla. As projects get larger they tend to have more modules associated with them, and as more programmers begin working on the project, it gets very difficult to keep track of which object modules are up to date.

This complexity would normally cause someone to recompile all modules in a project, even if many of the object files are up to date, simply because it might seem too difficult to keep track of which modules are up to date and which are not. Doing so, of course, would eliminate many of the benefits that separate compilation offers. Fortunately, the make program can solve this problem for you. The make program, with a little help, can figure out which files need to be reassemble and which files have up to date .OBJ files. With a properly defined make file, you can easily assemble only those modules that absolutely must be assembled to generate a consistent program.

A make file is a text file that lists compile-time dependencies between files. An .EXE file, for example, is dependent on the source code whose assembly produce the executable. If you make any changes to the source code you will (probably) need to reassemble or recompile the source code to produce a new executable file6.

Typical dependencies include the following:

  • An executable file generally depends only on the set of object files that the linker combines to form the executable.
  • A given object code file depends on the assembly language source files that were assembled to produce that object file. This includes the assembly language source files (.HLA) and any files included during that assembly (generally .HHF files).
  • The source files and include files generally don’t depend on anything.

A make file generally consists of a dependency statement followed by a set of commands to handle that dependency. A dependency statement takes the following form:

dependent-file : list of files

 

Example :

pgm.exe: pgma.obj pgmb.obj --Windows make/nmake example

 

This statement says that pgm.exe is dependent upon pgma.obj and pgmb.obj. Any changes that occur to pgma.obj or pgmb.obj will require the generation of a new pgm.exe file.

The make program uses a time/date stamp to determine if a dependent file is out of date with respect to the files it depends upon. Any time you make a change to a file, the operating system will update a modification time and date associated with the file. The make program compares the modification date/time stamp of the dependent file against the modification date/time stamp of the files it depends upon. If the dependent file’s modification date/time is earlier than one or more of the files it depends upon, or one of the files it depends upon is not present, then make assumes that some operation must be necessary to update the dependent file.

When an update is necessary, make executes the set of commands following the dependency statement. Presumably, these commands would do whatever is necessary to produce the updated file.

The dependency statement must begin in column one. Any commands that must execute to resolve the dependency must start on the line immediately following the dependency statement and each command must be indented one tabstop. The pgm.exe statement above would probably look something like the following:

 

pgm.exe: pgma.obj pgmb.obj

hla -e:pgm.exe pgma.obj pgmb.obj

 

(The “-e:pgm.exe” option tells HLA to name the executable file pgm.exe.)

If you need to execute more than one command to resolve the dependencies, you can place several commands after the dependency statement in the appropriate order. Note that you must indent all commands one tab stop. The make program ignores any blank lines in a make file. Therefore, you can add blank lines, as appropriate, to make the file easier to read and understand.

There can be more than a single dependency statement in a make file. In the example above, for example, executable (pgm.exe) depends upon the object files (pgma.obj and pgmb.obj). Obviously, the object files depend upon the source files that generated them. Therefore, before attempting to resolve the dependencies for the executable, make will first check out the rest of the make file to see if the object files depend on anything. If they do, make will resolve those dependencies first. Consider the following make file:

 

pgm.exe: pgma.obj pgmb.obj

hla -e:pgm.exe pgma.obj pgmb.obj

 

pgma.obj: pgma.hla

hla -c pgma.hla

 

pgmb.obj: pgmb.hla

hla -c pgmb.hla

 

The make.exe program will process the first dependency line it finds in the file. However, the files that pgm.exe depends upon themselves have dependency lines. Therefore, make will first ensure that pgma.obj and pgmb.obj are up to date before attempting to execute HLA to link these files together. Therefore, if the only change you’ve made has been to pgmb.hla, make takes the following steps (assuming pgma.obj exists and is up to date).

  • The make program processes the first dependency statement. It notices that dependency lines for pgma.obj and pgmb.obj (the files on which pgm.exe depends) exist. So it processes those statements first.
  • The make program processes the pgma.obj dependency line. It notices that the pgma.obj file is newer than the pgma.hla file, so it does not execute the command following this dependency statement.
  • The make program processes the pgmb.obj dependency line. It notes that pgmb.obj is older than pgmb.hla (since we just changed the pgmb.hla source file). Therefore, make executes the command following on the next line. This generates a new pgmb.obj file that is now up to date.
  • Having processed the pgma.obj and pgmb.obj dependencies, make now returns its attention to the first dependency line. Since make just created a new pgmb.obj file, its date/time stamp will be newer than pgm.exe’s. Therefore, make will execute the HLA command that links pgma.obj and pgmb.obj together to form the new pgm.exe file.

Note that a properly written make file will instruct the make program to assemble only those modules absolutely necessary to produce a consistent executable file. In the example above, make did not bother to assemble pgma.hla since its object file was already up to date.

There is one final thing to emphasize with respect to dependencies. Often, object files are dependent not only on the source file that produces the object file, but any files that the source file includes as well. In the previous example, there (apparently) were no such include files. Often, this is not the case. A more typical make file might look like the following:

 

pgm.exe: pgma.obj pgmb.obj

hla -e:pgm.exe pgma.obj pgmb.obj

 

pgma.obj: pgma.hla pgm.hhf

hla -c pgma.hla

 

pgmb.obj: pgmb.hla pgm.hhf

hla -c pgmb.hla

 

Note that any changes to the pgm.hhf file will force the make program to recompile both pgma.hla and pgmb.hla since the pgma.obj and pgmb.obj files both depend upon the pgm.hhf include file. Leaving include files out of a dependency list is a common mistake programmers make that can produce inconsistent executable files.

Note that you would not normally need to specify the HLA Standard Library include files. the Standard Library “.lib” files, or any Windows library files (e.g., kernel32.lib) in the dependency list. True, your resulting executable file does depend on this code, but this code rarely changes, so you can safely leave it out of your dependency list. Should you make a modification to the Standard Library, simply delete any old executable and object files to force a reassembly of the entire system.

The make program, by default, assumes that it will be processing a make file named makefile. When you run the make program, it looks for makefile in the current directory. If it doesn’t find this file, it complains and terminates7. Therefore, it is a good idea to collect the files for each project you work on into their own subdirectory and give each project its own makefile. Then to create an executable, you need only change into the appropriate subdirectory and run the make program.

The make program will only execute a single dependency in a make file, plus any other dependencies referenced by that one item (e.g., the pgm.exe dependency line in the previous example depends upon pgma.obj and pgmb.obj, both of which have their own dependencies). By default, the make program executes the first dependency it finds in the makefile plus any dependencies that are subservient to this first item. In particular, if a dependency line exists in the makefile that is not referenced (directly or indirectly) from the main dependency item, then make will ignore that dependency item unless you explicitly request it’s execution.

If you want to execute some dependency other than the first dependency in the make file, you can specify the dependency on the make command line when running make from the Windows’ command prompt. For example, a common convention in make files is to create a “clean” dependency that cleans up all the files the compile creates. A typical “clean” dependency line for an HLA compilation might look like the following:

 

clean:

del *.obj

del *.inc

del *.bak

 

The first thing you’ll notice is that the “clean” item doesn’t have a dependency list. When an item like “clean” appears without a dependency list, make will always execute the commands that follow. Another peculiarity to the “clean” dependency is that there (usually) isn’t a file named clean in the current directory whose date/time stamp the make program can check. If a file doesn’t exist, then make will assume that the file is always out of date. A common convention is to specify non-existent filenames (like clean) in a makefile as commands that someone would explicitly execute from within make. Of course, such usage (generally) assumes that you don’t actually build a file named “clean” (or whatever name you choose to use).

Since, by default, you typically don’t want to execute a command line “clean” when running make, you wouldn’t usually place the clean dependency first in the make file (nor would you typically refer to clean within some other dependency list). Since make doesn’t normally execute any dependency items that aren’t “reachable” from the first dependency item in the make file, you might wonder how you’d tell make to execute the clean command. To specify the execution of some dependency other than the first (default) item in the make file, all you need to is specify the target you want to create (e.g., “clean”) on the make command line. For example, to execute the clean command, you’d using a Windows command prompt statement like the following:

 

make clean

 

This command does not tell make to use a different make file. It will still open and use the file named makefile in the current directory8; however, instead of executing the first dependency it finds in makefile, it will search for the target “clean” and execute that dependency.

By convention, most programmers use the first dependency in a make file to build the executable based on the current build state of the program (that is, it will compile and link only those files necessary to create an up-to-date executable). Most programmers, by convention, will also include a “clean” target in their make file. The clean command deletes all object and intermediate files that the compiler generates; this ensures that the next build of the program will recompile every source file in the project, even if the original objects (and other targets) were up-to-date already. Doing a clean before building the application is useful when you’ve changed something that is not listed in the dependency lists but on which the final executable still depends (like the HLA Standard Library). Doing a clean is also a good way to do a sanity check when you’re running into problems and you suspect that the dependency lists aren’t completely correct.

Beyond clean there aren’t too many “standard” target definitions you’ll see programmers using in their make files, though it’s common for different make files to have some additional commands beyond building the default target and cleaning up temporary compiler files. When using make with the RadASM/HLA package, however, there is an assumption that you’ve created the following dependencies in your make file:

build: This will be the default command (i.e., the first command appearing in the make file). It will build an executable by building any out-of-date files and linking everything together. A typical build dependency will look like this:

 

build: pgm.exe

 

This tells make to go execute the dependency for pgm.exe (which would normally be the default dependency in the file).

buildall: This command will rebuild the entire application. It begins by doing a clean, and then it does a build. This command generally takes the following form:

 

buildall: clean pgm.exe

 

compileRC: This command will compile any resource files into .RES files. Though the current example does not have any resource files, a typical entry in the make file might look like the following:

 

compileRC: pgm.rc

rc pgm.rc

 

syntax: This command will compile any HLA files into .ASM files just to check their syntax. Using the pgma.hla/pgmb.hla example given earlier, a typical compile dependency line might look like the following:

 

syntax:

hla -s pgma.hla pgmb.hla

 

run: This command will build the executable (if necessary) and then run it. The dependency line typically looks like the following:

 

run: pgm.exe

pgm <<any necessary command line parameters>>

 

clean: This is the command that deletes any compiler/assembler/linker produced temporary files, backup files, and the executable file. A typical clean command is

clean:

del *.obj

del *.inc

del *.bak

del tmp\*.asm

del tmp\*.inc

del pgm.exe

 

One nice feature that a standard make program provides is variables. The make program allows you to create textual variables in a make file using the following syntax:

 

identifier=<<text>>

 

All text beyond the equals sign (“=”) to the end of the physical line9 is associated with the identifier and the make program will substitute that text whenever it encounters “$(identifier)” in your text file. This behavior is quite similar to TEXT constants in the HLA language. As an example, consider the following make file fragment:

 

sources= pgma.hla pgmb.hla

executable= pgm.exe

 

$(executable): $(sources)

hla -e:$(executable) $(sources)

 

Because of the textual substitution that takes place, this is equivalent to the following make file fragment:

 

pgm.exe: pgma.hla pgmb.hla

hla -e:pgm.exe pgma.hla pgmb.hla

 

You can even assign variable names from the make command line using syntax like the following:

make executable=pgm.exe sources="pgma.hla pgmb.hla"

This is an important fact we’ll use because it allows us to create a generic makefile that RadASM can use to compile a given project by simply supplying the file names on the command line.

Although this section discusses the make program in sufficient detail to handle most RadASM projects you will be working on, keep in mind that the make program provides considerable functionality that this document does not discuss. For more details, consult the vendor’s documentation accompanying the version of make that you’re using. This document will assume that you’re using Borland’s make (version 4.0 or later) or some version of Microsoft’s nmake. Every make file in this book has been tested with both of these versions of make. These make files may work with other versions of make as well. If you don’t already have a copy of make, note that you can download Borland’s make as part of the Borland C++ 5.5 compiler (see the directions for downloading this file earlier in this section).

Because of the variations in the way different make programs work, the makefiles appearing in this document will be relatively simple, not taking advantage of too many special make features. The generic makefile we’ll usually start with looks like this:

 

build: $(hlafile).exe

 

buildall: clean $(hlafile).exe

 

compilerc:

echo No Resource Files to Process!

 

syntax:

hla -s $(hlafile).hla

 

run: $(hlafile).exe

$(hlafile)

pause

 

clean:

delete tmp

delete *.exe

delete *.obj

delete *.link

delete *.inc

delete *.asm

delete *.map

 

 

$(hlafile).exe: $(hlafile).hla

hla $(DEBUG) $(WINAPP) -p:tmp $(hlafile)

 

RadASM will fill in the $(hlafile) make variable with the project’s (source file’s) name. The “$(DEBUG)” variable will be filled in by RadASM (you’ll see how later in this document) and will expand to an empty string if $(DEBUG) is not defined. The $(WINAPP) variable is another variable set by RadASM; it will contain the text “-w” if compiling a Windows GUI app, it will be the empty string if compiling a console application.

Installing RadASM

The easiest way to install RadASM/HLA is to run the hlasetup.exe program found on Webster (HLA v1.58 or later). This program automatically installs HLA and RadASM, sets up appropriate environment variables, and modifies various RadASM ini files for proper use on your system. Just run hlasetup.exe, answer a few questions about where you want the files placed, and you’re in business.

For those who’ve already installed HLA and don’t want to bother reinstalling everything, you can download the RadASM/HLA package from Webster, unzip that file, and install the code manually. The main thing you have to do is copy the RadASM directory into your x:\hla subdirectory and then execute the “PatchRadASM” application from within the “x:\hla” subdirectory. This goes in and patches all the *hla.ini files in the “x:\hla\radasm” subdirectory so that the know where the “x:\hla” subdirectory can be found. You may also edit these files manually and modify the line that says “$A=C:\HLA” so that it refers to the directory containing your HLA files and directories.

Note: Unless you’re willing to learn how to customize RadASM and modify several files yourself, you must install the RadASM directory in the HLA subdirectory (wherever it is on the disk). If you’re using RadASM with other assemblers and need to keep RadASM in some spot other than in the HLA subdirectory, please see the “RadASM customization” information at the end of this document and take a look at the *hla.ini files on Webster.

If you’re an expert RadASM user and you only want to add HLA support to an existing RadASM setup, you can download the HLA-specific RadASM files directly from Webster and make the appropriate modifications yourself. This document will not describe how to do this; this is a task intended for advanced RadASM users only (for support, check out the RadASM forum at www.masmforum.com).

Running RadASM

Like most Windows applications, you can run RadASM by double-clicking on its icon or by double clicking on a “RadASM Project” file (“.rap” suffix). Simply double-clicking on the RadASM icon brings up a window similar to the one appearing in See RadASM Opening Screen.

 

RadASM Opening Screen

The main portion of the RadASM window is broken down into three panes. The larger of the three panes is where text editing takes place. The upper right hand pane is the “project management” window. The pane in the lower right hand corner lists the properties of the currently opened project.

The RadASM Project Management Window

The project management window initially lists the project folders you’ve created; you can select an existing project by double-clicking on the project’s folder in this window. For example, RadASM ships with two sample projects, Dialog (that creates a small dialog box application) and hw (that creates a small “Hello World” console application). Assuming you’re running RadASM prior to creating any new projects beyond these two default projects, the Project pane will look something like See Default RadASM Project Pane.

 

Default RadASM Project Pane

Double-clicking on the hw folder opens the folder containing that project. This changes the pane to look something like that appearing in See RadASM Project Pane With hw Folder Opened.

RadASM Project Pane With hw Folder Opened

By default, RadASM does not show all the files present in the folder you’ve opened. Instead, RadASM filters out files that don’t have a certain file suffix. By default, RadASM only displays files with the following suffixes:

  • .asm
  • .inc
  • .rc
  • .txt
  • .doc
  • .rtf
  • .dlg
  • .mnu
  • .rap
  • .bmp
  • .ico
  • .cur
  • .hla
  • .hhf

This list is actually designed to generically handle all file types for every assembler that RadASM works with. HLA users might actually want to drop “.asm” and “.inc” from this list as files with these suffixes are temporary files that HLA produces (much like “.obj” files, which don’t normally appear in this list). You can change the filter suffixes in one of two places. The first place is in the radasm.ini file. Search for the “ [FileBrowser] ” section and edit the line that begins with “ Filter=... ”. You can delete or add suffixes to your heart’s content on this line. The second way to change the default filters, arguably the easiest way, is within RadASM itself. From the application’s menu, select “Option>File Browser” (that is, select the “File Browser” menu item from the “Option” menu). This brings up the dialog box appearing in See RadASM File Browser Options Dialog Box. The text edit box at the bottom of this dialog window (labelled “Filter:”) lets you edit the suffixes that RadASM uses for filtering files in the Project window pane.

RadASM File Browser Options Dialog Box

By default, RadASM only displays those files whose file suffixes appears in the filter list. If, for some reason, you need to see all files that appear in a project subdirectory, you can turn the file filtering off. There is a toolbar button at the top of the Project window pane that lets you activate or deactivate file filtering (this is the button in the middle of the project pane, if you let the mouse cursor hover over it for a few seconds the tool-tip help displays “file filter”). Clicking on this button toggles the display mode. So clicking on this button once will deactivate file filtering, to display all the files in the directory, clicking on this button a second time reactivates file filtering. See File Filtering in RadASM’s Project Pane shows the effects of clicking on this button.

File Filtering in RadASM’s Project Pane

If you’ve descended into a subdirectory by double-clicking on it’s folder icon and you decide to return to an upper level directory, you can move to that upper level directory by clicking on the “Up One Level” button in the RadASM Project pane

The left and right arrow buttons allow you to quickly scan through several different directories in the system. By default, RadASM displays a couple of interesting (HLA-related) subdirectories in the Project pane when you scan through the list using the left and right arrows in the Project pane. In general, however, you’ll want to customize the directories RadASM visits when you press these two arrow buttons. You can add (or change) directory paths in the “ [FileBrowser] ” section of the radasm.ini file, though it’s probably easier to select the “Option>File Browser” menu item to open up the File Browser Option dialog box and make your changes there (see See RadASM File Browser Options Dialog Box). The “Folders:” list in the File Browser Option dialog box lists all the directories that RadASM will rotate through when you press the left and right buttons in the Project window pane. You can add, delete, edit, and rearrange the items in this list.

To edit an existing entry, click on that entry with the mouse and then edit the directory path appearing in the text edit box immediately below the “Folders:” list. You may either type in the path directly, or browse for the path by pressing the “browse” button immediately to the right of the text entry box.

 

To delete an entry from the File Browser Option list, select that item with the mouse and then press the “Delete” button appearing in the File Browser Option Window. To add a new entry to the list, press the “Add” button and then type the path into the text edit box (or use the browse button to locate the subdirectory you want to add). Note: do not type the new entry in and then press “Add”. This sequence will change the currently selected item and then add a new, blank, entry. The correct sequence is to first press the “Add” button, and then edit the blank entry that RadASM creates.

The remaining buttons in the Project window are only applicable to open projects. Note that opening a project folder is not the same thing as opening a RadASM project. To open a RadASM project you must either create a new project or open an existing “.rap” file. For example, you can open the “Hello World” project in the hw directory by double-clicking on the hw.rap file that appears in the project window. Opening the hw.rap file does two things to the RadASM windows: first, it displays the hw.hla source file in the editor window and, second, it switches the Project window pane from “File Browser mode” to “Project Browser mode.” In project browser mode RadASM displays only the files you’ve explicitly added to the project. Any incidental or generated files will not appear here (unless you explicitly add them). For example, whereas the “File Browser” mode displays several “.inc” and “.asm” files (assuming you’ve not removed these suffixes from the file filter), the “Project Browser mode” only displays the hw.hla file because this is the only file that was originally added to the project. Another difference between the file browser and project browser modes is the fact that RadASM displays the files in “pseudo-directories” according to the file’s type. For example, it displays the hw.hla file under the sub-heading “Assembly” (see See Project Window “Project Browser Mode”). The hw.rap project is a relatively simple project, only having a single assembly file. The Dialog.rap project (that appears in the “Dialog” project folder) is a slightly more complex application, having a couple of resource files in addition to an assembly file (see See Dialog.rap Project Browser Display). Note that you can “flatten” RadASM’s view of these files by pressing the “Project Groups” button in the Project window pane (see See Effect of Pressing the “Project Groups” Button). Pressing this button a second time restores the project groups display (remember, you can always determine which button is which by letting the mouse cursor float above each button for a few seconds).

 

Project Window “Project Browser Mode”

 

Dialog.rap Project Browser Display

 

Effect of Pressing the “Project Groups” Button

When you’ve got a project loaded, RadASM displays the project view by default. By pressing the “File Browser” and “Project Browser” buttons in the Project window pane, you can switch between these two views of your files (see See The Project Browser and File Browser Buttons).

The Project Browser and File Browser Buttons
Compiling and Executing an Existing RadASM Project

To see how to use RadASM to compile and run a simple HLA program, begin by double-clicking on the hw.rap file. This is found in the ...Radasm\hla\projects\hw folder. When RadAsm opens up, you should see a display similar to See Selecting the HW.HLA Project; if not, then press the project browser and project groups buttons.

Selecting the HW.HLA Project

Just for fun, bring up the hw.hla program into the main editor by double-clicking on the hw.hla file icon in the project manager window. Here’s what that file looks like:

 

program HelloWorld;

#include( "stdlib.hhf" )

 

begin HelloWorld;

 

stdout.put( "Hello, World of Assembly Language", nl, nl );

 

end HelloWorld;

 

To run the Hello World program from RadASM, simply select the “Run” entry from the Make menu (see See Running the Hello World Program From RadASM). This produces the program output found in See HW.HLA Program Output. When you press the enter key, the console window will close and control returns to RadASM.

Running the Hello World Program From RadASM

 

HW.HLA Program Output

The other options in the RadASM “Make” menu have the following effect:

  • Build- compiles the project; if you’re using makefiles in your RadASM projects, this option will only compile those files absolutely necessary to create the executable.
  • Build All- cleans out all the old object and executable files and rebuilds the executable from scratch.
  • Compile Resource- Used to compile any resource files associated with this project (note that Build and Build All will also compile the resource files, if necessary).
  • Syntax Check - does a syntax compile on the HLA files, without actually building the whole application. Faster than building the whole project if you simply want to check for typos in your source code.
  • Run - Runs the executable (if you’re using makefiles, this will also build the application if the executable is not current; if you’re not using makefiles, you must manually build the application before running it).
Creating a New Project in RadASM

While the two default projects that RadASM supplies are useful for demonstrating the RadASM Project window pane, you’re probably far more interested in creating your own RadASM/HLA projects. Creating your own project is a relatively straight-forward process using RadASM’s project creation wizard. To begin this process, select the “File>New Project” menu item. This opens the project wizard dialog box (see See RadASM Project Wizard Dialog Box).

 

RadASM Project Wizard Dialog Box

The “Assembler” pop-up menu list lets you select the assembler that you want to use for this project. Remember, RadASM supports a variety of different assemblers and the “rules” are different for each one. Because you’re probably using HLA (if you’re reading this document), you’ll want to select the HLA assembler from this list. HLA should be the default (in fact, only) assembler in this list. If you’re not using the radasm.ini file supplied on Webster, then you should make sure that HLA appears first in this list in the radasm.ini file.

The “Project Type” group is a set of radio buttons that let you select the type of project you’re creating. RadASM populates this list of radio buttons from the “ [Project] ” section of the hla.ini file. The “ Type=... ” statement in this section specifies the valid projects that RadASM will create. RadASM creates the radio button items in the order the project type names appear in the “ Type=... ” list; the first item in the list is the one that will have the default selection. If you’re going to be developing Windows’ GUI applications most of the time, you’ll probably want to change this list so that “Windows App” appears first in the list. This will slightly streamline the use of the Project Wizard because you won’t have to explicitly select “Windows App” every time you create a new Windows application. The standard default is a Console App because that’s the type of program most beginning HLA programmers create. You can actually add new project types to this list by modifying the hla.ini file. However, most HLA programmers will be creating either Win32 GUI apps or Win32 console apps, hence the standard release of RadASM/HLA supports these two application types. If you want to creat your own project types, see the discussion on customizing RadASM later in this manual.

The “Project Name:” text entry box is where you specify the name of the project you’re creating. RadASM will create a folder by this name and any other default files it creates (within the project folder) will also have this name as their filename prefix. The text you enter at this point must be a valid Windows filename. Note that this should be a simple file name, not a path. You’ll supply the path to this file/directory in a moment. This name should be a base filename (that is, no extension). RadASM will create other filenames by attaching appropriate extensions to the name you supply here (e.g., “.hla” and “.exe”). So if you specify a name like “myProject” here, RadASM will create a directory named “myProject” to hold your files and it will also create a “myProject.hla” file (among other files). When you actually build your program, RadASM (by default) will create an exectuable named “myProject.exe”.

The “Project Description:” text entry box allows you to place a descriptive comment that describes the project. This is any arbitrary text you choose. It should be a brief (one-line) description of the project.

The “Projects Folder:” text entry box is where you select the path to the spot in the file system where RadASM will create the project folder. You can type the path in directly, or you can press the browse button to the right of this text entry box and use a Windows’ dialog box to select the subdirectory that will hold the project’s folder.

The “Template:” text entry box and browse button lets you select a template for your project. If you don’t select a template, then RadASM will create an empty project for you (i.e., the main “.hla” file will be empty). If you select one of the templates (e.g., the “.tpl” files found in the RadASM\Hla\Templates directory) then RadASM will create a “skeletal” project based on the project template you’ve chosen. See RadASM/HLA Templates lists some of the typical templates you will find.

RadASM/HLA Templates

 

Template Selection

Available if this project type is selected

Result

consApp.tpl

Console App

RadASM will create a simple console application. Builds are handled strictly by RadASM. Good for simple (one-file) HLA projects.

consAppBatch.tpl

Console App

RadASM will create a simple console application. Builds are handled by running one of several batch files (also created by this template) including build.bat, compilerc.bat, syntax.bat, and run.bat. By default, these batch files process a simple (one-source-file) project, but you can edit the batch files to handle more complex projects.

consAppMake.tpl

Console App

RadASM will create a simple console application. Builds are handled by running make.exe on a makefile that this template creates.

consAppNMake.tpl

 

Console App

Builds a project just like consAppMake.tpl except that it invokes Microsoft’s nmake.exe program rather than a generic make.exe program.

win32App.tpl

Windows App

RadASM will create a generic Win32 GUI project. Builds are handled strictly by RadASM. Good for simple (one-HLA-file) HLA projects.

win32AppBatch.tpl

Windows App

RadASM will create a generic Win32 GUI project. Builds are handled by running one of several batch files (also created by this template) including build.bat, compilerc.bat, syntax.bat, and run.bat. By default, these batch files process a simple (one-HLA-source-file) project, but you can edit the batch files to handle more complex projects.

win32AppMake.tpl

Windows App

RadASM will create a generic Win32 GUI project. Builds are handled by running make.exe on a makefile that this template creates.

win32AppNMake.tpl

Windows App

Builds a project just like win32AppMake.tpl except that it invokes Microsoft’s nmake.exe program rather than a generic make.exe program.

WPAApp.tpl

Windows App compatible with code from “Windows Programming in Assembly”

RadASM will create a Win32 GUI project based on the structure of the code described in “Windows Programming in Assembly”. These projects use the “wpa.hhf” header file and the “winmain.lib” library module described in Randy Hyde’s book “Windows Programming in Assembly Language” (found on Webster at http://webster.cs.ucr.edu). Builds are handled strictly by RadASM. Good for simple (one-HLA-file) HLA projects.

WPAAppBatch.tpl

 

Windows App compatible with code from “Windows Programming in Assembly”

RadASM will create a Win32 GUI project based on the structure of the code described in “Windows Programming in Assembly”. These projects use the “wpa.hhf” header file and the “winmain.lib” library module described in Randy Hyde’s book “Windows Programming in Assembly Language” (found on Webster at http://webster.cs.ucr.edu). Builds are handled by running one of several batch files (also created by this template) including build.bat, compilerc.bat, syntax.bat, and run.bat. By default, these batch files process a simple (one-HLA-source-file) project, but you can edit the batch files to handle more complex projects.

WPAAppMake.tpl

Windows App compatible with code from “Windows Programming in Assembly”

RadASM will create a Win32 GUI project based on the structure of the code described in “Windows Programming in Assembly”. These projects use the “wpa.hhf” header file and the “winmain.lib” library module described in Randy Hyde’s book “Windows Programming in Assembly Language” (found on Webster at http://webster.cs.ucr.edu). Builds are handled by running make.exe on a makefile that this template creates.

WPAAppNMAKE.tpl

 

Windows App compatible with code from “Windows Programming in Assembly”

Builds a project just like win32AppMake.tpl except that it invokes Microsoft’s nmake.exe program rather than a generic make.exe program.

emptyWinApp.tpl

Windows App

RadASM will create an empty Win32 GUI project. Builds are handled strictly by RadASM. Good for simple (one-HLA-file) HLA projects.

emptyWinAppBatch.tpl

 

Windows App

RadASM will create an empty Win32 GUI project. Builds are handled by running one of several batch files (also created by this template) including build.bat, compilerc.bat, syntax.bat, and run.bat. By default, these batch files process a simple (one-HLA-source-file) project, but you can edit the batch files to handle more complex projects.

emptyWinAppMake.tpl

 

Windows App

RadASM will create an empty Win32 GUI project. Builds are handled by running make.exe on a makefile that this template creates.

emptyWinAppNMake.tpl

 

Windows App

Builds a project just like emptyWinAppMake.tpl except that it invokes Microsoft’s nmake.exe program rather than a generic make.exe program.

Generally, it’s a good idea to select one of these templates when creating a new project. These templates automatically create any extraneous files a project needs (such as batch files and make files) and inserts these files into your new project. This spares you the effort of manually creating these files and inserting them into the project.

The RadASM/HLA package provides (at least) 16 different templates10. There are four different template categories, each category containing four templates. Not all of these template files will be visible when you press the “template browse” button. The cons*.tpl files are only visible if you’ve selected the “Console App” radio button. The win32*.tpl, WPA*.tpl, and empty*.tpl files will only be visible if you’ve selected the “Windows App” radio button in the “Project Type” box.

Within a given template category (cons*, win32*, WPA*, empty*) there are four choices available to you. For example when selecting one of the console templates you could choose consApp.tpl, consAppBatch.tpl, consAppMake.tpl, or consNMake.tpl. The difference between these project types is how RadASM will build (compile/assemble) the project.

The *App.tpl template files tell RadASM to directly build your application (using commands found in the .tpl file). You can think of this as the “native” RadASM build mode. The only problem with this approach is that it is not very flexible (in terms of handling multi-filecompilations) and it always rebuilds the entire project. As a result, projects that use the native RadASM build scheme are really suitable only for small (usually single-file) projects.

The *AppBatch.tpl template files tell RadASM to invoke various batch files when building the application. The template will actually create simple versions of these batch files for you: build.bat, compilerc.bat, syntax.bat, and run.bat. These batch files correspond to the items in the RadASM Make menu (note that the “Build” and “Build All” menu items both run the build.bat file). By default, these batch files only support a the creation of an application built around a single HLA source file (just like a native RadASM build). However, you can always edit these batch files to do a more sophisticated compilation. A later section will describe how to edit these batch files.

The *AppMake.tpl and *AppNMake.tpl template files tell RadASM to invoke a make utility (a generic make.exe program or Microsoft’s nmake.exe utility, based on which template you select). The template creates a generic makefile for you (automatically) that handles all the menu items in the RadASM Make menu. Using make is, without question, the best way to use RadASM. Make is far more efficient for larger projects than using batch files or RadASM’s built-in compilation capabilities. However, there are two drawbacks to using make: first, you have to have a copy of the make.exe (or nmake.exe) program (though this utility is available for free, see how to get a copy of this program in the section on make, earlier in this document); the second drawback is that you will have to edit the makefile that these templates create before you can build anything complex with them, i.e., if you want to create a sophisticated multi-file project, you’ll need to make other changes to the makefile that the template creates. See the section on make earlier in this document for the details associated with the make language.

Once you’ve selected the assembler type, project type, entered the project name and description, and optionally selected the folder and a template, press the “Next>” button to move on to the next window of the Project Wizard dialog. This dialog box appears in See Project Wizard Dialog Box #2. In this dialog box you select the initial set of files and folders that RadASM will create in the project’s folder for you. At the very least, you’re going to want a “.hla” file and a “Tmp” subdirectory. It’s probably a good idea to create a “BAK” subdirectory as well (RadASM will maintain backup files in that subdirectory, if it is present). More complex Windows applications will probably need a header file (“.HHF”) and if you’re creating fancy GUI applications, you may need a resource file (“.RC”) as well. If you’re creating a dynamically linked library (DLL), you’ll probably want a definition file (“.DEF”) as well. If you plan on writing documentation, you might want to create a DOC subdirectory - the choice is yours. Check the files and folders you want to create and press the “Next >” button in the dialog box. Note that simple console applications (the type of applications most beginning HLA users create) require only a “.hla” file and a “Tmp” directory.

 

Project Wizard Dialog Box #2

The last dialog box of the Project Wizard lets you specify the options present in the Make menu and the commands each of these options executes (see See Project Wizard Dialog Box #3). You should ignore all these options and just press the finish button. Generally, you will not customize this output; you will normally just hit the “finish” button to complete the construction of your project. If you do want to change these options, do it from the “Project>Project Options” menu item once you’ve created the project See Typical RadASM Window After Project Creation shows what the RadASM window looks like after create a sample “Windows App” application based on the win32app.tpl template (this project was given the name “MyApp”).

 
Project Wizard Dialog Box #3

 

Typical RadASM Window After Project Creation
Working With RadASM Projects

Of course, once you’ve created a RadASM project, you can open up that project and continue work on it at some later point. RadASM saves all the project information in a “.rap” (RadAsm Project) file. This “.rap” file keeps track of all the files associated with the project, project-specific options, and so on. These project files are actually text files, you can load them into a text editor (e.g., RadASM’s editor) if you want to see their format. As a general rule, however, you should not modify this file directly. Instead, let RadASM take care of this file’s maintenance.

There are several ways to open an existing RadASM project file - you can double-click on the .rap file’s icon within Windows and RadASM will begin running and automatically load that project. Another way to open a RadASM project is to select the “File>Open Project” menu item and open some “.rap” file via this open command. A third way to open a RadASM project is to use the File Browser to find a “.rap” file in one of your project directories and double-click on the project file’s icon (the “.rap” file) that appears in the project browser. Any one of these schemes will open the project file you’ve specified.

RadASM only allows one open project at a time. If you have a currently open project and you open a second project, RadASM will first close the original project. You can also explicitly close a project, without concurrently opening another project, by selecting the “File>Close Project” menu item.

Once you’ve opened a RadASM project, RadASM’s “Project” menu becomes a lot more interesting. When you create a project, RadASM gives you the option of adding certain “stock” files to the project (either empty files, or files with data if you select a template when creating the project). All of the files that RadASM creates bear the project’s name (with differing suffixes). As a result, you can only create one “.hla” file (and likewise, only one “.hhf” file, only one “.rc” file, etc.). For smaller assembly projects, this is all you’ll probably need. However, as you begin writing more complex applications, you’ll probably want additional assembly source files (“.hla” files), additional header files (“.hhf”), and so on. RadASM’s Project menu is where you’ll handle these tasks (and many others). See The RadASM Project Menu shows the entries that are present in the Project menu.

The RadASM Project Menu

To add new, empty, files to a RadASM project, you use the “Project > Add New” menu item. This opens up a new submenu that lets you select an assembly file (“.hla” file), an include file (“.hhf”), a resource compiler file (“.rc”), a text file, and so on. Selecting one of these submenu items opens up an “Add New File” dialog box that lets you specify the filename for the file. Enter the filename and RadASM will create an empty text file with the name you’ve specified. Later on, you can edit this source file with RadASM and add whatever text is necessary to that file. Note that RadASM will automatically add that file to the appropriate group based on the file’s type (i.e., it’s suffix).

The “Project > Add Existing” sub-menu lets you add a pre-existing file to a project. This is a useful option for creating a RadASM project out of an existing HLA (non-RadASM) project or adding files from some other project (e.g., library routines) into the current project. Note that this option does not create a copy of the files you specify, it simply notes (in the “.rad” file) that the current project includes that file. To avoid problems, you should make a copy of the actual source file to the current project’s folder before adding it to the project; then add the version you’ve just copied to your project. It’s generally unwise to add the same source file to several different projects. If you change that source file in one project, the changes are automatically reflected in every other project that links this file in. Sometimes this is desirable, but most of the time programmers expect changes to a source file to be localized to the current project. That’s why it’s always best to make a copy of a source file when adding that file to a new project. In those cases where you do want the changes reflect to every application that includes the file, it’s better to build a library module project and link the resulting “.lib” file with your project rather than recompile the source file in.

The “Project > Project options” menu item opens up a “Project Options” dialog box that lets you modify certain project options (see See “Project > Project Options” Dialog Box). This dialog box lets you change certain options that were set up when you first created the project using the “File > New Project” Project Wizard dialogs. Most of the items in this dialog box should have been described earlier, but a few of the items do need a short explanation.

“Project > Project Options” Dialog Box

The Project Options dialog box provides two radio buttons that let you select whether RadASM will do a “debug build” or a “release build.” Be sure that the “Release” radio button is selected. The Debug option instructs HLA to insert certain debugging information into your executable file (e.g., for use by OllyDbg). We will not consider that option in this document.

Build Options with RadASM/HLA

Before discussing how to actually edit and compile programs using RadASM, we need to stop for a moment and discuss the internal operation of RadASM and how it controls programs like HLA. RadASM was created to be a very flexible system supporting multiple assemblers and different ways of building applications. In one respect, this flexibility is very good - it is exactly this flexibility that allows RadASM to work with HLA (rather than just with Microsoft’s assembler, for which RadASM was initially created). On the other hand, there is a down side to this flexibility - creating HLA projects is a little bit more involved than it has to be had RadASM been written specifically for HLA. In this section we’ll discuss the extra work involved with creating and maintaining RadASM projects.

Take another look at See “Project > Project Options” Dialog Box. Beside the labels “Compile RC”, “Assemble”, “Link”, etc., you’ll find some editable strings. These strings are special RadASM commands that tell RadASM what to do whenever you select an item from RadASM’s “Make” menu. Originally, the labels next to each of these text edit boxes corresponded to menu items in the RadASM “Make” menu; HLA, however, has renamed the menu items in the “Make” menu, so they no longer correspond to the labels appearing in the “Project Options” dialog box (See “Project > Project Options” Dialog Box). See Correspondence Between Project Options and Make Menu shows the relationship between the labels in the Project Options dialog box and the Make menu.

Correspondence Between Project Options and Make Menu

The text appearing in the corresponding text edit box in the Project Options dialog box is a command that RadASM executes whenever you select the corresponding item from the Make menu. Here’s the syntax for each of these entries:

 

DEL, OUT, CMD, FILE {,FILE,...}

 

DEL is a numeric entry that specfies which files to delete prior to executing the command. Normally, this should be zero (which means “don’t delete any files.”).

OUT is either “O”, “OT”, or “C” meaning that the command’s output goes to the RadASM output windows (“OT”), the command produces no output (and any output is ignored, “O”), or RadASM opens up a console (command-line) window and sends all output to that window (“C”). For most commands except “RUN”, you’ll probably want the command’s output to go to the RadASM output window; when running the program you’ll probably want the output to go to a console window (at least, if you’re writing a console application).

CMD is the command (command-line prompt command) to execute in response to this Make menu selection. This includes the program’s name and any command line parameters (though you don’t usually specify the filenames to process here).

FILE is a special numeric designation (internal to RadASM) that specifies the file that the CMD is to process. We’ll normally leave this blank, see the discussion on RadASM customization later in this document for more details on this entry.

There are three common ways people use to have RadASM run HLA to compile an HLA project: direct command execution, batch file execution, and make file execution. Each of these execution modes have their own set of advantages and disadvantages.

Direction command execution is the default mode for RadASM/HLA “out of the box.” This mode has the advantage of being the easiest to use. For the most part, it doesn’t require the creation of any special files in order to build a given project (though the “run” command works best if you create a batch file for it). There are several disadvantages to this approach. First, it doesn’t work with HLA on all versions of Windows. Another disadvantage to this approach is that it’s mainly useful for single-file projects (unless you’re willing to delve deep into RadASM and learn all about customizing it for your own purposes). Yet another disadvantage is that you have to manually build eachcomponent of the project when using the direct command execution. In general, the disadvantages would outweigh the advantages of this execution mode were it not for the fact that the direct command approach works best for simple projects as it doesn’t require the creation of any batch or makefiles. However, once you’ve created a few HLA projects and get comfortable with RadASM, you’ll probably want to shift to one of the other RadASM operation modes. Note that running in this mode is equivalent to creating a project with one of the *App.tpl templates (also note that template settings always override the default settings).

Batch file execution is the second mode of operation that RadASM/HLA supports. In this mode of operation each of the RadASM commands in the Project Options dialog box executes a batch file and that batch file handles whatever set of tasks is necessary for the specified Make menu option. See RadASM in Batch Execution Mode (Project Options) shows the Project Options dialog box with the commands to execute when operating in batch mode. Note that each of the commands simply execute a batch file (build.bat, compilerc.bat, syntax.bat, and run.bat).

 

RadASM in Batch Execution Mode (Project Options)

The batch files specified in the Project Options dialog box must appear in the same directory as the other files for project (e.g., along with the source files). These batch files contain a list of command-prompt commands to execute whenever you select one of the menu items from the Make menu. Here are the contents for each of the generic batch files supplied with RadASM/HLA:

 

build.bat:

 

hla -p:tmp %1

 

compilerc.bat:

 

echo “No Resource Files to Compile!”

pause

 

syntax.bat:

 

hla -p:tmp -s %1

 

run.bat:

 

%1

pause

 

The advantage of the batch file execution scheme over the direct execution scheme is that you can execute several commands within a batch file (unlike the direct command execution scheme). This lets you compile multi-file projects and execute other command-line actions within the batch file. Also, the batch file scheme works with all versions of Windows. Furthermore, the batch file scheme doesn’t require any additional utility programs to achieve this flexibility. Batch files have two main disadvantages. First, you have to write a set of batch files for every project you create (though for single-file projects, the generic batch files work fine; editing the batch files is only necessary for more sophisticated projects). The second problem with batch files is they force a rebuild of every file in a multi-file project, even if such work is unnecessary.

The makefile execution scheme is the most flexible of the three. Like the batch file scheme, you can execute multiple commands and this scheme works with all versions of Windows. A big advantage of makefiles over batch files is that you can easily handle large multi-file projects using makefiles and you can build the projects only recompiling the files that are necessary. Like batch files, one disadvantage to using makefiles is that you have to maintain a separate “makefile” the directs the compilation. Another disadvantage to the makefile scheme is that you have to have a separate “make” utility installed on your system (if you don’t already have a copy of make, you can obtain one for free from Borland; see the section on “Make” for more details). See Makefile Execution Scheme (Project Options) show the command set for RadASM when using the makefile execution scheme with Borland’s “make.exe” program (note: to use Microsoft’s “nmake.exe” program, simply change each occurrence of “make” to “nmake” in the dialog box).

 

Makefile Execution Scheme (Project Options)

The version of RadASM that ships with HLA includes several versions of the “hla.ini” initialization file that RadASM uses. These files are the following:

  • hla.ini - this is the actual file that RadASM uses. As shipped, this is the same as hla_2000.ini (the direct execution mode file).
  • hla_2000.ini - this is the version of the hla.ini file that supports direct command execution. If you ever change hla.ini and you want to restore the direct execution form, simply make a copy of this file and rename it to hla.ini.
  • hla_bat.ini - this is the version of the hla.ini file that supports batch mode execution. If you want to use batch mode execution, make a copy of this file and rename the copy to “hla.ini”.
  • hla_make.ini - this is the version of the hla.ini file that supports Borland’s make.exe application for the makefile execution mode (actually, this .ini file supports makefile execution using any make program named “make.exe”). If you want to use makefile mode execution, make a copy of this file and rename the copy to “hla.ini”.
  • hla_nmake.ini - this is the version of the hla.ini file that supports Microsoft’s nmake.exe application for the makefile execution mode. If you want to use makefile mode execution, make a copy of this file and rename the copy to “hla.ini”.

Note that the execution mode specified by the “hla.ini” files is only available when you create a new project without using a template (template files override the settings in the hla.ini file). Each RadASM project file you create (the “.rap” file) maintains the execution mode as part of that project. Should you change the execution mode by copying some new file over the top of hla.ini, you do not change the execution modes for any pre-existing projects. If you want to change the execution mode of an existing project, you will have to select the “Project>Project Options” menu item and edit the entries in the project option dialog box.

The remainder of this document will assume that you’re using the flexible “makefile” execution mode and that you’re creating makefiles for each of your projects. Therefore, to follow along with the examples that appear in the remainder of this document, you should make a copy of the hla_make.ini (or hla_nmake.ini) file and rename it to hla.ini. Another alternative is to always use one of the *AppMake.tpl or *AppNMake.tpl templates when creating new projects.

Editing HLA Source Files Within RadASM

The RadASM text editor is quite similar to most Windows based text editors you’ve used in the past (i.e., RadASM generally adheres to the Microsoft Common User Access (CUA) conventions. So the cursor keys, the mouse, and various control-key combinations (e.g., ctrl-Z, ctrl-X, and ctrl-C) behave exactly as you would expect in a Windows application. Because this is an advanced programming book, this chapter will assume that you’ve used a CUA-compliant editor (e.g., Visual Studio) in the past and we’ll not waste time discussing mundane things like how to select text, cutting and pasting, and other stuff like that. Instead, this section will concentrate on the novel features you’ll find in the RadASM editor.

Of course, the first file navigation aid to consider is the Project Browser pane. We’ve already discussed this RadASM feature in earlier sections of this document, but it’s worth repeating that the Project Browser pane lets you quickly switch between the files you’re editing in a RadASM project. Just double-click on the icon of the file you want to edit and that file will appear in the RadASM editor window pane.

Immediately below the Project Browser pane is the “Properties” pane (if this pane is not present, you can bring it up by selecting “View > Properties” from the RadASM View menu). This pane contains two main components: a pull down-down menu item that lets you select the information that RadASM displays in the lower half of this window. If not already selected, you should select the “.code” item from this list. The “.code” item tells RadASM to list all the sections of code that it recognizes as procedures (or the main program) in an HLA source file (see See The HLA Properties Window Pane).

The HLA Properties Window Pane

One very useful RadASM feature is that you can quickly jump to the start of a procedure’s body (at the begin statement) by simply double-clicking on that procedure’s name in the Properties Window pane. In the example appearing in See The HLA Properties Window Pane (this is the “Dialog” project supplied with RadASM for HLA), double-clicking on the “Dialog;” and “DialogProc;” lines in this list box automatically navigates to the start of the code for the selected procedure.

The pull-down menu in the Properties window lets you select the type of objects the assembler provides. For example, by selecting “.const” you can take a look at constant declarations in HLA. The “macro” selection lets you view the macro definitions that appear in the source file. As this chapter was first being written, the other property items weren’t 100% functional; hopefully by the time you read this RadASM will have additional support for other types of HLA declarations.

Another neat feature that RadASM provides is an “outline” view of the source file. Looking back at See The HLA Properties Window Pane you’ll notice that “ begin DialogProc; ” statement has a rectangle with a minus sign in it just to the left of the source code line. Clicking on this box closes up all the code between the begin and the corresponding end in the source file. See RadASM Outline View (with Collapsed Procedures) shows what the source file looks like when the Dialog and DialogProc procedures are collapsed in outline mode. The neat thing about outline mode is that it lets you view the “big picture” without out the mind-numbing details of the source code for each procedure in the program. In outline view, you can quickly skim through the source file looking for important code and “drill down” to a greater level of detail by opening up the code for a procedure you’re interested in looking at. You can also rapidly collapse or expand all procedure levels by pressing the “expand” or “collapse” buttons appearing on the lower left hand corner of the text editor window (see See RadASM Outline View (with Collapsed Procedures)).

 
RadASM Outline View (with Collapsed Procedures)

Another useful feature RadASM provides is the ability to display line numbers with each line of source code. Pressing on the line number icon in the lower-left hand corner of the text editor window (the icon with the “123” in it) toggles the display of line numbers in the editor’s window. See See Displaying Line Numbers in RadASM’s Editor to see what the source file looks like with line numbers displayed. The line number display mode is quite useful when searching for a line containing a syntax error (as reported by HLA). Note that you can also navigate to a given line number by pressing ctrl-G and entering the line number (you can also select “Edit > Goto line” from the “Edit” menu).

 

Displaying Line Numbers in RadASM’s Editor

Another useful navigation feature in RadASM is support for bookmarks. A bookmark is just a point in the source file that you can mark. You can create a bookmark by selecting a line of text (by clicking the mouse on the gray bar next to the line) and selecting “Edit > Toggle BookMark” or by pressing shift-F8. You can navigate between the bookmarks by pressing F8 or ctrl-F8 (these move to the next or previous bookmarks in the source file). RadASM (by default) provides several icons on it’s toolbar to toggle bookmarks, navigate to the previous or next bookmark, or clear all the bookmarks. Which method (edit menu, function keys, or toolbar) is most convenient simply depends on where your hands and the mouse cursor currently sits.

The RadASM “Format” menu also provides some useful features for editing HLA programs. The “Format > Indent” and “Format > Outdent” items (also accessible by pressing F9 and ctrl-F9) move a selected block of text in or out four spaces (so you can indent text between an if and endif , for example). You can also convert tabs in a document to spaces (or vice versa) from the “Format > Convert > Spaces To Tab” and “Format > Convert > Tab To Spaces” menu selections.

You’ll notice that RadASM provides syntax coloring in the editor window (that is, it sets the text color for various classes of reserved words and symbols to different colors, making them easy to identify with a quick glance in the editor window). The hla.ini file accompanying the RadASM/HLA release contains a set of reasonable color definitions for HLA’s different reserved word types. However, if you don’t particularly agree with this color scheme, it’s really easy to change the colors that RadASM uses for syntax highlighting. Just select the “Options > Colors & Keywords” menu item and select an item from the Syntax/Group list box (See Option>Colors & Keywords Dialog Box with Group#00 Selected shows what this dialog box looks like with the Group #00 item selected). By double-clicking on an item within the Group list box, you can change the color for all the items in that particular group (e.g., see See Color Selection Dialog Box). RadASM automatically updates the hla.ini file to remember your choice of colors the next time you run RadASM.

Option>Colors & Keywords Dialog Box with Group#00 Selected

 

Color Selection Dialog Box

You can also set the display fonts to something you’re happier with if the default font (Courier New, typically) isn’t to your liking. This is also achievable from the RadASM “Option” menu.

Managing Complex Projects with RadASM

One of the main reasons for using a project-oriented integrated development environment like RadASM is to streamline the development of complex projects. For the sake of argument, we’ll define a “simple” project as any HLA project consisting of a single “.hla” source file and, possibly, a header file. A complex project will be any application that requires multiple source files, object modules, library modules, and resource files that must be separately compiled and linked together to form a single exectuable file. Though an IDE such as RadASM is helpful when working on simple projects, a development environment is most effective when working on larger, complex projects.

Although it is possible to maintain certain complex projects using RadASM’s native capabilities, by far the best solution for complex projects is to use a make utility or (if you don’t have access to a make utility) batch files to control the compilation process. Though it requires a little additional labor to set up a set of batch files or a make file, the flexibility you gain by using this approach is well worth the small amount of additional effort (effort that will be repaid many times over during the project’s development).

Before describing how to write batch files and makefiles to take over control of the compilation process from RadASM, perhaps it would be wise to offer a small justification for this approach. After all, RadASM has some very sophisiticated schemes for building projects, why not stick with RadASM’s native approach? Well, there are several reasons. First, although RadASM’s general nature is a wonderful attribute of the system (e.g., it allows HLA to work with RadASM even though it was originally designed for MASM), sometimes a specific solution is more efficient or more powerful than a general solution. Second, although RadASM is a great development environment, sometimes it’s just easier or more convenient to compile a project from the command line prompt; by using batch or make files in your RadASM projects, you can easily work from the command line or from within RadASM and know that you’re building your project exactly the same way in both cases. Also, tools like the make utility have been around for quite some time and contain lots of features that you won’t find in a less mature system like RadASM. Fortunately, RadASM is flexible enough to allow the use of batch and make files when working on a project, so you get the best of both worlds - the convenience of an integrated development environment, and the power and flexibility of make files.

Project Maintenance with Batch Files

The batch file approach is usable by those who do not have access to a make utility (or those who want to distribute RadASM projects to others who might not have a make utility available). Although batch files are more flexible than native RadASM builds, you should really attempt to use the make file approach unless there are some extenuating reasons why you would rather go with the batch file approach (e.g., the need to distribute RadASM/HLA projects to people who might not have a make utility).

A batch file is simple an ASCII text file that contains a sequence of command-line commands. The Windows command-line interpreter executes each line of text in a batch file just as though you’d typed those commands directly into a command window. By placing multiple commands in a batch file, you can execute as many commands as necessary to build your project. For example, suppose you have a little utility that increments a version number embedded in an HLA header file. You could execute a batch file that bumps up the version number and builds the HLA application using the following sequence of commands:

 

BumpVersion version.hhf

hla FileThatIncludesVersionFile.hla

 

Batch files also let you specify command-line parameters, e.g.,

 

someCmd parm1 parm2 parm3 ...

 

You may refer to these command-line parameters within the batch file using %1, %2, %3, etc. For example, the default build.bat file that RadASM will create for you if you specify the use of one of the *AppBatch.tpl template files is

 

hla -p:tmp %1

 

RadASM (by default) invokes this build.bat file using a command line like the following:

 

build filename.hla

 

The batch file processor substitutes “filename.hla” for the “%1” within the batch file.

The big problem with RadASM’s native compilation facilities is that it doesn’t particularly know what files you want to compile. It will supply the main project name (or, with appropriate customization, all the files in a given project), but it won’t let you easily pick and choose which files you want to process. That’s where batch files (and makefiles) are useful. In a project-specific batch file, you can easily specify any or all files that you want to compile and link together into a single executable. For example, if you have a project that combines two HLA files, a resource (.rc) file, and a specialized library, you could handle this compilation with the following command in a batch file:

 

hla myProj.hla subroutines.hla resources.rc speciallib.lib

 

Such a command line could not be built (automatically) from within RadASM. This is particularly true if some of the files are not present in the project’s directory (e.g., common object and library files present in a separate subdirectory).

If you create a project with one of the *AppBatch.tpl templates, or create a generic project using the hla_batch.ini file as your hla.ini file, then RadASM will, by default, connect the following Make menu items to the following batch files.

RadASM/HLA Make Menu/Batch File Correspondence

 

Make Menu Item

Corresponding Batch File

Build

build.bat

Build All

build.bat

Compile Resource

compilerc.bat

Syntax Check

syntax.bat

Run

run.bat

Note that the Build and Build All menu items both invoke the same batch file. The Build menu item’s intent is to build the application by compiling only those files that absolutely need to be compiled. This feature is generally available only if you’re using make files. Therefore, if you choose the Build menu item when using batch files, it will generally recompile all files in the application. The Compile Resource and Syntax Check menu items in the Make menu will invoke their corresponding batch files that will contain commands to compile a resource file (if any) or run the HLA compiler in a “compile to assembly” mode (no object or executable output, i.e., a syntax check of the file).

The run.bat file is somewhat special. The default run.bat file takes the following form:

 

%1

pause

 

RadASM will pass a command line parameter of the form “projectname.exe” to the run.bat file. Assuming that your project has compiled the files to produce the executable “projectname.exe”, this batch file will execute your application and then wait until you hit the enter key before it closes up the console window that executes the batch file (this gives you the opportunity to review any output produced by console applications).

If you would prefer to execute different batch commands when selecting items from RadASM’s Make menu, you can specify the commands to execute by selecting the “Project>Project Options” menu item. This opens up a dialog box that let’s you specify the command line parameters for each of the menu items. See the discussion elsewhere in this document for more details.

In general, batch files are not the most appropriate way to deal with complex projects. Make files are a much better solution. Therefore, unless you absolutely have to, you should avoid using the RadASM/HLA batch file compilation scheme.

Project Maintenance with Make Files

Makefiles provide the best way to build complex projects when using RadASM/HLA. They are more efficient, they are safer, and they give you more control over the compilation process than you will get with RadASM’s native mode or when using batch files. For most projects, the make file build scheme is, by far, the best. There are, of course, a couple of disadvantages to using make files. Specifically, you need to have a make utility in order to use makefiles and you need to learn the “make language” in order to use make files. Fortunately, a decent version of make is available for free from Borland and learning make is not that difficult (see the discussion of make earlier in this document). However, the advantages of make files far outweigh the disadvantages, so you should give make files serious consideration if you’re not sure which RadASM/HLA compilation scheme to use.

Here are the commands that RadASM executes whenever you select an item from RadASM’s make menu when using make files to build your application:

 

Build menu item: make build

Build All menu item: make buildall

Syntax Check menu item: make syntax

Compile Resource menu item: make compilerc

Run menu item: make run

 

These commands all assume that there is a single file, “makefile” present in the project directory. These commands will execute the build, buildall, syntax, compilerc, or run dependencies in the makefile, respectively. Here’s what the default makefile (supplied with RadASM/HLA) looks like:

 

build: $(hlafile).exe

 

buildall: clean $(hlafile).exe

 

compilerc:

echo No Resource Files to Process!

 

syntax:

hla -s $(hlafile).hla

 

run: $(hlafile).exe

$(hlafile)

pause

 

clean:

delete tmp

delete *.exe

delete *.obj

delete *.link

delete *.inc

delete *.asm

delete *.map

 

 

$(hlafile).exe: $(hlafile).hla

hla $(DEBUG) -p:tmp $(hlafile)

 

 

For simple projects (i.e., projects consisting of a single HLA source file), you’ll be able to use the makefile as-is. RadASM automatically supplies the project’s name as the “hlafile” variable to build your project whenever you select an item from the RadASM “Make” menu. For more complex projects, you’re going to want to edit this makefile extensively to add additional dependencies and commands.

The build dependency in this make file executes whenever someone selects the “Make>Build” menu item in RadASM. The intent of this command is to build the application with as little processing as possible. That is, if several of the files needed to build the final executable have already been compiled into object files, this command should not recompile those files, it should use the up-to-date objects as-is and only recompile those files whose source files are newer than the object files.

The buildall dependency in the makefile executes whenever someone selects the “Make>Build All” menu item in RadASM. The intent of this command is to do a complete build of the system, ignoring any object files that are already up to date. The typical execution of this command involves deleting all temporary files (e.g., object files) by executing the “clean” operation, and then doing a build.

The compilerc dependency executes whenever you select the RadASM “Make>Compile Resource” menu item. In the default make file provided with RadASM/HLA, this command simply displays a brief diagnostic message. If your project has some resource files that you need to compile with Microsoft’s resource compiler, then you would normally specify the dependencies and commands needed to process those resource files after the compilerc dependency. For example, if your project includes a resource file named “myProject.rc”, you’d typically edit the makefile to add/modify the following:

 

build: $(hlafile).exe $(hlafile).res

 

buildall: clean $(hlafile).exe $(hlafile).res

 

compilerc: $(hlafile).res

 

syntax: compilerc

hla -s $(hlafile).hla

 

$(hlafile).res: $(hlafile).rc

rc -v $(hlafile).rc

 

Of course, once you start making major modifications to the makefile, you can probably drop the use of the $(hlafile) variable and use the direct filenames (variables are great for generic makefiles; however, they tend to obscure makefiles created for a specific project). That is, for a project like “myProject” with a “myProject.hla” file and a “myProject.rc” file you’d probably just create a makefile like the following:

 

build: myProject.exe

 

buildall: clean myProject.exe

 

compilerc: myProject.res

 

syntax:

hla -s myProject.hla

 

run: myProject.exe

myProject

pause

 

clean:

delete tmp

delete *.exe

delete *.obj

delete *.link

delete *.inc

delete *.asm

delete *.map

delete *.res

 

 

myProject.exe: myProject.hla myProject.res

hla $(DEBUG) -p:tmp myProject myProject.res

 

myProject.res: myProject.rc

rc -v myProject.rc

 

RadASM Menus

The following sections describe many of the RadASM menu items and their applicability to HLA software development. For a full discussion of each menu item, please see the on-line RadASM help file.

The RadASM File Menu

The RadASM file menu provides all the common file operations you’d expect in a Windows application, plus a few RadASM specific entries (see See RadASM File Menu).

RadASM File Menu
File>New Project

This menu option lets you create a new project using RadASM (this process was explained in detail earlier in this document). When using RadASM/HLA, you’re best bet is to always create a new RadASM project using one of the RadASM templates supplied with the RadASM/HLA package. The end result of the “File>New Project” selection is a new project directory with associated files (including a RadASM “.rap” file that describes the project plus any source files you’ve created for the project).

File>Open Project

This menu option opens a dialog box that lets you open an existing RadASM project (.rap file). See See RadASM Open Project Dialog Box for details. From this dialog box you can locate the .rap file for your particular project and selecting that file will open up the project and its associated files.

RadASM Open Project Dialog Box
File>Close Project

Selecting this menu item closes any open project (you may only have one project open at a time). If any modifications have been made to any files in the project, you will be asked whether you want to save them before closing the project. Note that this menu item is only active if you have a currently open project.

File>Delete Project

This menu item deletes the currently open project. Use this option with care. Once you delete a project it is gone. This menu item is only active if you have an open project and it deletes that project.

File>New File

The options creates a new text file and opens up a window for that text file in the editor. Note that this file does not automatically become a part of any project (including the currently open project, if there is one). See the discussion of the Project menu earlier in this document if you want to insert a file into the currently opened project.

File>Open

This command opens a text file found on the disk. This file does not have to belong to a currently opened project, and once opened it does not become part of the current project.

File>Open as Hex

This command opens an arbtrarily typed file (not necessarily a text file) in a hex-editor window (see See Hex Editor Window for an example of the display of the “hw.exe” file in hex format). Note that you can edit this file (using hex values) and save the result back to disk. This is for advanced programmers only! You can do some serious damage to an executable file if you go poking around in it.

 

Hex Editor Window
File>Close File

This command closes the topmost open window. If there are any outstanding modifications, you will be asked if you want to save the file before closing it.

File>Save File

This saves the top-most open file to disk, without closing the file. Any old data in the file on the disk is replaced.

File>Save File As

This saves the data in the top-most open file under a different name. The old data in the original file is unchanged. Note that the default name for the top-most file changes to whatever name you supply, so future saves of this file will save their data to the new file rather than the old file.

File>Save All Files

This quickly saves all modifies files that are open in the editor.

File>Recent Files

This is a hierarchical menu item. Selecting this menu item opens up a secondary menu listing files you’ve recently edited with RadASM. You may open one of these files by selecting the specified file from the list.

File>Page Setup

Opens a generic Windows’ Printer Set-up dialog.

File>Print

Opens the generic Window’s printer dialog box.

File>Exit

Quits RadASM.

Edit Menu Items
Edit>Undo, Redo, Cut, Copy, Paste, Delete, Select All

Generic editing options available in most Windows applications

Edit>Find, Find Next, Find Previous, Replace, Find Word

Opens up a very typical find (or replace) dialog box. (see See Find Dialog Box). Note that by checking the “project” box, you can instruct RadASM to search for a string throughout a project. All the other options are standard Windows’ User Interface items that you’ve seen before.

Find Dialog Box
Edit>Goto Line

This menu item opens up a small dialog box that lets you enter a line number. RadASM displays that line in the top-most open window.

Edit>Expand Block

This menu item expands or compresses a begin..end block in an HLA source file (outline mode).

Edit> Next/Previous/Got/Toggle/Clear Bookmark

RadASM provides a bookmark facility that lets you place markers (bookmarks) on lines of code in your source file. You can quickly navigate between bookmarks by selecting the Next/Previous Bookmark menu items (or by pressing F8 or Ctrl-F8). You can also jump to a specific bookmark (Goto Bookmark) or clear all the set bookmarks in your source file. Bookmarks are especially useful for quickly switching between two sections of code in a source file.

The View Menu

The View menu lets you hide or make visible certain components of the RadASM user interface. This menu allows you to enable or disable the following components:

  • Toolbar
  • Toolbox
  • Output Window
  • Project Browser
  • Properties
  • Tab Select
  • Info Tool
  • Status Bar
Format Menu

The format menu contains several useful items that operate on the text within your source file.

Format>Indent

Indents the selected lines of text by one tab stop (the indentation is to the right).

Format>Outdent

Outdents the selected lines of text by one tab stop (the outdention is to the left).

Format>Comment

This command places the HLA line comment delimeter (“//”) at the beginning of each line.

Format>Uncomment

This command deletes the comment delimiters appearing at the beginning of a block of selected lines.

The Project Menu

The project menu contains several items that let you manage your RadASM projects.

Project>Add New

This menu item lets you add a new file to a project. This is a hierarchical menu item that lets you add source (.hla) files, header (.hhf) files, resource (.rc) files, text (.txt) files, dialog (.dlg) files, menu (.mnu) files, module (.hla) files, or other arbitrary files to your project.

With a bit of project customization, you can have RadASM build a multi-module project by adding module files to your project. However, if you’re interested in creating complex multi-module projects, you’re probably better off using makefiles to control your project builds. For more information about modules in RadASM, please consult the RadASM on-line documentation.

As its name suggests, this menu option creates a new (empty) file to add to a project. You will have to edit the file this option creates in order to place data in the file.

Project>Add Existing

This is another hierarchical menu item that lets you select some file on your hard drive and add it to the current project. If you’ve got some existing files you’d like to convert to a RadASM project, this is the option you use to add those files to a project. Note that this option does not copy the file into your project; it simply creates a link to the file whereever it is on the disk. If you want a copy of the file in your project’s directory you should copy the file to your project directory before adding the file to your project.

Project>Resource

This menu item lets you edit your resource file in a structured fashion. This includes AVI data, Bitmaps, cursors, icons, images, midi data, and so on. This option opens a dialog box that lets you select the resource type, the internal program identifier and value, and the file containing the resource data. By pressing the “export” button in the dialog box that comes up, you can get the text to cut and paste to a resource (.rc) file. See See Project>Resource Dialog Box (and Export Output) for an example.

Project>Resource Dialog Box (and Export Output)
Project>Stringtable

This menu item opens up a dialog box that lets you create string resources. You type in strings, identifiers, and values, and then press the export button (see See Project>Stringtable Dialog Box). The dialog box writes to the output window a data set that you can cut and paste into a resource (.rc) file.

Project>Stringtable Dialog Box
Project>Versioninfo

This menu item creates a version information resource. You enter all the appropriate information in the dialog box that comes up (see See Project>Versioninfo Menu Item), press the export button, and RadASM writes a resource (.rc) file compatible block of text to the output window that you can cut and paste to an appropriate resource file.

Project>Versioninfo Menu Item
Project>Set Assembler

This option is only available if you’ve set up RadASM to work with other assemblers in addition to HLA. The RadASM/HLA package leaves this option disabled, by default.

Project>Remove From Project

This menu item removes the selected file (selected in the project browser) from the project.

Project>Create Template

This menu item lets you create your own custom templates for RadASM projects. See the on-line help for more details concerning the creation of templates.

Project>Project Options

This is one of the more important options under the Project menu. This option (which has been discussed earlier) lets you set various options for the currently opened project. This includes the selection of debug/release mode, which items appear in the make menu, and the specification of commands to execute for each of the items in the make menu. See the discussion earlier in this document or the on-line help for more details.

Project>Main Project Files

This lets you specify the file types that a project can use. Note that it is *very* dangerous to modify this file list for an existing project. You can easily break RadASM/HLA’s build facility by changing these names. Only expert RadASM users should play with this dialog box/menu item See the on-line help and the RadASM customization guid for more details.

Make Menu

The make menu has been fully discussed elsewhere in this document. Note that RadASM/HLA uses a special layout for the Make menu that is not typical of RadASM when used with other assemblers. Therefore, when reading the on-line help, you’ll notice that the items don’t correspond to the items present in the RadASM/HLA make menu. As it turns out, the items in this menu are customizable on a project by project basis (which is how they got changed for RadASM/HLA). See the section on Customizing RadASM for more details.

The Tools Menu

This menu runs various little applications (“applets”) including the “sniplets” manager, the notepad editor, the Windows calculator, the command line prompt (command window), ASCII table, and a toolbar generator application. Of these, the “sniplets manager” is probably of greatest interest to HLA programmers. The sniplets manager lets you save short pieces of code (“sniplets”) that you can cut and paste into your current project. You can expand the available sniplets by saving files in the ..RadASM\HLA\Sniplets subdirectory.

The Window Menu

This menu lets you organize the window display in RadASM. The principle items you use in this menu are the file listings at the bottom of the Window Menu. From here, you can quickly select (and bring to the front) any given editor window that is currently open.

The Option Menu

This menu lets you open up dialog boxes that control how RadASM operates. Most of the items in this menu are for advanced RadASM users only, so we’ll not spend a whole lot of time discussing them, but a few of the menu items are quite useful and deserve a quick mention.

Option>Code Editor Options

This menu item opens the Code Editor Options dialog box (see See Code Editor Option Dialog Box) that lets you set various editor-wide options that are useful while editing projects.

Code Editor Option Dialog Box
Options>Colors & Keywords

This menu item lets you select the colors that RadASM will use for syntax high-lighting and other purposes within the editor. You can also choose the keywords that RadASM will recognize (for coloring purpose) within this dialog box (see See Colors & Keyword Dialog Box).

Colors & Keyword Dialog Box
Options>Code Editor Font

This menu item brings up a font selection dialog. RadASM uses this font whenever displaying source code. Note that you should always choose a fixed pitch font (e.g., courier or fixedsys) when editing source code. Mainly, you’ll use this option to change the size of the font in your display windows.

Options>Line Number Font

This brings up a font dialog box that lets you choose the font RadASM uses when displaying line numbers. This is usually a smaller font than used for code or text.

Options>Text Editor Font

This brings up a font selection dialog box that lets you choosethe font used when editing text files. Typically, this would be the same font as the code.

Options>Printer Options, Printer Font

Th Printer Options dialog lets you specify page headings and output color capabilities for print-outs from RadASM.

The Printer Font menu item opens up a font selection dialog that lets you choose the output font when printing text from RadASM.

Options>File Browser

This menu item opens up a small dialog box that lets you select the directories that RadASM will cycle through when pressing the arrow buttons in the project browser pane. This dialog also lets you select the file filters the project browser window will use when displaying files.

Options>External File Types

This menu lets you specify various non-RadASM recognized file types and the applications that RadASM will open in order to process such files.

Options>Sniplets

This menu item opens a dialog box that lets you tell RadASM how you want to cut and paste sniplets into your code.

Options>Set Paths

This option brings up a dialog box that lets you specify where RadASM can find certain folders in the system. Generally, it’s dangerous to mess with these paths as the RadASM/HLA installation should set these paths up properly for you. Be sure to consult the RadASMini.rtf document (shipped with RadASM) if you want to change these items.

Customizing RadASM

RadASM is a relatively generic integrated development environment for assembly language development. This single programs supports the HLA, MASM, TASM, NASM, and FASM assemblers. Each of these different assemblers feature different tool sets (executable programs), command line parameters, and ancillary tools. In order to control the execution of these different programs, the RadASM system uses “.INI” files to let you specifically configure RadASM for the assembler(s) you’re using. HLA users will probably want to make modifications to two different “.INI” files that RadASM reads: radasm.ini and hla.ini. You’ll find these two files in the subdirectory containing the radasm.exe executable file. Both files are plain ASCII text files that you can edit with any regular text editor (including the editor that is built into RadASM).

The RadASM package includes an “.RTF” (Word/Wordpad) documentation file that explains the basic format of these “.INI” files that RadASM uses. Readers interested in making major changes to these “.INI” files, or those attempting to adopt RadASM to a different assembler, will want to read that document. In this chapter, we’ll explore the modifications to a basic set of “.INI” files that a typical HLA user might want to make. The assumption is that you’re starting with the stock radasm.ini and hla.ini files that come with RadASM and you’re wanting to customize them to support the development paradigm that this document proposes.

The RADASM.INI Initialization File

The radasm.ini file specifies all the generic parameters that RadASM uses. In particular, this “.INI” file specifies initial window settings, file histories, OS and language information, and menu entries for certain user modifiable menus. RadASM, itself, actually modifies most of the information in this “.ini” file. However, there are a few entries an HLA user will need to change and a couple of entries an HLA user may want to change. We’ll discuss those sections here.

Note: there is a preconfigured radasm.ini file found in the WPA samples subdirectory. This initialization file is compatible with all the sample programs found in this book and is a good starting point should you decide to make your own customizations to RadASM.

The first item of interest in the radasm.ini file is the “[Assembler]” section. This section in the “.INI” file specifies which assemblers RadASM supports and which assembler is the default assembler it will use when creating new projects. By default, the “[Assembler]” section takes the following form:

 

[Assembler]

Assembler=masm,fasm,tasm,nasm,hla

 

The first assembler in this list is the default assembler RadASM will use when creating a new project. The standard radasm.ini file is set up to assume that MASM is the default assembler (the first assembler in the list is the default assembler). HLA users will probably want to tell RadASM to use HLA as the default assembler, this is easily achieved by changing the “Assembler=” statement to the following:

 

[Assembler]

Assembler=hla,masm,fasm,tasm,nasm

 

Changing the default assembler is the only “necessary” change that you’ll need to make. However, there are a few additional changes you’ll probably want that will make using RadASM a little nicer. Again, by default, RadASM assumes that you’re developing MASM32 programs. Therefore, the help menu contains several entries that bring up help information for MASM32 users. While some of this information is, arguably, of interest to HLA users, a good part of the default help information doesn’t apply at all to HLA. Fortunately, RadASM’s radasm.ini file lets you specify the entries in RadASM’s help menu and where to locate the help files for those menu entries. The “[MenuHelp]” and “[F1-Help]” sections specify where RadASM will look when the user requests help information (by selecting an item from the “Help” menu or by pressing the F1 key, respectively). The default radasm.ini file specifies these two sections as follows:

 

[MenuHelp]

1=&Win32 Api,0,H,$H\Win32.hlp

2=&X86 Op Codes,0,H,$H\x86eas.hlp

3=&Masm32,0,H,$H\Masm32.hlp

4=$Resource,0,H,$H\Rc.hlp

5=A&gner,0,H,$H\Agner.hlp

 

[F1-Help]

F1=$H\Win32.hlp

CF1=$H\x86eas.hlp

SH1=$H\Masm32.hlp

CSF1=$H\Rc.hlp

 

Each numbered line in the “[MenuHelp]” section corresponds to an entry in RadASM’s “Help” menu. These entries must be have sequential numbers starting from one and these numbers specify the order of the item in the “Help” menu (the order in the radasm.ini file does not specify the order of the entries in the “Help” menu, you do not have to specify the “[MenuHelp]” entries in numeric order, RadASM will rearrange them according to the numbers you specify). Entry entry in the “[MenuHelp]” section takes the following form:

 

menu# = Menu Text, accelerator, H, helpfile

 

where “menu#” is a numeric value (these values must start from one and there can be no gaps in the set), “Menu Text” is the text that RadASM will display in the menu for that particular item, accelerator is a Windows’ accelerator key value (generally, this is zero, meaning no accelerator value), “H” is required by RadASM to identify this as a “Help” entry, and “helpfile” is the path to the help file to display (or a program that will bring up a help file).

You may have noticed the ampersand character (“&”) in the menu text. The ampersand precedes the character you can press on the keyboard to select a menu item when the menu is opened. For example, pressing “X” when the menu is open (with the “[HelpMenu]” items in this example) selects the “X86 Op Codes” menu entry.

You will note that the paths in the “[MenuHelp]” section all begin with “$H”. This is a RadASM shorthand for “the path where RadASM can find all the help files.” There is no requirement that you use this shortcut or even place all your help files in the same directory. You could just also specify the path to a particular help file using a fully qualified pathname like c:\hla\doc\Win32.hlp. However, it’s often convenient to specify paths using the various shortcuts that RadASM provides. RadASM supplies the shortcuts found in See Path Shortcuts for Use in RadASM “.INI” Files.

Path Shortcuts for Use in RadASM “.INI” Files

 

Shortcut

Meaning

$A=

Path to where RadASM is installed

$B=

Where RadASM finds binaries and executables (e.g., c:\hla)

$D=

Where RadASM finds “Addin” modules. Usually $A\AddIns.

$H=

Where RadASM finds “Help” files. Default is $A\Help, but you’ll probably want to change this to $B\Doc.

$I=

Where RadASM finds include files. Default is $A\Include, but you’ll probably want to change this to $B\include.

$L=

Where RadASM finds library files. Default is $A\Lib but you’ll probably want to change this to $B\hlalib.

$R=

Path where RadASM is started (e.g., c:\RadASM).

$P=

Where RadASM finds projects. This is usually $R\Projects.

$S=

Where RadASM find snippets. This is usually $R\Snippets.

$T=

Where RadASM finds templates. This is usually $R\Templates

$M=

Where RadASM finds keyboard macros. This is usually $R\Macro

You can define several of these variables in the hla.ini file. See the next section for details.

As noted earlier, the default help entries are really intended for MASM32 users and do not particularly apply to HLA users. Therefore, it’s a good idea to change the “[MenuHelp]” entries to reflect the location of some HLA-related help files. Here are the “[MenuHelp]” entries that might be more appropriate for an HLA installation (assuming, of course, you’ve placed all these help files in a common directory on your system):

 

[MenuHelp]

1=&Win32 Api,0,H,$H\Win32.hlp

2=&Resource,0,H,$H\Rc.hlp

3=A&gner,0,H,$H\Agner.hlp

4=&HLA Reference,0,H,$H\PDF\HLARef.pdf

5=HLA Standard &Library,0,H,$H\pdf\HLAStdlib.pdf

6=&Kernel32 API,0,H,$H\pdf\kernelref.pdf

7=&User32 API,0,H,$H\pdf\userRef.pdf

8=&GDI32 API,0,H,$H\pdf\GDIRef.pdf

 

Here’s a suggestion for the F1, Ctrl-F1, Shift-F1, and Ctrl-Shift-F1 help items:

 

[F1-Help]

F1=$H\Win32.hlp

CF1=$H\PDF\HLARef.pdf

SF1=$H\pdf\HLAStdlib.pdf

CSF1=$H\Rc.hlp

 

These are probably the extent of the changes you’ll want to make to the radasm.ini file for HLA use; there are, however, several other options you can change in this file, please see the radASMini.rtf file that accompanies the RadASM package for more details on the contents of this file.

The HLA.INI Initialization File

The hla.ini file is actually where most of the customization for HLA takes place inside RadASM. This file lets you customize RadASM’s operation specifically for HLA11. The hla.ini file appearing in the WPA subdirectory (on the accompanying CD-ROM or in the Webster HLA/Examples download file) contains a set of default values that provide a good starting point for your own customizations.

Note: although hla.ini provides a good starting point for a system, you will probably need to make changes to this file in order for it to work on your specific system. Without these changes, RadASM may not work on your system.

Without question, the first section to look at in the hla.ini file is the section that begins with “ [Paths] ”. This is where you tell RadASM the paths to various directories where it expects to find various files it needs (see See Path Shortcuts for Use in RadASM “.INI” Files for the meaning of these various path values). A typical “ [Paths] ” section might look like the following:

 

[Paths]

$A=C:\Hla

$B=$A

$D=$R\AddIns

$H=$A\Doc

$I=$A\Include

$L=$A\hlalib

$P=$R\Hla\Projects

$S=$R\Hla\Sniplets

$T=$R\Hla\Templates

$M=$R\Hla\Macro

 

Note that the $A prefix specifies the path where RadASM can find the executables for HLA. In fact, RadASM does not run HLA directly (remember, we’re going to have the make program run HLA for us), but the application path ( $A ) becomes a prefix directory we’ll use for defining other directory prefixes. Be sure to check this path in your copy of the hla.ini file and verify that it points at your main HLA subdirectory (usually “C:\HLA” though this may be different if you’ve installed HLA elsewhere).

The $R prefix specifies the path to the subdirectory containing RadASM. RadASM automatically sets up this prefix, you don’t have to explicitly set its value. The remaining subdirectory paths are based off either the $A prefix or the $R prefix.

The “[Project]” section of the hla.ini file is where the fun really begins. This section takes the following form in the default file provided in the WPA subdirectory:

 

[Project]

Type=Console App,Dialog App,Windows App,DLL

Files=hla,hhf,rc,def

Folders=Bak,Res,Tmp,Doc

MenuMake=Build,Build All,Compile RC,Check Syntax,Run

Group=1

GroupExpand=1

 

The line beginning with “ Type= ” specifies the type of projects RadASM supports for HLA. The default configuration supports console applications (“Console App”), dialog applications (“Dialog App”), Windows applications (“Windows App”), and dynamic linked library (“DLL”). The names are arbitrary, though other sections of the hla.ini file will use these names. Whenever you create a new project in HLA, it will create a list of “Project Type” names based on the list of names appearing after “ Type= ” in the “ [Project] ” section. Adding a string to this comma-separated list will add a new name to the project types that the RadASM user can select from (note, however, that to actually support these project types requires some extra work later on in the hla.ini file). See RadASM Project Types shows what the New Project dialog box in RadASM displays in response to the entries on the “Type=...” line.

RadASM Project Types

 

The line beginning with “Files=” in the “[Project]” section specifies the suffixes for the files that RadASM will associate with this project. The “hla” and “hhf” entries, of course, are the standard file types that HLA uses. The “.rc” file type is for resource compiler files (we’ll talk about the resource compiler in a later chapter). If you want to be able to create additional file types and include them in a RadASM project, you would add their suffix here.

The “Folders=...” statement tells RadASM what subdirectories it should allow the user to create when they start a new project. The make file system we’re going to use will assume the presence of a “Tmp” directory, hence that option needs to be present in the list. the “bak”, “res”, and “doc” directories let the user create those subdirectories.

See File and Folder Creation Dialog Box in RadASM shows the dialog box that displays the information found on the “Files=” and “Folders=” lines. By checking the appropriate boxes in the File Creation group, the RadASM user can tell RadASM to create a file with the project’s name and the appropriate suffix as part of the project. Similarly, by checking the appropriate boxes in the Folder Creation group, the RadASM user can tell RadASM to create the appropriate directories.

File and Folder Creation Dialog Box in RadASM

The “MenuMake=...” line specifies the IDE options that will be available for processing the files in this project. Unlike the other options, you cannot specify an arbitrary list of commands here. RadASM provides five items in the Make menu that you can modify, you don’t have the option of adding additional items here (you can disable options if you want fewer, though). Originally, these five slots were intended for the following commands:

  • Compile RC (compile a resource file)
  • Assemble (assemble the currently open file)
  • Link (create an EXE from the object files)
  • Run (execute the EXE file, building it if necessary)
  • Res To Obj (convert a resource file to an object file)

Because these options aren’t as applicable to HLA projects as they are to MASM projects (on which the original list was built), the default hla.ini file co-opts these make items for operations that make more sense for the way we’re going to be building HLA applications in this book. You can actually turn these make items on or off on a project by project basis (for certain types of projects, certain make objects may not make sense). See RadASM Make Menu Selection Dialog Box shows the dialog box that RadASM displays and presents this information. Note that in the version used here, RadASM only displays the correct labels for the check boxes in the Make Menu group. The labels on the text entry boxes should also be “Build”, “Build All”, “Compile RC”, “Check Syntax”, and “Run” (in that order), but these labels turn out to be hard-coded to the original MASM specifications. Fortunately, you won’t normally use these text entry boxes (the default text appearing in them appears in the hla.ini file), so you can ignore the fact that they are mis-labelled here.

RadASM Make Menu Selection Dialog Box

 

For each of the project types you specify on the “ Type=... ” line in the “ [Project] ” section, you must add a section to the hla.ini file, using that project type’s name, that tells RadASM how to deal with projects of that type. In the hla.ini file we’re discussing here, the project types are “Console App”, “Dialog App”, “Windows App”, and “DLL” so we will need to have sections names “ [Console App] ”, “ [Dialog App] ”, “ [Windows App] ”, and “ [DLL] ”. It also turns out that RadASM requires one additional section named “ [MakeDefNoProject] ” that RadASM uses to process files in the IDE that are not associated with a specific project.

When running RadASM, you can have exactly one project open (or no project at all open, just some arbitrary files) at a time. This project will be one of the types specified on the “ Type=... ” line in the “ [Project] ” section. Based on the open project, RadASM may execute a different set of commands for each of the items in the Make menu; the actual commands selected are specified in the project-specific sections of the hla.ini file. Here’s what the “ [MakeDefNoProject] ” section looks like:

 

[MakeDefNoProject]

MenuMake=1,1,1,1,1

1=0,O,make build,

2=0,O,make buildall,

3=0,O,make compilerc,

4=0,O,make syntax,

5=0,O,make run,

11=0,O,make dbg_build,

12=0,O,make dbg_buildall,

13=0,O,make dbg_compilerc,

14=0,O,make dbg_syntax,

15=0,C,make dbg_run,

 

The “MenuMake=...” line specifies which items in the RadASM Make menu will be active when a project of this type is active in RadASM (or, in the case of MakeDefNoProject , when no project is loaded). This is a list of boolean values (true=1, false=0) that specify whether the menu items in the Make menu will be active or deactivated. Each of these values correspond to the items on the MenuMake line in the “[Project]” section (in our case, this corresponds to “Build”, “Build All”, “Compile RC”, “Syntax Check”, and “Run”, in that order). A “1” activates the corresponding menu item, a zero deactivates it. For most HLA project types, we’ll generally leave all of these options enabled. The exception is DLL; normally you don’t “run” DLLs so we’ll disable the run option when building DLL projects.

The remaining lines specify the actions RadASM will take whenever you select one of the items from the Make menu. To understand how these items work, let’s first take a look at another section in the hla.ini file, the “ [MenuMake] ” section:

 

[MenuMake]

1=&Build,55,M,1

2=Build &All,31,M,2

3=&Compile RC,91,M,3

4=&Syntax,103,M,4

5=-,0,M,

6=&Run,67,M,5

 

Each item in the “ [MenuMake] ” section corresponds to a menu entry in the Make menu. The numbers specify the index to the menu entry (e.g., “1=” specifies the first menu item, “2=” specifies the second menu item, etc.). The first item after the “n=” prefix specifies the actual text that will appear in the Make menu. If this text is just the character “-” then RadASM displays a menu separator for that particular entry. As you can see, the default menu entries are “Build”, “Build All”, “Compile RC”, “Syntax”, and “Run”.

The next item, following the menu item text, is the accelerator value. These are “magic” values that specify keystrokes that do the same job as selecting items from the menu. For example, 55 (in the “Build” item) corresponds to Shift+F5, 31 (in “Build All”) corresponds to F5. We’ll discuss accelerators in a later chapter. So just ignore (and copy verbatim) these files for right now.

The third item on each line is always the letter “M”. This tells RadASM that this is a make menu item.

The fourth entry on each line is probably the most important. This is the command to execute when someone selects this particular menu item. This is either some text containing the command line to execute or a numeric index into the current project type. As you can see in this example, each of the commands use an index value (one through five in this example). These numbers correspond to the lines in each of the project sections. For example, if you select the “Build” option from the Make menu, RadASM notes that it is to execute command #1. It goes to the current project type section and locates the line that begins with “1=...” and executes that operation, e.g.,

 

1=0,O,make build,

 

In a similar vein, selecting “Build All” from the Make menu instructs RadASM to execute the command that begins with “2=...” in the current project type’s section (i.e., “ 2=0,O,make buildall, ”). And so on.

The lines in the project type section are divided into two groups, those that begin with 1, 2, 3, 4, or 5 and those that begin with 11, 12, 13, 14, or 15. The “[MenuMake]” command index selects one of the commands from these two groups based on whether RadASM is producing a “release build” or a “debug build”. Release builds always execute the command specified by the “[MenuMake]” command index (i.e. 1-5). If you’re building a debug version, then RadASM executes the commands in the range 11-15 in response to command indexes 1-5. We’ll ignore debug builds for the time being (we’ll discuss them in a later chapter on debugging). So for right now, we’ll always assume that we’re building a release image.

The fields of each of the indexed commands in the project type section have the following meanings:

 

index = delete_option, output_option, command, files

 

The delete_option item specifies which files to delete before doing the build. If this entry is zero, then RadASM will not delete any files before the build. Because we’re having a make file do the actual build for us, and it can take care of cleaning up any files that need to be deleted first, we’ll always put a zero here when using RadASM with HLA.

The output_option item is either “C”, “O” (that’s an “oh” not a “zero”), or zero. This specifies whether the output of the command will go to a Windows console window (“C”), the RadASM output window (“O”, which is “oh”), or the output will simply be thrown away (zero). We’ll usually want the output sent to RadASM’s output window, so most of the time you’ll see the letter “O” (“oh”) here.

The command entry is the command line text that RadASM will pass on to windows whenever you execute this command. This can be any valid command prompt operation. For our purposes, we’ll always use a make command with a single parameter to specify the type of make operation to perform. Here are the commands we’re going to support in RadASM:

  • Build - “make build”
  • Build All - “make buildall”
  • Compile RC - “make compilerc”
  • Syntax - “make syntax”
  • Run - “make run”

Now it’s up to the makefile to handle each of these various commands properly (using the standard makefile scheme we defined in the first chapter).

This may seem like a considerable amount of indirection -- why not just place the commands directly in the “ [MenuMake] ” section? However, this scheme is quite flexible and makes it easy to adjust the options on a project type by project type basis (in fact, it’s even possible to set these options on a project by project basis).

With this discussion out of the way, it’s time to look at the various project type sections. Without further ado, here they are:

 

[Console App]

Files=1,1,1,1,0,0

Folders=1,0,1,0

MenuMake=1,1,1,1,1,0,0,0

1=0,O,make build,

2=0,O,make buildall,

3=0,O,make compilerc,

4=0,O,make syntax,

5=0,C,make run,

11=0,O,make build,

12=0,O,make buildall,

13=0,O,make compilerc,

14=0,O,make syntax,

15=0,C,make run,

 

Console applications, by default, want to create an .HLA file and a .HHF file, a BAK folder and a TMP folder. All menu items are active for building and running console apps (that is, there are five ones after “ MenuMake ”). Finally, the commands (“1=...” “2=...”, etc.) are all the standard build commands.

 

[Dialog App]

Files=1,1,1,0,0

Folders=1,1,1

MenuMake=1,1,1,1,1,0,0,0

1=0,O,make build,

2=0,O,make buildall,

3=0,O,make compilerc,

4=0,O,make syntax,

5=0,C,make run,

11=0,O,make build,

12=0,O,make buildall,

13=0,O,make compilerc,

14=0,O,make syntax,

15=0,C,make run,

 

By default, dialog applications will create HLA, HHF, RC, and DEF files and they will create a BAK and a TMP subdirectory. All five menu items will be active and dialog apps use the standard command set.

 

[Windows App]

Files=1,1,1,1,0

Folders=1,1,1,1

MenuMake=1,1,1,1,1,0,0,0

1=0,O,make build,

2=0,O,make buildall,

3=0,O,make compilerc,

4=0,O,make syntax,

5=0,C,make run,

11=0,O,make build,

12=0,O,make buildall,

13=0,O,make compilerc,

14=0,O,make syntax,

15=0,C,make run,

 

 

By default, window applications will create HLA, HHF, RC, and DEF files and they will create a BAK, RES, DOC, and a TMP subdirectory. All five menu items will be active and dialog apps use the standard command set.

The hla.ini file allows you to control several other features in RadASM. The options we’ve discussed in this chapter are the crucial ones you must set up, most of the remaining options are of an aesthetic or non-crucial nature, so we won’t bother discussing them here. Please see the RadASM documentation (the RTF file mentioned earlier) for details on these other options.

Once you’ve made the appropriate changes to the hla.ini file (and, of course, you’ve made a backup of your original file, right?), then you can copy the file to the RadASM subdirectory and replace the existing hla.ini file with your new one. After doing this, RadASM should operate with the new options when you run RadASM..

 

HLA Internal Operation

To effectively use HLA, it helps to understand how HLA translates HLA source files into executable machine code. This information is particularly useful if you install HLA incorrectly and you cannot successfully compile a simple demo program. Beyond that, this information can also help you take advantage of more advanced HLA and OS features.

As noted earlier in the HLA documentation, HLA is not a single application; the HLA system is a collection of programs that work together to translate your HLA source files into executable files. This is not unusual, most compilers and assemblers provide only part of the conversion from source to executable (e.g., you still have to run a linker with most compilers and assemblers to produce an executable).

The HLA system offers a rich set of different configurations that allow you to mix and match components to efficiently process your assembly language applications. First of all, HLA is relatively portable. The compiler itself is written with Flex, Bison, C/C++, along with some platform-independent assembly language code (written in HLA, of course). This makes it easy to move the compiler from one operating system to another. Currently, HLA is supported under Windows, Linux, FreeBSD, and Mac OSX. Plans include porting HLA to NetBSD, OpenBSD, QNX and, perhaps, Solaris at some point in the future. Even within a single operating system, HLA offers multiple configurations that you can employ, based on your needs and desires. This section will describe some of the possible configurations you might create.

The compilation of a typical HLA source file using a command line such as "hla hw" goes through three or four major phases:

There is a fifth, optional, step that can also take place under Windows. If you are creating an application that makes use of compiled resources, as the fourth step (before the linking stage) the hla.exe (Windows only) program can run a resource compiler to translate those resources into an object module (.res) that the linker can link into your final executable.

As it turns out, the HLA system can employ a wide variety of linkers, librarians, assemblers, and other tools based on the underlying operating system. Here is the list of tools that HLA has been qualified with:

Under Windows:

Note: you can use Borland’s TLINK and TLIB utilities with HLA, but you will have to manually run these applications; the HLA system will not automatically execute them. Note that HLA v2.0 and later have deprecated support for the Borland tools and future versions may not support them at all.

Under Linux, Mac OSX, and FreeBSD:

A couple of obvious questions that might come up: "Why provide all these options? Why not simply pick a single configuration and go with that?" Well, as it turns out, there are advantages and disadvantages to each configuration and allowing multiple configurations affords you the most flexibility when writing code.

The first point to consider is object code file versus assembly language output. The obvious choice is to have HLA produce an object code file. After all, the job of an assembler is to produce an object code file; why make the detour of producing an intermediate assembly language file that some other assembler must convert to an object file?

Historically, HLA provides this option because this was the only option available under HLA v1.x (direct object code output became available in HLA v2.0). Persons with old makefiles and other build systems may be excepting to run HLA output through some other assembler. The fact that HLA v2.x maintains the ability to produce object code in this manner preserves those existing make files and build systems.

Another reason for producing an intermediate assembly language file is because you want to see the output from the HLA compiler (for example, to see how it translates HLL-like statements into pure assembly language). The HLA compiler has an HLA output mode specifically for this purpose. It may seem silly, at first, to assemble an HLA source file into a (lower-level) HLA source file, but being able to look at the code that HLA generates is sometimes a very nice feature.

One last reason for producing an assembly language output file from an HLA source file is to allow you to translate HLA source code into the syntax for some other assembler (MASM, FASM, NASM, or Gas). This is useful when you want to combine HLA code with a project written with a different assembler.

Most of the time, of course, you’ll use the default setting and directly produce an object file from an HLA assembly. HLA produces standard PE/COFF, ELF, and Mach-o object-code files, so HLA’s output will properly link with other object files (perhaps written in different languages).

Using the HLA Command-Line Compiler

Once you’ve installed HLA and verified that it is operational, you can run the HLA compiler. The HLA compiler consists of two executables: hla(.exe)12, which is a shell that processes command line arguments, compiles ".hla" files to ".asm" files, assembles the ".asm" files by calling an assembler, and links the resulting files together using a linker program; the second executable is hlaparse(.exe) which compiles a single ".hla" file to an assembly language file. Generally, you would only run hla(.exe). The hla(.exe) program automatically runs the hlaparse(.exe) and assembler/linker programs. The hla(.exe) command uses the following syntax:

hla optional_command_line_parameters Filename_list

The filenames list consists of one or more unambiguous filenames having the extension: ".hla", ".asm" or ".obj"/".o"13. HLA will first run the hlaparse(.exe) program on all files with the HLA extension (producing files with the same basename and an ASM extension). Then HLA runs the assembler on all files with the ".asm" extension (including the files produced by hlaparse). Finally, HLA runs the linker to combine all the object files together (including the ".obj"/".o" files the assembler produces). The ultimate result, assuming there were no errors along the way, is an executable file (with an EXE extension under Windows, with no extension under Linux/FreeBSD/Mac OSX).

HLA supports the following command line parameters:

options:

-@ Do not generate linker response file.

-@@ Always generate a linker response file.

-thread Enable thread-safe code generation and linkage.

-axxxxx Pass xxxxx as command line parameter to assembler.

-dxx Define VAL symbol xx to have type BOOLEAN and value TRUE.

-dxx=yy Define VAL symbol xx to have type STRING and value "yy".

-e:name Executable output filename (appends ".exe" under Windows).

-x:name Executable output filename (does not append ".exe").

-b:name Binary object file output name (only when using HLABE).

-i:path Specifies path to HLA include file directory.

-lib:path Specifies path to the HLALIB.LIB file.

-license Displays copyright and license info for the HLA system.

-lxxxxx Pass xxxxx as command line parameter to linker.

-m Create a map file during link

-p:path Specifies path to hold temporary working files.

-r:name <name> is a text file containing cmd line options.

-obj:path Specifies path to place object files.

-main:name Use ‘name’ as the name of the HLA main program.

-source Compile to human readable source file format.

-s Compile to .ASM files only.

-c Compile and assemble to object files only.

-fasm Use FASM as back-end (applies to -s and -c)

-gas Use Gas as back-end (Linux/BSD, applies to -s and -c)

-gasx Use Gas as back-end (Mac OSX, applies to -s and -c)

-hla Produce a pseudo-HLA source file as output (implies -s).

-hlabe (Default) Produce obj file using the HLA Back Engine.

-masm Use MASM as back-end assembler (applies to -s and -c)

-nasm Use NASM as back-end assembler (applies to -s and -c)

-tasm Use TASM as back-end assembler (applies to -s and -c)

-sym Dump symbol table after compile.

-win32 Generate code for Win32 OS.

-linux Generate code for Linux OS.

-freebsd Generate code for FreeBSD OS.

-macos Generate code for Mac OSX.

-test Send diagnostic info to stdout rather than stderr (This

option is intended for HLA test/debug purposes).

-v Verbose compile.

-level=h High-level assembly language

-level=m Medium-level assembly language

-level=l Low-level assembly language

-level=v Machine-level assembly language (very low level).

-w Compile as windows app (default is console app).

-? Display this help message.

Note that HLA ignores case when processing command line parameters (unlike typical Linux/FreeBSD/Mac OSX programs). For example, "-s" is equivalent to "-S" when specifying a command line parameter.

-@
-@@

HLA will produce a "linker response file" that it supplies to the linker program during the link phase. This linker response file contains necessary segment declarations and other vital linker information. By default, HLA uses any existing "basename.link" file (where basename is the base name of the file you are compiling) whenever you run the compiler; it will create a new "basename.link" file only if one does not already exist. The "-@" option tells HLA not to create a new ".link" file, even if one does not already exist. The "-@@" option tells HLA to always create a ".link" file, even if one already exists.

If you specify multiple "basename.hla" filenames on the command line, HLA only generates a single "basename.link" file using the name of the first "basename.HLA" file it encounters.

-r:filename

The "-r:filename" option lets you specify a response file containing a sequence of HLA command-line parameters. The file specified after this option must contain a sequence of HLA command-line parameters, one per line, which HLA executes exactly as though they were specified on the command line. E.g.,

sampleFile.resp:

-fasm

sampleFile.hla

The following command treats each of the above lines as separate HLA command-line parameters:

hla -r:sampleFile.resp

-aXXXXX

The -aXXXXX option lets you pass assembler-specific command line options to the assembler during the assembler phase. This option is ignored if you use the -s option or you're compiling directly to object code using the HLA back engine. One common form of this command often used with the MASM assembler is "-aZi -aZf" that tells MASM to generate debugging information in the object file (for use with the Visual Studio debugger program).

-c

The -c option tells HLA to run the hlaparse compiler and the (default) assembler, producing "basename.obj"/"basename.o" files. HLA will process all filenames on the command line that have ".hla" or ".asm" extension, but it will ignore any filenames with ".obj" or ".o" extensions. If you compile an HLA unit without compiling an HLA program at the same time, you will need to use this option or the linker will complain about not finding the main program.

One common use of this option is to compile HLA units to object files. Since HLA units do not contain a main program, you cannot compile an HLA unit directly to an executable. To compile an HLA unit separately (i.e., without compiling an HLA main program during the same HLA.EXE invocation) you must specify the "-c" option or the compilation will generate an error when it attempts to link the program.

A second reason for using the "-c" option is that you want to explicitly run the linker yourself and supply linker command line options that are different from those that HLA automatically provides.

-fasm

Tells HLA to use FASM as the back-end assembler. This command-line overrides any back-end assembler specification previously on the command-line. Object code and executable file output with this command are available only under Windows. Source output is available under any operating system.

-gas

Tells HLA to use GAS as the back-end assembler. This command-line overrides any back-end assembler specification previously on the command-line. Object code and executable file output with this command are available only under Linux and FreeBSD. Source output is available under any operating system.

-gasx

Tells HLA to use GAS as the back-end assembler. This command-line overrides any back-end assembler specification previously on the command-line. Object code and executable file output with this command are available only under Mac OS X. Source output is available under any operating system.

-hla

Tells HLA to produce a human-readable pseudo-HLA-syntax assembly language output file. This command-line option implies "-source" and "-s". The main use for this option is to see how HLA expands macros, high-level-language-like statements, and other code.

-hlabe

Tells HLA to use the HLA Back Engine as the back-end assembler. This command-line overrides any back-end assembler specification previously on the command-line. This command causes HLA to directly produce an object code file without using an external back-end assembler. If you specify both of the "-source" and "-s" command-line options along with "-hlabe", then HLA will produce a human-readable ".asm" file rather than an object file; this option is useful for debugging HLA or for those curious about how HLABE operates.

-masm

Tells HLA to use MASM as the back-end assembler. This command-line overrides any back-end assembler specification previously on the command-line. Object code and executable file output with this command are available only under Windows. Source output is available under any operating system.

-nasm

Tells HLA to use NASM as the back-end assembler. This command-line overrides any back-end assembler specification previously on the command-line. Object code and executable file output with this command are available only under Windows. Source output is available under any operating system.

-tasm

Tells HLA to use TASM as the back-end assembler. This command-line overrides any back-end assembler specification previously on the command-line. Object code and executable file output with this command are available only under Windows. Source output is available under any operating system. Note that TASM support in HLA is deprecated, so it's not a good idea to depend on this option.

-d:XXXXX{=YYYYY}

The -dXXXXX option tells HLA to define the symbol XXXXX as a boolean val constant and initialize it with the value true. Generally you use such symbols to control the emission of code during assembly using statements like "#if( @defined( XXXXX )) ..."

The -dXXXX=YYYY option tells HLA to define the symbol XXXX as a string val constant and give it the initial value "YYYY".

-b:name

When compiling to an object file using the HLA back-engine (rather than a back-end assembler), this option specifies the name of the binary object file. By default, HLA uses the base name (before the ".hla" suffix) with a ".obj" (windows) or ".o" (*NIX) suffix. With the "-b:name" option you may specify a different name. Note that if you do not supply an ".obj" or ".o" suffix at the end of "-b:name" because HLA will automatically attach the suffix.

-e:name

By default, HLA creates an executable filename using the extension ".exe" (Windows) or without an extension (*NIX) and the basename of the first filename on the command line. You can use the -e name option to specify a different executable file name.

-x:name

Similar to the -e:name option, except there is no automatic ".exe" suffix applied under Windows. This lets you explicitly supply the suffix (e.g., ".dll" under Windows, or to force a ".exe" under *NIX).

-lXXXXX

The -lXXXXX option passes the text XXXXX on to the linker as a command line option. One common command to pass to the Microsoft linker is "-lDEBUG" that tells the linker to generate debugging information in the object file.

-m

The -m option tells the Microsoft linker or POLINK to produce a map file during the link phase. This is equivalent to the "-lmap" option. The *NIX version of HLA ignores this option.

-s

The -s option tells the HLA program to run only the hlaparse compiler to produce an assembly language source file; HLA will not run a back-end assembler or linker. As a result, HLA ignores any ".asm" or ".obj" filenames you supply on the command line. This option is useful if you wish to view the output of an HLA compilation in some other assembler's source format without producing any actual object code. This option must be used with the "-source" command-line option if you are using the HLA back engine and you wish to produce a human-readable source file of the HLABE output.

-sym

The -sym option dumps the symbol table after compiling each file with an HLA extension. This option is primarily intended for testing and debugging the HLA compiler; however, this information can be useful to the HLA programmer on occasion.

-thread

The -thread option tells HLA to generate thread-safe code (for the code it emits) and link in the thread-safe version of the HLA standard library. Specifying this command-line option sets the HLA @thread object to true, which you can test during the compilation of an HLA source file (that must behave differently for thread-safe versus non-thread-safe code).

-test

The -test option is intended for hlaparse testing and debugging purposes only. It causes the compiler to send all error messages to the standard output device rather than the standard error device. This allows the test code to redirect all errors to a text file for comparison against other files. This command also causes HLAPARSE to emit some extra comment information to the assembly language output file when producing output files for one of the back-end assemblers (other than the HLA back engine).

-v

The -v option (verbose) causes HLA to print additional information during compile to show the progress of the compilation. This option also prints certain statistics, such as the number of lines per second that HLA compiles.

-w

The -w option informs HLA that you are compiling a standard Windows (GUI) application rather than a console application. By default, HLA assumes that you are compiling a executable that will run from the command window. If you want to write a full Windows application, you will need to supply this option to tell HLA not to link the code for console operation. Obviously, this option doesn’t apply to *NIX systems.

The "-w" option tells HLA to invoke the linker using the command line option

-subsystem:windows

rather than the default

-subsystem:console

This provides a convenient mechanism for those who wish to create win32 GUI applications. Most likely, however, if you wish to create GUI applications, you will run the linker explicitly yourself (as this document will explain), so you’ll probably not use the "-w" option very frequently. It’s great for some short GUI demos, but larger GUI programs will probably not use this option. This option is only active if HLA compiles the program to an executable. If you compile the program to an OBJ or ASM file, HLA ignores this option.

If you want to develop Win32 GUI apps, look at Randy Hyde’s book "Windows Programming in Assembly". This book provides the linker commands and makefiles for generation such applications (as well as describing how you actually write such code).

-p:path

During compilation, HLA produces several temporary files (that it doesn’t delete, because they may be of interest to the HLA user). These files have a habit of cluttering up the current working directory. If you prefer, you can tell HLA to place these files in a temporary directory so they don’t clutter up your working directory. One way to accomplish this is by using the "-p:dirpath" command line option. For example, the option "-p:c:\hla\tmp" tells HLA to put all temporary files (for the current assembly) into the "c:\hla\tmp" subdirectory (which must exist). Note that you can set also set the temporary directory using the hla "hlatemp" environment variable. The "-p:dirpath" option will override the environment variable (if it exists). See the description of the hlatemp environment variable for more details.

-obj:path

During compilation, HLA normally writes all object files to the current working directory. Some programmers have requested a way to specify a different directory for the .OBJ (.o under *NIX) files that HLA produces. You can accomplish this using the "-obj:dirpath" command line option. The dirpath item has to be the path to a valid directory. HLA places all object files produced by the compiler and/or resource editor in this directory. Note that, unlike the -p option, there is no environment variable that lets you permanently set this path. You must specify the path on a compilation-by-compilation basis (use a makefile if you get tired of typing the path in on each compilation).

-level=h

-level=m

-level=l

-level=v

The -level options enable or disable certain HLA language features. These command-line options are intended for use in programming courses where the instructor needs to batch compile dozens or even hundreds of student projects at one time. This allows the instructor to ensure that the students aren’t using high-level control constructs that are inappropriate for that point in the course. For example, towards the end of a course, most instructors don’t allow the use of various high-level control constructs; some instructors may never allow them. The "-level" command-line options will "turn off" various statements in the HLA language so that the HLA compiler will report an error if the student attempts to use them in a source file.

The default, "-level=h" (high) enables the entire HLA language.

The "-level=m" (medium level) disables high-level language control constructs, such as "if", "while", and "for" but still allows the use of high-level-like procedure calls in the HLA language. Medium-level assembly language also allows the use of exceptions using HLA’s try..except..endtry and raise statements.

The "-level=l" (low-level assembly) disables all high-level control constructs other than the exception-handling statements and disables high-level-like procedure calls in HLA. This option also disables automatic stack frame generation and clean up in HLA procedures (that is, the programmer will be responsible for writing that code themselves).

The "-level=v" (very low-level assembly) option disables all high-level control constructs including exception handling. Only machine instructions (and user written macros) are legal in the source file. No high-level control constructs or high-level procedure calls are allowed.

-?

The -? option cause HLA to dump the list of command line options and immediately quit without further work.

Note that the command line options this document describes are for HLA v2.2 and later only. Earlier versions of HLA used a different command line set. See the documentation for the specific version you’re using if you have questions.

-license

This command displays license information for the entire HLA system. Although the HLA source code written by Randall Hyde is all public domain, certain components of the HLA system, including the back-end assemblers, the linker, and the resource editor, may come from other sources. The "-license" command-line parameter lists license information about these other products.

HLA v2.x Language Reference Manual

HLA Language Elements

Starting with this chapter we begin discussing the HLA source language. HLA source files must contain only seven-bit ASCII characters. These are text files with each source line record containing a carriage return/line feed (Windows) or a just a line feed (*NIX) termination sequence (HLA is actually happy with either sequence, so text files are portable between OSes without change). White space consists of spaces, tabs, and newline sequences. Generally, HLA does not appreciate other control characters in the file and may generate an error if they appear in the source file.

Comments

HLA uses "//" to lead off single line comments. It uses "/*" to begin an indefinite length comment and it uses "*/" to end an indefinite length comment. C/C++, Java, and Delphi users will be quite comfortable with this notation.

Special Symbols

The following characters are HLA lexical elements and have special meaning to HLA:

* / + - ( ) [ ] { } < > : ; , . = ? & | ^ ! @ !

The following character pairs are HLA lexical elements and also have special meaning to HLA:

&& || <= >= <> != == := .. << >> ## #( )# #{ }#

Reserved Words

Here are the HLA reserved words. You may not use any of these reserved words as HLA identifiers except as noted below (with respect to the #id and #rw operators). HLA reserved words are case insensitive. That is, "MOV" and "mov" (as well as any permutation with respect to case) both represent the HLA "mov" reserved word.

 

#append

#asm

#closeread

#closewrite

#else

#elseif

#emit

#endasm

#endfor

#endif

#endmacro

#endmatch

#endregex

#endstring

#endtext

#endwhile

#error

#for

#id

#if

#include

#includeonce

#keyword

#linker

#macro

#match

#openread

#openwrite

#print

#regex

#return

#rw

#string

#system

#terminator

#text

#while

#write

@a

@abs

@abstract

@ae

@align

@alignstack

@arb

@arity

@at

@b

@baseptype

@basereg

@basetype

@be

@boolean

@bound

@byte

@c

@cdecl

@ceil

@char

@class

@cos

@cset

@curdir

@curlex

@curobject

@curoffset

@date

@debughla

@defined

@delete

@dim

@display

@dword

@e

@elements

@elementsize

@enter

@enumsize

@env

@eos

@eval

@exactlynchar

@exactlyncset

@exactlynichar

@exactlyntomchar

@exactlyntomcset

@exactlyntomichar

@exceptions

@exp

@external

@extract

@fast

@filename

@firstnchar

@firstncset

@firstnichar

@floor

@forward

@fpureg

@frame

@g

@ge

@global

@here

@index

@insert

@int128

@int16

@int32

@int64

@int8

@into

@isalpha

@isalphanum

@isclass

@isconst

@isdigit

@IsExternal

@isfreg

@islower

@ismem

@isreg

@isreg16

@isreg32

@isreg8

@isspace

@istype

@isupper

@isxdigit

@l

@label

@lastobject

@le

@leave

@length

@lex

@linenumber

@localoffset

@localsyms

@log

@log10

@lowercase

@lword

@match

@match2

@matchchar

@matchcset

@matchichar

@matchid

@matchintconst

@matchistr

@matchiword

@matchnumericconst

@matchrealconst

@matchstr

@matchstrconst

@matchtoistr

@matchtostr

@matchword

@max

@min

@mmxreg

@na

@nae

@name

@nb

@nbe

@nc

@ne

@ng

@nge

@nl

@nle

@no

@noalignstack

@nodisplay

@noenter

@noframe

@noleave

@norlesschar

@norlesscset

@norlessichar

@normorechar

@normorecset

@normoreichar

@nostackalign

@nostorage

@np

@ns

@ntomchar

@ntomcset

@ntomichar

@nz

@o

@odd

@offset

@onechar

@onecset

@oneichar

@oneormorechar

@oneormorecset

@oneormoreichar

@oneormorews

@optstrings

@p

@parmoffset

@parms

@pascal

@pclass

@pe

@peekchar

@peekcset

@peekichar

@peekistr

@peekstr

@peekws

@po

@pointer

@pos

@ptype

@qword

@random

@randomize

@read

@real128

@real32

@real64

@real80

@reg

@reg16

@reg32

@reg8

@regex

@returns

@rindex

@s

@section

@sin

@size

@sort

@sqrt

@stackalign

@staticname

@stdcall

@strbrk

@string

@strset

@strspan

@substr

@system

@tab

@tan

@tbyte

@text

@thread

@time

@tokenize

@tostring

@trace

@trim

@type

@typename

@uns128

@uns16

@uns32

@uns64

@uns8

@uppercase

@uptochar

@uptocset

@uptoichar

@uptoistr

@uptostr

@use

@volatile

@wchar

@word

@ws

@wsoreos

@wstheneos

@wstring

@xmmreg

@z

@zeroormorechar

@zeroormorecset

@zeroormoreichar

@zeroormorews

@zerooronechar

@zerooronecset

@zerooroneichar

@zstring

aaa

aad

aam

aas

abstract

adc

add

addpd

addps

addsd

addss

addsubpd

addsubps

ah

al

align

and

andnpd

andnps

andpd

andps

anyexception

arpl

ax

begin

bh

bl

boolean

bound

bp

break

breakif

bsf

bsr

bswap

bt

btc

btr

bts

bx

byte

call

case

cbw

cdq

ch

char

cl

class

clc

cld

clflush

cli

clts

cmc

cmova

cmovae

cmovb

cmovbe

cmovc

cmove

cmovg

cmovge

cmovl

cmovle

cmovna

cmovnae

cmovnb

cmovnbe

cmovnc

cmovne

cmovng

cmovnge

cmovnl

cmovnle

cmovno

cmovnp

cmovns

cmovnz

cmovo

cmovp

cmovpe

cmovpo

cmovs

cmovz

cmp

cmpeqpd

cmpeqps

cmpeqsd

cmpeqss

cmplepd

cmpleps

cmplesd

cmpless

cmpltpd

cmpltps

cmpltsd

cmpltss

cmpneqpd

cmpneqps

cmpneqsd

cmpneqss

cmpnlepd

cmpnleps

cmpnlesd

cmpnless

cmpnltpd

cmpnltps

cmpnltsd

cmpnltss

cmpordpd

cmpordps

cmpordsd

cmpordss

cmppd

cmpps

cmpsb

cmpsd

cmpss

cmpsw

cmpunordpd

cmpunordps

cmpunordsd

cmpunordss

cmpxchg

cmpxchg8b

comisd

comiss

const

continue

continueif

cpuid

cr0

cr1

cr2

cr3

cr4

cr5

cr6

cr7

cseg

cset

cvtdq2pd

cvtdq2ps

cvtpd2dq

cvtpd2pi

cvtpd2ps

cvtpi2pd

cvtpi2ps

cvtps2dq

cvtps2pd

cvtps2pi

cvtsd2si

cvtsd2ss

cvtsi2sd

cvtsi2ss

cvtss2sd

cvtss2si

cvttpd2dq

cvttpd2pi

cvttps2dq

cvttps2pi

cvttsd2si

cvttss2si

cwd

cwde

cx

daa

das

dec

default

dh

di

div

divpd

divps

divsd

divss

dl

do

downto

dr0

dr1

dr2

dr3

dr4

dr5

dr6

dr7

dseg

dup

dword

dx

dx:ax

eax

ebp

ebx

ecx

edi

edx

edx:eax

else

elseif

emms

end

endclass

endconst

endfor

endif

endlabel

endproc

endreadonly

endrecord

endstatic

endstorage

endswitch

endtry

endtype

endunion

endval

endvar

endwhile

enter

enum

eseg

esi

esp

exception

exit

exitif

external

f2xm1

fabs

fadd

faddp

fbld

fbstp

fchs

fclex

fcmova

fcmovae

fcmovb

fcmovbe

fcmove

fcmovna

fcmovnae

fcmovnb

fcmovnbe

fcmovne

fcmovnu

fcmovu

fcom

fcomi

fcomip

fcomp

fcompp

fcos

fdecstp

fdiv

fdivp

fdivr

fdivrp

felse

ffree

fiadd

ficom

ficomp

fidiv

fidivr

fild

fimul

fincstp

finit

fist

fistp

fisttp

fisub

fisubr

fld

fld1

fldcw

fldenv

fldl2e

fldl2t

fldlg2

fldln2

fldpi

fldz

fmul

fmulp

fnclex

fninit

fnop

fnsave

fnstcw

fnstenv

fnstsw

for

foreach

forever

forward

fpatan

fprem

fprem1

fptan

frndint

frstor

fsave

fscale

fseg

fsin

fsincos

fsqrt

fst

fstcw

fstenv

fstp

fstsw

fsub

fsubp

fsubr

fsubrp

ftst

fucom

fucomi

fucomip

fucomp

fucompp

fwait

fxam

fxch

fxrstor

fxsave

fxtract

fyl2x

fyl2xp1

gseg

haddpd

haddps

hlt

hsubpd

hsubps

idiv

if

imod

imul

in

inc

inherits

insb

insd

insw

int

int128

int16

int32

int64

int8

intmul

into

invd

invlpg

iret

iretd

iterator

ja

jae

jb

jbe

jc

jcxz

je

jecxz

jf

jg

jge

jl

jle

jmp

jna

jnae

jnb

jnbe

jnc

jne

jng

jnge

jnl

jnle

jno

jnp

jns

jnz

jo

jp

jpe

jpo

js

jt

jz

label

lahf

lar

lazy

lddqu

ldmxcsr

lds

lea

leave

les

lfence

lfs

lgdt

lgs

lidt

lldt

lmsw

lock.adc

lock.add

lock.and

lock.btc

lock.btr

lock.bts

lock.cmpxchg

lock.dec

lock.inc

lock.neg

lock.not

lock.or

lock.sbb

lock.sub

lock.xadd

lock.xchg

lock.xor

lodsb

lodsd

lodsw

loop

loope

loopne

loopnz

loopz

lsl

lss

ltreg

lword

maskmovdqu

maskmovq

maxpd

maxps

maxsd

maxss

method

mfence

minpd

minps

minsd

minss

mm0

mm1

mm2

mm3

mm4

mm5

mm6

mm7

mod

monitor

mov

movapd

movaps

movd

movddup

movdq2q

movdqa

movdqu

movhlps

movhpd

movhps

movlhps

movlpd

movlps

movmskpd

movmskps

movntdq

movnti

movntpd

movntps

movntq

movq

movq2dq

movsb

movsd

movshdup

movsldup

movss

movsw

movsx

movupd

movups

movzx

mul

mulpd

mulps

mulsd

mulss

mwait

name

namespace

neg

nop

not

null

or

orpd

orps

out

outsb

outsd

outsw

overloads

override

overrides

packssdw

packsswb

packuswb

paddb

paddd

paddq

paddsb

paddsw

paddusb

paddusw

paddw

pand

pandn

pause

pavgb

pavgw

pcmpeqb

pcmpeqd

pcmpeqw

pcmpgtb

pcmpgtd

pcmpgtw

pextrw

pinsrw

pmaddwd

pmaxsw

pmaxub

pminsw

pminub

pmovmskb

pmulhuw

pmulhw

pmullw

pmuludq

pointer

pop

popa

popad

popf

popfd

por

prefetchnta

prefetcht0

prefetcht1

prefetcht2

proc

procedure

program

psadbw

pshufd

pshufhw

pshuflw

pshufw

pslld

pslldq

psllq

psllw

psrad

psraw

psrld

psrldq

psrlq

psrlw

psubb

psubd

psubq

psubsb

psubsw

psubusb

psubusw

psubw

punpckhbw

punpckhdq

punpckhqdq

punpckhwd

punpcklbw

punpckldq

punpcklqdq

punpcklwd

push

pusha

pushad

pushd

pushf

pushfd

pushw

pxor

qword

raise

rcl

rcpps

rcpss

rcr

rdmsr

rdpmc

rdtsc

readonly

real128

real32

real64

real80

record

regex

rep.insb

rep.insd

rep.insw

rep.movsb

rep.movsd

rep.movsw

rep.outsb

rep.outsd

rep.outsw

rep.stosb

rep.stosd

rep.stosw

repe.cmpsb

repe.cmpsd

repe.cmpsw

repe.scasb

repe.scasd

repe.scasw

repeat

repne.cmpsb

repne.cmpsd

repne.cmpsw

repne.scasb

repne.scasd

repne.scasw

repnz.cmpsb

repnz.cmpsd

repnz.cmpsw

repnz.scasb

repnz.scasd

repnz.scasw

repz.cmpsb

repz.cmpsd

repz.cmpsw

repz.scasb

repz.scasd

repz.scasw

result

ret

returns

rol

ror

rsm

rsqrtps

rsqrtss

sahf

sal

sar

sbb

scasb

scasd

scasw

segment

seta

setae

setb

setbe

setc

sete

setg

setge

setl

setle

setna

setnae

setnb

setnbe

setnc

setne

setng

setnge

setnl

setnle

setno

setnp

setns

setnz

seto

setp

setpe

setpo

sets

setz

sfence

sgdt

shl

shld

shr

shrd

shufpd

shufps

si

sidt

sldt

smsw

sp

sqrtpd

sqrtps

sqrtsd

sqrtss

sseg

st0

st1

st2

st3

st4

st5

st6

st7

static

stc

std

sti

stmxcsr

storage

stosb

stosd

stosw

streg

string

sub

subpd

subps

subsd

subss

switch

sysenter

sysexit

tbyte

test

text

then

this

thunk

to

try

type

ucomisd

ucomiss

ud2

union

unit

unpckhpd

unpckhps

unpcklpd

unpcklps

unprotected

uns128

uns16

uns32

uns64

uns8

until

val

valres

var

verr

verw

vmt

wait

wbinvd

wchar

welse

while

word

wrmsr

wstring

xadd

xchg

xlat

xmm0

xmm1

xmm2

xmm3

xmm4

xmm5

xmm6

xmm7

xor

xorpd

xorps

zstring

Note that @debughla is also a reserved compiler symbol. However, this is intended for internal (HLA) debugging purposes only. When the compiler encounters this symbol, it immediately stops the compiler with an assertion failure. Obviously, you should never put this statement in your source code unless you’re debugging HLA and you want to stop the compiler immediately after the compilation of some statement.

Because the set of HLA reserved words is changing frequently, a special feature was added to HLA to allow a programmer to "disable" HLA reserved words. This may allow an older program that uses new HLA reserved words as identifiers to continue working with only minor modifications to the HLA source code. The ability to disable certain HLA reserved words also allows you to create macros that override certain machine instructions.

All HLA reserved words take two forms: the standard, mutable, form (appearing in the table above) and a special immutable form that consists of a tilde character (’~’) followed by the reserved word. For example, ’mov’ is the mutable form of the move instruction while ’~mov’ is the immutable form. By default, the immutable and mutable forms are equivalent when you begin an assembly. However, you can use the #id compile-time statement to convert the mutable form to an identifier and you can use the #rw compile-time statement to turn it back into a reserved word. Regardless of the state of the mutable form, the immutable form always behaves like the reserved word as far as HLA is concerned. Here’s an example of the #id and #rw statements:

#id( mov ) //From this point forward, mov is an identifier, not a reserved word

mov:

~mov( i, eax ); // Must use ~mov while mov is a reserved word!

cmp( eax, 0 );

jne mov;

#rw( mov ) // Okay, now mov is a reserved word again.

mov( 0, eax );

Note that use can use the #id facility to disable certain instructions. For example, by default HLA handles almost all (32-bit flat model) instructions up through the latest Intel processors. If you want to write code for an earlier processor, you may want to disable instructions available only on later processors to help avoid their use. You can do this by placing the offending instructions in #id statements.

The #rw statement will not turn an arbitrary identifier into a reserved word. It will only revert a reserved word that was previously converted to an identifier back into a reserved word.

One use of the #id statement is to change the syntax of existing HLA instructions. For example, some x86 programmers are completely incapable of handling HLA's (and Gas') "source, dest" syntax and insist on using the original Intel "dest, source" syntax. This isn't a good reason for giving up on HLA because you can easily override HLA's syntax by using the #id statement and a set of macros. Consider the following example for the mov instruction:

#id( mov )

#macro mov( dest, source );

~mov( source, dest )

#endmacro

By creating an include file (let's calling "intel.hhf") with all the appropriate macros and #id statements, you can easily change HLA's syntax to take on a more "Intel" feel.

External Symbols and Assembler Reserved Words

HLA v2.x, in addition to directly producing object code, offers the option of producing an assembly language file during compilation and invoking an assembler such as MASM, FASM, NASM, or Gas to complete the compilation process. HLA automatically translates normal identifiers you declare in your program to benign identifiers in the assembly language program (in HLA v2.2 these identifiers typically took the form original_name__hla_xxxx where original_name is the original symbol and xxxx is a unique four-digit hexadecimal value). However, HLA does not translate external symbols, but preserves these names in the assembly language file it produces. Therefore, you must take care not to use external names that conflict with the underlying assembler’s set of reserved words or that assembler will generate an error when it attempts to process HLA’s output. Obviously, this is not an issue when directly producing object code with HLA (rather than producing an assembly language source file to be assembled by some other assembler).

For a list of assembler reserved words, please see the documentation for the back-end assembler you are using.

HLA Identifiers

HLA identifiers must begin with an alphabetic character or an underscore. After the first character, the identifier may contain alphanumeric and underscore symbols. There is no technical limit on identifier length in HLA, but you should avoid external symbols greater than about 32 characters in length since the assembler and linkers that process HLA identifiers may not be able to handle such symbols. Also note that if you are generating assembly language source output files, HLA may add some additional characters to the identifiers you use (typically something like "__HLA_xxxx" where "xxxx" is a 4-digit hexadecimal number) in order to prevent conflicts with the assembler's own reserved word set. As such, you may want to limit yourself to about 20-22 characters if you're using a back-end assembler that has limited identifier lengths.

HLA identifiers are always case neutral. This means that identifiers are case sensitive insofar as you must always spell an identifier exactly the same way (with respect to alphabetic case). However, you are not allowed to declare two identifiers whose only difference is alphabetic case.

Although technically legal in your program, do not use identifiers that begin and end with a single underscore. HLA reserves such identifiers for use by the compiler and the HLA standard library. If you declare such identifiers in your program, the possibility exists that you may interfere with HLA’s or the HLA Standard Library’s use of such a symbol.

By convention, HLA programmers use symbols beginning with two underscores to represent private fields in a class. Therefore, you should avoid such identifiers except when defining such private fields in your own classes.

External Identifiers

HLA lets you explicitly provide a string for external identifiers. External identifiers are not limited to the format for HLA identifiers. HLA allows any string constant to be used for an external identifier. If you're using a back-end assembler, it is your responsibility to use only those characters that are legal in that assembler. Note that this feature lets you use symbols that are not legal in HLA but are legal in external code (e.g., Win32 APIs use the ’@’ character in identifiers and some non-HLA code may use HLA reserved words as identifiers). See the discussion of the external option in the chapters on HLA Program Structure and HLA Procedures for more details.

HLA Literal Constants

HLA supports literal numeric, string, character, character set, Boolean, array, record, and union constants. For more details on these HLA language elements, please see the chapters on HLA Constants and Constant Expressions and HLA Data Types.

HLA Data Types

Data Types in HLA

Unlike traditional x86 assemblers that tend to work only with bytes, words, double-words, quad-words, and long- (oct-) words, HLA provides a rich set of basic primitive types. This chapter discusses all the built-in and user-definable types that HLA supports.

Native (Primitive) Data Types in HLA

HLA provides the following basic primitive types:

boolean One byte; zero represents false, one represents true (any non-zero value also represents true).

Enum One, two, or four bytes (program selectable, default is one byte); user defined IDs with unique values.

Uns8 Unsigned values in the range 0..255.

Uns16 Unsigned integer values in the range 0..65535.

Uns32 Unsigned integer values in the range 0..4,204,967,295.

Uns64 Unsigned 64-bit integer.

Uns128 Unsigned 128-bit integer.

Byte Generic eight-bit value.

Word Generic 16-bit value.

DWord Generic 32-bit value.

QWord Generic 64-bit value.

TByte Generic 80-bit value.

LWord Generic 128-bit value.

Int8 Signed integer values in the range -128..+127.

Int16 Signed integer values in the range -32768..+32767.

Int32 Signed integer values in the range -2,147,483,648..+2,147,483,647.

Int64 Signed 64-bit integer values.

Int128 Signed 128-bit integer values.

Char Character values.

WChar Unicode character values.

Real32 32-bit floating-point values.

Real64 64-bit floating-point values.

Real80 80-bit floating-point values.

Real128 128-bit floating-point values (for SSE/2 instructions).

String Dynamic length string constants. (Run-time implementation: four-byte pointer.)

ZString Zero-terminated dynamic length strings (run-time implementation: four-byte pointer).

Unicode Unicode strings.

CSet A set of up to 128 different ASCII characters (16-byte bitmap).

Text Similar to string, but text constants expand in-place (like #define in C/C++).

Thunk A set of machine instructions to execute.

Often, it is convenient to discuss the types above in various groups. The HLA language reference manual will often use the following terms:

Ordinal: boolean, enum, uns8, uns16, uns32, byte, word, dword, int8, int16, int32, char.

Unsigned: uns8, uns16, uns32, byte, word, dword.

Signed: int8, int16, int32, byte, word, dword.

Number: uns8, uns16, uns32, int8, int16, int32, byte, word, dword

Numeric: uns8, uns16, uns32, int8, int16, int32, byte, word, dword, real32, real64, real80

Enumerated Data Types

HLA provides the ability to associate a list of identifiers with a user-defined type. Such types are known as enumerated data types (because HLA enumerates, or numbers, each of the identifiers in the list to give them a unique value). The syntax for an enumerated type declaration (in an HLA type section, see the description a little later) takes the following form:

typename : enum{ list_of_identifiers };

Here is a typical example:

type

color_t :enum{ red, green, blue, magenta, yellow, cyan, black, white };

Internally, HLA treats enumerated types as though they were unsigned integer values (though enum types are not directly compatible with the unsigned types). HLA associates the value zero with the first identifier in the enum list and then attaches sequentially increasing values to the following identifiers in the list. For example, HLA will associate the following values with the color_t symbolic constants:

red 0

green 1

blue 2

magenta 3

yellow 4

cyan 5

black 6

white 7

Because each enumerated constant in a given enum list is unique, you may compare these values, use them in computations, etc. Also note that, because of the way HLA assigns internal values to these constant names, you may compare objects in an enumerated list for less than and greater than in addition to equal or not equal.

Note that HLA uses zero as the internal representation for the first symbol of every enum list. HLA only guarantees that the values it associates with enum types are unique for a single type; it does not make this guarantee across different enumerated types (in fact, you’re guaranteed that different enum types do not use unique values for their symbol sets). In the following example, HLA uses the value zero for both the internal representation of const0 and c0 . Likewise, HLA uses the value one for both const1 and c1 . And so on...

type

enumType1 :enum{ const0, const1, const2 };

enumType2 :enum( c0, c1, c2 };

Note that the enumerated constants you specify are not "private" to that particular type. That is, the constant names you supply in an enumerated data type list must be unique within the current scope (see the definition of identifier scope elsewhere in the HLA documentation). Therefore, the following is not legal:

type

enumType1 :enum{ et1, et2, et3, et4 };

enumType2 :enum{ et2, et2a, et2b, et2c }; //et2 is a duplicate symbol!

The problem here is that both type lists attempt to define the same symbol: et2 . HLA reports an error when you attempt this.

One way to view the enumerated constant list is to think of it as a list of constants in an HLA const section (see the description of declaration sections a little later in this document), e.g.,

const

red : color_t := 0;

green : color_t := 1;

blue : color_t := 2;

magenta : color_t := 3;

yellow : color_t := 4;

cyan : color_t := 5;

black : color_t := 6;

white : color_t := 7;

By default, HLA uses 8-bit values to represent enumerated data types. This means that you can represent up to 256 different symbols using an enumerated data type. This should prove sufficient for most applications. HLA provides a special "compile-time variable" that lets you change the size of an enumerated type from one to two or four bytes. All you have to do is assign the value two or four to this variable and HLA will automatically resize the storage for enumerated types to handle longer lists of objects. Example:

?@enumSize := 4; // Use dword size for enum types

type

enumDword :enum{ d0, d1, d2, d3};

var

ed :enumDword; // Reserves four bytes of storage

HLA Type Compatibility

HLA is unusual among assembly language insofar as it does some serious type checking on its operands. While the type checking isn’t quite as "strong" as some high-level languages, HLA clearly does a lot more type checking than other assemblers, even those that purport to do type checking on operands (e.g., MASM). The use of strong type checking can help you locate logical errors in your code that would otherwise go unnoticed (except via a laborious and time consuming testing/debug session).

The downside to strong type checking is that experienced assembly programmers may become somewhat annoyed with HLA’s reports that they are doing something wrong when, in fact, the programmer knows exactly what they are doing. There are two solutions to this problem: use type coercion (described a little bit later) or use the "untyped" types that reduce type checking to simply ensuring that the sizes of the operands match. However, before discussing how to override HLA’s type checking system, it’s probably a good idea to first describe how HLA uses data types.

Fundamentally, HLA divides the data types into classes based on the size of their underlying representation. Unless you explicitly override a type with a type coercion operation, attempting to mix object sizes in a memory or register operand will produce an error (in constant expressions, HLA is a bit more forgiving; it will automatically promote between certain types and adjust the type of the result accordingly). With most of HLA’s data types, it’s obvious what the size of the underlying representation is, because most HLA type names incorporate the size (in bits) in the type’s name. For example, the uns16 data type is a 16-bit (two-byte) type. Nevertheless, this rule isn’t true for all data types, so it’s a good idea to begin this discussion by looking at the underlying sizes of each of the HLA types.

8 bits: boolean, byte, char, enum, int8, uns8

16 bits: int16, uns16, wchar, word

32 bits: dword, int32, pointer types, real32, string, zstring, unicode, uns32

64 bits: int64, qword, real64, uns64

80 bits: real80, tbyte

128 bits: cset, int128, lword, uns128, real128

The byte, word, dword, qword, tbyte, and lword types are somewhat special. These are known as untyped data types. They are directly compatible with any scalar, ordinal, data type that is the same size as the type in question. For example, a byte object is directly compatible with any object of type boolean, byte, char, enum (assuming @enumSize is 1) , int8, or uns8 . No special coercion is necessary when assigning a byte value to an object that has one of these other types; likewise, no special coercion operation is necessary when assigning a value of one of these other types to a byte object.

Note that cset, real32, real64, real80, and real128 objects are not ordinal types. Therefore, you cannot directly mix these types with lword, dword, qword, tbyte, or lword objects without an explicit type coercion operation. Also, keep in mind that composite data types (see the next section) are not directly compatible with bytes, words, dwords, qwords, tbytes, and lwords, even if the composite data type has the same number of bytes (the only exception is the pointer data type, which is compatible with the dword type).

Composite Data Types

In addition to the primitive types above, HLA supports pointers, arrays, records (structures), unions, and classes of the primitive types (except for text objects).

Array Data Types

HLA allows you to create an array data type by specifying the number of array elements after a type name. Consider the following HLA type declaration that defines intArray to be an array of int32 objects:

type intArray : int32[ 16 ];

The "[ 16 ]" component tells HLA that this type has 16 four-byte integers. HLA arrays use a zero-based index, so the first element is always element zero. The index of the last element, in this example, is 15 (total of 16 elements with indices 0..15).

HLA also supports multidimensional arrays. You can specify multidimensional arrays by providing a list of indices inside the square brackets, e.g.,

type intArray4x4 : int32[ 4, 4 ];
type intArray2x2x4 : int32[ 2,2,4 ];

The mechanism for accessing array elements differs depending upon whether you are accessing compile-time array constants or run-time array variables. A complete discussion of this will appear in later sections.

Union Data Types

HLA implements the discriminate union type using the union..endunion reserved words. The following HLA type declaration demonstrates a union declaration:

type

allInts:

union
i8: int8;
i16: int16;
i32: int32;
endunion;

All fields in a union have the same starting address in memory. The size of a union object is the size of the largest field in the union. The fields of a union may have any type that is legal in a variable declaration section (see the discussion of the var section in the chapter on HLA Program Structure for more details).

Given a union object, say i of type allInts, you access the fields of the union using the familiar dot-notation. The following 80x86 mov instructions demonstrate how to access each of the fields of the i variable:

mov( i.i8, al );
mov( i.i16, ax );
mov( i.i32, eax );

Unions also support a special field type known as an anonymous record (see the next section for a description of records). The syntax for an anonymous record in a union is the following:

type

unionWrecord:

union

u1Field: byte;

u2Field: word;

u3Field: dword;

record

u4Field: byte[2];

u5Field: word[3];

endrecord;

u6Field: byte;

endunion;

Fields appearing within the anonymous record do not necessarily start at offset zero in the data structure. In the example above, u4Field starts at offset zero while u5Field immediately follows it two bytes later. The fields in the union outside the anonymous record all start at offset zero. If the size of the anonymous record is larger than any other field in the union, then the record’s size determines the size of the union. This is true for the example above, so the union’s size is 16 bytes since the anonymous record consumes 16 bytes.

Record Data Type14s

HLA’s records allow programmers to create data types whose fields can be different types. The following HLA type declaration defines a simple record with four fields:

type

Planet:

record

x: int32;
y: int32;
z: int32;
density: real64;

endrecord;

Objects of type Planet will consume 20 bytes of storage at run-time.

The fields of a record may be of any legal HLA data type including other composite data types. Like unions, anything that is legal in a var section is a legal field of a record. As for unions, you use the dot-notation to access fields of a record object.

In addition to the var-like declarations, you may also declare anonymous unions within a record. An anonymous union is a union declaration without a fieldname associated with the union, e.g.,

type

DemoAU:

record
x:real32;
union
u1:int32;
r1:real32;
endunion;
y:real32;
endrecord;

In this example, x, u1, r1, and y are all fields of DemoAU. To access the fields of a variable D of type DemoAU, you would use the following names: D.x, D.u1, D.r1, and D.y. Note that D.u1 and D.r1 share the same memory locations at run-time, while D.x and D.y have unique addresses associated with them.

Record types may inherit fields from other record types. Consider the following two HLA type declarations:

type
Pt2D:

record

x: int32;
y: int32;

endrecord;

Pt3D:

record inherits( Pt2D )

z: int32;

endrecord;

In this example, Pt3D inherits all the fields from the Pt2D type. The inherits keyword tells HLA to copy all the fields from the specified record (Pt2D in this example) to the beginning of the current record declaration (Pt3D in this example). Therefore, the declaration of Pt3D above is equivalent to:

Pt3D:

record

x: int32;
y: int32;
z: int32;

endrecord;

In some special situations, you may want to override a field from a previous field declaration. For example, consider the following record declarations:

 

BaseRecord:

record

a: uns32;

b: uns32;

endrecord;

DerivedRecord:

record inherits( BaseRecord )

b: boolean; // New definition for b!

c: char;

endrecord;

Normally, HLA will report a "duplicate" symbol error when attempting to compile the declaration for DerivedRecord since the b field is already defined via the "inherits( BaseRecord )" option. However, in certain cases it’s quite possible that the programmer wishes to make the original field inaccessible in the derived class by using the same name. That is, perhaps the programmer intends to actually create the following record:

DerivedRecord:

record

a: uns32; // Derived from BaseRecord

b: uns32; // Derived from BaseRecord, but inaccessible here.

b: boolean; // New definition for b!

c: char;

endrecord;

HLA allows a programmer explicitly override the definition of a particular field by using the overrides keyword before the field they wish to override. While the previous declarations for DerivedRecord produce errors, the following is acceptable to HLA:

BaseRecord:

record

a: uns32;

b: uns32;

endrecord;

DerivedRecord:

record inherits( BaseRecord )

overrides b: boolean; // New definition for b!

c: char;

endrecord;

Normally, HLA aligns each field on the next available byte offset in a record. If you wish to align fields within a record on some other boundary, you may use the align directive to achieve this. Consider the following record declaration as an example:

type

AlignedRecord:

record

b :boolean; // Offset 0

c :char; // Offset 1

align(4);

d :dword; // Offset 4

e :byte; // Offset 8

w :word; // Offset 9

f :byte; // Offset 11

endrecord;

Note that field d is aligned at a four-byte offset while w is not aligned. We can correct this problem by sticking another align directive in this record:

type

AlignedRecord2:

record

b :boolean; // Offset 0

c :char; // Offset 1

align(4);

d :dword; // Offset 4

e :byte; // Offset 8

align(2);

w :word; // Offset 10

f :byte; // Offset 12

endrecord;

Be aware of the fact that the align directive in a record only aligns fields in memory if the record object itself is aligned on an appropriate boundary. For example, if an object of type AlignedRecord2 appears in memory at an odd address, then the d and w fields will also be misaligned (that is, they will appear at odd addresses in memory). Therefore, you must ensure appropriate alignment of any record variable whose fields you’re assuming are aligned.

Note that the AlignedRecord2 type consumes 13 bytes. This means that if you create an array of AlignedRecord2 objects, every other element will be aligned on an odd address and three out of four elements will not be double-word aligned (so the d field will not be aligned on a four-byte boundary in memory). If you are expecting fields in a record to be aligned on a certain byte boundary, then the size of the record must be an even multiple of that alignment factor if you have arrays of the record. This means that you must pad the record with extra bytes at the end to ensure proper alignment. For the AlignedRecord2 example, we need to pad the record with three bytes so that the size is an even multiple of four bytes. This is easily achieved by using an align directive as the last declaration in the record:

type

AlignedRecord2:

record

b :boolean; // Offset 0

c :char; // Offset 1

align(4);

d :dword; // Offset 4

e :byte; // Offset 8

align(2);

w :word; // Offset 10

f :byte; // Offset 12

align(4) // Ensures we’re padded to a multiple of four bytes.

endrecord;

Note that you can only use values that are integral powers of two in the align directive and the value must be 16 or less.

If you want to ensure that all fields are appropriately aligned on some boundary within the record, but you don’t want to have to manually insert align directives throughout the record, HLA provides a second alignment option to solve your problem. Consider the following syntax:

type

alignedRecord3 : record[4]

<< Set of fields >>

endrecord;

The "[4]" immediately following the record reserved word tells HLA to start all fields in the record at offsets that are multiples of four, regardless of the object’s size (and the size of the objects preceding the field). HLA allows any integer expression that produces a value that is a power of two in the range 1..16 inside these parentheses. If you specify the value 1 (which is the default), then all fields are packed (aligned on a byte boundary). For values greater than 1, HLA will align each field of the record on the specified boundary. For arrays, HLA will align the field on a boundary that is a multiple of the array element’s size.

Note that if you set the record alignment using this syntactical form, any align directive you supply in the record may not produce the desired results. When HLA sees an align directive in a record that is using field alignment, HLA will first align the current offset to the value specified by align and then align the next field’s offset to the global record align value.

Nested record declarations may specify a different alignment value than the enclosing record, e.g.,

type

alignedRecord4 : record[4]

a :byte;

b :byte;

c :record[8]

d :byte;

e :byte;

endrecord;

f :byte;

g :byte;

endrecord;

In this example, HLA aligns fields a, b, f, and g on double-word boundaries, it aligns d and e (within c ) on 8-byte boundaries. Note that the alignment of the fields in the nested record is true only within that nested record. That is, if c turns out to be aligned on some boundary other than an 8-byte boundary, then d and e will not actually be on 8-byte boundaries; they will, however be on 8-byte boundaries relative to the start of c .

In addition to letting you specify a fixed alignment value, HLA also lets you specify a minimum and maximum alignment value for a record. The syntax for this is the following:

type

recordname : record[maximum : minimum]

<< fields >>

endrecord;

Whenever you specify a maximum and minimum value as above, HLA will align all fields on a boundary that is at least the minimum alignment value. However, if the object’s size is greater than the minimum value but less than or equal to the maximum value, then HLA will align that particular field on a boundary that is a multiple of the object’s size. If the object’s size is greater than the maximum size, then HLA will align the object on a boundary that is a multiple of the maximum size. As an example, consider the following record:

type

r: record[ 4:1 ];

a :byte; // offset 0

b :word; // offset 2

c :byte; // offset 4

d :dword;[2] // offset 8

e :byte; // offset 16

f :byte; // offset 17

g :qword; // offset 20

endrecord;

Note that HLA aligns g on a double-word boundary (not quad-word, which would be offset 24) since the maximum alignment size is four. Note that since the minimum size is one, HLA allows the f field to be aligned on an odd boundary (because it’s a byte).

If an array, record, or union field appears within a record, then HLA uses the size of an array element or the largest field of the record or union to determine the alignment size. That is, HLA will align the field within the outermost record on a boundary that is compatible with the size of the largest element of the nested array, union, or record.

HLA sophisticated record alignment facilities let you specify record field alignments that match that used by most major high-level language compilers. This lets you easily access data types used in those HLLs without resorting to inserting lots of ALIGN directives inside the record.

When declaring record variables in a var, static, readonly, or storage declaration section, HLA associates the offset zero with the first field of a record. Each additional field in the record is assigned an offset corresponding to the sum of the sizes of all the prior fields. So in the following example, x would have the offset zero, y would have the offset four, and z would have the offset eight.

Pt3D:

record

x: int32;
y: int32;
z: int32;

endrecord;

If you would like to specify a different starting offset, you can use the following syntax for a record declaration:

Pt3D:

record := 4;

x: int32;
y: int32;
z: int32;

endrecord;

The constant expression specified after the assignment operator (":=") specifies the starting offset of the first field in the record. In this example x, y, and z will have the offsets 4, 8, and 12, respectively.

Warning: setting the starting offset in this manner does not add padding bytes to the record. This record is still a 12-byte object. If you declare variables using a record declared in this fashion, you may run into problems because the field offsets do not match the actual offsets in memory. This option is intended primarily for mapping records to pre-existing data structures in memory. Only advanced assembly language programmers should use this option.

Pointer Types

HLA allows you to declare a pointer to some other type using syntax like the following:

pointer to base_type

The following example demonstrates how to create a pointer to a 32-bit integer within the type declaration section:

type pi32: pointer to int32;

HLA pointers are always 32-bit (near32) pointers.

HLA v1.x allowed you to define pointers to existing procedures using syntax like the following:

procedure someProc( parameter_list );

<< procedure options, followed by @external, @forward, or procedure body>>

.

.

.

type

p : pointer to procedure someProc;

However, this pointer syntax has been deprecated as of HLA v2.0 and this syntax will disappear sometime in HLA v2.x. The modern way to declare pointers that are compatible with a particular procedure is to use the "new style" procedure declarations in the HLA proc section. This is done as follows:

type

p : procedure( parameter_list );

.

.

.

proc

someProc:p {optional procedure options};

.

.

.

See the HLA reference manual chapter on HLA Procedures for more details about the proc section.

Note that HLA provides the reserved word null (or NULL, reserved words are case insensitive) to represent the nil pointer. HLA replaces NULL with the value zero. The NULL pointer is compatible with any pointer type (including strings, which are pointers).

Thunks

A "thunk" is an 8-byte variable that contains a pointer to a piece of code to execute and an execution environment pointer (i.e., a pointer to an activation record). The code associated with a thunk is, essentially, a small procedure that uses the activation record of the surrounding code rather than creating its own activation record. HLA uses thunks to implement the iterator "yield" statement as well as pass by name and pass by lazy evaluation parameters. In addition to these two uses of thunks, HLA allows you to declare your own thunk objects and use them for any purpose you desire. To declare a thunk variable is easy, just use a declaration like the following in a var, static, readonly, or storage section:

thunkVar: thunk;

This declaration reserves eight bytes of storage. The first double-word holds the address of the code to execute, the second double-word holds a pointer to the activation record to load into EBP when the thunk executes.

Of course, like almost any pointer variable, declaring a thunk variable is the easy part; the hard part is making sure the thunk variable is initialized before attempting to call the thunk. While you could manually load the address of some code and the frame pointer value into a thunk variable, HLA provides a better syntax for initializing thunks with small code fragments: the thunk statement. The thunk statement uses the following syntax:

thunk thunkVar := #{ sequence_of_statements }#;

Consider the following example:

program ThunkDemo;

#include( "stdio.hhf" );

procedure proc1;

var

i: int32;

p1Thunk: thunk;

procedure proc2( t:thunk );

var

i:int32;

begin proc2;

mov( 25, i );

t();

stdout.put( "Inside proc2, i=", i, nl );

end proc2;

begin proc1;

thunk p1Thunk := #{ mov( 0, i ); }#;

mov( 1, i );

proc2( p1Thunk );

stdout.put( "i=", i, nl );

end proc1;

begin ThunkDemo;

proc1();

end ThunkDemo;

In this example, proc1 has two local variables, i and p1Thunk . The thunk statement initializes the p1Thunk variable with the address of some code that moves a zero into the i variable. The thunk statement also initializes p1Thunk with a pointer to the current activation record (that is, a pointer to proc1 ’s activation record). Then proc1 calls proc2 passing p1Thunk as a parameter.

The proc2 routine has its own local variable named i . Of course, this is a different variable from the i in proc1 . Proc2 begins by setting its variable i to the value 25. Then proc2 invokes the thunk (passed to it as a parameter). This thunk sets the variable i to zero. However, because the thunk uses the current activation record when the thunk statement was executed, this statement sets proc1 ’s i variable to zero rather than proc2 ’s i variable. This program produces the following output:

Inside proc2, i=25

i=0

Although you probably won’t use thunks that often, they are quite nice for deferred execution. This is especially useful in AI (Artificial Intelligence) programs.

Class Types

Classes and object-oriented programming are the subject of a different HLA Reference Manual Document. See the chapter on HLA Classes for more details.

Regular Expression Types

The HLA compile-time language supports a special data type known as a "compiled regular expression". Please see the section on regular expression macros in the chapter on the HLA Compile-Time Language for more details on this data type.

HLA Literal Constants and Constant Expressions

HLA Literal Constants

Literal constants are those language elements that we normally think of as non-symbolic constant objects. HLA supports a wide variety of literal constants. The following sections describe those constants.

Numeric Constants

HLA lets you specify several different types of numeric constants.

Decimal Constants

The first and last characters of a decimal integer constant must be decimal digits (0..9). Interior positions may contain decimal digits and underscores. The purpose of the underscore is to provide a better presentation for large decimal values (i.e., use the underscore in place of a comma in large values). Example: 1_234_265.

Note: Technically, HLA does not allow negative literal integer constants. However, you can use the unary "-" operator to negate a value, so you’d never notice this omission (e.g., -123 is legal, it consists of the unary negation operator followed by a positive decimal literal constant). Therefore, HLA always returns type unsXX for all literal decimal constants. Also, note that HLA always uses a minimum size of uns32 for literal decimal constants. If you absolutely, positively, want a literal constant to be treated as some other type, use one of the compile-time type coercion functions to change the type (e.g., uns8(1), word(2), or int16(3)). Generally, the type that HLA uses for the object is irrelevant since HLA will automatically promote a value to a larger or smaller type as appropriate.

Here are the following ranges for the various HLA unsigned data types:

uns8: 0..255

uns16: 0..65,535

uns32: 0..4,294,967,295

uns64: 0..18,446,744,073,709,551,615

uns128: 0..340,282,366,920,938,463,463,374,607,431,768,211,455

Hexadecimal Constants

Hexadecimal literal constants must begin with a dollar sign ("$") followed by a hexadecimal digit and must end with a hexadecimal digit (0..9, A..F, or a..f). Interior positions may contain hexadecimal digits or underscores. Hexadecimal constants are easiest to read if each group of four digits (starting from the least significant digit) is separated from the others by an underscore. E.g., $1A_2F34_5438.

If the constant fits into 32 bits or less, HLA always returns the dword type for a hexadecimal constant. For larger values, HLA will automatically use the qword or lword type, as appropriate. If you would like the hexadecimal value to have a different type, use one of the HLA compile-time type coercion functions to change the type (e.g., byte($12) or qword($54)).

Here are the following ranges for the various HLA hexadecimal data types:

uns8: 0..$FF

uns16: 0..$FFFF

uns32: 0..$FFFF_FFFF

uns64: 0..$FFFF_FFFF_FFFF_FFFF

uns128: 0..$FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF

Binary Constants

Binary literal constants begin with a percent sign ("%") followed by at least one binary digit (0/1) and they must end with a binary digit. Interior positions may contain binary digits or underscore characters. Binary constants are easiest to read if each group of four digits (starting from the least significant digit) is separated from the others by an underscore. E.g., %10_1111_1010.

Like hexadecimal constants, HLA always associates the type dword with a "raw" binary constant; it will use the qword or lword type if the value is greater than 32 bits or 64 bits (respectively). If you want HLA to use a different type, use one of the compile-time type coercion functions to achieve this.

Obviously, binary constants may have as many binary digits as there are bits in the underlying type. This document will not attempt to write out the maximum binary literal constant!

Numeric Set Constants

HLA provides a special numeric constant form that lets you specify a numeric value by the bit positions containing ones. This corresponds to a powerset of integer values in the range 0..31. These constants take the following form:

@{ comma_separated_list_of_digits }

The comma_separate_list_of_digits can be empty (signifying no set bits, i.e., the value zero), a single digit, or a set of digits separated by commas. Here are some examples:

@{}

@{8}

@{1,2,8,24}

The corresponding numeric constant is given the type dword and is assigned the value that has ones in all the specified bit positions. For example, "@{8}" is equal to 256 since this value has a single set bit in bit position eight. Note that "@{0}" equals one, not zero (because the value one has a single set bit in position zero).

Real (Floating-Point) Constants

Floating-point (real) literal constants always begin with a decimal digit (never just a decimal point). A string of one or more decimal digits may be optionally followed by a decimal point and zero or more decimal digits (the fractional part). After the optional fractional part, a floating-point number may be followed by "e" or "E", a sign ("+" or "-"), and a string of one or more decimal digits (the exponent part). Underscores may appear between two adjacent digits in the floating-point number; their presence is intended to substitute for commas found in real-world decimal numbers.

Examples:

1.2

2.345e-2

0.5

1.2e4

2.3e+5

1_234_567.0

Literal real constants are always 80 bits and have the default type real80 . If you wish to specify real32 or real64 literal constants, then use the real32 or real64 compile-time coercion functions to convert the values, e.g., real32( 3.14159 ) . During compile time, it’s rare that you’d want to use one of the smaller types since they are less accurate at representing floating-point values (although this might be precisely why you decide to use the smaller real type, so the accuracy matches the computations you’re doing at run-time).

The range of real32 constants is approximately 10±38 with 6-1/2 digits of precision; the range of real64 values is approximately 10±308 with approximately 14-1/2 digits of precision, and the range of real80 constants is approximately 10±4096 with about 18 digits of precision.

Boolean Constants

Boolean constants consist of the two predefined identifiers true and false. Note that your program may redefine these identifiers, but doing so is incredibly bad programming style. Since these are actual identifiers in the symbol table (and not reserved words), you must spell these identifiers in all lower case or HLA will complain (unlike reserved words that are case insensitive).

Internally, HLA represents true with one and false with zero. In fact, HLA’s compile-time boolean operations only look at bit #0 of the boolean value (and always clear the other bits). HLA compile-time statements that expect a boolean expression do not use zero/not zero like C/C++ and a few other languages. Such expressions must have a boolean type with the values true/false; you cannot supply an integer expression and rely on zero/not zero evaluation as in C/C++ or BASIC.

Character Constants

Character literals generally consist of a single (graphic) character surrounded by apostrophes. To represent the apostrophe character use four apostrophes, e.g., ‘’’’.

Another way to specify a character constant is by typing the "#" symbol followed by a numeric literal constant (decimal, hexadecimal, or binary). Examples: #13, #$D, #%1101.

Unicode Character Constants

Unicode character constants are 16-bit values. HLA provides limited support for Unicode literal constants. HLA supports the UTF/7 code point (character set), which is just the standard seven-bit ASCII character set and nine high-order zero bits. To specify a 16-bit literal Unicode constant simply prefix a standard ASCII literal constant with a ’u’ or ’U’, e.g.,

u’A’ - UTF/7 character constant for ’A’

Note that UTF/7 constants are simply the ASCII character codes zero extended to 16 bits.

HLA provides a second syntax for Unicode character constants that lets you enter values whose character codes are outside the range $20..$7E. You can specify a Unicode character constant by its numeric value using the ’u#nnnn’ constant form. This form lets you specify a 16-bit value following the ’#’ in either binary, decimal, or hexadecimal form, e.g.,

u#1233

u#$60F

u%100100101001

String Constants

String literal constants consist of a sequence of (graphic) characters surrounded by quotes. To embed a quote within a string, insert a pair of quotes into the string, e.g., "He said ""This"" to me."

If two string literal constants are adjacent in a source file (with nothing but whitespace between them), then HLA will concatenate the two strings and present them to the parser as a single string. Furthermore, if a character constant is adjacent to a string, HLA will concatenate the character and string to form a single string object. This is useful, for example, when you need to embed control characters into a string, e.g.,

"This is the first line" #$d #$a "This is the second line" #$d #$a

HLA treats the above as a single string with a Windows newline sequence (CR/LF) at the end of each of the two lines of text.

Unicode String Constants

HLA lets you specify Unicode string literals by prefixing a standard string constant with a ’u’ or a ’U’. Such string constants use the UTF/7 character set (that is, the standard ASCII character set) but reserve 16 bits for each character in the string. Note that the high order nine bits of each character in the string will contain zero.

As this was being written, there is no support for Unicode strings in the HLA Standard Library, though support for Unicode string functions should appear shortly (note that Windows’ programmers can call the Unicode string functions that are part of the Windows’ API).

Character Set Constants

A character set literal constant consists of several comma delimited character set expressions within a pair of braces. The character set expressions can either be individual character values or a pair of character values separated by an ellipse (".."). If an individual character expression appears within the character set, then that character is a member of the set; if a pair of character expressions, separated by an ellipse, appears within a character set literal, then all characters between the first such expression and the second expression are members of the set. As a convenience, if a string constant appears between the braces, HLA will take the union of all the characters in that string and add those characters to the character set.

Examples:

{‘a’,’b’,’c’} // a, b, and c.

{‘a’..’c’} // a, b, and c.

{‘A’..’Z’,’a’..’z’} // Alphabetic characters.

{"cset"} // The character set ‘c’, ‘e’, ‘s’, and ‘t’.

{‘ ‘,#$d,#$a,#$9} // Whitespace (space, return, linefeed, tab).

HLA character sets are currently limited to holding characters from the 128-character ASCII character set. In the future, HLA may support an xcset type (supporting 256 elements) or even wcset (wide character sets), but that support does not currently exist.

Structured Constants

Structured constants are those whose data type is not a scalar. The structured constant types include array constants, record constants, union constants, and pointer constants.

Array Constants

HLA lets you specify an array literal constant by enclosing a set of values within a pair of square brackets. Since array elements must be homogenous, all elements in an array literal constant must be the same type or conformable to the same type. Examples:

[ 1, 2, 3, 4, 9, 17 ]
[ ’a’, ’A’, ’b’, ’B’ ]
[ "hello", "world" ]

Note that each item in the list of values can actually be a constant expression, not a simple literal value.

HLA array constants are always one-dimensional. This, however, is not a limitation because if you attempt to use array constants in a constant expression, the only thing that HLA checks is the total number of elements. Therefore, an array constant with eight integers can be assigned to any of the following arrays:

const
a8: int32[8] := [1,2,3,4,5,6,7,8];
a2x4: int32[2,4] := [1,2,3,4,5,6,7,8];
a2x2x2: int32[2,2,2] := [1,2,3,4,5,6,7,8];

Although HLA doesn’t support the notation of a multi-dimensional array constant, HLA does allow you to include an array constant as one of the elements in an array constant. If an array constant appears as a list item within some other array constant, then HLA expands the interior constant in place, lengthening the list of items in the enclosing list. E.g., the following three array constants are equivalent:

[ [1,2,3,4], [5,6,7,8] ]
[ [ [1,2], [3,4] ], [ [5,6], [7,8] ] ]
[1,2,3,4,5,6,7,8]

Although the three array constants are identical, as far as HLA is concerned, you might want to use these three different forms to suggest the shape of the array in an actual declaration, e.g.,

const
a8: int32[8] := [1,2,3,4,5,6,7,8];
a2x4: int32[2,4] := [ [1,2,3,4], [5,6,7,8] ];
a2x2x2: int32[2,2,2] := [[[1,2], [3,4] ], [[5,6], [7,8]]];

Also note that symbol array constants, not just literal array constants, may appear in a literal array constant. For example, the following literal array constant creates a nine-element array holding the values one through nine at indexes zero through eight:

const Nine: int32[ 9 ] := [ a8, 9 ];

This assumes, of course, that a8 was previously declared as above. Since HLA "flattens" all array constants, you could have substituted a2x4 or ax2x2x for a8 in the example above and obtained identical results.

You may also create an array constant using the HLA dup operator. This operator uses the following syntax:

expression DUP [expression_to_replicate]

Where expression is an integer expression and expression_to_replicate is some expression, possibly an array constant. HLA generates an array constant by repeating the values in the expression_to_replicate the number of times specified by the expression. (Note: this does not create an array with expression elements unless the expression_to_replicate contains only a single value; it creates an array whose element count is expression times the number of items in the expression_to_replicate ). Examples:

10 dup [1] -- equivalent to [1,1,1,1,1,1,1,1,1,1]

5 dup [1,2] -- equivalent to [1,2,1,2,1,2,1,2,1,2]

Please note that HLA does not allow class constants, so class objects may not appear in array constants. In addition, HLA does not allow generic pointer constants; only certain types of pointer constants are legal. See the discussion of pointer constants for more details.

Record Constants

HLA supports record constants using a syntax very similar to array constants. You enclose a comma-separated list of values for each field in a pair of square brackets. To differentiate array and record constants, the name of the record type and a colon must precede the opening square bracket, e.g.,

type

Planet:

record

x :int32;

y :int32;

z :int32;

density :real64;

endrecord;

const

p :Planet := Planet:[ 1, 12, 34, 1.96 ]

HLA associates the items in the list with the fields as they appear in the original record declaration. In this example, the values 1, 12, 34, and 1.96 are associated with fields x, y, z, and density, respectively. Of course, the types of the individual constants must match (or be conformable to) the types of the individual fields.

Note that you may not create a record constant for a particular record type if that record includes data types that cannot have compile-time constants associated with them. For example, if a field of a record is a class object, you cannot create a record constant for that type since you cannot create class constants.

Union Constants

Union constants allow you to initialize static union data structures in memory as well as initialize union fields of other data structures (including anonymous union fields in records). There are some important differences between HLA compile-time union constants and HLA run-time unions (as well as between the HLA run-time union constants and unions in other languages). Therefore, it’s a good idea to begin the discussion of HLA’s union constants with a description of these differences.

There are a couple of different reasons people use unions in a program. The original reason was to share a sequence of memory locations between various fields whose access is mutually exclusive. When using a union in this manner, one never reads the data from a field unless they’ve previous written data to that field and there are no intervening writes to other fields. The HLA compile-time language fully (and only) supports this use of union objects.

A second reason people use unions (especially in high-level languages) is to provide aliases to a given memory location; in particular, aliases whose data types are different. In this mode, a programmer might write a value to one field and then read that data using a different field (in order to access that data’s bit representation as a different type). HLA does not support this type of access to union constants. The reason is quite simple: internally, HLA uses a special "variant" data type to represent all possible constant types. Whenever you create a union constant, HLA lets you provide a value for a single data field. From that point forward, HLA effectively treats the union constant as a scalar object whose type is the same as the field you’ve initialized; access to the other fields through the union constant is no longer possible. Therefore, you cannot use HLA compile-time constants to do type coercion; nor is there any need to since HLA provides a set of type coercion operators like @byte, @word, @dword, @int8, etc. As noted above, the main purpose for providing HLA union constants is to allow you to initialize static union variables; since you can only store one value into a memory location at a time, union constants only need to be able to represent a single field of the union at one time. Of course, at run time, you may access any field of the static union object you’ve created; but at compile-time you may only access the single field associated with a union constant.

An HLA literal union constant takes the following form:

typename.fieldname:[ constant_expression ]

The typename component above must be the name of a previously declared HLA union data type (i.e., a union type you’ve created in the type section). The fieldname component must be the name of a field within that union type. The constant_expression component must be a constant value (expression) whose type is the same as, or is automatically coercible to, the type of the fieldname field. Here is a complete example:

type

u:union

b:byte;

w:word;

d:dword;

q:qword;

endunion;

static

uVar :u := u.w:[$1234];

The declaration for uVar initializes the first two bytes of this object in memory with the value $1234. Note that uVar is actually eight bytes long; HLA automatically zeros any unused bytes when initializing a static memory object with a union constant.

Note that you may place a literal union constant in records, arrays, and other composite data structures. The following is a simple example of a record constant that has a union as one of its fields:

type

r :record

b:byte;

uf:u;

d:dword;

endrecord;

static

sr :r := r:[0, u.d:[$1234_5678], 12345];

In this example, HLA initializes the sr variable with the byte value zero, followed by a double-word containing $1234_5678 and a double-word containing zero (to pad out the remainder of the union field), followed by a double-word containing 12345.

You can also create records that have anonymous unions in them and then initialize a record object with a literal constant. Consider the following type declaration with an anonymous union:

type

rau :record

b:byte;

union

c:char;

d:dword;

endunion;

w:word;

endrecord;

Because anonymous unions within a record do not have a type name associated with them, you cannot use the standard literal union constant syntax to initialize the anonymous union field (that syntax requires a type name). Instead, HLA offers you two choices when creating a literal record constant with an anonymous union field. The first alternative is to use the reserved word union in place of a typename when creating a literal union constant, e.g.,

static

srau :rau := rau:[ 1, union.d:[$12345], $5678 ];

The second alternative is a shortcut notation. HLA allows you to simply specify a value that is compatible with the first field of the anonymous union and HLA will assign that value to the first field and ignore any other fields in the union, e.g.,

static

srau2 :rau := rau:[ 1, ’c’, $5678 ];

This is slightly dangerous since HLA relaxes type checking a bit here, but when creating tables of record constants, this is very convenient if you generally provide values for only a single field of the anonymous union; just make sure that the commonly used field appears first and you’re in business.

Although HLA allows anonymous records within a union, there was no syntactically acceptable way to differentiate anonymous record fields from other fields in the union; therefore, HLA does not allow you to create union constants if the union type contains an anonymous record. The easy workaround is to create a named record field and specify the name of the record field when creating a union constant, e.g.,

type

r :record

c:char;

d:dword;

endrecord;

u :union

b:byte;

x:r;

w:word;

endunion;

static

y :u := u.x:[ r:[ ’a’, 5]];

You may declare a union constant and then assign data to the specific fields as you would a record constant. The following example provides some samples of this:

type

u_t :union

b:byte;

x:r;

w:word;

endunion;

val

u :u_t;

.

.

.

?u.b := 0;

.

.

.

?u.w := $1234;

The two assignments above are roughly equivalent to the following:

?u := u_t.b:[0];

and

?u := u_t.w:[$1234];

However, to use the straight assignment (the former example) you must first declare the value u as a u_t union.

To access a value of a union constant, you use the familiar "dot notation" from records and other languages, e.g.,

?x := u.b;

.

.

.

?y := u.w & $FF00;

Note, however, that you may only access the last field of the union into which you’ve stored some value. If you store data into one field and attempt to read the data from some other field of the union, HLA will report an error. Remember, you don’t use union constants as a sneaky way to coerce one value’s type to another (use the coercion functions for that purpose).

Pointer Constants

HLA allows a very limited form of a pointer constant. If you place an ampersand ("&") in front of a static object’s name (i.e., the name of a static variable, readonly variable, storage variable, procedure, or iterator), HLA will compute the run-time offset of that variable. Pointer constants may not be used in arbitrary constant expressions. You may only use pointer constants in expressions used to initialize static or readonly variables or as constant expressions in 80x86 instructions. The following example demonstrates how pointer constants can be used:

program pointerConstDemo;

static
t:int32;
pt: pointer to int32 := &t;

begin pointerConstDemo;

mov( &t, eax );

end pointerConstDemo;

Pointer constants also allow a fixed constant offset by appending "[ constant_expression ]" to the pointer constant, for example:

program pointerConstDemo;

static
t:int32;
pt: pointer to int32 := &t[2];

begin pointerConstDemo;

mov( &t[-4], eax );

end pointerConstDemo;

These pointer constants are the address of the specified object plus an offset that is the number of bytes specified by the constant integer expression.

Also note that HLA allows the use of the reserved word NULL anywhere a pointer constant is legal. HLA substitutes the value zero for NULL. You can also use the HLA compile-time function @pointer( n ) with an integer constant to tell HLA to treat that number as a pointer constant.

Note that you may obtain the address of the current location counter as a pointer constant by applying the "&" operator to the @here keyword, e.g.,

mov( &@here, eax );

This places the address of the start of the instruction into EAX.

Regular Expression Constants

HLA uses compile-time "regex"-typed variables to hold compiled versions of regular expression. There is no literal form of a regular expression constant. The only way to generate a regular expression constant is in a val, const, or "?" declaration by assigning the "value" of a #regex macro declaration to a symbol, e.g.,

#regex someRegexMacro;

<<regex macro body>>

#endregex

const

compiledRegex :regex := someRegexMacro;

See the section on regular expressions in the chapter on The HLA Compile-Time Language for more details.

Constant Expressions in HLA

HLA provides a rich expression evaluator to process assembly-time expressions. HLA supports the following operators (sorting by decreasing precedence):

! (unary not), - (unary negation)

*, div, mod, /, <<, >>

+, -

=, = =, <>, !=, <=, >=, <, >

&, |, &, in

The following subsections describe each of these operators in detail.

Type Checking and Type Promotion

Many dyadic (two-operand) operators expect the types of their operands to be the same. Prior to actually performing such an operation, HLA evaluates the types of the operands and attempts to make them compatible. HLA uses a type algebra to determine if two (different) types are compatible; if they are not, HLA will report a type mismatch error during assembly. If the types are compatible, HLA will attempt to make them identical via type promotion. The type algebra describes how HLA promotes one type to another in order to make the two types compatible.

Usually, you can state a type algebra easily enough by providing "algebraic" type equations. For example, in high-level languages one could use a statement like "r = r + i" to suggest that the type of the resulting sum is real when the left operand is real and the right operand is integer (around the "+" operator). Unfortunately, HLA supports so many different data types and operators that any attempt to describe the type algebra in this fashion will produce so many equations that it would be difficult for the reader to absorb it all. Therefore, this document will rely on an informal English description of the type algebra to explain how HLA operates.

First, if two operands have the same basic type, but are different sizes, HLA promotes the smaller object to the same size as the larger object. Basic types include the following sets: {uns8, uns16, uns32, uns64, uns128}, {int8, int16, int32, int64, int128}, {byte, word, dword, qword, lword}, and {real32, real64, real80}15. So, if any two operands appear from one of these sets, then both operands are promoted to the larger of the two types.

If an unsigned and a signed operand appear around an operator, HLA produces a signed result. If the unsigned operand is smaller than the signed operand, HLA assigns both operands the signed type prior to the operation. If the unsigned and signed operands are the same size (or the unsigned operand is larger), HLA will first check the H.O. bit of the unsigned operand. If it is set, then HLA promotes the unsigned operand to the next larger signed type (e.g., uns16 becomes int32 ). If the resulting signed type is larger than the other operand’s type, it will be promoted as well. This scheme fails if you have an uns128 value whose H.O. bit is set. In that case, HLA promotes both operands to int128 and will produce incorrect results (because the uns128 value just went negative when it’s really positive). Therefore, you should attempt to limit unsigned values to 127 bits if you’re going to be mixing signed and unsigned operations in the same expression.

Any mixture of hexadecimal types (byte, word, dword, qword, or lword) and an unsigned type produces an unsigned type; the size of the resulting unsigned type will be the larger of the two types. Likewise, any mixture of hexadecimal types and signed integer types will produce a signed integer whose size is the larger of the two types. This "strengthening" of the type (hexadecimal types are "weaker" than signed or unsigned types) may seem counter-intuitive to a die-hard assembly programmer; however, making the result type hexadecimal rather than signed/unsigned can create problems if the result has the H.O. bit set since information about whether the result is signed or unsigned would be lost at that point.

Mixing unsigned values and a real32 value will produce a real32 result or an error. HLA produces an error if the unsigned value requires more than 24 bits to represent exactly (which is the largest unsigned value you may represent within the real32 format). Note that in addition to promoting the unsigned type to real32 , HLA will also convert the unsigned value to a real32 value. Promoting the type is not the same thing as converting the value; e.g., promoting uns8 to uns16 simply involves changing the type designation of the uns8 object. HLA doesn’t have to deal with the actual value because it keeps all values in an internal 128-bit format. However, the binary representation for unsigned and real32 values is completely different, so HLA must do the value conversion as well. Note that if you really want to convert a value that requires more than 24 bits of precision to a real32 object (with truncation), just convert the unsigned operand to real64 or real80 and then convert the larger operand to real32 using the real32(expr) compile-time function. Since unsigned values are, well, unsigned and real32 objects are signed, the conversion process always produces a non-negative value.

Mixing signed and real32 values in an expression produces a real32 result. Like unsigned operands, signed operands are limited to 24 bits of precision or HLA will report an error. Technically, you should get one more bit of precision from signed operands (since the real32 format maintains its sign apart from the mantissa), but HLA still limits you to 24 bits during this conversion. If the signed integer value is negative, so will be the real32 result.

If you mix hexadecimal and real32 types, HLA treats the hexadecimal type as an unsigned value of the same size. See the discussion of unsigned and real32 values earlier for the details.

If you mix an unsigned, signed, or hexadecimal type with a real64 type, the result is an error (if HLA cannot exactly represent the value in real64 format) or a real64 result. The conversion is very similar to the real32 conversion discussed above except you get 52 bits of integer precision rather than only 24 bits.

If you mix an unsigned, signed, or hexadecimal type with a real80 type, the result is an error (if HLA cannot exactly represent the value in real80 format) or a real80 result. The conversion is very similar to the real32 conversion discussed above except you get 64 bits of integer precision rather than only 24 bits. Note that conversion of integer objects 64-bits or less will always proceed without error; 128-bit values are the only ones that will get you into trouble.

If you mix a pair of different sized real values in the same expression, HLA will promote (and convert) the smaller real value to the same size as the larger real value.

The only non-numeric promotions that take place in an expression are between characters and strings. If a character and a string both appear in an expression, HLA will promote the character to a string of length one.

Type Coercion in HLA

HLA will report a type mismatch error if objects of incompatible types appear within an expression. Note that you may use the type-coercion compile-time functions to convert between types that HLA does not automatically support in an expression (see the discussion later in this document). You can also use the HLA type coercion operator to attach a specific type to a constant expression. The type coercion operator takes the following form:

 

(type typename constexpr)

 

The typename component must be a valid, declared type identifier (including any of the built-in types or appropriate user-defined types). The constexpr component can be any constant expression that is reasonably compatible with the specified type. "Reasonably compatible" means that the types are the same size or one of the primitive types. Examples:

(type int8 ‘a’)

(type real32 constExpression+2)

(type boolean int8Val)

One important thing to remember is that type coercion is a bitwise operation. No conversion is done when coercing one type to another using this type coercion operation.

HLA also achieves type coercion using several compile-time functions. See the chapter on The HLA Compile-Time Language for more details on those type coercion functions.

!expr

The expression must be either boolean or a number. For boolean values, ! computes the standard logical not operation. Numerically, HLA inverts only the L.O. bit of boolean values and clears the remaining bits of the boolean value. Therefore, the result is always zero or one when NOTting a boolean value (even if the boolean object errantly contained other set bits prior to the "!" operation). Remember, the "!" operator only looks at the L.O. bit; if the value was originally non-zero but the L.O. bit was clear16, then "!" produces true. This is not a zero/not-zero operation.

For numbers, ! computes the bitwise not operation on the bits of the number, that is, it inverts all the bits. The exact semantics of this operation depend upon the original data type of the value you’re inverting. Therefore, the result of applying the "!" operator to an integer number may not always be intuitive because HLA always maintains 128-bits of precision, regardless of the underlying data type. Therefore, a full explanation of this operator’s semantics must be given on a type-by-type basis.

 

uns8 : Bits 8..127 of an Uns8 object are always zero. Therefore, when you apply the "!" operator to an Uns8 value, the result can no longer be an Uns8 object since bits 8..127 will now contain ones. Zeroing out the H.O. bits is not wise, because you could be assigning the result of this expression to a larger data type and you may very well expect those bits to be set. Therefore, HLA converts the type of "!u8expr" to type byte (which does allow the H.O. bits to contain non-zero values). If you assign an object of type byte to a larger object (e.g., type word ), HLA will copy over the H.O. bits from the byte object to the larger object. Example:

val

u8 :uns8 := 1;

b8 := !u8; // produces $FFF..FFFE but registers as byte $FE.

w16 :word := b8; // produces $FF..FFFE but registers as word $FFFE.

Note: If you really want to chop the value off at eight bits, you can use the compile-time byte function to achieve this, e.g.,

val

u8 :uns8 := 1;

b8 := byte(!u8); // produces $FE.

w16 :word := b8; // produces $00FE.

uns16 : The semantics are similar to uns8 except, of course, applying "!" to an uns16 value produces a word value rather than a byte value. Again, the "!" operator will set bits 16..127 to one in the result. If you want to ensure that the result contains no set bits beyond bit #15, use the compile-time word function to strip the value down to 16 bits (just like the byte function in the example above).

uns32 : The semantics are similar to uns8 except, of course, applying "!" to an uns32 value produces a dword value rather than a byte value. Again, the "!" operator will set bits 32..127 to one in the result. If you want to ensure the result contains no set bits beyond bit #31 use the compile-time dword function to strip the value down to 32 bits (just like the byte function in the example above).

uns64 : The semantics are similar to uns8 except, of course, applying "!" to an uns64 value produces a qword value rather than a byte value. Again, the "!" operator will set bits 64..127 to one in the result. If you want to ensure the result contains no set bits beyond bit #63 use the compile-time qword function to strip the value down to 64 bits (just like the byte function in the example above).

uns128 : Applying the "!" operator to an uns128 object simply inverts all the bits. There are no funny semantics here. Resulting expression type is set to lword .

int8 : Same semantics as byte (see explanation below).

int16 : Same semantics as word (see explanation below).

int32 : Same semantics as dword (see explanation below).

int64 : Same semantics as qword (see explanation below).

int128 : Applying the "!" operator to an int128 object simply inverts all the bits. There are no funny semantics here. Resulting expression type is set to lword .

byte : Bits 8..127 of a byte ( int8 ) value must all be zeros or all ones. The "!" operator enforces this. If any of the H.O. bits are non-zero, the "!" operator sets them all to zero in the result; if all of the H.O. bits are zero, the "!" operator sets the H.O. bits to ones in the result. Of course, this operator inverts bits 0..7 in the original value and returns this inverted result. Note that the type of the new value is always byte (even if the original sub-expression was int8 ).

word : Bits 16..127 of a word ( int16 ) value must all be zeros or all ones. The "!" operator enforces this. If any of the H.O. bits are non-zero, the "!" operator sets them all to zero in the result; if all of the H.O. bits are zero, the "!" operator sets the H.O. bits to ones in the result. Of course, this operator inverts bits 0..15 in the original value and returns this inverted result. Note that the type of the new value is always word (even if the original sub-expression was int16 ).

dword : Bits 32..127 of a d word ( int32 ) value must all be zeros or all ones. The "!" operator enforces this. If any of the H.O. bits are non-zero, the "!" operator sets them all to zero in the result; if all of the H.O. bits are zero, the "!" operator sets the H.O. bits to ones in the result. Of course, this operator inverts bits 0..31 in the original value and returns this inverted result. Note that the type of the new value is always d word (even if the original sub-expression was int32 ).

qword : Bits 64..127 of a q word ( int64 ) value must all be zeros or all ones. The "!" operator enforces this. If any of the H.O. bits are non-zero, the "!" operator sets them all to zero in the result; if all of the H.O. bits are zero, the "!" operator sets the H.O. bits to ones in the result. Of course, this operator inverts bits 0..63 in the original value and returns this inverted result. Note that the type of the new value is always q word (even if the original sub-expression was int64 ).

lword : Applying the "!" operator to an lword object simply inverts all the bits. There are no funny semantics here..

No other types are legal with the "!" operator. HLA will report a type conflict error if you attempt to apply this operator to some other type.

If the operand is one of the integer types (signed, unsigned, hexadecimal), then HLA will set the type of the result to the smallest type within that class (signed, unsigned, or hexadecimal) that can hold the result (not including sign extension bits for negative numbers or zero extension bits for non-negative values).

- expr (unary negation operator)

The expression must either be a numeric value or a character set. For numeric values, "-" negates the value. For character sets, the "-" operator computes the complement of the character set (that is, it returns all the characters not found in the set).

Again, the exact semantics depend upon the type of the expression you’re negating. The following paragraphs explain exactly what this operator does to its expression. For all integer values (unsXX, intXX, byte, word, dword, qword, and lword), the negation operator always does a full 128-bit negation of the supplied operand. The difference between these different data types is how HLA sets the resulting type of the expressions (as explained in the paragraphs below).

 

uns8 : If the original value was in the range 128..255, then the resulting type is int16 , otherwise the resulting type is int8 . Because uns8 values are always positive, the negated result is always negative, hence the result type is always a signed integer type.

uns16 : If the original value was in the range 32678..65535, then the resulting type is int32 , otherwise the resulting type is int16 . Because uns16 values are always positive, the negated result is always negative; hence, the result type is always a signed integer type.

uns32 : If the original value was in the range $8000_0000..$FFFF_FFFF, then the resulting type is int64 , otherwise the resulting type is int32 . Because uns32 values are always positive, the negated result is always negative; hence, the result type is always a signed integer type.

uns64 : If the original value was in the range $8000_0000_0000_0000..$FFFF_FFFF_FFFF_FFFF, then the resulting type is int128 , otherwise the resulting type is int64 . Because uns64 values are always positive, the negated result is always negative; hence, the result type is always a signed integer type.

uns128 : The result type is always set to int128 . Note that there is no check for overflow. Effectively, HLA treats uns128 operands as though they were int128 operands with respect to negation. So large positive ( uns128 ) values become smaller unsigned values after the negation. If you need to mix and match 128-bit values in an expression, you should attempt to limit your unsigned values to 127 bits.

byte, int8,

word, int16,

dword, int32,

qword, int64,

lword,

int128: Negates the expression (full 128 bits) and assigns the original expression type to the result.

real32 : Negates the real32 value and returns a real32 result.

real64 : Negates the real64 value and returns a real64 result.

real80 : Negates the real80 value and returns a real80 result.

cset : Computes the set complement (returns cset type). The set complement is all the items that were not previously in the set. Since HLA uses a bitmap representation for character sets, the complement of a character set is the same thing as inverting all the bits in the powerset.

If the operand is one of the integer types (signed, unsigned, hexadecimal), then HLA will set the type of the result to the smallest type within that class (signed, unsigned, or hexadecimal) that can hold the result (not including sign extension bits for negative numbers or zero extension bits for non-negative values).

expr1 * expr2

For numeric operands, the "*" operator produces their product. For character set operands, the "*"operator produces the intersection of the two sets. The exact result depends upon the types of the two operands to the "*" operator. To begin with, HLA attempts to make the types of the two operands identical if they are not already identical. HLA achieves this via type promotion (see the discussion earlier).

If the operands are unsigned or hexadecimal operands, HLA will compute their unsigned product. If the operands are signed, HLA computes their signed product. If the operands are real, HLA computes their real product. If the operands are integer (signed or unsigned) and less than (or equal to) 64 bits, HLA computes their exact result. If the operands are greater than 64 bits and their product would require more than 128 bits, HLA quietly overflows without error. Note that HLA always performs a 128-bit multiplication, regardless of the operands’ sizes; however, objects that require 64 bits or less of precision will always produce a product that is 128 bits or less. HLA automatically extends the size of the result to the next greater size if the product will not fit into an integer that is the same size as the operands. HLA will actually choose the smallest possible size for the product (e.g., if the result only requires 16 bits of precision, the resulting type will be uns16, int16 , or word ). The resulting type is always unsigned if the operands were unsigned, signed if the operands were signed, and hexadecimal if the operands were hexadecimal.

If the operands are real operands, HLA computes their product and always produces a real80 result. If you want to produce a smaller result via the ’*’ operator, use the real32 or real64 compile-time function to produce the smaller result, e.g., " real32( r32const * r32const2 ) ". Note that all real arithmetic inside HLA is always performed using the FPU, hence the results are always real80 . Other than trying to simulate the actual products a running program would produce, there is no real reason to coerce the product to a smaller value.

If the operands are character set operands, the ’*’ operator computes the intersection of the two sets. Since HLA uses a bitmap representation for character sets, this operator does a bitwise logical AND of the two 16-byte operands (this operation is roughly equivalent to applying the "&" operator to two lword operands).

If the operand is one of the integer types (signed, unsigned, hexadecimal), then HLA will set the type of the result to the smallest type within that class (signed, unsigned, or hexadecimal) that can hold the result (not including sign extension bits for negative numbers or zero extension bits for non-negative values).

expr1 div expr2

The two expressions must be integer (signed, unsigned, or hexadecimal) numbers. Supplying any other data type as an operand will produce an error. The div operator divides the first expression by the second and produces the truncated quotient result.

If the operands are unsigned, HLA will do a full 128/128-bit division and the resulting type will be unsigned (HLA sets the type to the smallest unsigned type that will completely hold the result). If the operands are signed, HLA will do a full 128/128 bit signed division and the resulting type will be the smallest intXX type that can hold the result. If the operands are hexadecimal values, HLA will do a full 128/128 bit unsigned division and set the resulting type to the smallest hex type that can hold the result.

Note that the div operator does not allow real operands. Use the "/" operator for real division.

HLA will set the type of the result to the smallest type within its class (signed, unsigned, or hexadecimal) that can hold the result (not including sign extension bits for negative numbers or zero extension bits for non-negative values).

expr1 mod expr2

The two expressions must be integer (signed, unsigned, or hexadecimal) numbers. The mod operator divides the first expression by the second and produces their remainder (this value is always positive).

If the operands are unsigned, HLA will do a full 128/128-bit division and return the remainder. The resulting type will be unsigned (HLA sets the type to the smallest unsigned type that will completely hold the result).

If the operands are signed, HLA will do a full 128/128 bit signed division and return the remainder. The resulting type will be the smallest intXX type that can hold the result.

If the operands are hexadecimal values, HLA will do a full 128/128 bit unsigned division and set the resulting type to the smallest hex type that can hold the result.

Note that the mod operator does not allow real operands. You’ll have to define real modulus and write the expression yourself if you need the remainder from a real division.

HLA will set the type of the result to the smallest type within its class (signed, unsigned, or hexadecimal) that can hold the result (not including sign extension bits for negative numbers or zero extension bits for non-negative values).

expr1 / expr2

The two expressions must be numeric. The ’/’ operator divides the first expression by the second and produces their (real80) quotient result.

If the operands are integers (unsigned, signed, or hexadecimal) or the operands are real32 or real64 , HLA first converts them to real80 before doing the division operation. The expression result is always real80 .

expr1 << expr2

The two expressions must be integer (signed, unsigned, or hexadecimal) numbers. The second operand must be a small (32-bit or less) non-negative value in the range 0..128. The << operator shifts the first expression to the left the number of bits specified by the second expression. Note that the result may require more bits to hold than the original type of the left operand. Any bits shifted out of bit position 127 are lost.

HLA will set the type of the result to the smallest type within the left operand’s class (signed, unsigned, or hexadecimal) that can hold the result (not including sign extension bits for negative numbers or zero extension bits for non-negative values). Note that the ’<<’ operator can yield a smaller type (specifically, an eight bit type) if it shifts all the bits off the H.O. end of the number; generally, though, this operation produces larger result types than the left operand.

expr1 >> expr2

The two expressions must be integer (signed, unsigned, or hexadecimal) numbers. The second operand must be a small (32-bit or less) non-negative value in the range 0..128. The >> operator shifts the first expression to the right the number of bits specified by the second expression. Any bits shifted out of the L.O. bit are lost. Note that this shift is a logical shift right, not an arithmetic shift right (this is true even if the left operand is an intXX value). Therefore, this operation always shifts a zero into bit position 127.

Shift rights may produce a smaller type that the value of the left operand. HLA will always set the type of the result value to the minimum type size that has the same base class as the left operand.

expr1 + expr2

If the two expressions are numeric, the "+" operator produces their sum.

If the two expressions are strings or characters, the "+" operator produces a new string by concatenating the right expression to the end of the left expression.

If the two operands are character sets, the "+" operator produces their union.

If the operands are integer values (signed, unsigned, or hexadecimal), then HLA adds them together. Any overflow out of bit #127 (unsigned or hexadecimal) or bit #126 (signed) is quietly lost. HLA sets the type of the result to the smallest type size that will hold the sum; the type class (signed, unsigned, hexadecimal) will be the same as the operands. Note that it is possible for the type size to grow or shrink depending on the values of the operands (e.g., adding a positive and negative number could reduce the type size, adding two positive or two negative numbers may expand the result type’s size).

When adding two real values (or a real and an integer value), HLA always produces a real80 result.

Since HLA uses a bitmap to represent character sets, taking the union of two character sets is the same as doing a bitwise logical OR of all 16 bytes in the character set.

expr1 - expr2

If the two expressions are numeric, the "-" operator produces their difference.

If the two expressions are character sets, the "-" operator produces their set difference (that is, all the characters in expr1 that are not also in expr2).

If the operands are integer values (signed, unsigned, or hexadecimal), then HLA subtracts the right operand from the left operand. Any overflow out of bit #127 (unsigned or hexadecimal) or bit #126 (signed) is quietly lost. HLA sets the type of the result to the smallest type size that will hold their difference; the type class (signed, unsigned, hexadecimal) will be the same as the operands. Note that it is possible for the type size to grow or shrink depending on the values of the operands (e.g., subtracting two negative or non-negative numbers could reduce the type size, subtracting a negative value from a non-negative value may expand the result type’s size).

When subtracting two real values (or a real and an integer value), HLA always produces a real80 result.

Since HLA uses a bitmap to represent character sets, taking the set of two character sets is the same as doing a bitwise logical AND of the left operand with the inverse of the right operand.

Comparisons (=, ==, <>, !=, <, <=, >, and >=)

expr1 = expr2

expr1 == expr2

expr1 <> expr2

expr1 != expr2

expr1 < expr2

expr1 <= expr2

expr1 > expr2

expr1 >= expr2

Note: "!=" and "<>" operators are identical. "=" and "==" operators are also identical.

The two expressions must be compatible (described earlier). These operators compare the two operands and return true or false depending upon the result of the comparison.

You may use the "=" and "<>" operators to compare two pointer constants (e.g., "&abc" or "&ptrVar[2]"). The other operators do not allow pointer constant operands.

All the above operators allow you to compare boolean values, enumerated values (types must match), integer (signed, unsigned, hexadecimal) values, character values, string values, real values, and character set values.

When comparing boolean values, note that false < true .

One character set is less than another is if it is a proper subset of the other. A character set is less than or equal to another set if it is a subset of that second set. Likewise, one character set is greater than, or greater than or equal to, another set if it is a proper superset, or a superset, respectively.

As with any programming language, you should take care when comparing two real values (especially for equality or inequality) as minor precision drifts can cause the comparison to fail.

expr1 & expr2

Note: "&&" and "&" mean different things to HLA. See the section on high-level language control structures for details on the "&&" operator.

The operands must both be boolean or they must both be numbers. With boolean operands the AND operator produces the logical and of the two operands (boolean result). With numeric operands, the AND operator produces the bitwise logical AND of the operands.

If the operand is one of the integer types (signed, unsigned, hexadecimal), then HLA will set the type of the result to the smallest type within that class (signed, unsigned, or hexadecimal) that can hold the result.

expr1 in expr2

The first expression must be a character value. The second expression must be a character set. The in operator returns true if the character is a member of the specified character set; it returns false otherwise.

expr1 | expr2

Note: "||" and "|" mean different things to HLA. See the section on high-level language control structures for details on the "||" operator.

The operands must both be boolean or they must both be numbers. With boolean operands the OR operator produces the logical OR of the two operands (boolean result). With numeric operands, the OR operator produces the bitwise or of the operands.

If the operand is one of the integer types (signed, unsigned, hexadecimal), then HLA will set the type of the result to the smallest type within that class (signed, unsigned, or hexadecimal) that can hold the result.

expr1 ^ expr2

The operands must both be boolean or they must both be numbers. With boolean operands the ^ operator produces the logical exclusive-or of the two operands (boolean result). With number operands, the ^ operator produces the bitwise exclusive-or of the operands.

If the operand is one of the integer types (signed, unsigned, hexadecimal), then HLA will set the type of the result to the smallest type within that class (signed, unsigned, or hexadecimal) that can hold the result.

( expr )

You may override the precedence of any operator(s) using parentheses in HLA constant expressions.

[ comma_separated_list_of_expressions ]

This produces an array expression. The type of the expression is an array type whose base element is the type of one of the expressions in the list. If there are two or more constant types in the array expression, HLA promotes the type of the array expression following the rules for mixed-mode arithmetic (see the rules earlier in this document).

record_type_name : [ comma separated list of field expressions ]

This produces a record expression. The expressions appearing within the brackets must match the respective fields of the specified record type. See the discussion earlier in this document.

identifier

An identifier is a legal component of a constant expression if the identifier’s classification is const or val (that is, the identifier was declared in a constant or value section of the program). The expression evaluator substitutes the current declared value and type of the symbol within the expression. Constant expressions allow the following types:

boolean, enumerated types, uns8, uns16, uns32, uns64, uns128 byte, word, dword, qword, lword, int8, int16, int32, int64, int128, char, real32, real64, real80, string, and cset.

You may also specify arrays whose element base type is one of the above types (or a record or union subject to the following restriction). Likewise, you can specify record or union constants if all of their respective fields are one of the above primitive types or a value array, record, or union constant.

HLA allows array, record, and union constants. If you specify the name of an array, for example, HLA works with all the values of that array. Likewise, HLA can copy all the values of a record or union with a single statement.

HLA allows literal Unicode character and string constants (e.g., u’a’ and u"unicode") or identifiers that are of wchar or wstring type in an expression, but no other terms are allowed in such an expression (as this is being written).

identifier1.identifier2 {...}

Selects a field from a record or union constant. Identifier1 must be a record or union object defined in a const or val section. Identifier2 (and any following dot-identifiers) must be a field of the record or union. HLA replaces this object with the value of the specified field.

Examples:

 

recval.fieldval

recval.subrecval.fieldval

Don’t forget that with union constant, you may only access the last field into which you’ve actually stored data (see the section on union constants for more details).

identifier [ index_list ]

Identifier must be an array constant defined in either a const or val section. Index_list is a list of constant expressions separated by commas. The index list selects a specified element of the "identifier" array. HLA reports an error if you supply more indices than the array has dimensions. HLA returns an array slice if you specify fewer indices than the array has dimensions (for example, if an array is declared as "a:uns8[4,4]" and you specify "a[2]" in a constant expression, HLA returns the third row of the array (a[2,0]..a[2,3]) as the value of this term).

Examples:

 

arrayval[0]

aval[1,4,0]

HLA Program Structure and Organization

HLA Program Structure

HLA supports two types of compilations: programs and units. A program is an HLA source file that includes the code (the "main program") that executes immediately after the operating system loads the program into memory. A unit is a module that contains procedures, methods, iterators, and data that is to be linked with other modules. Note that units may be linked with other HLA modules (including an HLA main program) or with code written in other languages (including high-level languages or other x86 assembly languages). This chapter will discuss the generic form of an HLA program; see the chapter on HLA Units and External Compilation for a detailed description of HLA units.

An executable file must have exactly one main program (written either in HLA or some other language). Therefore, most applications written entirely in HLA will have exactly one program module and zero or more units (it is possible to fake a program module using units; for more information see the on-line documents "Taking Control Over Code Emission" and "Calling HLA Code from Non-HLA Programs with Exception Handling" on Webster (http://webster.cs.ucr.edu). Therefore, the best place to begin discussion HLA program structure is by defining the HLA program. Here's the minimalist HLA program:

program pgmId;

begin pgmID;

end pgmID;

In this example, pgmID is a user-defined identifier that names the program. Note that this name is local to the program (that is, it is not visible outside the source file and neither the source file's name nor the executable's file name need be the same as this name (though it's not a bad idea to make them the same). Note that the exact same identifier following the program reserved word must follow the begin and end reserved words.

The minimalist HLA program, above, doesn't do much; if you compile and execute this program it will immediately return control to the operating system. However, this short program actually does quite a bit for an empty assembly language program. When you create an HLA program, you're asking HLA to automatically generate some template code to do certain operations such as initializing the exception-handling system, possibly setting up command-line parameters for use by the program, and emitting code to automatically return control to the operating system when the program completes execution (by running into the end pgmID clause). As this code is generally needed for every HLA assembly language program, it's nice that the HLA compiler will automatically emit this template code for you. If you happen to be a die-hard assembly programmer and you don't want the compiler emitting any instructions you haven't explicitly written, fear not, HLA doesn't force you to accept the code it's written; for more details, see the "Taking Control Over Code Emission" article on Webster that was mentioned earlier.

A non-minimalist HLA program takes the following generic form:

program pgmId;

<< declarations >>

begin pgmID;

<< main program instructions>>

end pgmID;

The <<declarations>> section is where you will put the declarations/definitions for constants, data types, variables, procedures, methods, iterators, tables, and other data. The << main program instructions >> section is where you will put machine instructions and HLA HLL-like statements.

An HLA unit is even simpler than an HLA program. It takes the following form:

unit unitId;

<< declarations >>

end unitID;

In this example, unitID is a user-defined identifier that names the unit. Note that this name is local to the unit (that is, it is not visible outside the source file and the source file's name need be the same as this name. Note that the exact same identifier following the unit reserved word must follow the end reserved word. Unlike programs, units do not have a begin clause following by a sequence of instructions; this is because units don't provide the main program code for the application. Again, for more details about units, see the chapter on HLA Units.

The HLA Declaration Section

The declaration section in an HLA program or unit is relatively complex, supporting the definition and declaration of most of the components in the HLA program or unit. An HLA declaration section generally contains one or more of the following items:

These sections may appear in any order in a program or unit declarations section and multiple instances of each of these sections may appear in the declarations. The following subsections describe each of these declaration sections in detail.

The HLA LABEL Declaration Section

The HLA label section is a very special-purpose (and rarely used) declaration section in which you declare forward-referenced and external statement labels. The syntax for the label section is either of the following:

label

<< label declarations >>

or

label

<< label declarations >>

endlabel;

The endlabel clause is optional. If it is present it explicitly marks the end of the forward label declaration section; if it is absent, then the next declaration section or the begin keyword will implicitly end the forward label declaration section.

Each label declaration takes one of the three following forms:

userLabel_1;

userLabel_2; external;

userLabel_3; external( "externalLabelName" );

In these examples, userLabel_x (x=1, 2, or 3) is a user-defined identifier.

The first example above is a forward label declaration. This tells HLA that you're promising to declare the statement label within the scope of the label section (HLA will generate an error if you fail to declare the statement label within the scope of the label statement).

The scope of a label statement is the body of instructions associated with the main program, procedure, method, or iterator that immediately contains the label declaration section. For example, if the label statement appears in the declaration section of an HLA program, the corresponding statement label must be defined in the body of that program:

program labelDemo;

label

someLabel;

<< other declarations >>

begin labelDemo;

<< main program instructions, part 1 >>

someLabel: // someLabel must be defined in this code.

<< main program instructions, part 2 >>

end labelDemo;

Note that HLA automatically handles forward-referenced labels within the (machine instructions) body of a program, procedure, method, or iterator, without an explicit label declaration. The following is legal even though you do not have a forward declaration of someLabel:

program labelDemo;

.

.

.

begin labelDemo;

.

.

.

lea( eax, &someLabel );

.

.

.

jmp someLabel;

.

.

.

someLabel: // someLabel's declaration appears after its use.

.

.

.

end labelDemo;

The above is legal because the procedure references someLabel in the same scope where it is declared. Now consider the following example:

program labelDemo;

.

.

.

procedure ReferencesSomeLabel;

.

.

.

begin ReferencesSomeLabel;

.

.

.

lea( eax, &someLabel ); // Illegal! someLabel is not defined in this procedure.

.

.

.

end ReferencesSomeLabel;

begin labelDemo;

.

.

.

someLabel: // someLabel's declaration appears outside the scope of its use.

.

.

.

end labelDemo;

HLA will generate an error in this example because forward references to statement labels must be resolved within the scope of the procedure (or program) containing the forward reference. When HLA encounters the "end ReferencesSomeLabel;" clause in the procedure above, it will report that you haven't defined someLabel in that procedure. The solution to this problem is to use the label statement to create a forward symbol definition so that someLabel is defined (albeit at a different lex level) when HLA encounters the lea statement in the previous example. The following code demonstrates how to do this:

program labelDemo;

label

someLabel;

.

.

.

procedure ReferencesSomeLabel;

.

.

.

begin ReferencesSomeLabel;

.

.

.

lea( eax, &someLabel ); // This is legal because of the label statement.

.

.

.

end ReferencesSomeLabel;

begin labelDemo;

.

.

.

someLabel: // someLabel had a forward declaration.

.

.

.

end labelDemo;

You can also create external label definitions by attaching the external option to a label definition. External label definitions take one of two forms:

label

someLabel; external;

someExtLabel; external( "externalName" );

The first form assumes that someLabel is defined (and the name is made public) in some other source/object module using the name someLabel. The second form assumes that "externalName" is defined in some other source/object module and uses the name someExtLabel to refer to that symbol.

To create a public label that you can reference in another source module, you put an external label definition in the same source file as the actual symbol declaration, e.g.,

program labelDemo;

label

someLabel; external;

.

.

.

begin labelDemo;

.

.

.

someLabel: // someLabel is a public symbol.

.

.

.

end labelDemo;

The label statement rarely appears in most HLA programs. It is very unusual to reference a symbol that is declared outside the scope of that usage. External symbols are usually procedures, methods, or iterators, and a program will typically use an external procedure, iterator, or method declaration rather than a label statement to declare such symbols. Nevertheless, label declarations are necessary on occasion, so you should keep the forward label declaration statement in mind.

Note that label declarations will not make a local symbol in some scope (that is, within some procedure) visible to code outside that scope. The following will generate an error:

program labelDemo;

label

someLabel;

.

.

.

procedure declaresSomeLabel;

.

.

.

begin declaresSomeLabel;

.

.

.

someLabel: // This is local to this procedure.

.

.

.

end declaresSomeLabel;

begin labelDemo;

.

.

.

// This does not reference someLabel in declaresSomeLabel!

lea( eax, &someLabel );

.

.

.

end labelDemo;

The scope of the symbol someLabel defined in declaresSomeLabel is limited to the declaresSomeLabel procedure. In order to make someLabel visible outside of declaresSomeLabel, you must make that symbol global. This is done by following the label declaration with two colons instead of one colon:

program labelDemo;

.

.

.

procedure declaresSomeLabel;

.

.

.

begin declaresSomeLabel;

.

.

.

someLabel:: // This is a global symbol.

.

.

.

end declaresSomeLabel;

begin labelDemo;

.

.

.

// This is legal

lea( eax, &someLabel );

.

.

.

end labelDemo;

Note that global symbols are not automatically public. If you need a symbol to be both global to a procedure and public (visible outside the source file), you must also define that global symbol as external in a label statement:

program labelDemo;

label

someLabel; external;

.

.

.

procedure declaresSomeLabel;

.

.

.

begin declaresSomeLabel;

.

.

.

someLabel:: // This is a global and public symbol.

.

.

.

end declaresSomeLabel;

begin labelDemo;

.

.

.

// This is legal

lea( eax, &someLabel );

.

.

.

end labelDemo;

Note that global label declarations only make the symbol global at the previous lex level, not across the whole program. The following will not work properly because label1 is only visible in the q and p procedures, not in the main program.

program t;

label

label1;

procedure p;

procedure q;

begin q;

label1::

end q;

begin p;

end p;

begin t;

lea( eax, label1 );

end t;

The solution to this problem is to make the symbol public by declaring it external in both the q procedure and in the main program:

program t;

label

label1; external;

procedure p;

procedure q;

label

label1; external;

begin q;

label1:

end q;

begin p;

end p;

begin t;

lea( eax, label1 );

end t;

Of course, referencing a label in a nested procedure like this is highly unusual and is probably an indication of a poorly designed program. If you find yourself writing this kind of code, you might want to reconsider your program's architecture.

The HLA CONST Declaration Section

The HLA const section is where you declare symbolic (manifest) constants in an HLA program or unit. The syntax for the const section is either of the following:

const

<< constant declarations >>

or

const

<< constant declarations >>

endconst;

The endconst clause is optional at the end of the constant declarations in a declaration section; some programmers prefer to explicitly end a constant declaration section with endconst, others prefer to implicitly end the constant declarations (by starting another declaration section or with the begin keyword). The choice is yours, the language doesn't prefer either method nor does good programming style particularly specify one syntax over the other.

Each constant declaration takes one of the following forms:

userDefinedID := <<constant expression>>;

or

userDefinedID : typeID := <<constant expression>>;

Here are some examples:

const

hasEdge_c := false;

elementCnt_c := 25;

weight_c := 32.5;

debugMode_c :boolean := true;

maxCnt_c :uns32 := 15;

oneHalf_c :real32 := 0.5;

The "_c" suffix is an HLA programming convention that tells the reader the identifier is a constant identifier. Although it's probably good programming style for you to follow thi