xbasic64 Compiler Language Guide
xbasic64 Compiler Language Guide
1 Overview 11
1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.1.1 What is xbasic64? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.1.2 Target Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.1.3 Educational Value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.1.4 What You’ll Learn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2 Prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2.1 Required Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
[Link] 1. Assembly Language Basics . . . . . . . . . . . . . . . . . . . . . . 13
[Link] 2. Rust Programming Basics . . . . . . . . . . . . . . . . . . . . . . 13
[Link] 3. Compiler Theory (Helpful but Not Required) . . . . . . . . . . . 13
1.2.2 Development Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3 Architecture Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3.1 The Three-Phase Pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
[Link] Phase 1: Lexical Analysis (Lexer) . . . . . . . . . . . . . . . . . . . 14
[Link] Phase 2: Syntax Analysis (Parser) . . . . . . . . . . . . . . . . . . . 15
[Link] Phase 3: Code Generation . . . . . . . . . . . . . . . . . . . . . . . 15
1.3.2 Why This Differs from Traditional Compilers . . . . . . . . . . . . . . . . . . 16
1.3.3 Runtime Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.4 Relationship to Compiler Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.4.1 Dragon Book Chapter Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.4.2 Key Theoretical Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.4.3 What’s Simplified . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.5 Documentation Structure and Reading Guide . . . . . . . . . . . . . . . . . . . . . . 18
1.5.1 Document Organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.5.2 Recommended Reading Order . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
[Link] Path 1: Complete Beginner . . . . . . . . . . . . . . . . . . . . . . . 18
[Link] Path 2: Theory Background . . . . . . . . . . . . . . . . . . . . . . 19
[Link] Path 3: Specific Interest . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.5.3 Document Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.5.4 Visual Aids . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.5.5 Code Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.5.6 Cross-References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.5.7 Navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.6 Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.6.1 1. Build the Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1
1.6.2 2. Try an Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.6.3 3. Explore the Source Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.6.4 4. Read the Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.6.5 5. Try the Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.8 Navigation: ← Back to README | Next: Compiler Pipeline → . . . . . . . . . . . 21
2 Compiler Pipeline 22
2.1 Table of Contents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.2 Three-Phase Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.2.1 Phase 1: Lexical Analysis (Lexer) . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.2.2 Phase 2: Syntax Analysis (Parser) . . . . . . . . . . . . . . . . . . . . . . . . 23
2.2.3 Phase 3: Code Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.3 Data Flow Through the Pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.3.1 Example: Simple BASIC Program . . . . . . . . . . . . . . . . . . . . . . . . 23
2.3.2 After Lexical Analysis (Tokens) . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.3.3 After Syntax Analysis (AST) . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.3.4 After Code Generation (Assembly) . . . . . . . . . . . . . . . . . . . . . . . . 24
2.4 Design Decisions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.4.1 Why No Intermediate Representation (IR)? . . . . . . . . . . . . . . . . . . . 25
2.4.2 Trade-offs of Direct Translation . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.4.3 Single-Pass Code Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.5 Comparison with Traditional Compilers . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.5.1 Traditional Multi-Phase Compiler . . . . . . . . . . . . . . . . . . . . . . . . 26
2.5.2 xbasic64 Simplified Pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.5.3 What’s Simplified for Education . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.6 Pipeline Flow Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.8 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.9 Navigation: ← Overview | Home | Next: Lexical Analysis → . . . . . . . . . . . . . 30
2
[Link] Program Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
[Link] Data Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
[Link] Logical Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
[Link] Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.3.4 4. Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
[Link] Arithmetic Operators . . . . . . . . . . . . . . . . . . . . . . . . . . 34
[Link] Comparison Operators . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.3.5 5. Punctuation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.3.6 6. Special Tokens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.4 BASIC-Specific Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.4.1 Case-Insensitive Keywords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.4.2 Type Suffixes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.4.3 Line Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.4.4 Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.5 Implementation Walkthrough: src/[Link] . . . . . . . . . . . . . . . . . . . . . . . . 38
3.5.1 The Lexer Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.5.2 Core Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
[Link] advance() and peek() . . . . . . . . . . . . . . . . . . . . . . . . . . 38
[Link] skip_whitespace() . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
[Link] read_string() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
[Link] read_number() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
[Link] read_hex() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
[Link] read_identifier() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
[Link] keyword_or_ident() . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.5.3 The Main Tokenization Loop: next_token() . . . . . . . . . . . . . . . . . . . 41
3.5.4 The tokenize() Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.6 Tokenization Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.6.1 Example 1: Simple Assignment . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.6.2 Example 2: FOR Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.6.3 Example 3: String with Escaped Quotes . . . . . . . . . . . . . . . . . . . . . 44
3.6.4 Example 4: IF Statement with Comparison . . . . . . . . . . . . . . . . . . . 44
3.6.5 Example 5: Line Numbers and Comments . . . . . . . . . . . . . . . . . . . . 45
3.6.6 Example 6: Type Suffixes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.6.7 Example 7: Hexadecimal and Scientific Notation . . . . . . . . . . . . . . . . 45
3.6.8 Example 8: Multiple Statements on One Line . . . . . . . . . . . . . . . . . . 46
3.6.9 Example 9: Array Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.6.10 Example 10: Function Call with Operators . . . . . . . . . . . . . . . . . . . 47
3.7 Common Pitfalls and Edge Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.7.1 1. Whitespace Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.7.2 2. Case Sensitivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.7.3 3. Type Suffix Ambiguity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.7.4 4. Operator Lookahead . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.7.5 5. Hexadecimal vs. Long Suffix . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.7.6 6. Comment Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.9 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.10 Previous: Compiler Pipeline - Overview of the compilation process . . . . . . . . . 49
3
4 Syntax Analysis 50
4.1 Table of Contents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.2 Parsing Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.2.1 What is Syntax Analysis? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.2.2 Context-Free Grammars . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.2.3 Parse Trees vs. Abstract Syntax Trees . . . . . . . . . . . . . . . . . . . . . . 51
4.2.4 Parsing Strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.2.5 Recursive Descent Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.3 Formal Grammar Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.3.1 Program Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.3.2 Statement Productions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.3.3 Expression Productions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.3.4 Operator Precedence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.3.5 Grammar Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.4 Parsing Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.4.1 Recursive Descent for Statements . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.4.2 Precedence Climbing for Expressions . . . . . . . . . . . . . . . . . . . . . . . 57
4.4.3 Handling Right-Associativity . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.4.4 Error Recovery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.5 Abstract Syntax Tree (AST) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.5.1 AST Node Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
[Link] Statement Nodes (Stmt) . . . . . . . . . . . . . . . . . . . . . . . . 58
[Link] Expression Nodes (Expr) . . . . . . . . . . . . . . . . . . . . . . . . 61
[Link] Supporting Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.5.2 AST Design Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.5.3 Example AST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.6 Parser Implementation Walkthrough . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.6.1 Parser Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.6.2 Core Parsing Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.6.3 Statement Parsing Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.6.4 Expression Parsing with Precedence Climbing . . . . . . . . . . . . . . . . . . 67
4.6.5 Parsing Complex Constructs . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.6.6 Parse Tree to AST Transformation . . . . . . . . . . . . . . . . . . . . . . . . 70
4.6.7 Example: Parsing a Complete Program . . . . . . . . . . . . . . . . . . . . . 70
4.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.8 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.9 Navigation: ← Lexical Analysis | Home | Next: Semantic Analysis → . . . . . . . . 72
5 Semantic Analysis 73
5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.2 Table of Contents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.3 What is Semantic Analysis? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.3.1 Purpose of Semantic Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.3.2 Semantic Analysis in xbasic64 . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.3.3 What Semantic Analysis Checks . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.4 The Type System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.4.1 BASIC’s Five Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.4.2 Type Suffixes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
4
5.4.3 Type Representation in Memory . . . . . . . . . . . . . . . . . . . . . . . . . 75
[Link] Integer Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
[Link] Floating-Point Types . . . . . . . . . . . . . . . . . . . . . . . . . . 75
[Link] String Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.4.4 Type Determination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.5 Type Coercion Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.5.1 The Type Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.5.2 Type Promotion Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.5.3 Special Operator Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
[Link] Division Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
[Link] Exponentiation (^) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
[Link] Comparison Operators . . . . . . . . . . . . . . . . . . . . . . . . . 78
[Link] Logical/Bitwise Operators . . . . . . . . . . . . . . . . . . . . . . . 78
5.5.4 Coercion Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.5.5 Coercion in Binary Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.5.6 Implicit vs. Explicit Conversion . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.6 Symbol Table Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.6.1 What is a Symbol Table? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.6.2 Symbol Table Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.6.3 Variable Storage Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.6.4 Implicit Variable Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.6.5 Scope Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
[Link] Global Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
[Link] Local Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
[Link] Procedure Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.6.6 Symbol Resolution Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.6.7 Array Storage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.6.8 Variable Lifetime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.6.9 Symbol Table Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.6.10 Case Insensitivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
5.7 Semantic Analysis Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
5.7.1 Example 1: Type Coercion in Mixed Expressions . . . . . . . . . . . . . . . . 86
5.7.2 Example 2: Integer Division and Modulo . . . . . . . . . . . . . . . . . . . . 88
5.7.3 Example 3: Boolean Expressions . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.7.4 Example 4: Symbol Table States . . . . . . . . . . . . . . . . . . . . . . . . . 90
5.7.5 Example 5: Local vs. Global Scope . . . . . . . . . . . . . . . . . . . . . . . . 91
5.7.6 Example 6: Type Coercion in Assignment . . . . . . . . . . . . . . . . . . . . 92
5.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.8.1 Type System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.8.2 Type Coercion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.8.3 Symbol Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.8.4 Integration with Code Generation . . . . . . . . . . . . . . . . . . . . . . . . 94
5.8.5 Design Philosophy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.9 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.10 Navigation: ← Syntax Analysis | Home | Next: Code Generation → . . . . . . . . 94
5
6.1.1 What is Code Generation? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
6.1.2 The Direct Translation Strategy . . . . . . . . . . . . . . . . . . . . . . . . . 95
6.1.3 The Code Generation Process . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
6.1.4 Output Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
6.2 Target Architecture: x86-64 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
6.2.1 Why x86-64? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
6.2.2 Key Architecture Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
[Link] Registers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
[Link] SSE Registers for Floating-Point . . . . . . . . . . . . . . . . . . . . 97
6.2.3 System V AMD64 ABI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
[Link] Function Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
[Link] Return Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
[Link] Register Preservation . . . . . . . . . . . . . . . . . . . . . . . . . . 98
[Link] Stack Alignment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
6.3 Register Allocation Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
6.3.1 Primary Result Registers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
6.3.2 Binary Operation Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
6.3.3 Why This Strategy? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
6.4 Stack Frame Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.4.1 Stack Frame Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.4.2 Function Prologue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.4.3 Function Epilogue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.4.4 Variable Storage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.4.5 Stack Alignment Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . 101
6.4.6 Accessing Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
6.5 Implementation Walkthrough: src/[Link] . . . . . . . . . . . . . . . . . . . . . . 101
6.5.1 The CodeGen Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
6.5.2 Type System and Coercion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
6.5.3 Expression Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
6.5.4 Binary Expression Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
6.5.5 Statement Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
[Link] Variable Assignment . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
[Link] IF Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
[Link] FOR Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
6.5.6 Built-in Function Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
6.5.7 Array Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
6.6 Side-by-Side Examples: BASIC to Assembly . . . . . . . . . . . . . . . . . . . . . . . 111
6.6.1 Example 1: Simple Variable Assignment . . . . . . . . . . . . . . . . . . . . . 111
6.6.2 Example 2: Arithmetic Expression . . . . . . . . . . . . . . . . . . . . . . . . 111
6.6.3 Example 3: Type Coercion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.6.4 Example 4: IF Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.6.5 Example 5: FOR Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
6.6.6 Example 6: Function Call . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
6.6.7 Example 7: String Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.6.8 Example 8: Array Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
6.7 Common Patterns and Idioms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.7.1 Pattern 1: Zero a Register . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.7.2 Pattern 2: Boolean to Integer . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6
6.7.3 Pattern 3: Load Floating-Point Constant . . . . . . . . . . . . . . . . . . . . 119
6.7.4 Pattern 4: Sign Extension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.7.5 Pattern 5: Function Prologue/Epilogue . . . . . . . . . . . . . . . . . . . . . 119
6.8 Optimization Opportunities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.8.1 1. Register Allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.8.2 2. Constant Folding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.8.3 3. Common Subexpression Elimination . . . . . . . . . . . . . . . . . . . . . . 120
6.8.4 4. Dead Code Elimination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.8.5 5. Strength Reduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.8.6 6. Loop Invariant Code Motion . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.10 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6.11 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6.12 Previous: ← Semantic Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
7
7.6.3 Inline Math Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
7.6.4 Math Function Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
7.6.5 Why Inline vs. Runtime? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
7.7 Runtime Implementation Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
7.7.1 Source File Organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
[Link] src/runtime/data_defs.s - Data Section Definitions . . . . . . . . 148
[Link] src/runtime/print.s - Print Functions . . . . . . . . . . . . . . . 148
[Link] src/runtime/input.s - Input Functions . . . . . . . . . . . . . . . 148
[Link] src/runtime/string.s - String Functions . . . . . . . . . . . . . . 149
[Link] src/runtime/math.s - Math and Utility Functions . . . . . . . . . 149
[Link] src/runtime/data.s - DATA/READ/RESTORE Support . . . . . 149
[Link] src/runtime/file.s - File I/O Functions . . . . . . . . . . . . . . 149
7.7.2 Runtime Assembly Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
7.7.3 Linking Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
7.7.4 Runtime Function Naming Convention . . . . . . . . . . . . . . . . . . . . . . 151
7.7.5 Performance Considerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
7.7.6 Debugging Runtime Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 152
7.7.7 Extending the Runtime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
7.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
7.9 Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
7.9.1 Related Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
7.9.2 External Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
7.9.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
8
[Link] 2.5.1 Integer Literals . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
[Link] 2.5.2 Floating-Point Literals . . . . . . . . . . . . . . . . . . . . . . 163
[Link] 2.5.3 String Literals . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
8.4.6 2.6 Operators and Punctuation . . . . . . . . . . . . . . . . . . . . . . . . . . 164
8.4.7 2.7 Token Classification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
8.5 3. Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
8.5.1 3.1 Assignment Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
8.5.2 3.2 PRINT Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
8.5.3 3.3 INPUT Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
8.5.4 3.4 LINE INPUT Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
8.5.5 3.5 IF Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
8.5.6 3.6 FOR…NEXT Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
8.5.7 3.7 WHILE…WEND Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
8.5.8 3.8 DO…LOOP Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
8.5.9 3.9 SELECT CASE Statement . . . . . . . . . . . . . . . . . . . . . . . . . . 169
8.5.10 3.10 GOTO Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
8.5.11 3.11 GOSUB and RETURN Statements . . . . . . . . . . . . . . . . . . . . . 170
8.5.12 3.12 ON…GOTO Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
8.5.13 3.13 DIM Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
8.5.14 3.14 SUB Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
8.5.15 3.15 FUNCTION Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
8.5.16 3.16 Call Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
8.5.17 3.17 DATA, READ, and RESTORE Statements . . . . . . . . . . . . . . . . 173
8.5.18 3.18 File I/O Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
[Link] 3.18.1 OPEN Statement . . . . . . . . . . . . . . . . . . . . . . . . . 174
[Link] 3.18.2 CLOSE Statement . . . . . . . . . . . . . . . . . . . . . . . . 174
[Link] 3.18.3 File PRINT Statement . . . . . . . . . . . . . . . . . . . . . . 174
[Link] 3.18.4 File INPUT Statement . . . . . . . . . . . . . . . . . . . . . . 174
8.5.19 3.19 CLS Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
8.5.20 3.20 END Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
8.5.21 3.21 STOP Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
8.6 4. Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
8.6.1 4.1 Expression Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
8.6.2 4.2 Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
8.6.3 4.3 Type Coercion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
8.6.4 4.4 Arithmetic Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
8.6.5 4.5 Comparison Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
8.6.6 4.6 Logical Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
8.6.7 4.7 String Concatenation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
8.6.8 4.8 Operator Precedence Table . . . . . . . . . . . . . . . . . . . . . . . . . . 179
8.6.9 4.9 Built-in Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
[Link] 4.9.1 Mathematical Functions . . . . . . . . . . . . . . . . . . . . . . 180
[Link] 4.9.2 String Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 180
[Link] 4.9.3 Type Conversion Functions . . . . . . . . . . . . . . . . . . . . 181
[Link] 4.9.4 Other Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 181
8.6.10 4.10 Expression Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
8.7 5. Language Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
8.7.1 5.1 Variables and Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
9
8.7.2 5.2 Arithmetic Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
8.7.3 5.3 Comparison and Logical Operations . . . . . . . . . . . . . . . . . . . . . 183
8.7.4 5.4 String Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
8.7.5 5.5 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
8.7.6 5.6 Control Flow - IF Statement . . . . . . . . . . . . . . . . . . . . . . . . . 185
8.7.7 5.7 Control Flow - Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
8.7.8 5.8 SELECT CASE Statement . . . . . . . . . . . . . . . . . . . . . . . . . . 188
8.7.9 5.9 Subroutines and Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 189
8.7.10 5.10 DATA, READ, and RESTORE . . . . . . . . . . . . . . . . . . . . . . . 190
8.7.11 5.11 File I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
8.7.12 5.12 GOTO and GOSUB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
8.7.13 5.13 Complete Program Example: Guessing Game . . . . . . . . . . . . . . . 192
8.7.14 5.14 Complete Program Example: Fibonacci Sequence . . . . . . . . . . . . . 193
8.7.15 5.15 Complete Program Example: Array Sorting . . . . . . . . . . . . . . . . 194
8.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
10
Chapter 1
Overview
1.1 Introduction
Welcome to the xbasic64 compiler documentation! This guide will take you on a journey through the
design and implementation of a real, working compiler that transforms 1980s-era BASIC programs
into native x86-64 machine code.
11
…this documentation will help you understand the complete compilation pipeline from source code
to executable binary.
1.2 Prerequisites
To get the most out of this documentation, you should have some background knowledge in a few
key areas. Don’t worry if you’re not an expert—we’ll explain concepts as we go—but familiarity
with these topics will help you follow along more easily.
12
1.2.1 Required Background
[Link] 1. Assembly Language Basics
You should understand fundamental assembly language concepts:
• Registers: What they are and how they’re used to hold values
• Instructions: Basic operations like MOV, ADD, CALL, JMP
• Stack: How the stack works for function calls and local variables
• Memory addressing: How to access memory locations
You don’t need to be an x86-64 expert, but basic assembly knowledge will help you understand the
code generation phase. If you’ve taken a computer architecture or systems programming course,
you’re well-prepared.
Learning Resources: - x86-64 Assembly Language Programming - Quick reference - Intel 64 and
IA-32 Architectures Software Developer Manuals - Comprehensive reference - System V AMD64
ABI - Calling convention specification
13
1.2.2 Development Environment
To build and experiment with xbasic64, you’ll need:
• Rust toolchain: Install from [Link]
• System assembler: as (usually pre-installed on Linux/macOS)
• C compiler/linker: cc with libc (for linking the final executable)
• Text editor: Any editor with Rust support (VS Code, Vim, Emacs, etc.)
See the main README for detailed build instructions.
14
Token::Newline
Token::Next
Token::Ident("I")
The lexer handles BASIC-specific features like: - Case-insensitive keywords (FOR, for, For are all
the same) - Type suffixes (%, &, !, #, $) - Line numbers (optional) - String literals with escape
sequences
See Lexical Analysis for details.
15
mov eax, DWORD PTR [rbp-4] # Load I for PRINT
call print_integer # Print it
16
• String operations: Concatenation, substring, search
• Math functions: SIN, COS, SQR, etc. (wrapping libc functions)
• Memory management: String allocation and deallocation
Compiled programs link against this runtime library to access these features. The runtime uses
libc for portability across Linux and macOS.
See Runtime System for details.
17
Runtime System: - Hand-written assembly for performance-critical operations - libc integration
for portability - Dynamic string memory management
For a detailed comparison of xbasic64 to compiler theory, see Theory Comparison.
18
5. Semantic Analysis - Learn about type checking
6. Code Generation - Understand assembly generation
7. Runtime System - See how runtime support works
8. Exercises - Practice what you’ve learned
Use the Language Reference as needed when you encounter unfamiliar BASIC features.
19
• Type Hierarchy: Visualizes type system and coercion
1.5.6 Cross-References
Documents include links to: - Related documentation sections - Source code files (src/[Link],
src/[Link], etc.) - Example programs (examples/[Link], etc.) - External resources (Dragon
Book chapters, x86-64 references)
1.5.7 Navigation
Each document includes: - Previous/Next links: Navigate sequentially through the documenta-
tion - Table of contents: Jump to specific sections - Back to README: Return to the main
documentation index
20
• src/[Link] - Syntax analyzer (parser)
• src/[Link] - Code generator (assembly emitter)
• src/[Link] - Runtime library integration
• src/runtime/ - Hand-written assembly runtime functions
1.7 Summary
xbasic64 is an educational BASIC compiler that demonstrates all the essential phases of compilation:
lexical analysis, syntax analysis, semantic analysis, and code generation. Its simple three-phase ar-
chitecture makes it easy to understand while still being a complete, working compiler that generates
real x86-64 executables.
This documentation will guide you through each phase of the compiler, relating practical implemen-
tation to theoretical concepts. Whether you’re a student learning compiler design or an educator
looking for teaching materials, xbasic64 provides a clear, accessible example of how compilers work.
Next Steps: - Continue to Compiler Pipeline to understand how the phases fit together - Jump
to a specific phase if you have a particular interest - Try building and running the compiler with
the example programs
Happy compiling! �
21
Chapter 2
Compiler Pipeline
The xbasic64 compiler uses a streamlined three-phase architecture that transforms BASIC source
code into executable x86-64 machine code. This document explains how data flows through the
compilation process and why this design differs from traditional multi-phase compilers.
22
2.2.2 Phase 2: Syntax Analysis (Parser)
Purpose: Build an Abstract Syntax Tree (AST) from the token stream
Input: Vector of tokens (Vec<Token>)
Output: Program AST (Program struct containing Vec<Stmt>)
The parser (src/[Link]) consumes tokens and constructs a hierarchical tree structure repre-
senting the program’s syntactic structure. It uses:
• Recursive descent parsing for statements
• Precedence climbing for expressions (handling operator precedence and associativity)
The AST captures the program’s structure without the syntactic noise of keywords and punctuation.
Each node represents a language construct (statement or expression) with its components.
23
Integer(20),
Print,
String("The answer is"),
Semicolon,
Ident("x%"),
Newline,
]
The lexer has identified line numbers, keywords, identifiers with type suffixes, operators, and literals.
_start:
# Prologue
push rbp
mov rbp, rsp
sub rsp, 16 # Allocate space for x% (8 bytes, rounded to 16)
L10:
# LET x% = 42
mov eax, 42
movsx rax, ax # Sign-extend to 64-bit
24
mov [rbp-8], rax # Store x%
L20:
# PRINT "The answer is"; x%
lea rdi, [_str_0] # Load string pointer
mov rsi, 14 # String length
call _print_string
# Exit
mov rax, 60 # sys_exit
xor rdi, rdi
syscall
.section .data
_str_0: .ascii "The answer is"
The code generator has produced x86-64 assembly that allocates variables on the stack, loads values
into registers, and calls runtime functions for I/O operations.
25
4. No Optimization Passes
Production compilers use IR to enable optimization passes (constant folding, dead code elimina-
tion, register allocation, etc.). Since xbasic64 prioritizes clarity over performance, we skip these
optimizations.
26
Traditional compilers separate concerns into many phases, each with a specific responsibility. This
enables:
• Multiple optimization passes on the IR
• Target independence (multiple backends can consume the same IR)
• Sophisticated analysis (data flow, control flow, alias analysis)
• Better code quality through optimization
27
� BASIC Source Code �
� "10 PRINT \"Hello\"\n20 END" �
�������������������������������������������������������������������
�
�
��������������������������
� Lexical Analysis �
� (src/[Link]) �
� �
� • Character scanning �
� • Keyword recognition �
� • Token generation �
��������������������������
�
�
��������������������������
� Token Stream �
� [Integer(10), Print, �
� String("Hello"), ...]�
��������������������������
�
�
��������������������������
� Syntax Analysis �
� (src/[Link]) �
� �
� • Recursive descent �
� • Precedence climbing �
� • AST construction �
��������������������������
�
�
��������������������������
� Abstract Syntax Tree �
� Program { stmts: [ �
� Label(10), �
� Print {...}, ...]} �
��������������������������
�
�
��������������������������
� Code Generation �
� (src/[Link]) �
� �
� • AST traversal �
� • Type coercion �
� • Assembly emission �
��������������������������
28
�
�
��������������������������
� x86-64 Assembly �
� .section .text �
� _start: �
� push rbp �
� ... �
��������������������������
�
�
��������������������������
� System Assembler (as) �
��������������������������
�
�
��������������������������
� System Linker (cc) �
��������������������������
�
�
��������������������������
� Executable Binary �
� (ELF on Linux) �
��������������������������
Each phase transforms the data into a form more suitable for the next phase, ultimately producing
executable machine code.
2.7 Summary
The xbasic64 compiler uses a three-phase pipeline: Lexer → Parser → Code Generator. This sim-
plified architecture omits intermediate representations and optimization passes found in production
compilers, making it easier to understand while still producing working executables.
Key takeaways:
• Three phases: Lexical analysis, syntax analysis, and code generation
• Direct translation: AST maps directly to assembly without an IR
• Educational focus: Simplicity and clarity over optimization and performance
• Single-pass generation: Code is emitted in one traversal of the AST
• System tools: Relies on as and cc for assembly and linking
This design makes xbasic64 an excellent vehicle for learning compiler fundamentals without the
complexity of production-grade compilers.
29
• Syntax Analysis - How the parser builds the AST
• Code Generation - Assembly generation and x86-64 details
• Theory Comparison - How xbasic64 relates to compiler textbooks
30
Chapter 3
3.1 Introduction
Lexical analysis (also called scanning or tokenization) is the first phase of compilation. The lexer
reads the source code as a stream of characters and groups them into meaningful units called
tokens. This phase simplifies the parser’s job by converting raw text into a structured sequence of
categorized elements.
In xbasic64, the lexer is implemented in src/[Link] and transforms BASIC source code into a
vector of tokens that the parser can process.
31
– DFA (Deterministic Finite Automaton): Each state has exactly one transition per
input symbol
– NFA (Non-deterministic Finite Automaton): States may have multiple transitions
per symbol
The xbasic64 lexer uses a hand-written approach rather than generated automata, but the underly-
ing principles are the same. Each token recognition function implements a small finite automaton.
3.3.1 1. Literals
Literals represent constant values in the source code:
32
HEX = &HFF ' Hexadecimal (255)
MSG$ = "Hello!" ' String literal
3.3.2 2. Identifiers
Identifiers are names for variables, functions, and labels:
• Must start with a letter (A-Z, case-insensitive)
• Can contain letters, digits, and underscores
• May end with a type suffix: %, &, !, #, $
• Stored as Token::Ident(String) with the name in uppercase
Type Suffixes: - % - Integer (16-bit) - & - Long (32-bit) - ! - Single-precision float - # - Double-
precision float - $ - String
Examples:
Counter ' Identifier (becomes "COUNTER")
X% ' Integer variable
Total& ' Long variable
Price! ' Single-precision float
Value# ' Double-precision float
Name$ ' String variable
3.3.3 3. Keywords
Keywords are reserved words with special meaning in BASIC. The xbasic64 lexer recognizes 47
keywords:
33
[Link] Procedures
• SUB, ENDSUB - Subroutine definition
• FUNCTION, ENDFUNCTION - Function definition
[Link] Comments
• REM - Remark (comment)
Examples:
PRINT "Hello" ' PRINT keyword
FOR I = 1 TO 10 ' FOR, TO keywords
IF X > 5 THEN ' IF, THEN keywords
3.3.4 4. Operators
Operators perform operations on values:
34
Examples:
X = Y + 5 ' Assignment and addition
IF A <> B THEN ' Not equal comparison
Z = 2 ^ 8 ' Exponentiation (256)
3.3.5 5. Punctuation
Punctuation tokens structure the code:
• ( - Left parenthesis (Token::LParen)
• ) - Right parenthesis (Token::RParen)
• , - Comma (Token::Comma)
• ; - Semicolon (Token::Semicolon)
• : - Colon (Token::Colon) - statement separator
• # - Hash (Token::Hash) - file number prefix
Examples:
DIM A(10) ' Parentheses for array dimensions
PRINT A; B, C ' Semicolon and comma separators
X = 1 : Y = 2 ' Colon separates statements
OPEN "[Link]" FOR OUTPUT AS #1 ' Hash for file number
35
fn read_identifier(&mut self, first: char) -> String {
let mut s = String::new();
[Link](first.to_ascii_uppercase());
36
The lexer recognizes line numbers only at the start of a line (tracked by the at_line_start flag):
Implementation Detail:
// Check for line number at start of line
if self.at_line_start {
if let Some(c) = [Link]() {
if c.is_ascii_digit() {
let mut num = String::new();
while let Some(c) = [Link]() {
if c.is_ascii_digit() {
[Link]([Link]().unwrap());
} else {
break;
}
}
self.at_line_start = false;
self.skip_whitespace();
return Ok(Token::LineNumber([Link]().unwrap_or(0)));
}
}
}
Line numbers are optional in xbasic64—modern BASIC code typically omits them.
3.4.4 Comments
BASIC supports two comment styles:
1. REM statement: REM This is a comment
2. Apostrophe: ' This is a comment
Both comment styles cause the lexer to skip to the end of the line and return a Token::Newline:
X = 1 REM Set X to 1
Y = 2 ' Set Y to 2
Implementation Detail:
// Handle REM keyword
if ident == "REM" {
self.skip_comment();
return Ok(Token::Newline);
}
// Handle apostrophe
'\'' => {
self.skip_comment();
Ok(Token::Newline)
}
37
3.5 Implementation Walkthrough: src/[Link]
Let’s examine the key components of the xbasic64 lexer implementation:
[Link] skip_whitespace()
Whitespace (spaces, tabs, carriage returns) is not significant in BASIC except as a token separator:
fn skip_whitespace(&mut self) {
while let Some(c) = [Link]() {
if c == ' ' || c == '\t' || c == '\r' {
[Link]();
} else {
break;
}
}
}
38
Note: Newlines (\n) are NOT skipped—they’re significant in BASIC as statement terminators.
[Link] read_string()
String literals are enclosed in double quotes, with "" as an escape sequence for a literal quote:
fn read_string(&mut self) -> Result<String, String> {
let mut s = String::new();
[Link](); // consume opening "
loop {
match [Link]() {
Some('"') => {
// Check for escaped quote ""
if [Link]() == Some('"') {
[Link]();
[Link]('"');
} else {
break; // End of string
}
}
Some('\n') | None => {
return Err("Unterminated string".to_string());
}
Some(c) => [Link](c),
}
}
Ok(s)
}
Example:
"Hello" → "Hello"
"Say ""Hi""" → "Say "Hi""
"Unterminated → Error: Unterminated string
[Link] read_number()
Numbers can be integers or floats, with optional scientific notation:
fn read_number(&mut self, first: char) -> Token {
let mut s = String::new();
[Link](first);
39
is_float = true;
[Link]([Link]().unwrap());
} else if (c == 'e' || c == 'E' || c == 'd' || c == 'D') && !has_exponent {
has_exponent = true;
is_float = true;
[Link]([Link]().unwrap());
// Handle optional sign after exponent
if let Some(sign) = [Link]() {
if sign == '+' || sign == '-' {
[Link]([Link]().unwrap());
}
}
} else {
break;
}
}
if is_float {
Token::Float([Link]().unwrap_or(0.0))
} else {
Token::Integer([Link]().unwrap_or(0))
}
}
This function handles: - Integer literals: 42 - Decimal literals: 3.14 - Scientific notation: 1E5, 2e-3
- BASIC double notation: 1D5 (converted to 1e5)
[Link] read_hex()
Hexadecimal literals use the &H prefix:
fn read_hex(&mut self) -> Token {
let mut s = String::new();
while let Some(c) = [Link]() {
if c.is_ascii_hexdigit() {
[Link]([Link]().unwrap());
} else {
break;
}
}
let val = i64::from_str_radix(&s, 16).unwrap_or(0);
Token::Integer(val)
}
Example:
&HFF → Token::Integer(255)
40
&h10 → Token::Integer(16)
[Link] read_identifier()
Identifiers are alphanumeric names with optional type suffixes:
fn read_identifier(&mut self, first: char) -> String {
let mut s = String::new();
[Link](first.to_ascii_uppercase());
s
}
[Link] keyword_or_ident()
After reading an identifier, we check if it’s a keyword:
fn keyword_or_ident(&self, s: &str) -> Token {
let base = s.trim_end_matches(['%', '&', '!', '#', '$']);
KEYWORDS
.get(base)
.cloned()
.unwrap_or_else(|| Token::Ident(s.to_string()))
}
The keyword lookup strips type suffixes first, so PRINT% would be recognized as the PRINT keyword
(though this would be unusual).
41
if self.at_line_start {
if let Some(c) = [Link]() {
if c.is_ascii_digit() {
// ... read line number
}
}
}
self.at_line_start = false;
match c {
'\n' => {
[Link] += 1;
self.at_line_start = true;
Ok(Token::Newline)
}
'"' => {
// ... read string
}
'\'' => {
self.skip_comment();
Ok(Token::Newline)
}
'<' => {
if [Link]() == Some('>') {
[Link]();
Ok(Token::Ne)
} else if [Link]() == Some('=') {
[Link]();
Ok(Token::Le)
} else {
Ok(Token::Lt)
}
}
'&' => {
if [Link]() == Some('H') || [Link]() == Some('h') {
42
[Link]();
Ok(self.read_hex())
} else {
Ok(Token::Ident("&".to_string()))
}
}
_ if c.is_ascii_alphabetic() => {
let ident = self.read_identifier(c);
if ident == "REM" {
self.skip_comment();
return Ok(Token::Newline);
}
Ok(self.keyword_or_ident(&ident))
}
43
3.6.1 Example 1: Simple Assignment
Input:
X = 42
Token Stream:
Token::Ident("X")
Token::Eq
Token::Integer(42)
Token::Eof
44
Token::And
Token::Ident("Y")
Token::Lt
Token::Integer(5)
Token::Then
Token::Print
Token::Ident("X")
Token::Eof
45
Token Stream:
Token::Ident("X")
Token::Eq
Token::Integer(255)
Token::Newline
Token::Ident("Y")
Token::Eq
Token::Float(1.5e-10)
Token::Eof
46
3.6.10 Example 10: Function Call with Operators
Input:
Result = SIN(X) * 2 + COS(Y) ^ 2
Token Stream:
Token::Ident("RESULT")
Token::Eq
Token::Ident("SIN")
Token::LParen
Token::Ident("X")
Token::RParen
Token::Star
Token::Integer(2)
Token::Plus
Token::Ident("COS")
Token::LParen
Token::Ident("Y")
Token::RParen
Token::Caret
Token::Integer(2)
Token::Eof
47
3.7.4 4. Operator Lookahead
Some operators require lookahead to distinguish:
X < Y ' Less than
X <> Y ' Not equal
X <= Y ' Less than or equal
The lexer must peek at the next character to decide.
3.8 Summary
The lexical analysis phase transforms raw BASIC source code into a structured sequence of tokens.
Key takeaways:
1. Purpose: The lexer abstracts character-level details and provides meaningful tokens to the
parser
2. Token Categories: Literals, identifiers, keywords, operators, punctuation, and special to-
kens
3. BASIC Features: Case-insensitive keywords, type suffixes, line numbers, and two comment
styles
4. Implementation: Hand-written lexer using character-by-character scanning with one-
character lookahead
5. Error Handling: Detects malformed tokens like unterminated strings and unexpected char-
acters
The token stream produced by the lexer becomes the input to the parser, which we’ll explore in
the next chapter.
48
• Unicode handling in lexers (xbasic64 uses ASCII for simplicity)
Next: Syntax Analysis Phase - Learn how tokens are parsed into an Abstract Syntax Tree
49
Chapter 4
Syntax Analysis
Syntax analysis (parsing) is the second phase of compilation, where the flat stream of tokens
from the lexer is transformed into a hierarchical Abstract Syntax Tree (AST) that represents the
program’s structure. This document explains the theory behind parsing, the grammar of xbasic64’s
BASIC dialect, and how the parser implementation works.
50
• Non-terminals: Syntactic categories (statement, expression, etc.)
• Production rules: Rules that define how non-terminals expand into terminals and other
non-terminals
• Start symbol: The top-level non-terminal (usually “program”)
Example grammar fragment:
statement → IF expression THEN statement
statement → PRINT print_list
expression → term (('+' | '-') term)*
term → factor (('*' | '/') factor)*
factor → NUMBER | IDENT | '(' expression ')'
Each production rule shows how a non-terminal (left side) can be replaced by a sequence of terminals
and non-terminals (right side).
51
4.2.4 Parsing Strategies
There are two main approaches to parsing:
Top-Down Parsing - Starts from the start symbol and tries to derive the input - Builds the parse
tree from root to leaves - Examples: Recursive descent, LL(k) parsers - xbasic64 uses this approach
Bottom-Up Parsing - Starts from the input and tries to reduce it to the start symbol - Builds
the parse tree from leaves to root - Examples: LR(k), LALR parsers (used by yacc/bison) - More
powerful but more complex
52
| dim_stmt
| sub_stmt
| function_stmt
| call_stmt
| data_stmt
| read_stmt
| restore_stmt
| open_stmt
| close_stmt
| cls_stmt
| end_stmt
| stop_stmt
(* Output *)
print_stmt ::= PRINT ('#' INTEGER ',')? print_list?
(* Input *)
input_stmt ::= INPUT ('#' INTEGER ',')? (STRING ',')? ident_list
(* Control Flow *)
if_stmt ::= IF expression THEN (single_line_if | block_if)
block_if ::= NEWLINE statement* (ELSEIF expression THEN NEWLINE statement*)* (ELSE NEWLINE stat
for_stmt ::= FOR IDENT '=' expression TO expression (STEP expression)? NEWLINE
statement*
NEXT IDENT?
53
statement*
WEND
(* Arrays *)
dim_stmt ::= DIM array_decl (',' array_decl)*
(* Procedures *)
sub_stmt ::= SUB IDENT ('(' param_list ')')? NEWLINE
statement*
END SUB
(* Data *)
data_stmt ::= DATA literal (',' literal)*
54
(* File I/O *)
open_stmt ::= OPEN expression FOR (INPUT | OUTPUT | APPEND) AS '#' INTEGER
(* Miscellaneous *)
cls_stmt ::= CLS
comparison_expr ::= additive_expr (('=' | '<>' | '<' | '>' | '<=' | '>=') additive_expr)?
55
Level Operators Associativity Description
1 OR Left Logical/bitwise OR
2 XOR Left Logical/bitwise XOR
3 AND Left Logical/bitwise AND
4 =, <>, <, >, <=, >= Left Comparison
5 +, - Left Addition, subtraction
6 *, /, \, MOD Left Multiplication, division,
integer division, modulo
7 ^ Right Exponentiation
8 -, +, NOT Right Unary negation, unary
plus, logical NOT
56
let var = self.expect_ident()?;
[Link](Token::Eq)?;
let start = self.parse_expression()?;
[Link](Token::To)?;
let end = self.parse_expression()?;
57
6. Repeat from step 2
Example: Parsing 2 + 3 * 4
parse_prec(min_prec=1):
left = 2
see '+' (prec=5 >= 1), consume it
right = parse_prec(min_prec=6):
left = 3
see '*' (prec=6 >= 6), consume it
right = parse_prec(min_prec=7):
left = 4
see EOF (no operator)
return 4
return Binary(Mul, 3, 4)
return Binary(Add, 2, Binary(Mul, 3, 4))
This automatically produces the correct tree: 2 + (3 * 4).
58
pub enum Stmt {
Label(u32), // Line number label
If { // Conditional execution
condition: Expr,
then_branch: Vec<Stmt>,
else_branch: Option<Vec<Stmt>>,
},
59
},
// File I/O
Open {
filename: Expr,
60
mode: FileMode,
file_num: i32,
},
Close { file_num: i32 },
PrintFile {
file_num: i32,
items: Vec<PrintItem>,
newline: bool,
},
InputFile {
file_num: i32,
vars: Vec<String>,
},
}
61
[Link] Supporting Types
pub enum Literal {
Integer(i64),
Float(f64),
String(String),
}
62
expressions, and some statements (like If) contain other statements.
4. Explicit Structure: Array subscripts, function arguments, and other lists are explicitly
represented as Vec<Expr>.
5. Optional Components: Optional parts (like STEP in FOR loops or ELSE in IF statements)
use Option<T>.
63
4.6 Parser Implementation Walkthrough
Let’s examine key parts of src/[Link] to see how the theory translates to code.
64
4.6.3 Statement Parsing Examples
Parsing LET statements:
fn parse_let(&mut self) -> Result<Stmt, String> {
[Link](); // consume LET
self.parse_assignment()
}
[Link](Token::Eq)?;
let value = self.parse_expression()?;
65
return Ok(Stmt::If { condition, then_branch, else_branch });
}
[Link](Token::Eq)?;
let start = self.parse_expression()?;
[Link](Token::To)?;
let end = self.parse_expression()?;
self.skip_newlines();
66
The parser collects statements until it encounters NEXT, using error returns as a signaling mechanism
for loop terminators.
left = Expr::Binary {
op,
left: Box::new(left),
right: Box::new(right),
};
}
Ok(left)
}
Operator precedence table:
fn binary_op_info(token: &Token) -> Option<(u8, BinaryOp)> {
match token {
Token::Or => Some((1, BinaryOp::Or)),
Token::Xor => Some((3, BinaryOp::Xor)),
Token::And => Some((2, BinaryOp::And)),
67
Token::Eq => Some((4, BinaryOp::Eq)),
Token::Ne => Some((4, BinaryOp::Ne)),
// ... other comparison operators at level 4
Token::Plus => Some((5, BinaryOp::Add)),
Token::Minus => Some((5, BinaryOp::Sub)),
Token::Star => Some((6, BinaryOp::Mul)),
Token::Slash => Some((6, BinaryOp::Div)),
// ... other multiplicative operators at level 6
Token::Caret => Some((7, BinaryOp::Pow)),
_ => None,
}
}
Parsing unary expressions:
fn parse_unary(&mut self) -> Result<Expr, String> {
match [Link]() {
Token::Minus => {
[Link]();
let operand = self.parse_unary()?;
Ok(Expr::Unary { op: UnaryOp::Neg, operand: Box::new(operand) })
}
Token::Plus => {
[Link]();
self.parse_unary() // Unary plus is a no-op
}
_ => self.parse_primary(),
}
}
Parsing primary expressions:
fn parse_primary(&mut self) -> Result<Expr, String> {
match [Link]().clone() {
Token::Integer(n) => {
[Link]();
Ok(Expr::Literal(Literal::Integer(n)))
}
Token::Float(f) => {
[Link]();
Ok(Expr::Literal(Literal::Float(f)))
}
Token::String(s) => {
[Link]();
Ok(Expr::Literal(Literal::String(s)))
}
Token::Ident(name) => {
[Link]();
if matches!([Link](), Token::LParen) {
[Link]();
68
let args = self.parse_expr_list()?;
[Link](Token::RParen)?;
Ok(Expr::FnCall { name, args })
} else {
Ok(Expr::Variable(name))
}
}
Token::LParen => {
[Link]();
let expr = self.parse_expression()?;
[Link](Token::RParen)?;
Ok(expr)
}
tok => Err(format!("Unexpected token in expression: {:?}", tok)),
}
}
Primary expressions are the “atoms” of expressions: literals, variables, function calls, and paren-
thesized expressions.
loop {
// Check for END SELECT
if matches!([Link](), Token::End | Token::EndSelect) {
if matches!([Link](), Token::End) {
[Link]();
[Link](Token::Select)?;
} else {
[Link]();
}
break;
}
[Link](Token::Case)?;
69
None
} else {
Some(self.parse_expression()?)
};
self.skip_newlines();
[Link]((case_value, body));
}
70
30 PRINT I * I
40 NEXT I
Parsing steps:
1. parse() starts, calls parse_statement() repeatedly
2. First statement: 10 INPUT...
• Sees Token::LineNumber(10) → creates Label(10)
• Sees Token::Input → calls parse_input()
• Parses prompt string and variable list
• Returns Input { prompt: Some("Enter N: "), vars: ["N"] }
3. Second statement: 20 FOR...
• Sees Token::LineNumber(20) → creates Label(20)
• Sees Token::For → calls parse_for()
• Parses loop variable, bounds, and body
• Body contains labels 30 and 40, and PRINT statement
• Returns For { var: "I", start: Literal(1), end: Variable("N"), step:
None, body: [...] }
4. parse() returns Program { statements: [Label(10), Input{...}, Label(20),
For{...}] }
Resulting AST:
Program {
statements: [
Label(10),
Input {
prompt: Some("Enter N: "),
vars: vec!["N".to_string()],
},
Label(20),
For {
var: "I".to_string(),
start: Literal(Integer(1)),
end: Variable("N".to_string()),
step: None,
body: vec![
Label(30),
Print {
items: vec![
PrintItem::Expr(Binary {
op: Mul,
left: Box::new(Variable("I".to_string())),
right: Box::new(Variable("I".to_string())),
}),
],
newline: true,
},
Label(40),
],
71
},
],
}
4.7 Summary
Syntax analysis transforms a flat token stream into a hierarchical AST that represents the program’s
structure. Key takeaways:
• Context-free grammars define the syntax of programming languages
• Recursive descent parsing is simple and effective for predictable grammars
• Precedence climbing elegantly handles operator precedence and associativity
• ASTs focus on semantic structure, omitting syntactic noise
• Direct AST construction is more efficient than building parse trees first
The xbasic64 parser demonstrates these concepts in a clean, understandable implementation that’s
perfect for learning compiler design.
72
Chapter 5
Semantic Analysis
5.1 Introduction
Semantic analysis is the phase of compilation that checks whether a syntactically correct program
makes sense according to the language’s rules. While the parser ensures the program follows gram-
matical structure, semantic analysis ensures it follows meaning-based rules like type compatibility,
variable declarations, and scope resolution.
In xbasic64, semantic analysis is integrated into the code generation phase rather than being a
separate pass. This document explains the semantic rules enforced by the compiler, focusing on
the type system, type coercion, and symbol management.
73
In traditional compilers, semantic analysis is a separate phase that: 1. Walks the AST 2. Builds
symbol tables 3. Annotates the AST with type information 4. Reports semantic errors
74
Type Suffix Size Description Range/Precision
String $ Variable Character string Heap-allocated, (pointer,
length) pair
75
– [rbp + offset]: pointer
– [rbp + offset - 8]: length
Example memory layout:
High addresses
�������������������
� String pointer � [rbp - 8] (rax)
�������������������
� String length � [rbp - 16] (rdx)
�������������������
� Integer var � [rbp - 24] (eax)
�������������������
Low addresses
This representation allows: - Efficient substring operations (no copying) - O(1) length queries -
Embedded null characters
76
Example:
X% = 10 ' Integer
Y& = 20 ' Long
Z = X% + Y& ' Result is Long (both promoted to Long)
// String concatenation
if left == DataType::String && right == DataType::String {
return DataType::String;
}
77
}
}
78
}
// Integer/Long to Single
(DataType::Integer | DataType::Long, DataType::Single) => {
[Link](" cvtsi2ss xmm0, eax");
}
// Integer/Long to Double
(DataType::Integer | DataType::Long, DataType::Double) => {
[Link](" cvtsi2sd xmm0, eax");
}
// Single to Double
(DataType::Single, DataType::Double) => {
[Link](" cvtss2sd xmm0, xmm0");
}
// Double to Single
(DataType::Double, DataType::Single) => {
[Link](" cvtsd2ss xmm0, xmm0");
}
79
);
result_type
}
Example: X% + Y#
1. Determine types: X% is Integer, Y# is Double
2. Promote: Result type is Double
3. Evaluate X% → Integer in eax
4. Coerce to Double: cvtsi2sd xmm0, eax
5. Save: movsd [rsp], xmm0
80
6. Evaluate Y# → Double in xmm0
7. Coerce to Double: (already Double, no-op)
8. Restore: movsd xmm1, xmm0 (right), movsd xmm0, [rsp] (left)
9. Add: addsd xmm0, xmm1
10. Result: Double in xmm0
struct CodeGen {
vars: HashMap<String, VarInfo>, // Global variables
81
proc_vars: HashMap<String, VarInfo>, // Local variables (current procedure)
current_proc: Option<String>, // Current procedure name
stack_offset: i32, // Current stack allocation position
// ...
}
Key points: - vars: Global symbol table for module-level variables - proc_vars: Local symbol
table for the current procedure - current_proc: Tracks whether we’re inside a SUB/FUNCTION
- stack_offset: Grows downward (negative) as variables are allocated
82
}
info
}
Allocation process: 1. Check if variable exists in local scope (if in procedure) 2. Check if
variable exists in global scope 3. If not found, allocate new storage: - Determine type from suffix -
Decrement stack_offset by 8 - Create VarInfo entry - Insert into appropriate symbol table
SUB PrintX
PRINT X ' Can access global X
END SUB
83
SUB Test
X = 20 ' Local X (shadows global)
PRINT X ' Prints 20 (local)
END SUB
Test
PRINT X ' Prints 10 (global unchanged)
Shadowing: Local variables with the same name as globals hide the global variable within the
procedure.
SUB Test
Y = 200 ' Allocates local Y (Double)
X = 300 ' Allocates local X (shadows global)
PRINT X, Y ' Uses local X and local Y
84
END SUB
Test
PRINT X ' Uses global X (still 100)
PRINT Y ' Allocates global Y (was not visible outside Test)
struct CodeGen {
arrays: HashMap<String, ArrayInfo>, // Array metadata
// ...
}
Array allocation:
DIM A(10) ' Allocate array with 11 elements (0-10)
Generated code: 1. Allocate space for array pointer on stack 2. Allocate space for dimension
bounds on stack 3. Call runtime function to allocate heap memory 4. Store pointer and bounds in
stack slots
Array access:
A(5) = 42 ' Store to array element
X = A(5) ' Load from array element
Generated code: 1. Load array pointer from stack 2. Load dimension bounds from stack 3.
Evaluate subscript expression 4. Calculate element address: base + (index * element_size)
5. Load/store value at calculated address
85
SUB Test(A)
B = 30 ' Local B (Double)
X = 40 ' Local X (shadows global)
PRINT A, B, X, Y%
END SUB
Test 50
PRINT X, Y%
Symbol tables after compilation:
Global symbol table (vars):
"X" -> VarInfo { offset: -8, data_type: Double }
"Y%" -> VarInfo { offset: -16, data_type: Integer }
Local symbol table for Test (proc_vars when inside Test):
"A" -> VarInfo { offset: -8, data_type: Double } (parameter)
"B" -> VarInfo { offset: -16, data_type: Double }
"X" -> VarInfo { offset: -24, data_type: Double } (shadows global)
Variable resolution in PRINT A, B, X, Y%: - A: Found in proc_vars → local parameter - B:
Found in proc_vars → local variable - X: Found in proc_vars → local variable (shadows global) -
Y%: Not in proc_vars, found in vars → global variable
Result = A% + B& * C! / D#
Type Analysis:
Step-by-step evaluation of A% + B& * C! / D#:
86
1. B& * C!:
• Left: Long, Right: Single
• Promote to: Single (Long → Single)
• Result: Single
2. (B& * C!) / D#:
• Left: Single, Right: Double
• Special rule: / always produces Double
• Promote to: Double (Single → Double)
• Result: Double
3. A% + ((B& * C!) / D#):
• Left: Integer, Right: Double
• Promote to: Double (Integer → Double)
• Result: Double
Generated Assembly (simplified):
; A% + B& * C! / D#
; Evaluate B& * C!
mov eax, DWORD PTR [rbp-16] ; Load B& (Long)
cvtsi2ss xmm0, eax ; Convert to Single
movss xmm1, DWORD PTR [rbp-24] ; Load C! (Single)
mulss xmm0, xmm1 ; Multiply (Single)
; Evaluate D#
movsd xmm0, QWORD PTR [rbp-32] ; Load D# (Double)
; Evaluate A%
movsx eax, WORD PTR [rbp-8] ; Load A% (Integer, sign-extend)
cvtsi2sd xmm0, eax ; Convert to Double
87
add rsp, 16
addsd xmm0, xmm1 ; Add (Double)
88
idiv ecx ; eax = eax / ecx (17 / 5 = 3)
; Store result
mov DWORD PTR [rbp-24], eax ; IntDiv = 3
; X# MOD Y# (modulo)
; Perform modulo
cdq ; Sign-extend eax to edx:eax
idiv ecx ; edx = eax % ecx (17 % 5 = 2)
mov eax, edx ; Move remainder to eax
; Store result
mov DWORD PTR [rbp-32], eax ; Remainder = 2
Key observations: - Float operands are truncated (not rounded) for integer operations -
cvttsd2si performs truncation toward zero - idiv produces both quotient (eax) and remainder
(edx)
89
returns Long (-1 for true) 3. (-1) AND (-1): Bitwise AND of Long values 4. Result: -1 (true)
For (A > B) OR (C < B): 1. A > B: Comparison returns Long (0 for false) 2. C < B: Comparison
returns Long (-1 for true) 3. 0 OR (-1): Bitwise OR of Long values 4. Result: -1 (true)
For NOT (A = B): 1. A = B: Comparison returns Long (0 for false) 2. NOT 0: Bitwise NOT of Long
value 3. Result: -1 (true)
Generated Assembly (simplified):
; (A < B) AND (C > A)
; Evaluate A < B
movsd xmm0, QWORD PTR [rbp-8] ; Load A
movsd xmm1, QWORD PTR [rbp-16] ; Load B
ucomisd xmm0, xmm1 ; Compare A with B
setb al ; Set al if A < B
movzx eax, al ; Zero-extend to eax
neg eax ; Convert 1 to -1 (true)
; Save result
sub rsp, 16
mov [rsp], eax
; Evaluate C > A
movsd xmm0, QWORD PTR [rbp-24] ; Load C
movsd xmm1, QWORD PTR [rbp-8] ; Load A
ucomisd xmm0, xmm1 ; Compare C with A
seta al ; Set al if C > A
movzx eax, al ; Zero-extend to eax
neg eax ; Convert 1 to -1 (true)
; Store result
mov DWORD PTR [rbp-32], eax ; Result1 = -1 (true)
Key observations: - Comparisons return -1 (true) or 0 (false) - Logical operators are bitwise
operations on Long values - -1 AND -1 = -1 (all bits set) - 0 OR -1 = -1 (any bit set) - NOT 0 =
-1 (flip all bits)
90
30 GOSUB 100
40 PRINT X, Y%, Z
50 END
100 Z = 300
110 X = 400
120 RETURN
Symbol Table Evolution:
After line 10:
Global vars:
"X" -> { offset: -8, type: Double }
After line 20:
Global vars:
"X" -> { offset: -8, type: Double }
"Y%" -> { offset: -16, type: Integer }
After line 100 (in subroutine):
Global vars:
"X" -> { offset: -8, type: Double }
"Y%" -> { offset: -16, type: Integer }
"Z" -> { offset: -24, type: Double }
After line 110 (in subroutine):
Global vars:
"X" -> { offset: -8, type: Double } (modified to 400)
"Y%" -> { offset: -16, type: Integer }
"Z" -> { offset: -24, type: Double }
After line 40 (back in main): - All three variables are accessible - X has value 400 (modified by
subroutine) - Y% has value 200 (unchanged) - Z has value 300 (created by subroutine)
Key observations: - Variables are global by default - GOSUB doesn’t create a new scope (unlike
SUB/FUNCTION) - Variables created in subroutines are accessible after RETURN
SUB Test(A)
X = 300 ' Local X (shadows global)
Z = 400 ' Local Z
PRINT A, X, Y, Z
END SUB
91
Test 500
PRINT X, Y
Symbol Table States:
Global symbol table (after initial assignments):
"X" -> { offset: -8, type: Double }
"Y" -> { offset: -16, type: Double }
Local symbol table inside Test:
"A" -> { offset: -8, type: Double } (parameter)
"X" -> { offset: -16, type: Double } (shadows global)
"Z" -> { offset: -24, type: Double }
Variable resolution in PRINT A, X, Y, Z: - A: Found in local table → parameter (500) - X:
Found in local table → local variable (300) - Y: Not in local table, found in global table → global
variable (200) - Z: Found in local table → local variable (400)
Variable resolution in PRINT X, Y (after Test returns): - X: Found in global table → global
variable (100, unchanged) - Y: Found in global table → global variable (200)
Key observations: - Local variables shadow globals with the same name - Globals are accessible
from procedures unless shadowed - Local variables don’t persist after procedure returns - Parameters
are local variables initialized from arguments
92
Z! = 42: 1. Expression type: Long (integer literal) 2. Target type: Single (from ! suffix) 3.
Coercion: Long → Single 4. Result: 42.0 (exact conversion)
mov eax, 42
cvtsi2ss xmm0, eax ; Convert to Single
movss DWORD PTR [rbp-24], xmm0
W# = 5: 1. Expression type: Long (integer literal) 2. Target type: Double (from # suffix) 3.
Coercion: Long → Double 4. Result: 5.0 (exact conversion)
mov eax, 5
cvtsi2sd xmm0, eax ; Convert to Double
movsd QWORD PTR [rbp-32], xmm0
Key observations: - Float-to-integer conversion truncates (doesn’t round) - Integer-to-float con-
version is exact (within precision limits) - Target variable type determines coercion, not expression
type
5.8 Summary
Semantic analysis in xbasic64 ensures that syntactically correct programs follow the language’s
type and scope rules. Key takeaways:
93
5.8.4 Integration with Code Generation
• Semantic analysis happens during code generation (single pass)
• Type checking and coercion are interleaved with assembly emission
• Symbol table lookups occur during variable reference generation
• Errors are detected during code generation, not in a separate phase
94
Chapter 6
6.1 Overview
The code generation phase is the final stage of the xbasic64 compiler, where the Abstract Syntax
Tree (AST) is translated directly into x86-64 assembly language. Unlike traditional compilers that
use intermediate representations (IR) and multiple optimization passes, xbasic64 takes a simplified
approach: direct AST-to-assembly translation in a single pass.
95
1. Preprocessing Pass: Collect DATA statements and check for GOSUB usage
2. Procedure Generation: Generate code for SUB and FUNCTION definitions
3. Main Program: Generate the main program body
4. Data Section: Emit string literals, DATA tables, and global variables
For each AST node, the generator emits appropriate assembly instructions that: - Evaluate expres-
sions (leaving results in registers) - Execute statements (performing side effects) - Manage control
flow (jumps, branches, loops)
main:
push rbp
mov rbp, rsp
sub rsp, 64 # Allocate stack space
.data
_str_0: .ascii "Hello, World!"
96
6.2.2 Key Architecture Features
[Link] Registers
x86-64 provides 16 general-purpose 64-bit registers:
Register naming conventions: - 64-bit: rax, rbx, rcx, etc. - 32-bit: eax, ebx, ecx, etc. (lower
32 bits) - 16-bit: ax, bx, cx, etc. (lower 16 bits) - 8-bit: al, bl, cl, etc. (lower 8 bits)
SSE instructions operate on these 128-bit registers: - movss / movsd: Move single/double precision
floats - addss / addsd: Add single/double precision - mulss / mulsd: Multiply single/double
precision - cvtsi2sd: Convert integer to double - cvttsd2si: Convert double to integer (truncate)
97
Argument Integer/Pointer Floating-Point
4th rcx xmm3
5th r8 xmm4
6th r9 xmm5
7th+ Stack (right-to-left) Stack
98
BASIC Type C Type Result Location Notes
STRING ($) (ptr, len) rax + rdx Pointer in rax,
length in rdx
Convention: After evaluating any expression, the result is in the appropriate register(s).
; Save X
sub rsp, 16 ; 16-byte aligned temp
movsd QWORD PTR [rsp], xmm0
; Evaluate Y
movsd xmm0, QWORD PTR [rbp - 16] ; Load Y
; Perform addition
addsd xmm0, xmm1 ; X + Y
99
6.4 Stack Frame Layout
The stack frame is the region of the stack dedicated to a single function invocation. It holds local
variables, saved registers, and temporary values.
100
BASIC Type Actual Size Allocated Size Reason
INTEGER (%) 2 bytes 8 bytes Alignment
LONG (&) 4 bytes 8 bytes Alignment
SINGLE (!) 4 bytes 8 bytes Alignment
DOUBLE (#) 8 bytes 8 bytes Natural size
STRING ($) 16 bytes 16 bytes Pointer + length
Why 8 bytes for everything? - Simplifies offset calculation - Maintains 8-byte alignment (re-
quired for efficient memory access) - Wastes some space but makes code generation simpler
101
vars: HashMap<String, VarInfo>, // Global variables
arrays: HashMap<String, ArrayInfo>, // Array metadata
stack_offset: i32, // Current stack offset
label_counter: u32, // For generating unique labels
string_literals: Vec<String>, // String constants
data_items: Vec<Literal>, // DATA statement values
current_proc: Option<String>, // Current SUB/FUNCTION name
proc_vars: HashMap<String, VarInfo>,// Local variables
gosub_used: bool, // Whether GOSUB is used
}
The CodeGen struct maintains all state needed during code generation: - output: The assembly
code being built up - vars: Maps variable names to their stack offsets and types - stack_offset:
Tracks the current position in the stack frame (grows negative) - label_counter: Ensures unique
labels for branches and loops - string_literals: Collects all string constants for the data section
102
[Link](" cvttsd2si eax, xmm0");
}
// Single to Double
(DataType::Single, DataType::Double) => {
[Link](" cvtss2sd xmm0, xmm0");
}
// ... more conversions
}
}
Expr::Variable(name) => {
let info = self.get_var_info(name);
match info.data_type {
DataType::Long => {
[Link](&format!(" mov eax, DWORD PTR [rbp + {}]",
[Link]));
}
DataType::Double => {
[Link](&format!(" movsd xmm0, QWORD PTR [rbp + {}]",
[Link]));
}
103
// ... other types
}
info.data_type
}
104
[Link](" add rsp, 16");
// Perform operation
match op {
BinaryOp::Add => {
if result_type.is_integer() {
[Link](" add eax, ecx");
} else {
[Link](" addsd xmm0, xmm1");
}
}
// ... other operations
}
result_type
}
Why 16-byte temporaries? If evaluating the right operand involves a function call, the stack
must be 16-byte aligned. Using 16-byte increments maintains this invariant.
105
[Link] IF Statement
Stmt::If { condition, then_branch, else_branch } => {
let else_label = self.new_label("else");
let end_label = self.new_label("endif");
// Evaluate condition
let cond_type = self.gen_expr(condition);
self.emit_label(&end_label);
}
106
// Store end value
self.stack_offset -= 8;
let end_offset = self.stack_offset;
let end_type = self.gen_expr(end);
self.gen_coercion(end_type, DataType::Double);
[Link](&format!(" movsd QWORD PTR [rbp + {}], xmm0", end_offset));
self.emit_label(&format!(".Lfor_body_{}", self.label_counter));
self.label_counter += 1;
107
// Increment loop variable
[Link](&format!(" movsd xmm0, QWORD PTR [rbp + {}]", var_offset));
[Link](&format!(" addsd xmm0, QWORD PTR [rbp + {}]", step_offset));
[Link](&format!(" movsd QWORD PTR [rbp + {}], xmm0", var_offset));
[Link](&format!(" jmp {}", start_label));
self.emit_label(&end_label);
}
// String functions
match upper_name.as_str() {
"LEFT$" => {
self.gen_expr(&args[0]); // string: rax=ptr, rdx=len
[Link](" mov rdi, rax");
[Link](" mov rsi, rdx");
let count_type = self.gen_expr(&args[1]);
if count_type.is_integer() {
[Link](" movsxd rdx, eax");
} else {
[Link](" cvttsd2si rdx, xmm0");
}
[Link](" call _rt_left");
}
// ... more string functions
108
}
}
109
fn gen_array_load(&mut self, name: &str, indices: &[Expr]) {
let arr_info = [Link](name).expect("Array not declared");
let elem_size = if is_string_var(name) { 16 } else { 8 };
// Load value
if is_string_var(name) {
[Link](" mov rcx, rax");
[Link](" mov rax, QWORD PTR [rcx]");
[Link](" mov rdx, QWORD PTR [rcx + 8]");
} else {
[Link](" movsd xmm0, QWORD PTR [rax]");
}
}
110
6.6 Side-by-Side Examples: BASIC to Assembly
Let’s see how various BASIC constructs translate to x86-64 assembly.
; Y& = 100
mov eax, 100
mov DWORD PTR [rbp - 16], eax
Explanation: - Floating-point literals are loaded as 64-bit hex values into rax, then moved to
xmm0 - Integer literals are loaded directly into eax - Variables are stored at negative offsets from
rbp
; Evaluate B#
movsd xmm0, QWORD PTR [rbp - 16]
; A# + B#
addsd xmm0, xmm1
111
; Save (A# + B#)
sub rsp, 16
movsd QWORD PTR [rsp], xmm0
; Evaluate C#
movsd xmm0, QWORD PTR [rbp - 24]
; (A# + B#) * C#
mulsd xmm0, xmm1
; Store Result#
movsd QWORD PTR [rbp - 32], xmm0
Explanation: - Each binary operation follows the save-evaluate-restore pattern - Temporary re-
sults are saved on the stack with 16-byte alignment - The final result is in xmm0 and stored to the
result variable
; Coerce I% to Double
cvtsi2sd xmm0, eax
; Save I% as Double
sub rsp, 16
movsd QWORD PTR [rsp], xmm0
; Evaluate 3.14
mov rax, 0x40091EB851EB851F
movq xmm0, rax
112
movsd xmm1, xmm0
movsd xmm0, QWORD PTR [rsp]
add rsp, 16
; I# + 3.14
addsd xmm0, xmm1
; Store D#
movsd QWORD PTR [rbp - 16], xmm0
Explanation: - Integer is loaded with sign extension: movsx eax, WORD PTR - cvtsi2sd converts
signed integer in eax to double in xmm0 - Addition is performed in double precision
; Evaluate 0
xorpd xmm1, xmm1
; Compare X > 0
ucomisd xmm0, xmm1
seta al
movzx eax, al
neg eax ; -1 if true, 0 if false
; Test condition
test eax, eax
je .Lelse_0
; THEN branch
lea rdi, [rip + _str_0] ; "Positive"
mov rsi, 8
call _rt_print_string
call _rt_print_newline
jmp .Lendif_0
.Lelse_0:
; ELSE branch
113
lea rdi, [rip + _str_1] ; "Non-positive"
mov rsi, 12
call _rt_print_string
call _rt_print_newline
.Lendif_0:
Explanation: - ucomisd compares two doubles (unordered compare) - seta sets al to 1 if above
(unsigned), 0 otherwise - neg eax converts 1 to -1 (BASIC true value) - test eax, eax checks if
zero, je jumps if equal (false)
.Lfor_0:
; Load I, end, step
movsd xmm0, QWORD PTR [rbp - 8] ; I
movsd xmm1, QWORD PTR [rbp - 16] ; end
movsd xmm2, QWORD PTR [rbp - 24] ; step
114
jmp .Lfor_body_0
.Lfor_neg_0:
; Negative step: exit if I < end
ucomisd xmm0, xmm1
jb .Lendfor_0
.Lfor_body_0:
; PRINT I
movsd xmm0, QWORD PTR [rbp - 8]
call _rt_print_float
call _rt_print_newline
; Increment I by step
movsd xmm0, QWORD PTR [rbp - 8]
addsd xmm0, QWORD PTR [rbp - 24]
movsd QWORD PTR [rbp - 8], xmm0
jmp .Lfor_0
.Lendfor_0:
Explanation: - Loop variable, end, and step are all stored as doubles - The loop checks step sign
to determine exit condition - Positive step: exit when I > end - Negative step: exit when I < end
- Loop variable is incremented by adding step value
; Save SIN(X#)
sub rsp, 16
movsd QWORD PTR [rsp], xmm0
; Evaluate COS(X#)
movsd xmm0, QWORD PTR [rbp - 8] ; Load X# again
call cos ; Call libc cos()
115
; SIN(X#) + COS(X#)
addsd xmm0, xmm1
; Store Y#
movsd QWORD PTR [rbp - 16], xmm0
Explanation: - Math functions call the C standard library (sin, cos, etc.) - Arguments are
passed in xmm0 (System V ABI for float args) - Return values come back in xmm0 - Results are
saved/restored like any other subexpression
; Store Greeting$
mov QWORD PTR [rbp - 24], rax
mov QWORD PTR [rbp - 32], rdx
116
; PRINT Greeting$
mov rax, QWORD PTR [rbp - 24]
mov rdx, QWORD PTR [rbp - 32]
mov rdi, rax
mov rsi, rdx
call _rt_print_string
call _rt_print_newline
Explanation: - Strings are represented as (pointer, length) pairs - String variables occupy 16
bytes (two 8-byte slots) - String concatenation calls the runtime function _rt_strcat - Arguments
follow System V ABI: rdi, rsi, rdx, rcx
117
movsxd rax, eax
imul rax, QWORD PTR [rbp - 16] ; 5 * 21
mov ecx, 10
movsxd rcx, ecx
add rax, rcx ; + 10
; Save address
sub rsp, 16
mov QWORD PTR [rsp], rax
; Evaluate 42.0
mov rax, 0x4045000000000000
movq xmm0, rax
; Store to array
mov rcx, QWORD PTR [rsp]
add rsp, 16
movsd QWORD PTR [rcx], xmm0
; X# = A(5, 10)
; Calculate same index and address
mov eax, 5
movsxd rax, eax
imul rax, QWORD PTR [rbp - 16]
mov ecx, 10
movsxd rcx, ecx
add rax, rcx
imul rax, 8
add rax, QWORD PTR [rbp - 24]
; Store to X#
movsd QWORD PTR [rbp - 32], xmm0
Explanation: - Arrays are allocated on the heap using malloc - Dimensions are stored on the
stack for index calculation - Multi-dimensional arrays use row-major order: index = (i * dim1)
+ j - Element address: base_ptr + (index * element_size)
118
6.7 Common Patterns and Idioms
6.7.1 Pattern 1: Zero a Register
xor eax, eax ; Set eax to 0 (faster than mov eax, 0)
xorpd xmm0, xmm0 ; Set xmm0 to 0.0
; Epilogue
xor eax, eax ; Return 0
leave ; Restore rbp and rsp
ret ; Return to caller
119
Optimization: Track register liveness and reuse registers - Reduces memory traffic - Improves
cache performance - Requires liveness analysis and register allocation algorithm
6.9 Summary
The code generation phase transforms the AST into executable x86-64 assembly through:
1. Direct translation: One-pass traversal of the AST
120
2. Simple register allocation: Convention-based, predictable register usage
3. Stack-based temporaries: All intermediate values spill to stack
4. ABI compliance: Follows System V AMD64 calling convention
5. Type coercion: Automatic conversion between numeric types
Key takeaways: - Code generation is the bridge between high-level semantics and machine execu-
tion - Register allocation and calling conventions are critical for correctness - Stack alignment is
essential for ABI compliance - Direct translation is simple but leaves optimization opportunities -
Understanding assembly output helps debug compiler issues
The generated code is correct and functional, though not optimized. For an educational compiler,
this trade-off favors clarity and simplicity over performance.
6.11 Exercises
1. Modify the code generator to use lea (load effective address) for simple arithmetic like
X = Y + 4
2. Add optimization to detect multiplication by powers of 2 and use shift instructions instead
3. Implement peephole optimization to eliminate redundant load/store pairs
4. Add register pressure tracking to count how many registers are in use at each point
5. Generate code with debug symbols to enable source-level debugging with gdb
6. Compare assembly output between xbasic64 and gcc -O0 for equivalent C code
7. Measure code size and execution time for various BASIC programs with and without
optimizations
121
Chapter 7
Runtime System
7.1 Introduction
The runtime system is the support library that provides essential services to compiled BASIC pro-
grams. While the compiler translates BASIC source code into x86-64 assembly, many operations—
like printing to the console, reading user input, manipulating strings, and performing complex
math—are too complex or platform-specific to generate inline. Instead, the compiler generates
calls to runtime functions that handle these operations.
This document explores xbasic64’s runtime library: its design philosophy, implementation strategies,
and how compiled programs interact with it. You’ll learn how high-level BASIC operations like
PRINT "Hello" and INPUT X are implemented at the assembly level.
122
2. Code Reuse
Many BASIC operations are used repeatedly throughout a program. For example, every PRINT
statement needs to format and output data. Rather than generating the same formatting code
inline at every PRINT statement, the compiler generates a call to a shared runtime function. This
produces smaller executables and makes the compiler simpler.
3. Complex Operations
Some operations are too complex to generate inline efficiently: - String concatenation requires
memory allocation - String search (INSTR) requires sophisticated algorithms - Math functions
(SIN, COS, LOG) require floating-point algorithms - File I/O requires buffering and error handling
The runtime library implements these operations once, correctly, and efficiently.
4. Consistent Behavior
By centralizing operations in the runtime library, xbasic64 ensures consistent behavior across all
programs. For example, all BASIC programs format numbers the same way because they all call
the same _rt_print_float function.
123
The runtime function _rt_print_string handles all the details of formatting and outputting the
string using libc’s printf.
output.push_str(&PRINT_FUNCS.replace("{libc}", libc_prefix));
124
No Explicit Initialization: - Most runtime state is initialized statically in the data section - The
RNG state is initialized to a fixed seed (programs can call RANDOMIZE to reseed) - No constructor
functions need to be called
This simplicity means compiled programs can start executing immediately without runtime setup
overhead.
125
mov QWORD PTR [rbp-8], rax # Store pointer
mov QWORD PTR [rbp-16], rdx # Store length
126
.Lstr_len:
cmp BYTE PTR [rax + rcx], 0
je .Lstr_done
inc rcx
jmp .Lstr_len
.Lstr_done:
mov rax, rdx
mov rdx, rcx # Length in rdx
leave
ret
Note: Uses static buffer _str_buf (64 bytes). Result is only valid until the next STR$() call.
_rt_chr - Convert ASCII Code to Character (CHR$ function)
Creates a 1-character string from an ASCII code:
; CHR$(65) returns "A"
mov rdi, 65
call _rt_chr # Returns: rax = pointer, rdx = 1
Implementation:
_rt_chr:
push rbp
mov rbp, rsp
lea rax, [rip + _chr_buf]
mov BYTE PTR [rax], dil # Store character
mov BYTE PTR [rax + 1], 0 # Null terminate
mov rdx, 1 # Length = 1
leave
ret
Note: Uses static buffer _chr_buf (2 bytes). Result is only valid until the next CHR$() call.
127
cmp rdx, rsi # If count > length
cmova rdx, rsi # count = length
ret
_rt_right - Extract Rightmost Characters (RIGHT$ function)
Returns the last N characters of a string:
; RIGHT$("Hello", 3) returns "llo"
lea rdi, [rip + .Lstr_1] # Source pointer
mov rsi, 5 # Source length
mov rdx, 3 # Count
call _rt_right # Returns: rax = pointer + 2, rdx = 3
Implementation:
_rt_right:
cmp rdx, rsi # If count > length
cmova rdx, rsi # count = length
mov rax, rdi # Start with source pointer
add rax, rsi # Point to end
sub rax, rdx # Back up by count
ret
_rt_mid - Extract Substring (MID$ function)
Returns a substring starting at a given position (1-based):
; MID$("Hello", 2, 3) returns "ell"
lea rdi, [rip + .Lstr_1] # Source pointer
mov rsi, 5 # Source length
mov rdx, 2 # Start position (1-based)
mov rcx, 3 # Count
call _rt_mid # Returns: rax = pointer + 1, rdx = 3
Implementation:
_rt_mid:
dec rdx # Convert to 0-based
cmp rdx, rsi # If start >= length
jae .Lmid_empty # return empty string
128
.Lmid_rest:
mov rdx, rsi # Return rest of string
ret
.Lmid_empty:
mov rax, rdi
xor rdx, rdx # Length = 0
ret
129
jz .Linstr_at_start
.Linstr_loop:
; Check if enough room for needle
cmp r13, r15
jb .Linstr_not_found
.Linstr_found:
mov rax, rbx
add rax, 1 # Convert to 1-based
jmp .Linstr_done
.Linstr_at_start:
mov rax, rbx
add rax, 1
jmp .Linstr_done
.Linstr_not_found:
xor rax, rax # Return 0
.Linstr_done:
pop r15
pop r14
pop r13
pop r12
pop rbx
leave
ret
130
; "Hello" + " World"
lea rdi, [rip + .Lstr_left] # Left pointer
mov rsi, 5 # Left length
lea rdx, [rip + .Lstr_right] # Right pointer
mov rcx, 6 # Right length
call _rt_strcat # Returns: rax = new pointer, rdx = 11
Implementation:
_rt_strcat:
push rbp
mov rbp, rsp
push r12
push r13
push r14
push r15
; Save arguments
mov r12, rdi # Left pointer
mov r13, rsi # Left length
mov r14, rdx # Right pointer
mov r15, rcx # Right length
; Null terminate
pop rax
lea rcx, [r13 + r15] # Total length
mov BYTE PTR [rax + rcx], 0
131
pop r15
pop r14
pop r13
pop r12
leave
ret
Memory Management:
String concatenation allocates heap memory using malloc. In xbasic64’s simple memory model,
this memory is never freed—the program relies on the OS to reclaim memory when the process
exits. For long-running programs with many string operations, this could lead to memory leaks. A
production compiler would implement garbage collection or reference counting.
Important: Functions using static buffers return pointers that are only valid until the next call
to the same function. For example:
A$ = STR$(123)
B$ = STR$(456)
PRINT A$ ' Prints "456" (not "123"!) because A$ points to overwritten buffer
To avoid this issue, the compiler should copy static buffer results to heap-allocated strings when
they’re assigned to variables. Currently, xbasic64 doesn’t do this, which is a known limitation.
132
; PRINT "Hello"
lea rdi, [rip + .Lstr_1] # String pointer
mov rsi, 5 # String length
call _rt_print_string
Implementation:
_rt_print_string:
push rbp
mov rbp, rsp
leave
ret
Format string: "%.*s" - The .* precision specifier takes the length as an argument, allowing us
to print non-null-terminated strings.
_rt_print_float - Print a Number
Prints a numeric value, automatically choosing between integer and floating-point format:
; PRINT 3.14159
movsd xmm0, QWORD PTR [rbp-8]
call _rt_print_float
Implementation:
_rt_print_float:
push rbp
mov rbp, rsp
sub rsp, 16
133
.Lprint_as_float:
; Print as floating point
lea rdi, [rip + _fmt_float] # "%g"
mov eax, 1 # 1 vector register argument
call {libc}printf
.Lprint_float_done:
leave
ret
Smart Formatting: - Whole numbers (3.0, 42.0) print without decimal point: 3, 42 - Fractional
numbers print with decimals: 3.14159 - Uses %g format which automatically chooses between %f
and %e notation
_rt_print_char - Print a Single Character
Prints a single ASCII character:
; PRINT CHR$(65) ' Prints "A"
mov rdi, 65
call _rt_print_char
Implementation:
_rt_print_char:
push rbp
mov rbp, rsp
mov rsi, rdi # char → rsi (2nd arg)
lea rdi, [rip + _fmt_char] # "%c"
xor eax, eax
call {libc}printf
leave
ret
_rt_print_newline - Print a Newline
Prints a newline character (called at end of PRINT statement unless suppressed with ; or ,):
; PRINT "Hello" (implicit newline)
lea rdi, [rip + .Lstr_1]
mov rsi, 5
call _rt_print_string
call _rt_print_newline # Add newline
Implementation:
_rt_print_newline:
push rbp
mov rbp, rsp
lea rdi, [rip + _fmt_newline] # "\n"
xor eax, eax
call {libc}printf
134
leave
ret
135
_rt_input_number:
push rbp
mov rbp, rsp
sub rsp, 16
; scanf("%lf", &result)
lea rsi, [rbp - 8] # Address of local variable
lea rdi, [rip + _fmt_input] # "%lf"
xor eax, eax
call {libc}scanf
; scanf("%1023[^\n]", buffer)
lea rsi, [rip + _input_buf]
lea rdi, [rip + _fmt_input_str] # "%1023[^\n]"
xor eax, eax
call {libc}scanf
136
; Calculate string length
lea rax, [rip + _input_buf]
xor rdx, rdx
.Linput_len:
cmp BYTE PTR [rax + rdx], 0
je .Linput_done
inc rdx
jmp .Linput_len
.Linput_done:
leave
ret
Format string: "%1023[^\n]" - Reads up to 1023 characters, stopping at newline (which is not
included in the result).
137
push r13
push r14
; Save arguments
mov r12, rdi # Filename pointer
mov r13, rsi # Filename length
mov r14d, edx # Mode
mov ebx, ecx # File number
.Ldo_fopen:
; fopen(filename, mode)
lea rdi, [rip + _file_name_buf]
call {libc}fopen
pop r14
pop r13
pop r12
pop rbx
leave
ret
_rt_file_close - Close a File (CLOSE statement)
Closes a file and clears its handle:
138
CLOSE #1
Generated assembly:
mov rdi, 1 # File number
call _rt_file_close
Implementation:
_rt_file_close:
push rbp
mov rbp, rsp
139
lea rsi, [rip + _file_fmt_str]
mov rdx, r8 # Length (precision)
xor eax, eax
call {libc}fprintf
add rsp, 8
pop rbx
leave
ret
_rt_file_print_float - Write Number to File
Similar to _rt_print_float, but writes to a file instead of stdout.
_rt_file_input_number - Read Number from File (INPUT# statement)
Reads a number from a file:
INPUT #1, X
Generated assembly:
mov rdi, 1 # File number
call _rt_file_input_number # Returns: xmm0 = value
movsd QWORD PTR [rbp-8], xmm0 # Store in X
_rt_file_input_string - Read String from File
Reads a line from a file using fgets:
INPUT #1, Name$
Generated assembly:
mov rdi, 1 # File number
call _rt_file_input_string # Returns: rax = pointer, rdx = length
mov QWORD PTR [rbp-8], rax # Store pointer
mov QWORD PTR [rbp-16], rdx # Store length
Implementation:
_rt_file_input_string:
push rbp
mov rbp, rsp
push rbx
sub rsp, 8
140
; Check for EOF/error
test rax, rax
jz .Lfile_input_string_empty
.Lfile_input_string_done:
lea rax, [rip + _file_input_buf]
add rsp, 8
pop rbx
leave
ret
.Lfile_input_string_empty:
lea rax, [rip + _file_input_buf]
mov BYTE PTR [rax], 0
xor edx, edx
add rsp, 8
pop rbx
leave
ret
141
Example:
DATA 42, 3.14, "hello"
Generates:
.data
_data_table:
.quad 0 # Type: integer
.quad 42 # Value: 42
.quad 1 # Type: float
.quad 0x40091EB851EB851F # Value: 3.14 (double bits)
.quad 2 # Type: string
.quad .Ldata_str_1 # Value: pointer to "hello"
142
• Sign: SGN - Comparison and conditional moves
These functions are implemented directly in src/[Link] and don’t require runtime support.
2. Runtime Library Functions (Complex Operations)
More complex operations are implemented in the runtime library:
• Exponentiation: ^ operator - Uses libc pow()
• Random Numbers: RND - Custom Xorshift64 PRNG
• Timer: TIMER - Uses libc time()
• Screen Control: CLS - ANSI escape sequences
; Xorshift64 algorithm
mov rcx, rax
shl rcx, 13
xor rax, rcx # state ^= state << 13
143
or rax, rcx # Combine: value in [1, 2)
movq xmm0, rax
leave
ret
Algorithm: Xorshift64
Xorshift64 is a fast, high-quality pseudo-random number generator with 64-bit state:
1. Apply three XOR-shift operations to the state
2. Period: 2^64 - 1 (all non-zero states)
3. Passes statistical tests (TestU01 SmallCrush)
Conversion to [0, 1):
1. Take top 52 bits of state (mantissa precision of IEEE 754 double)
2. Set exponent to 1023 (representing 1.0 in binary: [Link])
3. This gives a value in [1, 2)
4. Subtract 1.0 to get [0, 1)
Initial State:
.data
_rng_state: .quad 0x12345678DEADBEEF
The RNG is initialized with a fixed seed. Programs can call RANDOMIZE to reseed based on the
current time (though this isn’t currently implemented in xbasic64).
_rt_timer - Seconds Since Midnight (TIMER function)
Returns the number of seconds elapsed since midnight as a floating-point number:
; T = TIMER
call _rt_timer # Returns: xmm0 = seconds since midnight
movsd QWORD PTR [rbp-8], xmm0
Implementation:
_rt_timer:
push rbp
mov rbp, rsp
sub rsp, 16
144
xor rdx, rdx
mov rcx, 86400
div rcx # rax = quotient, rdx = remainder
leave
ret
Note: This returns UTC-based “seconds since midnight”, not local time. For timing purposes
(measuring elapsed time), this doesn’t matter.
_rt_cls - Clear Screen (CLS statement)
Clears the terminal screen using ANSI escape sequences:
; CLS
call _rt_cls
Implementation:
_rt_cls:
push rbp
mov rbp, rsp
lea rdi, [rip + _cls_seq] # ANSI escape sequence
xor eax, eax
call {libc}printf
leave
ret
ANSI Escape Sequence:
.data
_cls_seq: .asciz "\033[2J\033[H"
• \033[2J - Clear entire screen
• \033[H - Move cursor to home position (top-left)
This works on most modern terminals (Linux, macOS, Windows 10+).
145
fstp QWORD PTR [rsp] # Store result
movsd xmm0, [rsp] # Load to SSE
add rsp, 8
movsd QWORD PTR [rbp-16], xmm0 # Store in Y
Square Root (SSE)
; Y = SQR(X)
movsd xmm0, QWORD PTR [rbp-8] # Load X
sqrtsd xmm0, xmm0 # Compute square root
movsd QWORD PTR [rbp-16], xmm0 # Store in Y
Exponentiation (libc pow)
; Y = X ^ 2.5
movsd xmm0, QWORD PTR [rbp-8] # Load X (base)
movsd xmm1, QWORD PTR [rbp-16] # Load 2.5 (exponent)
call {libc}pow # Returns: xmm0 = X ^ 2.5
movsd QWORD PTR [rbp-24], xmm0 # Store in Y
Absolute Value (Bit Manipulation)
; Y = ABS(X)
movsd xmm0, QWORD PTR [rbp-8] # Load X
mov rax, 0x7FFFFFFFFFFFFFFF # Mask: clear sign bit
movq xmm1, rax
andpd xmm0, xmm1 # Clear sign bit
movsd QWORD PTR [rbp-16], xmm0 # Store in Y
Sign Function (Comparison)
; Y = SGN(X)
movsd xmm0, QWORD PTR [rbp-8] # Load X
xorpd xmm1, xmm1 # Zero
ucomisd xmm0, xmm1 # Compare X with 0
ja .Lsgn_positive # X > 0
jb .Lsgn_negative # X < 0
xor eax, eax # X = 0, result = 0
jmp .Lsgn_done
.Lsgn_positive:
mov eax, 1 # Result = 1
jmp .Lsgn_done
.Lsgn_negative:
mov eax, -1 # Result = -1
.Lsgn_done:
cvtsi2sd xmm0, eax # Convert to double
movsd QWORD PTR [rbp-16], xmm0 # Store in Y
146
Function Implementation Location Notes
+, -, *, / SSE instructions Inline addsd, subsd,
mulsd, divsd
\ (integer div) Integer division Inline idiv
instruction
MOD Integer modulo Inline idiv
(remainder in
edx)
^ (power) libc pow() Inline call Always
returns
Double
SIN, COS, TAN x87 FPU Inline fsin, fcos,
fptan
ASIN, ACOS, ATAN x87 FPU Inline fpatan with
setup
LOG x87 FPU Inline fyl2x (log
base 2, scaled)
EXP x87 FPU Inline f2xm1 (2^x -
1, adjusted)
SQR SSE Inline sqrtsd
ABS Bit manipulation Inline Clear sign bit
SGN Comparison Inline Compare with
zero
INT Truncation Inline cvttsd2si
then cvtsi2sd
FIX Truncation Inline Same as INT
RND Xorshift64 PRNG Runtime _rt_rnd
TIMER Unix time mod 86400 Runtime _rt_timer
CLS ANSI escape Runtime _rt_cls
147
[Link] src/runtime/data_defs.s - Data Section Definitions
Contains all static data used by the runtime:
Format Strings:
_fmt_str: .asciz "%.*s" # String with precision (for PRINT)
_fmt_int: .asciz "%ld" # Long integer
_fmt_float: .asciz "%g" # Floating point (compact)
_fmt_char: .asciz "%c" # Single character
_fmt_newline: .asciz "\n" # Newline
_fmt_input: .asciz "%lf" # Read double (for INPUT)
_fmt_input_str: .asciz "%1023[^\n]" # Read string (for INPUT)
Static Buffers:
_input_buf: .skip 1024 # Console string input buffer
_chr_buf: .skip 2 # CHR$() result buffer
_str_buf: .skip 64 # STR$() result buffer
Global State:
_rng_state: .quad 0x12345678DEADBEEF # RNG state (Xorshift64)
_cls_seq: .asciz "\033[2J\033[H" # ANSI clear screen sequence
Purpose: Centralizes all data definitions in one place, making it easy to see what static resources
the runtime uses.
148
[Link] src/runtime/string.s - String Functions
Implements string manipulation operations:
Conversion Functions: - _rt_val - Convert string to number (VAL function) - _rt_str - Convert
number to string (STR$ function) - _rt_chr - Convert ASCII code to character (CHR$ function)
Substring Functions (Zero-Copy): - _rt_left - Extract leftmost characters (LEFT$ function) -
_rt_right - Extract rightmost characters (RIGHT$ function) - _rt_mid - Extract substring (MID$
function)
Search and Concatenation: - _rt_instr - Find substring position (INSTR function) -
_rt_strcat - Concatenate strings (+ operator)
Key Features: - Substring functions are zero-copy (return pointers into original string) - String
concatenation allocates new memory with malloc - Uses memcmp for efficient substring search -
Conversion functions use static buffers
Lines of Code: ~300 lines
149
File Output: - _rt_file_print_string - Write string to file (PRINT# statement) -
_rt_file_print_float - Write number to file - _rt_file_print_char - Write character to
file - _rt_file_print_newline - Write newline to file
File Input: - _rt_file_input_number - Read number from file (INPUT# statement) -
_rt_file_input_string - Read string from file
Key Features: - Uses libc file operations (fopen, fclose, fprintf, fscanf, fgets) - Maintains
file handle table (16 FILE* pointers) - Supports three modes: INPUT (read), OUTPUT (write),
APPEND - Null-terminates filenames for libc compatibility
Lines of Code: ~400 lines
// Header
output.push_str("# BASIC Runtime Library\n");
output.push_str(".intel_syntax noprefix\n\n");
// Data section
output.push_str(DATA_DEFS);
output.push_str("\[Link]\n\n");
output
}
Process: 1. Determine platform-specific libc prefix 2. Assemble header and data section 3. Con-
catenate all function sections 4. Replace {libc} placeholder with appropriate prefix 5. Return
complete assembly code
Result: A single assembly file containing the entire runtime library, ready to be appended to the
generated program code.
150
7.7.3 Linking Process
When compiling a BASIC program, xbasic64:
1. Generates program code from the AST
2. Appends runtime library using generate_runtime()
3. Writes combined assembly to a temporary file
4. Invokes system assembler (as) to create object file
5. Invokes linker (cc) to create executable, linking with libc
Example compilation:
# User runs:
xbasic64 [Link]
151
LEFT$, RIGHT$, and MID$ return pointers into the original string without copying. This is extremely
fast (O(1)) but means the original string must remain valid.
Memory Allocation:
String concatenation (+) allocates heap memory with malloc. In xbasic64’s simple memory model,
this memory is never freed. For programs with many string operations, this could lead to memory
growth.
152
mov r12, rdi # Save source pointer
mov r13, rsi # Save length
# Allocate memory
lea rdi, [rsi + 1] # length + 1 for null terminator
call {libc}malloc
.Lreverse_done:
mov BYTE PTR [rax + r13], 0 # Null terminate
mov rdx, r13 # Return length
pop r13
pop r12
leave
ret
Then in the code generator, generate calls to _rt_reverse when compiling a hypothetical
REVERSE$() function.
7.8 Summary
The xbasic64 runtime library is a collection of assembly functions that provide essential services to
compiled BASIC programs. By centralizing complex operations like I/O, string manipulation, and
file handling in the runtime, the compiler can generate simpler, more maintainable code.
Key Takeaways:
1. Platform Abstraction: The runtime uses libc for portability across Linux and macOS, with
platform-specific prefixes handled at compile time.
2. String Representation: BASIC strings are (pointer, length) pairs, not null-terminated.
This enables efficient substring operations and O(1) length queries.
3. Zero-Copy Operations: Substring functions (LEFT$, RIGHT$, MID$) return pointers into
the original string without copying, providing excellent performance.
4. Smart Formatting: The print functions automatically format numbers cleanly (whole num-
153
bers without decimals, fractional numbers with appropriate precision).
5. Modular Design: The runtime is organized into focused modules (print, input, string, math,
data, file), making it easy to understand and extend.
6. Inline vs. Runtime: Simple operations are generated inline for performance, while complex
operations use runtime functions for code reuse and maintainability.
7. System V AMD64 ABI: All runtime functions follow the standard calling convention,
ensuring seamless integration with libc and compiled BASIC code.
8. Static Buffers: Some functions use static buffers to avoid allocation overhead, trading off
thread-safety and persistence for performance.
The runtime library demonstrates how high-level language features are implemented at the assembly
level, bridging the gap between BASIC’s user-friendly syntax and the low-level operations required
to interact with the operating system.
7.9.3 Exercises
Try these exercises to deepen your understanding of the runtime system:
Beginner: 1. Add a BEEP statement that prints the ASCII bell character (7) 2. Implement
LCASE$() and UCASE$() functions for case conversion 3. Add a SPACE$() function that returns a
string of N spaces
154
Intermediate: 4. Implement proper memory management for string concatenation (reference
counting or garbage collection) 5. Add a REPLACE$() function that replaces all occurrences of a
substring 6. Implement LTRIM$() and RTRIM$() functions to remove leading/trailing whitespace
Advanced: 7. Replace the linear search in _rt_instr with Boyer-Moore algorithm 8. Implement
a rope data structure for efficient string concatenation 9. Add support for Unicode strings (UTF-8
encoding) 10. Implement a mark-and-sweep garbage collector for heap-allocated strings
155
Chapter 8
8.1 Overview
This document provides a complete formal specification of the BASIC dialect supported by xba-
sic64. It serves as both a reference for users writing BASIC programs and a specification for
compiler implementers. The language targets 1980s-era BASIC dialects, particularly GW-BASIC
and QuickBASIC, with some modern simplifications.
Key Characteristics: - Case-insensitive keywords and identifiers - Optional line numbers for
labels - Five data types with suffix notation - Implicit variable declaration - Pass-by-value parameter
semantics - Direct compilation to x86-64 native code
156
" " Terminal symbol (literal)
< > Non-terminal symbol
157
<print-item> ::= <expression>
158
"END" "SELECT"
159
8.3.10 1.10 Data Statements
<data-stmt> ::= "DATA" <literal> { "," <literal> }
160
| "(" <expression> ")"
<exponent> ::= ( "E" | "e" | "D" | "d" ) [ "+" | "-" ] <digit> { <digit> }
<letter> ::= "A" | "B" | ... | "Z" | "a" | "b" | ... | "z"
<digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<octal-digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7"
161
8.4 2. Lexical Structure
This section describes the lexical elements that make up xbasic64 programs: the character set,
comments, identifiers, keywords, literals, and operators.
162
Type Suffixes: Identifiers may end with a type suffix character: - % - Integer (16-bit signed) - &
- Long (32-bit signed) - ! - Single-precision float (32-bit) - # - Double-precision float (64-bit) - $ -
String
Examples:
Counter ' Unsuffixed (defaults to Double)
Count% ' Integer
TotalAmount& ' Long
Temperature! ' Single
PreciseValue# ' Double
UserName$ ' String
my_var_123 ' Valid identifier with underscore
Reserved Words: The following keywords cannot be used as identifiers:
AND APPEND AS CASE CLOSE CLS DATA
DIM DO ELSE ELSEIF END ENDIF ENDFUNCTION
ENDSUB ENDSELECT FOR FUNCTION GOSUB GOTO IF
INPUT LET LINE LOOP MOD NEXT NOT
ON OPEN OR OUTPUT PRINT READ REM
RESTORE RETURN SELECT STEP STOP SUB THEN
TO UNTIL WEND WHILE XOR
163
Scientific Notation: Using E or e for exponent:
1E5 ' 100000.0
2.5E-3 ' 0.0025
1.23E+10 ' 12300000000.0
Double-Precision Notation: Using D or d for exponent:
1D5 ' 100000.0 (double precision)
2.5D-3 ' 0.0025 (double precision)
The D exponent marker is converted to E internally but indicates double-precision intent.
164
AND Bitwise/logical AND
OR Bitwise/logical OR
XOR Bitwise/logical XOR
NOT Bitwise/logical NOT (unary)
Punctuation:
( Left parenthesis
) Right parenthesis
, Comma (separator)
; Semicolon (print separator, no space)
: Colon (statement separator)
# Hash (file number marker)
8.5 3. Statements
This section documents all statement types supported by xbasic64, including their syntax and
semantics.
165
8.5.2 3.2 PRINT Statement
Syntax:
PRINT [#filenumber,] [item [separator item]...] [separator]
Separators: - ; (semicolon) - No space between items - , (comma) - Tab to next print zone
Semantics: Outputs values to the console or a file. A trailing semicolon suppresses the newline.
Examples:
PRINT "Hello, World!"
PRINT X; Y; Z ' No spaces
PRINT A, B, C ' Tab-separated
PRINT "Value: "; X ' Mixed
PRINT "Enter value: "; ' No newline
PRINT ' Blank line
PRINT #1, "File output" ' Write to file #1
166
8.5.5 3.5 IF Statement
Single-Line Syntax:
IF condition THEN statement [ELSE statement]
Block Syntax:
IF condition THEN
statements...
[ELSEIF condition THEN
statements...]
[ELSE
statements...]
END IF
Semantics: Conditional execution. The condition is evaluated; if non-zero (true), the THEN
branch executes; otherwise, the ELSE branch (if present) executes.
Examples:
' Single-line
IF X > 0 THEN PRINT "Positive"
IF X > 0 THEN Y = 1 ELSE Y = 0
167
FOR J = 10 TO 1 STEP -1
PRINT J
NEXT
168
IF X = 0 THEN GOTO ExitLoop
LOOP
169
CASE ELSE
PRINT "F"
END SELECT
GOTO StartLoop
GOTO 1000
Warning: Excessive use of GOTO can lead to “spaghetti code.” Prefer structured control flow
(FOR, WHILE, DO) when possible.
170
1000 PRINT "In subroutine"
1000 RETURN
Note: For modern code, prefer SUB and FUNCTION procedures over GOSUB.
171
SUB PrintGreeting(Name$)
PRINT "Hello, "; Name$; "!"
END SUB
SUB Swap(A, B)
Temp = A
A = B
B = Temp
END SUB
FUNCTION Factorial(N)
IF N <= 1 THEN
Factorial = 1
ELSE
Factorial = N * Factorial(N - 1)
END IF
END FUNCTION
172
8.5.16 3.16 Call Statement
Syntax:
name [([argument [, argument...]])]
name argument [, argument...]
Semantics: Calls a user-defined subroutine or function. Parentheses are optional for subroutines.
Examples:
MySub ' No arguments
MySub X, Y ' With arguments
MySub(X, Y) ' With parentheses
Result = MyFunc(A, B) ' Function call
READ A, B, C
READ X$, Y$
PRINT A, B, C
PRINT X$, Y$
173
8.5.18 3.18 File I/O Statements
[Link] 3.18.1 OPEN Statement
Syntax:
OPEN filename$ FOR mode AS #filenumber
Modes: - INPUT - Open for reading - OUTPUT - Open for writing (truncates existing file) - APPEND
- Open for writing (appends to existing file)
Examples:
OPEN "[Link]" FOR INPUT AS #1
OPEN "[Link]" FOR OUTPUT AS #2
OPEN "[Link]" FOR APPEND AS #3
File Numbers: Range from 1 to 255. Each open file must have a unique number.
174
INPUT #1, X, Y, Z
LINE INPUT #1, TextLine$
Complete File I/O Example:
' Write data
OPEN "[Link]" FOR OUTPUT AS #1
PRINT #1, "John"
PRINT #1, 25
PRINT #1, 50000.00
CLOSE #1
175
Semantics: Terminates program execution. Historically used for debugging breakpoints. In xba-
sic64, behaves identically to END.
Example:
IF Error THEN STOP
8.6 4. Expressions
This section describes the expression syntax, operators, precedence, and associativity rules in xba-
sic64.
A% = 5 ' Integer
B& = 1000000 ' Long
C = A% + B& ' C is Long
176
String/Numeric Mixing: Not allowed. Use VAL() to convert string to number, STR$() to
convert number to string.
Boolean Values: - True: -1 (all bits set: 0xFFFFFFFF for Long, 0xFFFF for Integer) - False:
0
This matches GW-BASIC/QuickBASIC semantics and allows bitwise operations on boolean results.
Examples:
177
PRINT 5 > 3 ' -1 (true)
PRINT 5 < 3 ' 0 (false)
PRINT 5 = 5 ' -1 (true)
PRINT 5 <> 5 ' 0 (false)
Dual Purpose: These operators work as both logical operators (for boolean expressions) and
bitwise operators (for integer manipulation).
Logical Examples:
IF X > 0 AND Y > 0 THEN PRINT "Both positive"
IF A = 1 OR B = 1 THEN PRINT "At least one is 1"
IF NOT (X = 0) THEN PRINT "X is not zero"
IF (A > 0) XOR (B > 0) THEN PRINT "Exactly one is positive"
Bitwise Examples:
Flags% = &H0F ' Binary: 00001111
Mask% = &H03 ' Binary: 00000011
178
8.6.7 4.7 String Concatenation
Operator: +
Syntax:
string1 + string2
Semantics: Concatenates two strings into a new string.
Examples:
First$ = "Hello"
Last$ = "World"
Full$ = First$ + " " + Last$ ' "Hello World"
179
[Link] 4.9.1 Mathematical Functions
RND Behavior:
X = RND ' Next random number
X = RND(0) ' Same as RND
X = RND(-1) ' Reseed with system time
String Indexing: String functions use 1-based indexing (first character is at position 1).
Examples:
180
S$ = "Hello, World!"
PRINT LEN(S$) ' 13
PRINT LEFT$(S$, 5) ' "Hello"
PRINT RIGHT$(S$, 6) ' "World!"
PRINT MID$(S$, 8, 5) ' "World"
PRINT INSTR(S$, "World") ' 8
Rounding: CINT and CLNG use “banker’s rounding” (round to nearest even).
Example:
Start# = TIMER
' ... do work ...
Elapsed# = TIMER - Start#
PRINT "Time: "; Elapsed#; " seconds"
181
' Check if X is in range [10, 20]
InRange = (X >= 10) AND (X <= 20)
' String
Name$ = "Alice"
Message$ = "Hello, World!"
182
PRINT "Long: "; Population&
PRINT "Single: "; Temperature!
PRINT "Double: "; PreciseValue#
PRINT "String: "; Name$
A = 10
B = 3
' Precedence
PRINT "2 + 3 * 4 = "; 2 + 3 * 4 ' 14
PRINT "(2 + 3) * 4 = "; (2 + 3) * 4 ' 20
X = 10
Y = 20
183
PRINT "NOT (A = 0): "; NOT (A = 0) ' -1 (true)
First$ = "Hello"
Last$ = "World"
' Concatenation
Full$ = First$ + " " + Last$
PRINT Full$ ' "Hello World"
Value = 42
Str$ = STR$(Value)
PRINT "Number to string: "; Str$ ' "42"
184
DIM Names$(5) ' String array
FOR I = 0 TO 2
PRINT Names$(I)
NEXT I
' Single-line IF
185
X = 10
IF X > 0 THEN PRINT "Positive"
' Block IF
Score = 85
IF Score >= 90 THEN
PRINT "Grade: A"
PRINT "Excellent!"
ELSEIF Score >= 80 THEN
PRINT "Grade: B"
PRINT "Good job!"
ELSEIF Score >= 70 THEN
PRINT "Grade: C"
PRINT "Passing"
ELSE
PRINT "Grade: F"
PRINT "Need improvement"
END IF
' Nested IF
Age = 25
HasLicense = -1 ' True
186
PRINT "FOR loop (10 to 1, step -2):"
FOR I = 10 TO 1 STEP -2
PRINT I;
NEXT I
PRINT
187
PRINT X;
X = X + 1
LOOP
PRINT
188
' SELECT CASE with strings
INPUT "Continue (yes/no)? ", Answer$
FUNCTION Factorial(N)
IF N <= 1 THEN
Factorial = 1
ELSE
Factorial = N * Factorial(N - 1)
END IF
END FUNCTION
FUNCTION Max(A, B)
IF A > B THEN
Max = A
ELSE
Max = B
END IF
END FUNCTION
189
' Use functions
PRINT "Square of 5: "; Square(5)
PRINT "Factorial of 5: "; Factorial(5)
PRINT "Max of 10 and 20: "; Max(10, 20)
190
' Write to file
OPEN "[Link]" FOR OUTPUT AS #1
PRINT #1, "Line 1: Hello"
PRINT #1, "Line 2: World"
PRINT #1, 42
PRINT #1, 3.14159
CLOSE #1
191
1000 REM Subroutine
1000 PRINT "Inside subroutine"
1000 RETURN
CLS
PRINT "==================================="
PRINT " NUMBER GUESSING GAME"
PRINT "==================================="
PRINT
192
IF Guess = Target THEN
PRINT "Congratulations! You guessed it in "; Guesses; " tries!"
GOTO GameOver
ELSEIF Guess < Target THEN
PRINT "Too low! ";
ELSE
PRINT "Too high! ";
END IF
GameOver:
PRINT
INPUT "Play again (yes/no)? ", Answer$
CLS
PRINT "Fibonacci Sequence Calculator"
PRINT "=============================="
PRINT
IF N < 1 THEN
PRINT "Must be at least 1"
END
END IF
193
ELSE
Fib = Fib(N - 1) + Fib(N - 2)
END IF
END FUNCTION
FibIterative N
CLS
PRINT "Array Sorting Example"
PRINT "====================="
PRINT
194
PRINT
PRINT
8.8 Summary
This language reference provides a complete specification of xbasic64, including:
1. Formal Grammar: EBNF notation for all language constructs
2. Lexical Structure: Character set, comments, identifiers, literals, and operators
3. Statements: Complete documentation of all 21 statement types
4. Expressions: Operators, precedence, associativity, and built-in functions
5. Examples: Comprehensive code examples for every language feature
For implementation details and compiler internals, see the other documentation files in this series.
195