
Rpg Migration Analyzer
- 32 installs
- 14 repo stars
- Updated January 23, 2026
- dauquangthanh/hanoi-rainbow
RPG Migration Analyzer is an agent skill that analyzes Report Program Generator sources on IBM i systems so teams can plan Java migrations with POJO mappings, dependency reports, and implementation strategies.
About
RPG Migration Analyzer is a Hanoi Rainbow skill for AS/400 and IBM i modernization: it parses RPG source, preserves business logic, maps data structures and file I/O to Java equivalents, and outputs migration reports and complexity estimates. Reach for it when planning or executing RPG-to-Java rewrites rather than generic API design.
- D-spec to POJO and BigDecimal type mapping
- F-spec READ/CHAIN/WRITE to JPA and JDBC patterns
- Program dependency and CALLB/CALLP analysis
- Migration reports with Java implementation strategies
Rpg Migration Analyzer by the numbers
- 32 all-time installs (skills.sh)
- Ranked #53 of 89 Java & JVM skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dauquangthanh/hanoi-rainbow --skill rpg-migration-analyzerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 32 |
|---|---|
| repo stars | ★ 14 |
| Last updated | January 23, 2026 |
| Repository | dauquangthanh/hanoi-rainbow ↗ |
How do you understand RPG business logic, data structures, and file dependencies well enough to estimate a credible Java migration?
Analyze RPG III/IV/ILE sources on IBM i, map D-specs and file I/O to Java POJOs and JPA, and estimate modernization complexity.
Who is it for?
Engineers modernizing AS/400 or IBM i applications who have .rpg, .rpgle, or .RPGLE sources and target Java services.
Skip if: Greenfield Java APIs with no RPG legacy or teams only migrating OS infrastructure without application rewrite.
When should I use this skill?
You mention RPG analysis, AS/400 modernization, IBM i migration, packed decimal conversion, or RPG-to-Java mapping.
What you get
You receive migration reports, complexity estimates, Java POJO and JPA sketches, and mapped equivalents for RPG specs and I/O operations.
Files
RPG Migration Analyzer
Analyzes legacy RPG programs (RPG III/IV/ILE) from AS/400 and IBM i systems for migration to modern Java applications, extracting business logic, data structures, file operations, and generating actionable migration strategies.
Overview
This skill provides comprehensive analysis and migration planning for RPG (Report Program Generator) applications. It extracts program specifications, converts RPG data types to Java equivalents, maps file operations to modern database access patterns, and generates implementation-ready Java code structures.
Key Migration Focus: RPG to Java with proper handling of packed decimals (BigDecimal), data structures (POJOs), file operations (JPA/JDBC), indicators (boolean variables), and business logic preservation.
When to Use This Skill
Use this skill when:
- Analyzing RPG source files (.rpg, .rpgle, .RPGLE) for modernization
- Planning migration from AS/400 or IBM i systems to Java
- Converting RPG data structures (D-specs) to Java classes
- Mapping RPG file operations (F-specs) to database access patterns
- Understanding RPG program dependencies and call chains
- Generating Java code equivalents from RPG business logic
- Estimating complexity and effort for RPG migration projects
- Creating migration documentation and strategy reports
- Modernizing legacy mainframe applications to microservices
- User mentions: RPG analysis, AS/400 migration, IBM i modernization, Report Program Generator, packed decimal conversion
Core Capabilities
1. Program Analysis
Extract and analyze RPG program components:
- Specification types: H-spec (header/control), F-spec (file definitions), D-spec (data definitions), C-spec (calculation/logic), P-spec (procedures)
- Data structures: D-specs with nested structures, arrays (DIM), external references (EXTNAME), qualifiers (LIKEDS, QUALIFIED)
- File definitions: Physical files, logical files, display files (WORKSTN), printer files
- Business logic: Calculation specifications, control structures (IF/ELSE/DO/FOR), expressions (EVAL)
- Indicators: Legacy indicators (IN01-IN99), built-in indicators (INLR,INOF)
- Built-in functions: String functions (%SUBST, %TRIM, %SCAN), date functions (%DATE, %DAYS), math functions (%DEC, %INT), file status (%EOF, %FOUND, %ERROR)
- Error handling: %ERROR, %STATUS, ON-ERROR blocks
2. Data Structure Mapping
Convert RPG data definitions to Java equivalents:
- D-spec conversion: Data structure definitions to Java classes (POJOs)
- Data type mapping:
- Packed decimal (P) →
BigDecimal(preserve precision) - Zoned decimal (S) →
BigDecimal(decimal with sign) - Character (A) →
String - Date (D) →
LocalDate - Time (T) →
LocalTime - Timestamp (Z) →
LocalDateTime - Indicator (N) →
boolean - Binary integer (I) →
intorlong - Arrays (DIM): Convert to Java
List<T>or arraysT[] - Nested data structures: Convert LIKEDS to nested Java classes
- External data structures (EXTNAME): Generate JPA entities from database table definitions
- Initialization (INZ): Map to Java field initializers or constructors
3. File Operations
Parse and convert RPG file I/O to modern database access:
- File types: Physical files (DISK), logical files (keyed access), display files (WORKSTN), printer files (PRINTER)
- Access methods: Sequential (full read), keyed (direct access by key), arrival sequence
- I/O operations:
- READ/READE → JPA query methods, JDBC ResultSet iteration
- WRITE → JPA persist(), JDBC INSERT
- UPDATE → JPA merge(), JDBC UPDATE
- DELETE → JPA remove(), JDBC DELETE
- CHAIN → JPA findById(), Optional pattern
- SETLL/READE loop → JPA findBy...() queries with ordering
- File status: %EOF (end of file), %FOUND (record found), %ERROR (I/O error) → Java exceptions or Optional
- Transaction boundaries: Identify commit boundaries (COMMIT operation code)
4. Java Migration Strategy
Generate modern Java implementation patterns:
- POJOs: Plain Old Java Objects from D-spec data structures
- JPA Entities: @Entity annotations for database tables (from EXTNAME files)
- Repository pattern: Spring Data JPA repositories for file operations
- Service methods: Business logic from procedures and subroutines
- Bean Validation: @NotNull, @Size, @DecimalMin/Max from RPG field validations
- Exception handling: Convert %ERROR patterns to try-catch blocks with custom exceptions
- Collections: Java Collections API (List, Map, Set) from RPG arrays and data structures
- DTOs: Data Transfer Objects for service boundaries
- Transaction management: @Transactional annotations for commit boundaries
5. Dependency Analysis
Map program relationships and external dependencies:
- Program calls: CALLB (bound procedure calls), CALLP (prototyped procedure calls)
- Service programs: BNDDIR (binding directories), *SRVPGM objects
- File dependencies: All physical/logical files accessed by the program
- Database tables: DB2 for i tables referenced (EXTNAME)
- /COPY members: Include files, copy source members, prototypes
- Call chains: Identify calling programs and called programs
- Shared data areas: *DTAARA usage
- Message queues: QMHSNDPM (send program message)
Instructions
Follow these steps to analyze and migrate RPG programs to Java:
Step 1: Locate RPG Source Files
Find RPG source files (.rpg, .rpgle, .RPGLE extensions for RPG III/IV/ILE free-format).
find . -name "*.rpg" -o -name "*.rpgle" -o -name "*.RPGLE"Step 2: Analyze Program Structure
Extract specifications (H, F, D, C, P), data structures, file definitions, procedures, and dependencies.
Automation: Run scripts/extract-structure.py for automated extraction.
Step 3: Map Data Types
Convert RPG to Java types - CRITICAL: Always use BigDecimal for packed/zoned decimals (never float/double).
| RPG Type | Java Type | Key Notes |
|---|---|---|
nP m (packed) | BigDecimal | MUST preserve precision |
nS m (zoned) | BigDecimal | Decimal with sign |
A (char) | String | Character data |
D/T/Z (date/time) | LocalDate/LocalTime/LocalDateTime | Date fields |
N (indicator) | boolean | True/False flags |
I (integer) | int or long | Binary integer |
DIM(n) (array) | List<T> or T[] | Arrays |
Step 4: Convert Code Patterns
Transform RPG operations to Java - key conversions:
- Calculations: EVAL expressions → BigDecimal arithmetic methods
- File I/O: CHAIN →
findById()with Optional, READ → query methods - Arrays: Adjust 1-based (RPG) to 0-based (Java) indexing
- Strings: %SUBST(1-based) → substring(0-based)
- Indicators: *IN01 → named boolean variables
See pseudocode-rpg-rules.md for comprehensive conversion patterns.
Step 5: Generate Java Implementation
Create:
1. POJOs from D-spec data structures (scripts/generate-java-classes.py) 2. JPA entities for database tables 3. Repository interfaces (Spring Data JPA) 4. Service methods for business logic 5. Exception handling and validation
Step 6: Analyze Dependencies
Map program calls (CALLB/CALLP), file dependencies, /COPY members, service programs.
Automation: Run scripts/analyze-dependencies.sh or .ps1
Step 7: Create Migration Report
Generate documentation with program overview, dependencies, data mappings, Java design, and complexity estimate.
Template: Use assets/migration-report-template.md
Step 8: Validate and Test
Verify: BigDecimal usage, index adjustments, transaction boundaries, error handling, unit tests with AS/400 data samples.
Quick Reference
Critical Migration Rules
1. ALWAYS use BigDecimal for RPG packed (P) and zoned (S) decimals - never float/double 2. Adjust indexing: RPG uses 1-based arrays/strings, Java uses 0-based 3. Replace indicators: Convert IN01-IN99 to descriptive boolean variables 4. File operations: CHAIN → findById(), READ → query methods with Optional 5. String functions: %SUBST(1:10) → substring(0, 10) - adjust positions 6. Date operations: RPG date functions → LocalDate/LocalTime API 7. Transactions: Identify COMMIT operations → @Transactional annotations 8. Error handling: %ERROR/%STATUS → try-catch with custom exceptions
Example: Data Structure to Java Class
RPG D-spec:
D Employee DS
D EmpId 6 0
D EmpName 30 A
D Salary 63 2PJava POJO:
public class Employee {
private int empId;
private String empName;
private BigDecimal salary; // 6 digits, 2 decimals
// getters/setters
}Example: File Operation Conversion
RPG CHAIN:
C custId CHAIN CUSTFILE
C IF %FOUND(CUSTFILE)Java with JPA:
customerRepository.findById(custId).ifPresent(customer -> {
// process customer
});// Service usage
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
public Optional<Customer> findCustomer(Integer custId) {
return customerRepository.findById(custId);
}
}Edge Cases
Case 1: Packed Decimal Precision
Problem: Using double/float causes precision errors. Solution: Always use BigDecimal from String literals: new BigDecimal("123.45")
Case 2: Array Index Shift
Problem: RPG 1-based, Java 0-based. Solution: Adjust all array/string index references. Test thoroughly.
Case 3: External Data Structures
Problem: EXTNAME without DDL source. Solution: Use DSPFFD command, query DB2 SYSTABLES/SYSCOLUMNS, or create entities from runtime data.
Case 4: Legacy Indicators
Problem: IN01-IN99 for control flow. Solution: Replace with descriptive booleans: boolean invalidAmount = false;
Case 5: Date Century Handling
Problem: 2-digit years (Y2K). Solution: Use 4-digit LocalDate, apply century window logic, document assumptions.
Guidelines
1. BigDecimal mandatory: Never float/double for packed/zoned decimals 2. Named booleans: Replace IN01-99 with descriptive names 3. Database access: Map file I/O to JPA/JDBC operations 4. JPA entities: Create from EXTNAME physical file definitions 5. Exception handling: Convert %ERROR/%FOUND to exceptions/Optional 6. Test with AS/400 data: Validate with actual legacy system data 7. Transactions: Identify COMMIT operations → @Transactional 8. Document rules: Extract and document implicit business logic 9. Character encoding: Verify EBCDIC → Unicode conversions 10. Batch processing*: Convert batch jobs to Spring Batch framework
Error Handling
Type 1: File I/O Errors
Detection: %ERROR or %STATUS checks. Handling: Use try-catch with custom exceptions (CustomerNotFoundException, DataAccessException)
Type 2: Arithmetic Overflow
Detection: Insufficient field size. Handling: BigDecimal with appropriate scale/precision, catch ArithmeticException
Type 3: Missing Dependencies
Detection: Missing /COPY members. Handling: Track all includes, create shared interfaces, use Maven/Gradle dependencies
Additional Resources
See detailed documentation in the references/ directory:
RPG Translation Rules (Organized by Topic)
The RPG translation rules are organized into focused, topic-specific files for easier navigation:
- [pseudocode-rpg-rules.md](references/pseudocode-rpg-rules.md) - Master index with quick start guide and file navigation
- [pseudocode-rpg-core-rules.md](references/pseudocode-rpg-core-rules.md) - Foundation: specs, data types, basic operations, file I/O
- [pseudocode-rpg-functions.md](references/pseudocode-rpg-functions.md) - Built-in functions (BIFs): string, date/time, math, array functions
- [pseudocode-rpg-data-structures.md](references/pseudocode-rpg-data-structures.md) - Data structure patterns: QUALIFIED, LIKEDS, OVERLAY, I/O specs
- [pseudocode-rpg-patterns.md](references/pseudocode-rpg-patterns.md) - Common idioms, translation patterns, pitfalls, critical rules
- [pseudocode-rpg-advanced.md](references/pseudocode-rpg-advanced.md) - ILE RPG, embedded SQL, web services, IFS, XML/JSON, threading
- [pseudocode-rpg-migration-guide.md](references/pseudocode-rpg-migration-guide.md) - Migration workflow, best practices, refactoring strategies
Other Reference Documentation
- [pseudocode-common-rules.md](references/pseudocode-common-rules.md) - General pseudocode syntax and conventions
- [testing-strategy.md](references/testing-strategy.md) - Testing approach for RPG to Java migration validation
- [transaction-handling.md](references/transaction-handling.md) - AS/400 transaction patterns to Java transaction management
- [performance-patterns.md](references/performance-patterns.md) - Performance optimization patterns for migrated code
- [messaging-integration.md](references/messaging-integration.md) - Message queue and integration patterns for IBM i systems
Scripts
Python and shell scripts for automated analysis in scripts/:
analyze-dependencies.sh/ps1- Scans RPG source for CALLB, CALLP, /COPY; generates dependency graphextract-structure.py- Parses RPG specs (H, F, D, C, P); outputs structured JSONgenerate-java-classes.py- Creates Java POJOs from RPG data structures with proper typesestimate-complexity.py- Calculates migration complexity score and effort estimate
Templates
- [migration-report-template.md](assets/migration-report-template.md) - Standard format for migration analysis reports
- [java-class-template.java](assets/java-class-template.java) - Template for generated Java classes
analyze-dependencies.sh / .ps1
- Scans RPG source files for CALLB, CALLP, /COPY references
- Generates dependency graph in JSON format
- Identifies circular dependencies
Usage:
./scripts/analyze-dependencies.sh /path/to/rpg/sourceextract-structure.py
- Extracts program structure (H/F/D/C/P specs)
- Lists all variables, data structures, files
- Identifies subroutines and procedures
- Outputs JSON structure file
Usage:
python scripts/extract-structure.py PROGRAM.rpgle --output structure.jsongenerate-java-classes.py
- Generates Java POJO classes from RPG data structures
- Creates proper field types (BigDecimal for packed decimals)
- Adds getters, setters, constructors
- Generates Bean Validation annotations
Usage:
python scripts/generate-java-classes.py structure.json --output-dir ./src/main/javaestimate-complexity.py
- Calculates migration complexity score
- Analyzes LOC, dependencies, file operations
- Provides effort estimate (hours/days)
- Generates priority ranking
Usage:
python scripts/estimate-complexity.py structure.json --report complexity-report.mdTemplates
Use the migration report template for consistent documentation:
- migration-report-template.md - Standard format for migration analysis reports
- java-class-template.java - Template for generated Java classes with proper structure
Integration with Development Tools
This skill integrates with various development and analysis tools:
IBM i / AS/400 Tools
- Source Entry Utility (SEU): Extract source code from AS/400
- Programming Development Manager (PDM): Access member lists and source files
- WRKMBRPDM: Work with source members
- DSPFFD: Display file field descriptions for database structure analysis
- DSPPGMREF: Display program references and dependencies
Database Tools
- DB2 for i: Query system catalogs (SYSTABLES, SYSCOLUMNS) for metadata
- IBM Data Studio: Visual database design and SQL development
- DBeaver: Universal database tool with DB2 support
Modern Development Environment
- IntelliJ IDEA: Java development with Spring Boot support
- Eclipse: Java IDE with JPA tooling
- VS Code: Lightweight editor with Java extensions
- Git: Version control for both legacy source and new Java code
Migration Support Tools
- Spring Initializr: Bootstrap Spring Boot projects
- JPA Buddy: IntelliJ plugin for JPA entity generation
- Liquibase/Flyway: Database migration version control
- Maven/Gradle: Build automation and dependency management
Testing and Validation
- JUnit 5: Unit testing framework
- Spring Boot Test: Integration testing support
- Mockito: Mocking framework for unit tests
- TestContainers: Database integration testing with containers
package {{PACKAGE_NAME}};
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.ArrayList;
/**
* Generated from RPG data structure: {{STRUCTURE_NAME}}
*
* Migration Date: {{GENERATION_DATE}}
* Original Source: {{SOURCE_FILE}}
*
* This class represents the Java equivalent of the RPG data structure.
* Auto-generated - review and adjust as needed for your application.
*/
public class {{CLASS_NAME}} {
// ========================================
// Fields (from RPG D-spec)
// ========================================
{{FIELD_DECLARATIONS}}
// ========================================
// Constructors
// ========================================
public {{CLASS_NAME}}() {
// Default constructor
}
public {{CLASS_NAME}}({{CONSTRUCTOR_PARAMETERS}}) {
{{CONSTRUCTOR_ASSIGNMENTS}}
}
// ========================================
// Getters and Setters
// ========================================
{{GETTERS_AND_SETTERS}}
// ========================================
// Business Methods (from RPG Subroutines/Procedures)
// ========================================
{{BUSINESS_METHODS}}
// ========================================
// Validation Methods
// ========================================
/**
* Validates the data in this object.
* Implements RPG field validation rules.
*
* @return true if valid, false otherwise
*/
public boolean isValid() {
// TODO: Implement validation logic from COBOL
return true;
}
// ========================================
// Utility Methods
// ========================================
@Override
public String toString() {
return "{{CLASS_NAME}}{" +
{{TO_STRING_FIELDS}} +
"}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
{{CLASS_NAME}} that = ({{CLASS_NAME}}) o;
// TODO: Implement equality check
return false;
}
@Override
public int hashCode() {
// TODO: Implement hash code
return 0;
}
}
RPG Program Migration Report
Program: {{PROGRAM_NAME}} Migration Date: {{MIGRATION_DATE}} Analyst: {{ANALYST_NAME}} Status: {{STATUS}}
---
1. Executive Summary
Program Overview
- Purpose: {{PROGRAM_PURPOSE}}
- Type: {{PROGRAM_TYPE}} (Batch/Online/Utility)
- Lines of Code: {{LOC}}
- Complexity: {{COMPLEXITY_LEVEL}}
- Estimated Effort: {{EFFORT_DAYS}} person-days
Migration Recommendation
{{RECOMMENDATION}}
---
2. Source Analysis
Program Structure
Specifications
- H-Spec (Header/Control): Line {{H_SPEC_LINE}}
- F-Spec (File Definitions): Line {{F_SPEC_LINE}}
- D-Spec (Data Definitions): Line {{D_SPEC_LINE}}
- C-Spec and P-Spec (Calculation/Procedures): Line {{C_SPEC_LINE}}
Key Data Structures
| Structure | Type | Lines | Description |
|---|
{{DATA_STRUCTURES_TABLE}}
/COPY Members Used
| Copy Member | Purpose | Lines |
|---|
{{COPY_MEMBERS_TABLE}}
Business Logic Summary
{{BUSINESS_LOGIC_SUMMARY}}
Control Flow
{{CONTROL_FLOW_DESCRIPTION}}
---
3. Dependencies
Program Calls
| Called Program | Purpose | Frequency |
|---|
{{PROGRAM_CALLS_TABLE}}
File Operations
| File Name | Access Mode | Operations |
|---|
{{FILE_OPERATIONS_TABLE}}
Database Operations
| Table | Operations | Estimated Rows |
|---|
{{DATABASE_OPERATIONS_TABLE}}
Dependency Graph
{{DEPENDENCY_GRAPH}}---
4. Java Design
Proposed Architecture
Package Structure
{{PACKAGE_STRUCTURE}}Class Design
| Java Class | Purpose | RPG Equivalent |
|---|
{{CLASS_DESIGN_TABLE}}
Method Signatures
{{METHOD_SIGNATURES}}Data Model
{{DATA_MODEL_DESCRIPTION}}
Service Layer
{{SERVICE_LAYER_DESCRIPTION}}
---
5. Complexity Assessment
Metrics
- Cyclomatic Complexity: {{CYCLOMATIC_COMPLEXITY}}
- Number of Paragraphs: {{PARAGRAPH_COUNT}}
- External Dependencies: {{DEPENDENCY_COUNT}}
- File Operations: {{FILE_OP_COUNT}}
- Database Operations: {{DB_OP_COUNT}}
- Complexity Score: {{COMPLEXITY_SCORE}}
Risk Factors
{{RISK_FACTORS_LIST}}
Technical Challenges
1. {{CHALLENGE_1}} 2. {{CHALLENGE_2}} 3. {{CHALLENGE_3}}
---
6. Migration Strategy
Approach
{{MIGRATION_APPROACH}}
Phase 1: Preparation ({{PHASE1_DAYS}} days)
- [ ] {{PREP_TASK_1}}
- [ ] {{PREP_TASK_2}}
- [ ] {{PREP_TASK_3}}
Phase 2: Implementation ({{PHASE2_DAYS}} days)
- [ ] {{IMPL_TASK_1}}
- [ ] {{IMPL_TASK_2}}
- [ ] {{IMPL_TASK_3}}
Phase 3: Testing ({{PHASE3_DAYS}} days)
- [ ] {{TEST_TASK_1}}
- [ ] {{TEST_TASK_2}}
- [ ] {{TEST_TASK_3}}
Phase 4: Deployment ({{PHASE4_DAYS}} days)
- [ ] {{DEPLOY_TASK_1}}
- [ ] {{DEPLOY_TASK_2}}
- [ ] {{DEPLOY_TASK_3}}
---
7. Testing Plan
Unit Testing
{{UNIT_TEST_PLAN}}
Integration Testing
{{INTEGRATION_TEST_PLAN}}
Parallel Run Testing
{{PARALLEL_RUN_PLAN}}
Performance Testing
{{PERFORMANCE_TEST_PLAN}}
---
8. Data Migration
Data Structures to Migrate
{{DATA_MIGRATION_STRUCTURES}}
Migration Scripts Required
- [ ] {{MIGRATION_SCRIPT_1}}
- [ ] {{MIGRATION_SCRIPT_2}}
- [ ] {{MIGRATION_SCRIPT_3}}
Validation Approach
{{VALIDATION_APPROACH}}
---
9. Implementation Notes
Special Considerations
{{SPECIAL_CONSIDERATIONS}}
Code Patterns
{{CODE_PATTERNS}}
Performance Optimization
{{PERFORMANCE_NOTES}}
---
10. Timeline and Resources
Schedule
| Phase | Start Date | End Date | Duration |
|---|---|---|---|
| Preparation | {{P1_START}} | {{P1_END}} | {{P1_DURATION}} |
| Implementation | {{P2_START}} | {{P2_END}} | {{P2_DURATION}} |
| Testing | {{P3_START}} | {{P3_END}} | {{P3_DURATION}} |
| Deployment | {{P4_START}} | {{P4_END}} | {{P4_DURATION}} |
Resource Requirements
- Java Developers: {{JAVA_DEV_COUNT}}
- RPG Analysts: {{RPG_ANALYST_COUNT}}
- QA Engineers: {{QA_COUNT}}
- DevOps: {{DEVOPS_COUNT}}
Dependencies
- [ ] {{EXTERNAL_DEPENDENCY_1}}
- [ ] {{EXTERNAL_DEPENDENCY_2}}
- [ ] {{EXTERNAL_DEPENDENCY_3}}
---
11. Success Criteria
Functional
- [ ] All business logic correctly migrated
- [ ] 100% test coverage for critical paths
- [ ] Parallel run matches RPG output (99.9%+)
Non-Functional
- [ ] Performance meets or exceeds RPG
- [ ] Response time < {{RESPONSE_TIME_TARGET}}ms
- [ ] Throughput >= {{THROUGHPUT_TARGET}} TPS
Quality
- [ ] Code review completed
- [ ] Documentation complete
- [ ] Knowledge transfer completed
---
12. Appendices
A. RPG Source Reference
{{RPG_SOURCE_EXCERPTS}}
B. Generated Java Code Samples
{{JAVA_CODE_SAMPLES}}
C. Test Cases
{{TEST_CASES}}
D. References
- RPG Program:
{{RPG_FILE_PATH}} - Design Document:
{{DESIGN_DOC_PATH}} - Test Data:
{{TEST_DATA_PATH}}
---
Report Generated: {{REPORT_DATE}} Tool: RPG Migration Analyzer Agent Skill v1.0.0
Messaging Integration Patterns
Guide for migrating mainframe messaging patterns (MQ, CICS transient data) to modern Java messaging.
Overview
Mainframe systems use IBM MQ, CICS queues, and transient data for asynchronous communication. This guide shows how to migrate these patterns to modern Java messaging frameworks.
Common Mainframe Patterns
1. IBM MQ Messages
COBOL Example:
EXEC CICS WRITEQ TS
QUEUE('ORDERQ')
FROM(ORDER-MESSAGE)
LENGTH(ORDER-LENGTH)
END-EXEC.
EXEC CICS READQ TS
QUEUE('ORDERQ')
INTO(ORDER-MESSAGE)
LENGTH(ORDER-LENGTH)
END-EXEC.2. CICS Transient Data
EXEC CICS WRITEQ TD
QUEUE('LOGG')
FROM(LOG-MESSAGE)
LENGTH(LOG-LENGTH)
END-EXEC.Java Messaging Patterns
Pattern 1: Spring JMS with ActiveMQ
Configuration:
@Configuration
@EnableJms
public class JmsConfig {
@Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory("tcp://localhost:61616");
}
@Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate template = new JmsTemplate(connectionFactory);
template.setDefaultDestinationName("order.queue");
return template;
}
}Sender (equivalent to WRITEQ):
@Service
public class OrderMessageSender {
@Autowired
private JmsTemplate jmsTemplate;
public void sendOrder(Order order) {
jmsTemplate.convertAndSend("order.queue", order, message -> {
message.setStringProperty("orderType", order.getType());
message.setStringProperty("priority", order.getPriority());
return message;
});
}
}Receiver (equivalent to READQ):
@Service
public class OrderMessageReceiver {
@JmsListener(destination = "order.queue")
public void receiveOrder(Order order) {
log.info("Received order: {}", order.getId());
processOrder(order);
}
// Manual receive (blocking)
public Order receiveOrderManually() {
return (Order) jmsTemplate.receiveAndConvert("order.queue");
}
}Pattern 2: Spring AMQP with RabbitMQ
Configuration:
@Configuration
public class RabbitConfig {
@Bean
public Queue orderQueue() {
return new Queue("order.queue", true); // durable
}
@Bean
public Exchange orderExchange() {
return new TopicExchange("order.exchange");
}
@Bean
public Binding binding(Queue queue, Exchange exchange) {
return BindingBuilder
.bind(queue)
.to(exchange)
.with("order.#")
.noargs();
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(new Jackson2JsonMessageConverter());
return template;
}
}Sender:
@Service
public class RabbitOrderSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrder(Order order) {
rabbitTemplate.convertAndSend(
"order.exchange",
"order.new",
order
);
}
}Receiver:
@Service
public class RabbitOrderReceiver {
@RabbitListener(queues = "order.queue")
public void handleOrder(Order order) {
processOrder(order);
}
}Pattern 3: Spring Kafka
Configuration:
@Configuration
public class KafkaConfig {
@Bean
public ProducerFactory<String, Order> producerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
return new DefaultKafkaProducerFactory<>(config);
}
@Bean
public KafkaTemplate<String, Order> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
@Bean
public ConsumerFactory<String, Order> consumerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
config.put(ConsumerConfig.GROUP_ID_CONFIG, "order-processor");
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
config.put(JsonDeserializer.TRUSTED_PACKAGES, "*");
return new DefaultKafkaConsumerFactory<>(config);
}
}Producer:
@Service
public class KafkaOrderProducer {
@Autowired
private KafkaTemplate<String, Order> kafkaTemplate;
public void sendOrder(Order order) {
kafkaTemplate.send("order-topic", order.getId(), order)
.addCallback(
success -> log.info("Order sent: {}", order.getId()),
failure -> log.error("Failed to send order", failure)
);
}
}Consumer:
@Service
public class KafkaOrderConsumer {
@KafkaListener(topics = "order-topic", groupId = "order-processor")
public void consume(Order order) {
log.info("Consumed order: {}", order.getId());
processOrder(order);
}
}Migration Patterns
Pattern 1: Request-Reply
COBOL/MQ:
* Send request
MOVE 'TEMP-REPLY-Q' TO MQMD-REPLYTOQ
CALL 'MQPUT' USING ...
* Wait for reply
CALL 'MQGET' USING MQMD MQGMO REPLYQ-NAME ...Java/JMS:
@Service
public class RequestReplyService {
@Autowired
private JmsTemplate jmsTemplate;
public Response sendRequest(Request request) {
return (Response) jmsTemplate.convertSendAndReceive(
"request.queue",
request,
message -> {
message.setJMSReplyTo(
new ActiveMQQueue("reply.queue")
);
return message;
}
);
}
}Pattern 2: Dead Letter Queue
Configuration:
@Bean
public ReplyingKafkaTemplate<String, Order, OrderResult> replyingKafkaTemplate(
ProducerFactory<String, Order> pf,
KafkaMessageListenerContainer<String, OrderResult> container) {
return new ReplyingKafkaTemplate<>(pf, container);
}
@Bean
public DeadLetterPublishingRecoverer deadLetterPublishingRecoverer(
KafkaTemplate<String, Order> template) {
return new DeadLetterPublishingRecoverer(template);
}Usage:
@KafkaListener(topics = "order-topic")
public void processOrder(Order order) {
try {
validateAndProcess(order);
} catch (ValidationException e) {
// Will be sent to DLQ automatically
throw new RuntimeException("Invalid order", e);
}
}Pattern 3: Message Retry
Configuration:
@Configuration
public class RetryConfig {
@Bean
public ErrorHandler errorHandler(KafkaTemplate<String, Order> template) {
// Retry 3 times with exponential backoff
DefaultErrorHandler handler = new DefaultErrorHandler(
new DeadLetterPublishingRecoverer(template),
new FixedBackOff(1000L, 3L)
);
handler.addNotRetryableExceptions(ValidationException.class);
return handler;
}
}Message Format Conversion
COBOL Fixed-Length to JSON
COBOL Message:
01 ORDER-MESSAGE.
05 ORDER-ID PIC X(10).
05 CUSTOMER-ID PIC X(10).
05 AMOUNT PIC 9(7)V99 COMP-3.
05 ORDER-DATE PIC 9(8).Java DTO:
@Data
public class OrderMessage {
private String orderId;
private String customerId;
private BigDecimal amount;
private LocalDate orderDate;
// Converter from COBOL format
public static OrderMessage fromCobolBytes(byte[] bytes) {
// Parse fixed-length format
String orderId = new String(bytes, 0, 10).trim();
String customerId = new String(bytes, 10, 10).trim();
BigDecimal amount = CobolConverter.fromPackedDecimal(bytes, 20, 5);
LocalDate orderDate = CobolConverter.fromCobolDate(bytes, 25, 8);
return new OrderMessage(orderId, customerId, amount, orderDate);
}
}Transaction Management
XA Transactions with Messaging
@Configuration
@EnableTransactionManagement
public class XAMessagingConfig {
@Bean
public JmsTransactionManager transactionManager(
ConnectionFactory connectionFactory) {
return new JmsTransactionManager(connectionFactory);
}
}
@Service
public class TransactionalMessagingService {
@Transactional
public void processWithTransaction(Order order) {
// Save to database
orderRepository.save(order);
// Send message (part of same transaction)
jmsTemplate.convertAndSend("order.processed", order);
// Both commit or rollback together
}
}Monitoring and Error Handling
Message Metrics
@Component
public class MessagingMetrics {
private final Counter messagesReceived;
private final Counter messagesFailed;
private final Timer processingTime;
public MessagingMetrics(MeterRegistry registry) {
this.messagesReceived = registry.counter("messages.received");
this.messagesFailed = registry.counter("messages.failed");
this.processingTime = registry.timer("messages.processing.time");
}
@Around("@annotation(JmsListener)")
public Object monitorMessageProcessing(ProceedingJoinPoint pjp) throws Throwable {
messagesReceived.increment();
Timer.Sample sample = Timer.start();
try {
Object result = pjp.proceed();
sample.stop(processingTime);
return result;
} catch (Exception e) {
messagesFailed.increment();
throw e;
}
}
}Error Handling
@Service
public class MessagingErrorHandler {
@JmsListener(destination = "order.queue")
public void handleOrder(Order order, @Header("JMSRedelivered") boolean redelivered) {
try {
processOrder(order);
} catch (Exception e) {
if (redelivered) {
// Already retried, send to DLQ
sendToDeadLetterQueue(order, e);
} else {
// First failure, throw to trigger redelivery
throw new RuntimeException("Processing failed", e);
}
}
}
}Migration Checklist
- [ ] Identify all MQ queue usages
- [ ] Map CICS transient data queues
- [ ] Choose appropriate messaging technology (JMS/AMQP/Kafka)
- [ ] Design message formats (JSON vs. binary)
- [ ] Implement message converters
- [ ] Configure error handling and DLQ
- [ ] Set up monitoring and alerting
- [ ] Test message ordering guarantees
- [ ] Verify transaction behavior
- [ ] Performance test under load
- [ ] Document migration patterns
Performance Patterns for Migrated Code
This document provides guidance on optimizing Java code migrated from COBOL, addressing common performance challenges.
Overview
COBOL programs often process large volumes of data efficiently using mainframe-optimized patterns. When migrating to Java, it's important to maintain or improve performance while adapting to modern architectures.
Common Performance Patterns
1. Batch Processing with Streams
COBOL Pattern: Sequential file processing Java Solution: Use Java Streams for efficient batch processing
// Instead of loading all records into memory
List<Record> records = loadAllRecords(); // DON'T
// Use streaming
try (Stream<String> lines = Files.lines(path)) {
lines.map(this::parseRecord)
.filter(this::isValid)
.forEach(this::processRecord);
}2. Database Batch Operations
COBOL Pattern: Cursor processing with commits every N records Java Solution: JDBC batch updates
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
for (Record record : records) {
pstmt.setString(1, record.getId());
pstmt.setString(2, record.getName());
pstmt.addBatch();
if (++count % 1000 == 0) {
pstmt.executeBatch();
conn.commit();
}
}
pstmt.executeBatch();
conn.commit();
}3. Memory Management
Challenge: COBOL's fixed memory model vs. Java's heap Solution: Process in chunks, use pagination
public void processLargeFile(Path file) {
int batchSize = 1000;
List<Record> batch = new ArrayList<>(batchSize);
try (Stream<String> lines = Files.lines(file)) {
lines.forEach(line -> {
batch.add(parseRecord(line));
if (batch.size() >= batchSize) {
processBatch(new ArrayList<>(batch));
batch.clear();
}
});
if (!batch.isEmpty()) {
processBatch(batch);
}
}
}4. Parallel Processing
COBOL Pattern: Single-threaded sequential processing Java Solution: Parallel streams for CPU-bound operations
// For independent record processing
records.parallelStream()
.map(this::transform)
.forEach(this::save);
// Control parallelism
ForkJoinPool customPool = new ForkJoinPool(4);
customPool.submit(() ->
records.parallelStream()
.forEach(this::process)
).get();5. String Operations
COBOL Pattern: Fixed-length strings with spaces Java Solution: Efficient string handling
// Avoid creating many temporary strings
StringBuilder sb = new StringBuilder();
for (Record r : records) {
sb.append(r.getId()).append('|')
.append(r.getName()).append('\n');
}
String output = sb.toString();
// For fixed-length COBOL fields
String padded = String.format("%-20s", value); // Left-padded
String numeric = String.format("%010d", number); // Zero-padded6. Caching Lookup Tables
COBOL Pattern: In-memory tables loaded at startup Java Solution: Use efficient caching
// Simple cache
private final Map<String, RateEntry> rateCache = new HashMap<>();
// Caffeine cache with eviction
LoadingCache<String, RateEntry> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build(key -> loadRate(key));7. File I/O Optimization
COBOL Pattern: Blocked records for efficient I/O Java Solution: Buffered I/O with appropriate buffer sizes
// Reading
try (BufferedReader reader = new BufferedReader(
new FileReader(file), 8192 * 4)) { // 32KB buffer
String line;
while ((line = reader.readLine()) != null) {
processLine(line);
}
}
// Writing
try (BufferedWriter writer = new BufferedWriter(
new FileWriter(file), 8192 * 4)) {
for (Record r : records) {
writer.write(r.toLine());
writer.newLine();
}
}Performance Testing
Benchmark Template
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class MigrationBenchmark {
@Param({"1000", "10000", "100000"})
private int recordCount;
@Setup
public void setup() {
// Initialize test data
}
@Benchmark
public void testProcessing() {
// Your processing logic
}
}Best Practices
1. Profile Before Optimizing: Use JProfiler, YourKit, or JFR 2. Set Realistic Goals: Match or exceed COBOL performance 3. Test with Production Volumes: Use representative data sizes 4. Monitor JVM Metrics: Heap, GC, thread pools 5. Use Appropriate Data Structures: ArrayList vs. LinkedList, HashMap vs. TreeMap 6. Minimize Object Creation: Reuse objects in tight loops 7. Consider Parallel Processing: But measure - not always faster 8. Optimize Database Access: Use connection pooling, prepared statements
Common Pitfalls
❌ DON'T: Load entire files into memory ✅ DO: Stream and process incrementally
❌ DON'T: Use + for string concatenation in loops ✅ DO: Use StringBuilder or StringJoiner
❌ DON'T: Create new SimpleDateFormat in loops (not thread-safe) ✅ DO: Use DateTimeFormatter (thread-safe) or ThreadLocal
❌ DON'T: Ignore connection pooling ✅ DO: Use HikariCP or similar
❌ DON'T: Assume parallel is always faster ✅ DO: Benchmark and measure
Monitoring Migration Performance
// Add metrics
public class ProcessingService {
private final Timer processingTimer;
private final Counter recordCounter;
public void processRecords(List<Record> records) {
Timer.Sample sample = Timer.start();
try {
records.forEach(this::process);
recordCounter.increment(records.size());
} finally {
sample.stop(processingTimer);
}
}
}Pseudocode Common Rules - All Languages
Naming Conventions
- Variables: camelCase (
inputCount,customerName) - Types/Structures: PascalCase (
CustomerRecord,OrderDetail) - Constants: UPPER_SNAKE_CASE (
MAX_RECORDS,TAX_RATE) - Functions/Procedures: PascalCase with verb (
ProcessRecord,CalculateTotal)
Data Types
INTEGER- whole numbersDECIMAL(n,m)- financial precision (n digits, m decimals)STRING[n]- text (max n characters)BOOLEAN- true/falseDATE/DATETIME- dates/timesARRAY[n] OF TYPE- arraysSTRUCTURE- composite types
Pseudocode Syntax
Structure Definition
STRUCTURE StructureName:
field: TYPE[length] // Description
END STRUCTUREConstants
CONSTANTS:
NAME = value
END CONSTANTSFunctions/Procedures
FUNCTION Name(param: TYPE) RETURNS TYPE
BEGIN
RETURN value
END FUNCTION
PROCEDURE Name(param: TYPE)
BEGIN
statements
END PROCEDUREControl Flow
IF condition THEN ... ELSE ... END IF
WHILE condition DO ... END WHILE
FOR i FROM start TO end BY step DO ... END FOR
SWITCH expr: CASE val: ... BREAK; DEFAULT: ... END SWITCHFile Operations
file = OPEN(path) FOR READING|WRITING|APPENDING
record = READ_RECORD(file)
WRITE_RECORD(file, record)
CLOSE(file)
IF END_OF_FILE(file) THEN ...Error Handling
TRY:
statements
CATCH ExceptionType:
handler
FINALLY:
cleanup
END TRYFinancial Precision Rule
CRITICAL: Always use DECIMAL(n,m) for money. Never use floating point.
amount: DECIMAL(15,2)
result = ROUND(calculation, 2) // Use HALF_UP roundingMermaid Flowchart Template
flowchart TD
Start([Start])
Process[Process Step]
Decision{Condition?}
End([End])
Start --> Process
Process --> Decision
Decision -->|Yes| End
Decision -->|No| ProcessDocument Structure Template
# [PROGRAM-NAME] - Description
## Program Overview
- Purpose, Input, Output, Original Source
## Data Structures
[Structures and constants]
## Main Algorithm
[High-level flow]
## Core Processing Logic
[Detailed procedures]
## Flowchart (Mermaid)
[Diagram]
## Decision Logic / Special Cases / Error Handling
[Business rules and edge cases]
## Example Traces / Testing / Integration Points
[Walkthroughs and dependencies]]633;E;cat /tmp/rpg-migration.txt >> /Users/thanhdq.coe/99-hanoi-rainbow/hanoi-rainbow/skills/rpg-migration-analyzer/references/pseudocode-rpg-migration-guide.md && echo "" >> /Users/thanhdq.coe/99-hanoi-rainbow/hanoi-rainbow/skills/rpg-migration-analyzer/references/pseudocode-rpg-migration-guide.md && echo "---" >> /Users/thanhdq.coe/99-hanoi-rainbow/hanoi-rainbow/skills/rpg-migration-analyzer/references/pseudocode-rpg-migration-guide.md && echo "" >> /Users/thanhdq.coe/99-hanoi-rainbow/hanoi-rainbow/skills/rpg-migration-analyzer/references/pseudocode-rpg-migration-guide.md && echo "Reference: IBM RPG IV Reference, ILE RPG Programmer's Guide" >> /Users/thanhdq.coe/99-hanoi-rainbow/hanoi-rainbow/skills/rpg-migration-analyzer/references/pseudocode-rpg-migration-guide.md;a450241c-8948-4114-8aa1-a76bbe99bad9]633;C# RPG Advanced Features
Prerequisites: Read pseudocode-rpg-core-rules.md and pseudocode-rpg-patterns.md.
---
This file covers advanced RPG features including ILE RPG, embedded SQL, web services, and modern RPG capabilities.
Advanced Features
File Information Data Structure (INFDS)
F CustMast IF E K DISK INFDS(FileInfo)
D FileInfo DS
D FileName *FILE
D FileStatus *STATUS
D OpCode *OPCODE
D Routine *ROUTINE
D NumRecs *RECORD→
STRUCTURE FileInformationDS:
fileName: STRING[10]
fileStatus: INTEGER
opCode: STRING[6]
routine: STRING[8]
numRecs: INTEGER
END STRUCTURE
fileInfo: FileInformationDS
// Access: fileInfo.fileStatus after file operationsProgram Status Data Structure (PSDS)
D PSDS SDS
D PgmName *PROC
D PgmStatus *STATUS
D PrevStatus 16 20S 0
D LineNum 21 28
D Routine 29 36
D UserName 254 263→
STRUCTURE ProgramStatusDS:
pgmName: STRING[10]
pgmStatus: INTEGER
prevStatus: DECIMAL(5,0)
lineNum: STRING[8]
routine: STRING[8]
userName: STRING[10]
END STRUCTURE
psds: ProgramStatusDS
// Access: psds.pgmStatus for error handlingData Area Operations
D Counter S 5P 0 DTAARA(MYCOUNTER)
C *DTAARA DEFINE Counter
C IN Counter
C EVAL Counter = Counter + 1
C OUT Counter→
counter: DECIMAL(5,0)
// Read from persistent storage
counter = READ_DATA_AREA("MYCOUNTER")
counter = counter + 1
// Write back to persistent storage
WRITE_DATA_AREA("MYCOUNTER", counter)Commitment Control
C COMMIT
C IF %ERROR
C EVAL ErrMsg = 'Commit failed'
C ENDIF
C ROLBK→
TRY:
COMMIT_TRANSACTION()
CATCH Exception:
errMsg = "Commit failed"
ROLLBACK_TRANSACTION()
END TRYAPI Call Pattern
D QCmdExc PR EXTPGM('QCMDEXC')
D Command 3000A CONST OPTIONS(*VARSIZE)
D Length 15P 5 CONST
D Cmd S 3000A
D CmdLen S 15P 5
C EVAL Cmd = 'DLTF FILE(MYLIB/MYFILE)'
C EVAL CmdLen = %LEN(%TRIM(Cmd))
C CALLB QCmdExc(Cmd:CmdLen)→
// Define API interface
FUNCTION QCmdExc(command: STRING, length: DECIMAL(15,5))
BEGIN
// External system API call
END FUNCTION
cmd: STRING[3000]
cmdLen: DECIMAL(15,5)
cmd = "DLTF FILE(MYLIB/MYFILE)"
cmdLen = LENGTH(TRIM(cmd))
CALL QCmdExc(cmd, cmdLen)Service Programs and Binding
H NOMAIN // Service program indicator
/COPY QRPGLESRC,PROTOTYPES
D GetCustomer PR EXTPROC('GetCustomer')
D LIKEDS(Customer)
D CustNo 10P 0 CONST
P GetCustomer B EXPORT
D GetCustomer PI LIKEDS(Customer)
D CustNo 10P 0 CONST
// Implementation
P GetCustomer E→
// Service program module (no main entry point)
// IMPORT: PROTOTYPES module
FUNCTION GetCustomer(custNo: DECIMAL(10,0)) RETURNS Customer EXPORTED
BEGIN
customer: Customer
// Implementation to retrieve customer
RETURN customer
END FUNCTION
// Notes:
// - NOMAIN indicates service program (library of procedures)
// - EXPORT makes procedure available to external programs
// - /COPY includes common definitions
// - Binding directory references needed for linkingBinding Directory (H-Spec)
H BNDDIR('MYLIB/MYBNDDIR')
H ACTGRP(*NEW)→
// Program Configuration
CONSTANTS:
BINDING_DIRECTORY = "MYLIB/MYBNDDIR" // External references
ACTIVATION_GROUP = "NEW" // Isolated execution
END CONSTANTS
// Notes:
// - BNDDIR specifies external service programs to link
// - ACTGRP controls resource isolation and cleanup
// - *NEW = new activation group (recommended for modern programs)
// - *CALLER = use caller's activation groupParameter Passing Options
D ProcessRecord PR
D Record LIKEDS(Customer) CONST
D Options OPTIONS(*NOPASS:*OMIT)
D ErrCode LIKEDS(ApiError) OPTIONS(*NOPASS)
P ProcessRecord B
D ProcessRecord PI
D Record LIKEDS(Customer) CONST
D Options OPTIONS(*NOPASS:*OMIT)
D ErrCode LIKEDS(ApiError) OPTIONS(*NOPASS)
C IF %PARMS >= 2 AND %ADDR(Options) <> *NULL
C // Use optional parameter
C ENDIF
P ProcessRecord E→
FUNCTION ProcessRecord(
record: Customer, // CONST - pass by value
options: OPTIONAL NULLABLE STRING, // *NOPASS:*OMIT
errCode: OPTIONAL ApiError // *NOPASS
)
BEGIN
// Check if optional parameters provided
IF PARAMETER_COUNT() >= 2 AND options IS NOT NULL THEN
// Use optional parameter
END IF
END FUNCTION
// Parameter Options:
// - CONST: Pass by value (read-only)
// - VALUE: Pass by value (copy)
// - *NOPASS: Parameter is optional
// - *OMIT: Parameter can be passed as *OMIT (null)
// - *STRING: Null-terminated string
// - *VARSIZE: Variable-length parameter
// - OPTIONS(*TRIM): Trim trailing blanksData Structure Parameter Passing
D ProcessDS PR
D InDS LIKEDS(InputDS) CONST
D OutDS LIKEDS(OutputDS)
C EVAL OutDS = InDS // Structure assignment
C CALLP ProcessDS(MyInput:MyOutput)→
FUNCTION ProcessDS(inDS: InputDS, outDS: OUTPUT OutputDS)
BEGIN
outDS = inDS // Copy all fields from input to output
END FUNCTION
// Call with structures
CALL ProcessDS(myInput, myOutput)
// Note: Structure assignment copies all fieldsReturn Value vs. Output Parameters
// Return value style (modern)
D CalcTotal PR 15P 2
D Quantity 7P 0 CONST
D Price 11P 2 CONST
C EVAL Total = CalcTotal(Qty:Price)
// Output parameter style (legacy)
D CalcTotal2 PR
D Total 15P 2
D Quantity 7P 0 CONST
D Price 11P 2 CONST
C CALLP CalcTotal2(Total:Qty:Price)→
// Modern style - return value
FUNCTION CalcTotal(quantity: DECIMAL(7,0), price: DECIMAL(11,2)) RETURNS DECIMAL(15,2)
BEGIN
RETURN quantity * price
END FUNCTION
total = CalcTotal(qty, price)
// Legacy style - output parameter
PROCEDURE CalcTotal2(total: OUTPUT DECIMAL(15,2), quantity: DECIMAL(7,0), price: DECIMAL(11,2))
BEGIN
total = quantity * price
END PROCEDURE
CALL CalcTotal2(total, qty, price)Performance Optimization Patterns
// Pre-allocate structures in loops (avoid in tight loops)
C DOW NOT %EOF(File)
C CLEAR TempDS // Expensive if repeated
C READ File
C ENDDO
// Better approach - reuse
C CLEAR TempDS
C DOW NOT %EOF(File)
C // Reuse TempDS, clear only needed fields
C READ File
C ENDDO→
// AVOID: Clearing structure in every iteration
WHILE NOT END_OF_FILE(file) DO
tempDS = NEW TempStructure() // Expensive
record = READ_RECORD(file)
END WHILE
// BETTER: Reuse structure, clear only when needed
tempDS = NEW TempStructure()
WHILE NOT END_OF_FILE(file) DO
// Reuse tempDS, update only changed fields
record = READ_RECORD(file)
END WHILE
// Performance notes:
// - Minimize object allocation in loops
// - Reuse data structures when possible
// - Use *NOPASS parameters to avoid unnecessary copying
// - Avoid string concatenation in tight loopsActivation Group Management
H ACTGRP(*NEW) // New activation group
H ACTGRP(*CALLER) // Caller's activation group
H ACTGRP('MYGRP') // Named activation group
C EVAL *INLR = *ON // End program, reclaim resources→
// Program Configuration
CONSTANTS:
ACTIVATION_GROUP_NEW = TRUE
// or ACTIVATION_GROUP_NAME = "MYGRP"
END CONSTANTS
PROCEDURE Terminate()
BEGIN
lastRecord = TRUE // *INLR = *ON
// Automatic cleanup:
// - Close all files
// - Deallocate memory
// - Free resources
// - Reclaim activation group if *NEW
END PROCEDURE
// Activation Group Notes:
// - *NEW: Isolated, automatic cleanup, recommended for batch
// - *CALLER: Share resources with caller, use for service programs
// - Named: Shared resources across multiple programs
// - *INLR=*ON: Triggers full cleanup and resource reclamationEmbedded SQL Operations
Basic SQL Select
C/EXEC SQL
C+ SELECT CUSTNO, NAME, BALANCE
C+ INTO :CustNo, :Name, :Balance
C+ FROM CUSTOMER
C+ WHERE CUSTNO = :SearchCustNo
C/END-EXEC
C IF SQLCOD = 0
C // Record found
C ENDIF→
TRY:
EXECUTE SQL
SELECT CUSTNO, NAME, BALANCE
INTO :custNo, :name, :balance
FROM CUSTOMER
WHERE CUSTNO = :searchCustNo
END SQL
IF SQL_CODE = 0 THEN
// Record found
END IF
CATCH SQLException:
// Handle SQL error
END TRYSQL Cursor Processing
C/EXEC SQL
C+ DECLARE C1 CURSOR FOR
C+ SELECT CUSTNO, NAME, BALANCE
C+ FROM CUSTOMER
C+ WHERE STATE = :StateCode
C+ ORDER BY NAME
C/END-EXEC
C/EXEC SQL OPEN C1 END-EXEC
C DOU SQLCOD <> 0
C/EXEC SQL
C+ FETCH C1 INTO :CustNo, :Name, :Balance
C/END-EXEC
C IF SQLCOD = 0
C EXSR ProcessCustomer
C ENDIF
C ENDDO
C/EXEC SQL CLOSE C1 END-EXEC→
// Declare cursor
CURSOR c1 FOR
SELECT CUSTNO, NAME, BALANCE
FROM CUSTOMER
WHERE STATE = :stateCode
ORDER BY NAME
END CURSOR
OPEN_CURSOR(c1)
DO
FETCH c1 INTO custNo, name, balance
IF SQL_CODE = 0 THEN
CALL ProcessCustomer(custNo, name, balance)
END IF
WHILE SQL_CODE = 0
CLOSE_CURSOR(c1)SQL Insert/Update/Delete
C/EXEC SQL
C+ INSERT INTO CUSTOMER
C+ (CUSTNO, NAME, BALANCE)
C+ VALUES (:CustNo, :Name, :Balance)
C/END-EXEC
C/EXEC SQL
C+ UPDATE CUSTOMER
C+ SET BALANCE = BALANCE + :Amount
C+ WHERE CUSTNO = :CustNo
C/END-EXEC
C/EXEC SQL
C+ DELETE FROM CUSTOMER
C+ WHERE CUSTNO = :CustNo
C/END-EXEC
C EVAL RowsAffected = SQLERRD(3)→
// Insert
EXECUTE SQL
INSERT INTO CUSTOMER (CUSTNO, NAME, BALANCE)
VALUES (:custNo, :name, :balance)
END SQL
// Update
EXECUTE SQL
UPDATE CUSTOMER
SET BALANCE = BALANCE + :amount
WHERE CUSTNO = :custNo
END SQL
// Delete
EXECUTE SQL
DELETE FROM CUSTOMER
WHERE CUSTNO = :custNo
END SQL
rowsAffected = SQL_ROWS_AFFECTED()Dynamic SQL
D SqlStmt S 512A
D CustNo S 9P 0
C EVAL SqlStmt = 'SELECT CUSTNO ' +
C 'FROM CUSTOMER ' +
C 'WHERE STATE = ?'
C/EXEC SQL
C+ PREPARE S1 FROM :SqlStmt
C/END-EXEC
C/EXEC SQL
C+ EXECUTE S1 USING :StateCode INTO :CustNo
C/END-EXEC→
sqlStmt: STRING[512]
custNo: DECIMAL(9,0)
sqlStmt = "SELECT CUSTNO " +
"FROM CUSTOMER " +
"WHERE STATE = ?"
PREPARE_SQL_STATEMENT("S1", sqlStmt)
EXECUTE_PREPARED("S1", [stateCode], custNo)SQL Error Handling
D SQLSTT S 5A
D SQLCOD S 10I 0
C/EXEC SQL
C+ WHENEVER SQLERROR CONTINUE
C/END-EXEC
C/EXEC SQL
C+ SELECT NAME INTO :Name
C+ FROM CUSTOMER
C+ WHERE CUSTNO = :CustNo
C/END-EXEC
C SELECT
C WHEN SQLCOD = 0
C EVAL Msg = 'Success'
C WHEN SQLCOD = 100
C EVAL Msg = 'Not found'
C OTHER
C EVAL Msg = 'SQL Error: ' + SQLSTT
C ENDSL→
sqlState: STRING[5]
sqlCode: INTEGER
TRY:
EXECUTE SQL
SELECT NAME INTO :name
FROM CUSTOMER
WHERE CUSTNO = :custNo
END SQL
SWITCH sqlCode:
CASE 0:
msg = "Success"
BREAK
CASE 100:
msg = "Not found"
BREAK
DEFAULT:
msg = "SQL Error: " + sqlState
END SWITCH
CATCH SQLException:
msg = "SQL Error: " + GET_SQL_STATE()
END TRY
// SQL Status Codes:
// 0 = Success
// 100 = No data found
// negative = Error occurred
// SQLSTT/SQLSTATE = 5-character error codeStored Procedure Calls
C/EXEC SQL
C+ CALL MYPROC(:InParm1, :InParm2, :OutParm)
C/END-EXEC→
EXECUTE SQL
CALL MYPROC(:inParm1, :inParm2, :outParm)
END SQLSQL Transaction Control
C/EXEC SQL
C+ SET TRANSACTION ISOLATION LEVEL READ COMMITTED
C/END-EXEC
C/EXEC SQL COMMIT END-EXEC
C/EXEC SQL ROLLBACK END-EXEC→
// Set isolation level
EXECUTE SQL
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
END SQL
// Commit transaction
COMMIT_TRANSACTION()
// Rollback transaction
ROLLBACK_TRANSACTION()IFS (Integrated File System) Operations
IFS File Operations
D FD S 10I 0
D Buffer S 1024A
D BytesRead S 10I 0
C EVAL FD = open('/home/myfile.txt':
C O_RDONLY)
C IF FD >= 0
C EVAL BytesRead = read(FD:Buffer:%SIZE(Buffer))
C CALLP close(FD)
C ENDIF→
fileDescriptor: INTEGER
buffer: STRING[1024]
bytesRead: INTEGER
fileDescriptor = IFS_OPEN("/home/myfile.txt", READ_ONLY)
IF fileDescriptor >= 0 THEN
bytesRead = IFS_READ(fileDescriptor, buffer, SIZE_OF(buffer))
IFS_CLOSE(fileDescriptor)
END IF
// IFS File Modes:
// O_RDONLY - Read only
// O_WRONLY - Write only
// O_RDWR - Read and write
// O_CREAT - Create if doesn't exist
// O_TRUNC - Truncate to zero length
// O_APPEND - Append to endIFS Directory Operations
D Dir S *
D Entry DS LIKEDS(Dirent)
C EVAL Dir = opendir('/home/mydir')
C IF Dir <> *NULL
C DOW readdir(Dir:Entry) <> *NULL
C // Process Entry.d_name
C ENDDO
C CALLP closedir(Dir)
C ENDIF→
STRUCTURE DirectoryEntry:
name: STRING[256]
type: STRING[10]
END STRUCTURE
directory: POINTER
entry: DirectoryEntry
directory = IFS_OPEN_DIR("/home/mydir")
IF directory IS NOT NULL THEN
WHILE IFS_READ_DIR(directory, entry) IS NOT NULL DO
// Process entry.name
END WHILE
IFS_CLOSE_DIR(directory)
END IFIFS File Information
D StatDS DS QUALIFIED
D FileSize 10I 0
D ModTime 10I 0
D FileType 5I 0
C IF stat('/home/myfile.txt':StatDS) = 0
C // File exists, check StatDS fields
C ENDIF→
STRUCTURE FileStats:
fileSize: INTEGER
modTime: INTEGER
fileType: INTEGER
permissions: INTEGER
END STRUCTURE
stats: FileStats
IF IFS_STAT("/home/myfile.txt", stats) = 0 THEN
// File exists, access stats.fileSize, etc.
END IFXML and JSON Operations
XML Parsing (xml-into)
D Customer DS QUALIFIED
D Name 30A
D City 20A
D Balance 15P 2
D XmlDoc S 1000A
C EVAL XmlDoc = '<customer>' +
C '<name>John Smith</name>' +
C '<city>Chicago</city>' +
C '<balance>1500.00</balance>' +
C '</customer>'
C XML-INTO Customer %XML(XmlDoc)→
STRUCTURE Customer:
name: STRING[30]
city: STRING[20]
balance: DECIMAL(15,2)
END STRUCTURE
xmlDoc: STRING[1000]
customer: Customer
xmlDoc = "<customer>" +
"<name>John Smith</name>" +
"<city>Chicago</city>" +
"<balance>1500.00</balance>" +
"</customer>"
customer = PARSE_XML(xmlDoc, Customer)JSON Parsing (DATA-INTO)
D Order DS QUALIFIED
D OrderNo 9P 0
D CustName 30A
D Total 15P 2
D JsonDoc S 1000A
C EVAL JsonDoc = '{"orderNo":12345,' +
C '"custName":"John Smith",' +
C '"total":1500.00}'
C DATA-INTO Order %DATA(JsonDoc:'doc=string')→
STRUCTURE Order:
orderNo: DECIMAL(9,0)
custName: STRING[30]
total: DECIMAL(15,2)
END STRUCTURE
jsonDoc: STRING[1000]
order: Order
jsonDoc = '{"orderNo":12345,' +
'"custName":"John Smith",' +
'"total":1500.00}'
order = PARSE_JSON(jsonDoc, Order)XML Generation (xml-sax)
C CALLP StartElement('customer')
C CALLP AddElement('name':'John Smith')
C CALLP AddElement('city':'Chicago')
C CALLP AddElement('balance':'1500.00')
C CALLP EndElement('customer')→
XML_START_ELEMENT("customer")
XML_ADD_ELEMENT("name", "John Smith")
XML_ADD_ELEMENT("city", "Chicago")
XML_ADD_ELEMENT("balance", "1500.00")
XML_END_ELEMENT("customer")JSON Generation (YAJL)
D JsonGen S *
C EVAL JsonGen = yajl_genOpen(*OFF)
C CALLP yajl_beginObj(JsonGen)
C CALLP yajl_addNum(JsonGen:'orderNo':'12345')
C CALLP yajl_addChar(JsonGen:'custName':'John Smith')
C CALLP yajl_addNum(JsonGen:'total':'1500.00')
C CALLP yajl_endObj(JsonGen)
C EVAL JsonDoc = yajl_getString(JsonGen)
C CALLP yajl_genClose(JsonGen)→
jsonGenerator: POINTER
jsonGenerator = JSON_OPEN_GENERATOR()
JSON_BEGIN_OBJECT(jsonGenerator)
JSON_ADD_NUMBER(jsonGenerator, "orderNo", 12345)
JSON_ADD_STRING(jsonGenerator, "custName", "John Smith")
JSON_ADD_NUMBER(jsonGenerator, "total", 1500.00)
JSON_END_OBJECT(jsonGenerator)
jsonDoc = JSON_GET_STRING(jsonGenerator)
JSON_CLOSE_GENERATOR(jsonGenerator)HTTP/Web Services
HTTP GET Request
D HttpResp S 65535A
D Rc S 10I 0
C EVAL Rc = http_get(
C 'http://api.example.com/data':
C HttpResp)
C IF Rc = 200
C // Process HttpResp
C ENDIF→
httpResponse: STRING[65535]
responseCode: INTEGER
responseCode = HTTP_GET("http://api.example.com/data", httpResponse)
IF responseCode = 200 THEN
// Process httpResponse
END IFHTTP POST Request
D PostData S 1000A
D HttpResp S 65535A
D Rc S 10I 0
C EVAL PostData = '{"key":"value"}'
C EVAL Rc = http_post_stmf(
C 'http://api.example.com/update':
C '/tmp/request.json':
C '/tmp/response.json':
C HTTP_TIMEOUT)→
postData: STRING[1000]
httpResponse: STRING[65535]
responseCode: INTEGER
postData = '{"key":"value"}'
responseCode = HTTP_POST(
"http://api.example.com/update",
postData,
httpResponse,
TIMEOUT_SECONDS
)Web Service Call (SOAP)
D SoapRequest S 5000A
D SoapResponse S 65535A
C EVAL SoapRequest =
C '<?xml version="1.0"?>' +
C '<soap:Envelope>' +
C '<soap:Body>' +
C '<GetCustomer>' +
C '<CustNo>12345</CustNo>' +
C '</GetCustomer>' +
C '</soap:Body>' +
C '</soap:Envelope>'
C EVAL Rc = http_post_xml(
C 'http://api.example.com/soap':
C SoapRequest:
C SoapResponse)→
soapRequest: STRING[5000]
soapResponse: STRING[65535]
soapRequest =
'<?xml version="1.0"?>' +
'<soap:Envelope>' +
'<soap:Body>' +
'<GetCustomer>' +
'<CustNo>12345</CustNo>' +
'</GetCustomer>' +
'</soap:Body>' +
'</soap:Envelope>'
responseCode = HTTP_POST_XML(
"http://api.example.com/soap",
soapRequest,
soapResponse
)Advanced File Locking Patterns
Record Locking
C Key CHAIN(N) File // Read without lock
C IF %FOUND(File)
C Key CHAIN File // Lock record
C IF %FOUND(File)
C EVAL Balance = Balance + Amount
C UPDATE FileRec
C ENDIF
C ENDIF→
// Read without lock for validation
record = READ_BY_KEY_NO_LOCK(file, key)
IF RECORD_FOUND(file) THEN
// Lock and read again for update
record = READ_BY_KEY_WITH_LOCK(file, key)
IF RECORD_FOUND(file) THEN
record.balance = record.balance + amount
UPDATE_RECORD(file, record)
// Lock automatically released after update
END IF
END IFFile Override and Open Options
C CALL 'QCMDEXC'
C PARM Cmd
C PARM CmdLen
C // Cmd = 'OVRDBF FILE(MYFILE) SHARE(*YES)'
C OPEN MyFile→
// Override database file attributes
EXECUTE_COMMAND("OVRDBF FILE(MYFILE) SHARE(*YES)")
// Open file with overrides applied
OPEN_FILE(myFile)
// File sharing options:
// *YES - Allow sharing
// *NO - Exclusive access
// *SHRREAD - Share for read only
// *SHRUPD - Share for read and updateUser Space Operations
Creating and Using User Spaces
D UserSpace DS QUALIFIED
D Name 10A INZ('MYUSRSPC')
D Lib 10A INZ('MYLIB')
D UsrSpcPtr S *
D DataPtr S * BASED(UsrSpcPtr)
D DataString S 32767A BASED(DataPtr)
C CALL 'QUSCRTUS'
C PARM UserSpace
C // ... other parameters
C CALL 'QUSPTRUS'
C PARM UserSpace
C PARM UsrSpcPtr
C EVAL DataString = 'My Data'→
STRUCTURE UserSpaceID:
name: STRING[10] = "MYUSRSPC"
library: STRING[10] = "MYLIB"
END STRUCTURE
userSpace: UserSpaceID
userSpacePointer: POINTER
dataString: STRING[32767]
// Create user space
CREATE_USER_SPACE(userSpace, SIZE=65536, AUTHORITY="*ALL")
// Get pointer to user space
userSpacePointer = GET_USER_SPACE_POINTER(userSpace)
// Access data through pointer
dataString = READ_FROM_POINTER(userSpacePointer, LENGTH=32767)
WRITE_TO_POINTER(userSpacePointer, "My Data")
// Notes:
// - User spaces provide shared memory between programs
// - Useful for large data structures or inter-program communication
// - Must manage memory layout manuallyMultiple Threading (Limited Support)
Job Submission (Parallel Processing)
D JobName S 10A
D JobNumber S 6A
C CALL 'QCMDEXC'
C PARM 'SBMJOB CMD(CALL PGM(PROCESS))' Cmd
C PARM %LEN(%TRIM(Cmd)) CmdLen→
jobName: STRING[10]
jobNumber: STRING[6]
// Submit job to run in parallel
SUBMIT_JOB(
COMMAND="CALL PGM(PROCESS)",
JOB_NAME=jobName,
JOB_NUMBER=jobNumber
)
// Notes:
// - RPG traditionally single-threaded
// - Use SBMJOB for parallel batch processing
// - Java migration: Consider Thread pools or ExecutorService
// - Monitor job completion via QSYSOPR message queue or data areasData Queue for Inter-Job Communication
D DtaQMsg S 100A
// Send message to data queue
C CALL 'QSNDDTAQ'
C PARM 'MSGQUEUE' DQName
C PARM 'MYLIB' DQLib
C PARM 100 MsgLen
C PARM DtaQMsg
// Receive from data queue (wait 5 seconds)
C CALL 'QRCVDTAQ'
C PARM 'MSGQUEUE' DQName
C PARM 'MYLIB' DQLib
C PARM 100 MsgLen
C PARM DtaQMsg
C PARM 5 WaitTime→
dataQueueMessage: STRING[100]
// Send message (producer)
SEND_TO_DATA_QUEUE(
QUEUE="MSGQUEUE",
LIBRARY="MYLIB",
MESSAGE=dataQueueMessage
)
// Receive message (consumer, wait 5 seconds)
dataQueueMessage = RECEIVE_FROM_DATA_QUEUE(
QUEUE="MSGQUEUE",
LIBRARY="MYLIB",
WAIT_SECONDS=5
)
// Migration note: Consider message queues (RabbitMQ, Kafka) or RedisExternal Program Calls
Dynamic Program Call
D PgmName S 10A
D PgmLib S 10A
D ParmList DS
D Parm1 10A
D Parm2 15P 2
C EVAL PgmName = 'CUSTPGM'
C EVAL PgmLib = 'MYLIB'
C CALL(E) PgmName(PgmLib)
C PARM Parm1
C PARM Parm2
C IF %ERROR
C // Handle error
C ENDIF→
programName: STRING[10]
programLibrary: STRING[10]
parm1: STRING[10]
parm2: DECIMAL(15,2)
programName = "CUSTPGM"
programLibrary = "MYLIB"
TRY:
CALL_EXTERNAL_PROGRAM(
PROGRAM=programName,
LIBRARY=programLibrary,
PARAMETERS=[parm1, parm2]
)
CATCH ProgramException:
// Handle program not found or other errors
END TRYCall Java Program from RPG
D JavaClass S 256A
D JavaMethod S 256A
D JavaParm S 100A
C EVAL JavaClass = 'com.example.MyClass'
C EVAL JavaMethod = 'processData'
C EVAL JavaParm = 'Input Data'
C CALL 'QJVACMDSRV'
C // Parameters for Java invocation→
javaClass: STRING[256]
javaMethod: STRING[256]
javaParm: STRING[100]
javaClass = "com.example.MyClass"
javaMethod = "processData"
javaParm = "Input Data"
// Call Java method
CALL_JAVA_METHOD(
CLASS=javaClass,
METHOD=javaMethod,
PARAMETERS=[javaParm]
)
// Migration note: Direct method call in target languageObject-Oriented Features (Limited)
Object Reference (Pointers to Procedures)
D ProcPtr S * PROCPTR
D ProcessFunc PR
D EXTPROC(ProcPtr)
D Data 100A
C EVAL ProcPtr = %PADDR('PROCESSDATA')
C CALLB ProcessFunc(MyData)→
processProcedure: POINTER TO FUNCTION
processProcedure = GET_PROCEDURE_ADDRESS("ProcessData")
CALL_FUNCTION_POINTER(processProcedure, [myData])
// Migration note: Use function references, lambdas, or strategy patternFactory Pattern with Procedure Pointers
D ProcessorPtr S * PROCPTR
D Processor PR
D EXTPROC(ProcessorPtr)
D Record LIKEDS(DataRec)
C SELECT
C WHEN Type = 'A'
C EVAL ProcessorPtr = %PADDR('PROCESS_A')
C WHEN Type = 'B'
C EVAL ProcessorPtr = %PADDR('PROCESS_B')
C ENDSL
C CALLB Processor(Record)→
processorFunction: POINTER TO FUNCTION
SWITCH type:
CASE "A":
processorFunction = GET_PROCEDURE_ADDRESS("ProcessA")
BREAK
CASE "B":
processorFunction = GET_PROCEDURE_ADDRESS("ProcessB")
BREAK
END SWITCH
CALL_FUNCTION_POINTER(processorFunction, [record])
// Migration note: Use Strategy or Factory pattern with interfaces---
Next: See pseudocode-rpg-migration-guide.md for migration best practices.
RPG Core Translation Rules
Prerequisites: Read pseudocode-common-rules.md for syntax, naming, and structure.
---
Specification Mapping
| RPG Spec | Pseudocode Section |
|---|---|
| H-spec (Header) | Program Overview + Constants |
| F-spec (File) | Data Structures (file controls) |
| D-spec (Data) | Data Structures |
| I-spec (Input) | Data Structures (legacy input) |
| C-spec (Calculation) | Main Algorithm + Core Processing |
| O-spec (Output) | Core Processing (legacy output) |
| P-spec (Procedure) | Core Processing Logic |
F-Spec File Declarations
Database Files
F CustMast IF E K DISK USROPN
F OrderFile UF A E K DISK
F TransFile O E DISK→
// Input File - Keyed access, user open
CONSTANTS:
CUSTMAST_FILE = "CustMast"
FILE_TYPE_INPUT = "INPUT"
FILE_KEYED = TRUE
END CONSTANTS
// Update File - Keyed access, auto open
// OrderFile: INPUT/UPDATE mode, keyed access
// Output File - Sequential
// TransFile: OUTPUT mode, sequentialDisplay Files (Workstation)
F ScreenDsp CF E WORKSTN SFILE(Detail:RRN)
F INDDS(Indicators)→
// Display file with subfile
STRUCTURE ScreenDisplay:
subfileRecords: ARRAY OF DetailRecord
subfileRRN: INTEGER
indicators: IndicatorDS
END STRUCTUREPrinter Files
F ReportPrt O E PRINTER OFLIND(*INOF)→
// Printer output file
reportPrinter: OUTPUT_FILE
overflowIndicator: BOOLEAN // Set when page overflowFile Keywords Translation
| F-Spec Keyword | Meaning | Pseudocode |
|---|---|---|
USROPN | User controlled open | Manual OPEN_FILE() required |
INFDS(ds) | File info data structure | Capture file status in structure |
SFILE(fmt:rrn) | Subfile definition | Array with relative record number |
INDDS(ds) | Indicator data structure | Map indicators to boolean structure |
OFLIND(*INxx) | Overflow indicator | Boolean for page overflow |
COMMIT | Under commitment control | Transaction-controlled file |
IGNORE(fmt) | Ignore record format | Skip specified format |
INCLUDE(fmt) | Include record format | Process specified format |
PREFIX(str) | Prefix field names | Add prefix to all field names |
RENAME(old:new) | Rename record format | Use new name for format |
Data Types
| RPG | Pseudocode | Notes |
|---|---|---|
nP m (packed) | DECIMAL(n,m) | Packed - preserve precision! |
nS m (zoned) | DECIMAL(n,m) | Zoned decimal |
A (character) | STRING[n] | Character |
D (date) | DATE | Date |
T (time) | TIME | Time |
Z (timestamp) | DATETIME | Timestamp |
N (indicator) | BOOLEAN | True/False |
I (integer) | INTEGER | Binary integer |
U (unsigned) | UNSIGNED_INTEGER | Unsigned integer |
F (float) | FLOAT | Floating point (avoid for money!) |
* (pointer) | POINTER | Memory address |
DIM(n) | ARRAY[n] OF TYPE | Arrays |
LIKEDS(ds) | STRUCTURE_INSTANCE | Data structure reference |
RPG Special Values
| RPG Value | Pseudocode | Notes |
|---|---|---|
*BLANK / *BLANKS | "" or EMPTY_STRING | Empty string |
*ZERO / *ZEROS | 0 | Numeric zero |
*HIVAL | MAX_VALUE | Highest value for type |
*LOVAL | MIN_VALUE | Lowest value for type |
*ALL'x' | REPEAT('x', length) | Repeated character |
*ON | TRUE | Boolean true |
*OFF | FALSE | Boolean false |
*NULL | NULL | Null pointer |
Operation Mapping
Basic Operations
| RPG | Pseudocode |
|---|---|
EVAL result = expr | result = expr |
ADD(E) a b result | result = a + b |
SUB(E) a b result | result = a - b |
MULT(E) a b result | result = a * b |
DIV(E) a b result | result = a / b |
MVR remainder | remainder = MODULO(a, b) |
Z-ADD value result | result = value (zero and add) |
Z-SUB value result | result = -value (zero and subtract) |
Data Movement
| RPG | Pseudocode |
|---|---|
MOVE src dest | dest = RIGHT_ALIGN(src, LENGTH(dest)) |
MOVEL src dest | dest = LEFT_ALIGN(src, LENGTH(dest)) |
CLEAR field | field = DEFAULT_VALUE |
MOVEA arr1 arr2 | COPY_ARRAY(arr1, arr2) |
String Operations
| RPG | Pseudocode |
|---|---|
CAT str1:str2 result | result = CONCATENATE(str1, str2) |
SCAN pattern str | position = FIND(str, pattern) |
CHECK charset str | position = FIND_INVALID_CHAR(str, charset) |
CHECKR charset str | position = FIND_INVALID_CHAR_REVERSE(str, charset) |
XLATE from:to str | str = TRANSLATE(str, from, to) |
SUBST str:pos:len dest | dest = SUBSTRING(str, pos, len) |
Control Flow
| RPG | Pseudocode |
|---|---|
IF condition | IF condition THEN |
ELSE | ELSE |
ELSEIF condition | ELSE IF condition THEN |
ENDIF | END IF |
DOW condition | WHILE condition DO |
DOU condition | DO ... WHILE NOT condition |
FOR index = start TO end | FOR index FROM start TO end DO |
ITER | CONTINUE |
LEAVE | BREAK |
SELECT | SWITCH |
WHEN condition | CASE condition: |
OTHER | DEFAULT: |
ENDSL | END SWITCH |
Comparison
| RPG | Pseudocode |
|---|---|
COMP a b | COMPARE(a, b) |
IFEQ / IF a = b | IF a = b THEN |
IFNE / IF a <> b | IF a != b THEN |
IFGT / IF a > b | IF a > b THEN |
IFLT / IF a < b | IF a < b THEN |
IFGE / IF a >= b | IF a >= b THEN |
IFLE / IF a <= b | IF a <= b THEN |
Procedure Calls
| RPG | Pseudocode |
|---|---|
EXSR subroutine | CALL SubroutineName() |
CALLP procedure(parms) | CALL Procedure(parms) |
CALLB procedure(parms) | result = CALL_BOUND(procedure, parms) |
CALL pgm with PLIST | CALL_PROGRAM(pgm, paramList) |
RETURN value | RETURN value |
File Operations - Basic
| RPG | Pseudocode |
|---|---|
READ file | record = READ_RECORD(file) |
READP file | record = READ_PREVIOUS(file) |
READE key file | record = READ_EQUAL_KEY(file, key) |
READPE key file | record = READ_PREVIOUS_EQUAL(file, key) |
CHAIN key file | record = READ_BY_KEY(file, key) |
WRITE file | WRITE_RECORD(file, record) |
UPDATE file | UPDATE_RECORD(file, record) |
DELETE file | DELETE_RECORD(file) |
UNLOCK file | UNLOCK_RECORD(file) |
File Operations - Positioning
| RPG | Pseudocode |
|---|---|
SETLL key file | POSITION_LOWER_LIMIT(file, key) |
SETGT key file | POSITION_GREATER_THAN(file, key) |
OPEN file | OPEN_FILE(file) |
CLOSE file | CLOSE_FILE(file) |
FEOD file | FORCE_END_OF_DATA(file) |
Output Operations
| RPG | Pseudocode |
|---|---|
EXCEPT format | WRITE_OUTPUT_FORMAT(format) |
WRITE format | WRITE_RECORD(format) |
Data Validation
| RPG | Pseudocode |
|---|---|
TEST(DE) date | IS_VALID_DATE(date) |
TEST(T) time | IS_VALID_TIME(time) |
TEST(Z) timestamp | IS_VALID_TIMESTAMP(timestamp) |
TEST(N) field | IS_NUMERIC(field) |
---
Next: See pseudocode-rpg-functions.md for built-in functions.
RPG Data Structure Patterns
Prerequisites: Read pseudocode-rpg-core-rules.md for basic data types.
---
Data Structure Patterns
Simple Data Structure
D Customer DS
D CustNo 10P 0
D Name 30A
D Balance 15P 2→
STRUCTURE Customer:
custNo: DECIMAL(10,0)
name: STRING[30]
balance: DECIMAL(15,2)
END STRUCTUREQualified Data Structure
D Customer DS QUALIFIED
D CustNo 10P 0
D Name 30A→
STRUCTURE Customer: // Qualified - access as Customer.custNo
custNo: DECIMAL(10,0)
name: STRING[30]
END STRUCTUREData Structure with LIKEDS
D Address DS QUALIFIED
D Street 50A
D City 30A
D
D Customer DS QUALIFIED
D Name 30A
D HomeAddr LIKEDS(Address)
D BillAddr LIKEDS(Address)→
STRUCTURE Address:
street: STRING[50]
city: STRING[30]
END STRUCTURE
STRUCTURE Customer:
name: STRING[30]
homeAddr: Address
billAddr: Address
END STRUCTUREOverlay
D FullDate DS
D Year 4S 0
D Month 2S 0 OVERLAY(FullDate:5)
D Day 2S 0 OVERLAY(FullDate:7)→
STRUCTURE FullDate:
year: DECIMAL(4,0) // Positions 1-4
month: DECIMAL(2,0) // Positions 5-6 (overlay)
day: DECIMAL(2,0) // Positions 7-8 (overlay)
// Note: month and day share memory space with year
END STRUCTUREMultiple Occurrence Data Structure
D LineItem DS OCCURS(99)
D ItemNo 7P 0
D Qty 5P 0
D Price 11P 2
C EVAL *IN01 = %OCCUR(LineItem)
C OCCUR 5 LineItem→
STRUCTURE LineItem:
itemNo: DECIMAL(7,0)
qty: DECIMAL(5,0)
price: DECIMAL(11,2)
END STRUCTURE
lineItems: ARRAY[99] OF LineItem
currentOccurrence: INTEGER
currentOccurrence = 5
currentLine = lineItems[currentOccurrence]I-Spec (Input Specification) Patterns
Fixed Format Input
I AA 01
I 1 10 CustNo
I 11 40 CustName
I 41 48 0Balance→
STRUCTURE InputRecord_AA:
custNo: STRING[10] // Positions 1-10
custName: STRING[30] // Positions 11-40
balance: DECIMAL(8,0) // Positions 41-48
END STRUCTURE
PROCEDURE ParseInputRecord(line: STRING) RETURNS InputRecord_AA
BEGIN
record: InputRecord_AA
record.custNo = SUBSTRING(line, 1, 10)
record.custName = SUBSTRING(line, 11, 30)
record.balance = TO_DECIMAL(SUBSTRING(line, 41, 8), 8, 0)
RETURN record
END PROCEDURERecord Identification
I AA 01
I BB 02→
// Record type AA = indicator 01
// Record type BB = indicator 02
IF recordType = "AA" THEN
indicator01 = TRUE
ELSE IF recordType = "BB" THEN
indicator02 = TRUE
END IFO-Spec (Output Specification) Patterns
Report Output
O ReportPrt E Heading
O 10 'CUSTOMER REPORT'
O CustNo 50
O CustName 80→
PROCEDURE WriteHeading()
BEGIN
WRITE_LINE(reportPrinter, "CUSTOMER REPORT", POSITION=10)
END PROCEDURE
PROCEDURE WriteDetail(record: CustomerRecord)
BEGIN
line = FORMAT_FIELD(record.custNo, 50) +
FORMAT_FIELD(record.custName, 80)
WRITE_LINE(reportPrinter, line)
END PROCEDUREConditional Output
O N01 'NO DATA'
O 01 'DATA FOUND'→
IF NOT indicator01 THEN
WRITE_LINE(reportPrinter, "NO DATA")
ELSE
WRITE_LINE(reportPrinter, "DATA FOUND")
END IF---
Next: See pseudocode-rpg-patterns.md for common translation patterns.
RPG Built-in Functions
Prerequisites: Read pseudocode-rpg-core-rules.md for basic operations.
---
String Functions
| RPG BIF | Pseudocode |
|---|---|
%SUBST(str:pos:len) | SUBSTRING(str, pos, len) |
%TRIM(str) | TRIM(str) |
%TRIML(str) | TRIM_LEFT(str) |
%TRIMR(str) | TRIM_RIGHT(str) |
%LEN(str) | LENGTH(str) |
%SIZE(var) | SIZE_OF(var) |
%SCAN(pattern:str:start) | FIND(str, pattern, start) |
%CHECK(charset:str:start) | FIND_INVALID_CHAR(str, charset, start) |
%CHECKR(charset:str:start) | FIND_INVALID_CHAR_REVERSE(str, charset, start) |
%REPLACE(repl:str:pos:len) | REPLACE(str, repl, pos, len) |
%XLATE(from:to:str) | TRANSLATE(str, from, to) |
Conversion Functions
| RPG BIF | Pseudocode |
|---|---|
%CHAR(value) | TO_STRING(value) |
%INT(value) | TO_INTEGER(value) |
%UNS(value) | TO_UNSIGNED(value) |
%DEC(value:digits:decimals) | TO_DECIMAL(value, digits, decimals) |
%DECH(value:digits:decimals) | TO_DECIMAL_HALF_ADJUST(value, digits, decimals) |
%FLOAT(value) | TO_FLOAT(value) |
%EDITC(num:code) | FORMAT_NUMERIC(num, code) |
%EDITW(num:pattern) | FORMAT_WITH_PATTERN(num, pattern) |
%EDITFLT(num) | FORMAT_FLOAT(num) |
Date/Time Functions
| RPG BIF | Pseudocode |
|---|---|
%DATE() | CURRENT_DATE() |
%TIME() | CURRENT_TIME() |
%TIMESTAMP() | CURRENT_TIMESTAMP() |
%DATE(value:format) | PARSE_DATE(value, format) |
%TIME(value:format) | PARSE_TIME(value, format) |
%TIMESTAMP(value:format) | PARSE_TIMESTAMP(value, format) |
%DIFF(dt1:dt2:unit) | DATE_DIFFERENCE(dt1, dt2, unit) |
%YEARS(num) | DURATION_YEARS(num) |
%MONTHS(num) | DURATION_MONTHS(num) |
%DAYS(num) | DURATION_DAYS(num) |
%HOURS(num) | DURATION_HOURS(num) |
%MINUTES(num) | DURATION_MINUTES(num) |
%SECONDS(num) | DURATION_SECONDS(num) |
%MSECONDS(num) | DURATION_MILLISECONDS(num) |
File Status Functions
| RPG BIF | Pseudocode |
|---|---|
%EOF(file) | END_OF_FILE(file) |
%EQUAL(file) | EQUAL_CONDITION(file) |
%FOUND(file) | RECORD_FOUND(file) |
%OPEN(file) | IS_FILE_OPEN(file) |
%ERROR | ERROR_OCCURRED() |
%STATUS | GET_STATUS_CODE() |
Array/Data Structure Functions
| RPG BIF | Pseudocode |
|---|---|
%ELEM(array) | ELEMENT_COUNT(array) |
%OCCUR(ds) | GET_OCCURRENCE(ds) |
%ADDR(var) | ADDRESS_OF(var) |
%PADDR(proc) | PROCEDURE_ADDRESS(proc) |
%LOOKUP(val:arr) | ARRAY_SEARCH(arr, val) |
%TLOOKUP(val:arr:seq) | TABLE_LOOKUP(arr, val, seq) |
Arithmetic/Utility Functions
| RPG BIF | Pseudocode |
|---|---|
%ABS(value) | ABSOLUTE_VALUE(value) |
%DIV(a:b) | INTEGER_DIVIDE(a, b) |
%REM(a:b) | REMAINDER(a, b) |
%SQRT(value) | SQUARE_ROOT(value) |
%INTH(value) | INTEGER_HALF_ADJUST(value) |
%DECH(value:d:p) | DECIMAL_HALF_ADJUST(value, d, p) |
Conditional Functions
| RPG BIF | Pseudocode |
|---|---|
%NULLIND(field) | GET_NULL_INDICATOR(field) |
%PARMS | PARAMETER_COUNT() |
%PARMNUM(parm) | PARAMETER_NUMBER(parm) |
---
Next: See pseudocode-rpg-data-structures.md for data structure patterns.
]633;E;cat /tmp/rpg-migration.txt >> /Users/thanhdq.coe/99-hanoi-rainbow/hanoi-rainbow/skills/rpg-migration-analyzer/references/pseudocode-rpg-migration-guide.md && echo "" >> /Users/thanhdq.coe/99-hanoi-rainbow/hanoi-rainbow/skills/rpg-migration-analyzer/references/pseudocode-rpg-migration-guide.md && echo "---" >> /Users/thanhdq.coe/99-hanoi-rainbow/hanoi-rainbow/skills/rpg-migration-analyzer/references/pseudocode-rpg-migration-guide.md && echo "" >> /Users/thanhdq.coe/99-hanoi-rainbow/hanoi-rainbow/skills/rpg-migration-analyzer/references/pseudocode-rpg-migration-guide.md && echo "Reference: IBM RPG IV Reference, ILE RPG Programmer's Guide" >> /Users/thanhdq.coe/99-hanoi-rainbow/hanoi-rainbow/skills/rpg-migration-analyzer/references/pseudocode-rpg-migration-guide.md;a450241c-8948-4114-8aa1-a76bbe99bad9]633;C# RPG Migration Guide
Prerequisites: Read all other RPG translation rule files first.
---
This file provides comprehensive guidance for planning and executing RPG to modern language migrations.
Advanced Translation Patterns
Refactoring Global Indicators
// Legacy: Global indicators throughout program
C EVAL *IN01 = *ON
C IF *IN01
C EXSR Process
C ENDIF→
// Better: Named boolean at appropriate scope
isRecordFound: BOOLEAN = TRUE
IF isRecordFound THEN
CALL Process()
END IF
// Best practices:
// - Use descriptive names
// - Limit scope (local vs global)
// - Consider state objects for complex indicator setsRefactoring %PARMS Checks
D MyProc PR
D Required 10A
D Optional1 10A OPTIONS(*NOPASS)
D Optional2 10A OPTIONS(*NOPASS)
P MyProc B
C SELECT
C WHEN %PARMS = 1
C // Use only Required
C WHEN %PARMS = 2
C // Use Required and Optional1
C WHEN %PARMS = 3
C // Use all parameters
C ENDSL
P MyProc E→
FUNCTION MyProc(
required: STRING[10],
optional1: OPTIONAL STRING[10] = NULL,
optional2: OPTIONAL STRING[10] = NULL
)
BEGIN
// Use null checks instead of parameter count
IF optional1 IS NOT NULL THEN
// Process optional1
END IF
IF optional2 IS NOT NULL THEN
// Process optional2
END IF
END FUNCTION
// Modern approach: Use optional parameters with defaults
// or method overloading in target languageConverting Fixed-Format to Free-Format Logic
// Legacy fixed-format
C CustNo CHAIN CustMast
C IF %FOUND
C EVAL Name = CustName
C ENDIF→
record = READ_BY_KEY(custMast, custNo)
IF RECORD_FOUND(custMast) THEN
name = record.custName
END IF
// Note: Free-format RPG and pseudocode are already similar
// Focus on extracting business logic from I/O operationsTranslation Workflow
1. Analyze Program Structure
- Identify H-spec compiler directives (ACTGRP, BNDDIR, NOMAIN)
- Map F-spec files to data structures/interfaces
- Catalog all D-spec definitions
2. Extract Data Definitions
- Convert standalone fields (preserve packed decimal precision!)
- Transform data structures (handle QUALIFIED, LIKEDS, OVERLAY)
- Document multiple occurrence DS as arrays
- Identify special data structures (PSDS, INFDS, DTAARA)
3. Convert Control Flow
- Map BEGSR/ENDSR → Named procedures
- Convert P-spec procedures → Functions with prototypes
- Translate parameter lists (PLIST/PARM)
4. Translate Operations
- C-spec calculations → Pseudocode expressions
- File I/O operations → Standard file functions
- Embedded SQL → Database query patterns
- Error handling → TRY-CATCH blocks
- Indicator logic → Boolean variables
5. Handle Special Cases
- API calls → Function interfaces
- Data area operations → Persistent storage
- Commitment control → Transaction management
- Output specs → Report formatting logic
6. Generate Documentation
- Create Mermaid flowchart
- Document business rules
- Add example traces
- Note integration points
7. Verification
- Verify decimal precision preserved
- Confirm error handling coverage
- Validate date/time conversions
- Check array bounds handling
- Test IFS and web service integrations
- Validate transaction boundaries
Migration Best Practices
1. Decimal Precision Strategy
// CRITICAL: RPG packed decimals must map to exact precision types
// RPG: 15P 2 → Target: DECIMAL(15,2) or BigDecimal
// NEVER use: float, double for financial data
CONSTANTS:
ROUNDING_MODE = HALF_UP // RPG default rounding
END CONSTANTS
FUNCTION CalculateTax(amount: DECIMAL(15,2), rate: DECIMAL(5,3)) RETURNS DECIMAL(15,2)
BEGIN
result: DECIMAL(18,5)
result = amount * rate
RETURN ROUND(result, 2, ROUNDING_MODE)
END FUNCTION2. Indicator Refactoring Strategy
// Phase 1: Direct translation (indicators → booleans)
indicator01: BOOLEAN // EOF
indicator02: BOOLEAN // Record found
indicator03: BOOLEAN // Error occurred
// Phase 2: Semantic naming
endOfFile: BOOLEAN
recordFound: BOOLEAN
errorOccurred: BOOLEAN
// Phase 3: Encapsulation (for complex programs)
STRUCTURE ProgramState:
endOfFile: BOOLEAN
recordFound: BOOLEAN
errorOccurred: BOOLEAN
// ... other state
END STRUCTURE
// Phase 4: Eliminate where possible
// Replace indicator checks with direct return values or exceptions3. File I/O Abstraction
// Abstraction layer for file operations
INTERFACE FileOperations:
FUNCTION ReadRecord(key: STRING) RETURNS Record
FUNCTION UpdateRecord(record: Record) RETURNS BOOLEAN
FUNCTION DeleteRecord(key: STRING) RETURNS BOOLEAN
END INTERFACE
// Implementation can be:
// - Database (most common for RPG files)
// - REST API
// - Message queue
// - Legacy file system
CLASS DatabaseFileOperations IMPLEMENTS FileOperations:
FUNCTION ReadRecord(key: STRING) RETURNS Record
BEGIN
EXECUTE SQL
SELECT * INTO :record FROM TABLE WHERE KEY = :key
END SQL
RETURN record
END FUNCTION
END CLASS4. Error Handling Modernization
// RPG pattern: Indicators and status codes
// Modern pattern: Exceptions with context
STRUCTURE FileOperationException EXTENDS Exception:
fileName: STRING
operation: STRING
statusCode: INTEGER
recordKey: STRING
END STRUCTURE
FUNCTION ReadCustomer(custNo: DECIMAL(10,0)) RETURNS Customer
BEGIN
TRY:
record = READ_BY_KEY(custMast, custNo)
IF NOT RECORD_FOUND(custMast) THEN
THROW NEW RecordNotFoundException(
MESSAGE="Customer not found",
KEY=custNo
)
END IF
RETURN record
CATCH DatabaseException AS e:
THROW NEW FileOperationException(
MESSAGE="Failed to read customer",
FILE="CUSTMAST",
OPERATION="READ",
KEY=custNo,
CAUSE=e
)
END TRY
END FUNCTION5. Transaction Pattern
// Preserve RPG commitment control in modern transactions
FUNCTION ProcessOrder(order: Order) RETURNS BOOLEAN
BEGIN
TRY:
BEGIN_TRANSACTION()
// Update inventory
inventory = READ_BY_KEY_WITH_LOCK(invFile, order.itemNo)
inventory.quantity = inventory.quantity - order.quantity
UPDATE_RECORD(invFile, inventory)
// Create order record
WRITE_RECORD(orderFile, order)
// Update customer balance
customer = READ_BY_KEY_WITH_LOCK(custFile, order.custNo)
customer.balance = customer.balance + order.total
UPDATE_RECORD(custFile, customer)
COMMIT_TRANSACTION()
RETURN TRUE
CATCH Exception AS e:
ROLLBACK_TRANSACTION()
LOG_ERROR("Order processing failed", e)
RETURN FALSE
END TRY
END FUNCTION6. Testing Strategy
// Unit test template for migrated RPG logic
TEST_SUITE CustomerProcessing:
SETUP:
// Initialize test database
testDb = CREATE_TEST_DATABASE()
// Load test data
LOAD_TEST_DATA("test-customers.sql")
END SETUP
TEST CalculateDiscount_StandardCustomer:
// Given
customer = NEW Customer(type="STANDARD", balance=1000.00)
orderAmount: DECIMAL(15,2) = 500.00
// When
discount = CalculateDiscount(customer, orderAmount)
// Then
ASSERT_EQUALS(discount, 25.00) // 5% discount
ASSERT_PRECISION(discount, 2) // Verify decimal places
END TEST
TEST ProcessOrder_InsufficientInventory:
// Given
order = NEW Order(itemNo="ITEM001", quantity=100)
inventory = NEW Inventory(itemNo="ITEM001", quantity=50)
// When/Then
ASSERT_THROWS(InsufficientInventoryException,
ProcessOrder(order))
END TEST
TEARDOWN:
testDb.CLOSE()
END TEARDOWN
END TEST_SUITE7. Performance Considerations
// Maintain RPG batch processing efficiency
// Pattern 1: Bulk read and process
FUNCTION ProcessDailyTransactions()
BEGIN
BATCH_SIZE = 1000
transactions: ARRAY OF Transaction
// Use cursor or streaming for large datasets
CURSOR txCursor FOR
SELECT * FROM TRANSACTIONS
WHERE PROCESS_DATE = CURRENT_DATE
ORDER BY TRANSACTION_TIME
END CURSOR
OPEN_CURSOR(txCursor)
transactions = FETCH_BATCH(txCursor, BATCH_SIZE)
WHILE SIZE(transactions) > 0 DO
// Process batch
FOR EACH tx IN transactions DO
CALL ProcessTransaction(tx)
END FOR
// Commit batch
COMMIT_TRANSACTION()
// Fetch next batch
transactions = FETCH_BATCH(txCursor, BATCH_SIZE)
END WHILE
CLOSE_CURSOR(txCursor)
END FUNCTION
// Pattern 2: Parallel processing (where appropriate)
FUNCTION ProcessBatchParallel(records: ARRAY OF Record)
BEGIN
// Split into chunks
chunks = SPLIT_INTO_CHUNKS(records, THREAD_COUNT)
// Process in parallel
PARALLEL_FOR_EACH chunk IN chunks DO
FOR EACH record IN chunk DO
CALL ProcessRecord(record)
END FOR
END PARALLEL_FOR_EACH
END FUNCTIONCommon Migration Challenges
Challenge 1: MOVE/MOVEL Operations
// RPG MOVE is right-aligned, MOVEL is left-aligned
// Must preserve padding behavior
FUNCTION RPG_MOVE(source: STRING, targetLength: INTEGER) RETURNS STRING
BEGIN
// Right-align and pad with spaces on left
IF LENGTH(source) >= targetLength THEN
RETURN SUBSTRING(source, LENGTH(source) - targetLength + 1, targetLength)
ELSE
padding = REPEAT(" ", targetLength - LENGTH(source))
RETURN padding + source
END IF
END FUNCTION
FUNCTION RPG_MOVEL(source: STRING, targetLength: INTEGER) RETURNS STRING
BEGIN
// Left-align and pad with spaces on right
IF LENGTH(source) >= targetLength THEN
RETURN SUBSTRING(source, 1, targetLength)
ELSE
padding = REPEAT(" ", targetLength - LENGTH(source))
RETURN source + padding
END IF
END FUNCTIONChallenge 2: Overlay and Shared Memory
// RPG OVERLAY shares memory space
// Must preserve data relationships
STRUCTURE DateStructure:
fullDate: STRING[8] // YYYYMMDD
year: COMPUTED_FIELD // Positions 1-4
month: COMPUTED_FIELD // Positions 5-6
day: COMPUTED_FIELD // Positions 7-8
END STRUCTURE
// Implementation with computed properties
CLASS DateStructure:
PRIVATE fullDate: STRING[8]
PROPERTY Year:
GET: RETURN SUBSTRING(fullDate, 1, 4)
SET: fullDate = value + SUBSTRING(fullDate, 5, 4)
END PROPERTY
PROPERTY Month:
GET: RETURN SUBSTRING(fullDate, 5, 2)
SET: fullDate = SUBSTRING(fullDate, 1, 4) + value +
SUBSTRING(fullDate, 7, 2)
END PROPERTY
PROPERTY Day:
GET: RETURN SUBSTRING(fullDate, 7, 2)
SET: fullDate = SUBSTRING(fullDate, 1, 6) + value
END PROPERTY
END CLASSChallenge 3: Multiple Occurrence Data Structures
// RPG: Multiple occurrence DS with %OCCUR
// Modern: Array with explicit indexing
// RPG concept:
// LineItem DS OCCURS(99)
// %OCCUR(LineItem) = 5 // Set to 5th occurrence
// Modern equivalent:
lineItems: ARRAY[99] OF LineItem
currentLineIndex: INTEGER = 5
currentLine = lineItems[currentLineIndex]
// Better: Use collections with iteration
lineItems: LIST OF LineItem
FOR EACH item IN lineItems DO
CALL ProcessLineItem(item)
END FORChallenge 4: *ALL and Special Value Comparisons
// RPG special values need careful translation
// *BLANK/*BLANKS
IF field = EMPTY_STRING OR TRIM(field) = "" THEN ...
// *ZERO/*ZEROS
IF field = 0 THEN ...
// *HIVAL (highest value for type)
IF field >= MAX_VALUE_FOR_TYPE THEN ...
// *LOVAL (lowest value for type)
IF field <= MIN_VALUE_FOR_TYPE THEN ...
// *ALL'X' (all positions contain 'X')
IF field = REPEAT('X', LENGTH(field)) THEN ...Documentation Template
When documenting migrated RPG programs, include:
# [PROGRAM-NAME] - Description
## Original RPG Information
- **Original Source**: [Path/Library/Program]
- **RPG Version**: [RPG III/RPG IV/ILE RPG]
- **Compile Date**: [Date]
- **Dependencies**: [Called programs, files, procedures]
## Migration Information
- **Migration Date**: [Date]
- **Target Platform**: [Java/Python/C#/etc.]
- **Migrated By**: [Name/Team]
- **Verification Status**: [Unit Tested/Integration Tested/Production Ready]
## Program Overview
- **Purpose**: [What the program does]
- **Trigger**: [Batch job/Online/API/Event]
- **Frequency**: [Daily/Real-time/On-demand]
- **Input**: [Files, parameters, databases]
- **Output**: [Files, reports, databases, messages]
## Data Structures
[All structures with field definitions and RPG equivalents]
## Core Business Logic
[Key procedures/functions with pseudocode]
## Translation Notes
[Special cases, assumptions, deviations from original]
## Testing Notes
[Test cases, edge cases, known issues]
## Performance Benchmarks
[RPG vs. migrated performance comparison if available]Reference: IBM RPG IV Reference, ILE RPG Programmer's Guide
---
Reference: IBM RPG IV Reference, ILE RPG Programmer's Guide
RPG Translation Patterns
Prerequisites: Read pseudocode-rpg-core-rules.md and pseudocode-rpg-data-structures.md.
---
This file contains common RPG translation patterns, gotchas, and critical rules for everyday RPG programming constructs.
Translation Patterns
Indicators → Boolean
D EOF S N INZ(*OFF)
C IF EOF
C EVAL EOF = *ON→
eof: BOOLEAN = FALSE
IF eof THEN ...
eof = TRUESubroutine → Procedure
C CALC_TOTAL BEGSR
C EVAL Total = Qty * Price
C ENDSR
C EXSR CALC_TOTAL→
PROCEDURE CalcTotal()
BEGIN
total = qty * price
END PROCEDURE
CALL CalcTotal()File Loop with CHAIN
C Key CHAIN File
C DOW %FOUND(File)
C EXSR ProcessRec
C Key CHAIN File
C ENDDO→
record = READ_BY_KEY(file, key)
WHILE RECORD_FOUND(file) DO
CALL ProcessRec(record)
record = READ_BY_KEY(file, key)
END WHILESequential Read Loop
C READ CustMast
C DOW NOT %EOF(CustMast)
C EXSR ProcessCustomer
C READ CustMast
C ENDDO→
record = READ_RECORD(custMast)
WHILE NOT END_OF_FILE(custMast) DO
CALL ProcessCustomer(record)
record = READ_RECORD(custMast)
END WHILESETLL/READE Pattern (Key Processing)
C Key SETLL File
C Key READE File
C DOW NOT %EOF(File)
C EXSR Process
C Key READE File
C ENDDO→
POSITION_LOWER_LIMIT(file, key)
record = READ_EQUAL_KEY(file, key)
WHILE NOT END_OF_FILE(file) DO
CALL Process(record)
record = READ_EQUAL_KEY(file, key)
END WHILEUpdate Record Pattern
C CustNo CHAIN CustMast
C IF %FOUND(CustMast)
C EVAL Balance = Balance + Amount
C UPDATE CustRec
C ENDIF→
record = READ_BY_KEY(custMast, custNo)
IF RECORD_FOUND(custMast) THEN
record.balance = record.balance + amount
UPDATE_RECORD(custMast, record)
END IFWrite New Record Pattern
C CLEAR CustRec
C EVAL CustNo = NewCustNo
C EVAL Name = NewName
C WRITE CustRec→
record = NEW CustomerRecord
record.custNo = newCustNo
record.name = newName
WRITE_RECORD(custMast, record)Modern Error Handling (MONITOR)
C MONITOR
C Key CHAIN File
C EVAL Result = Amt1 / Amt2
C ON-ERROR 1211:1299
C EVAL ErrMsg = 'File error occurred'
C ON-ERROR *ALL
C EVAL ErrMsg = 'Unknown error'
C ENDMON→
TRY:
record = READ_BY_KEY(file, key)
result = amt1 / amt2
CATCH FileException:
errMsg = "File error occurred"
CATCH Exception:
errMsg = "Unknown error"
END TRYLegacy Error Handling (%ERROR)
C Key CHAIN(E) File
C IF %ERROR
C EVAL ErrMsg = 'CHAIN failed'
C ENDIF→
TRY:
record = READ_BY_KEY(file, key)
CATCH Exception:
errMsg = "CHAIN failed"
END TRYProcedure with Parameters
D CalcTax PR 15P 2
D Amount 15P 2 CONST
D TaxRate 5P 3 CONST
P CalcTax B
D CalcTax PI 15P 2
D Amount 15P 2 CONST
D TaxRate 5P 3 CONST
C RETURN Amount * TaxRate
P CalcTax E→
FUNCTION CalcTax(amount: DECIMAL(15,2), taxRate: DECIMAL(5,3)) RETURNS DECIMAL(15,2)
BEGIN
RETURN amount * taxRate
END FUNCTIONString Manipulation
C EVAL FullName = %TRIM(FirstName) + ' ' +
C %TRIM(LastName)
C EVAL Pos = %SCAN('&':Message)
C IF Pos > 0
C EVAL Message = %REPLACE(Value:Message:Pos:1)
C ENDIF→
fullName = TRIM(firstName) + " " + TRIM(lastName)
pos = FIND(message, "&")
IF pos > 0 THEN
message = REPLACE(message, value, pos, 1)
END IFDate Arithmetic
C EVAL Today = %DATE()
C EVAL DueDate = Today + %DAYS(30)
C EVAL DaysLate = %DIFF(Today:InvDate:*DAYS)
C IF DaysLate > 30
C EVAL Status = 'OVERDUE'
C ENDIF→
today = CURRENT_DATE()
dueDate = today + DURATION_DAYS(30)
daysLate = DATE_DIFFERENCE(today, invDate, DAYS)
IF daysLate > 30 THEN
status = "OVERDUE"
END IFNumeric Formatting
C EVAL Display = %EDITC(Amount:'J')
C EVAL Formatted = %EDITW(SSN:'0 - - ')→
display = FORMAT_NUMERIC(amount, COMMA_WITH_DECIMALS)
formatted = FORMAT_WITH_PATTERN(ssn, "0 - - ")SELECT/WHEN Pattern
C SELECT
C WHEN Status = 'A'
C EVAL Desc = 'Active'
C WHEN Status = 'I'
C EVAL Desc = 'Inactive'
C OTHER
C EVAL Desc = 'Unknown'
C ENDSL→
SWITCH status:
CASE "A":
desc = "Active"
BREAK
CASE "I":
desc = "Inactive"
BREAK
DEFAULT:
desc = "Unknown"
END SWITCHSubfile Operations (Interactive Programs)
// Clear and load subfile
C EVAL *IN31 = *OFF
C WRITE SflCtl
C EVAL *IN31 = *ON
C EVAL RRN = 0
C READ DataFile
C DOW NOT %EOF(DataFile)
C EVAL RRN = RRN + 1
C EVAL SflCustNo = CustNo
C EVAL SflName = Name
C WRITE SflRec
C READ DataFile
C ENDDO
C EVAL *IN32 = *ON
C EXFMT SflCtl→
// Clear subfile
subfileClear = TRUE
WRITE_SCREEN_FORMAT(screen, "SflCtl")
subfileClear = FALSE
subfileDisplay = TRUE
// Load subfile records
rrn = 0
record = READ_RECORD(dataFile)
WHILE NOT END_OF_FILE(dataFile) DO
rrn = rrn + 1
subfileRecord.custNo = record.custNo
subfileRecord.name = record.name
subfileRecord.rrn = rrn
WRITE_SUBFILE_RECORD(screen, "SflRec", subfileRecord)
record = READ_RECORD(dataFile)
END WHILE
// Display subfile and wait for input
subfileDisplayControl = TRUE
DISPLAY_AND_READ(screen, "SflCtl")Subfile Processing User Selections
C EVAL RRN = 0
C READC SflRec
C DOW NOT %EOF(ScreenDsp)
C IF SflSelect = 'X'
C EXSR ProcessSelection
C ENDIF
C READC SflRec
C ENDDO→
rrn = 0
changedRecord = READ_CHANGED_SUBFILE_RECORD(screen, "SflRec")
WHILE RECORD_FOUND(screen) DO
IF changedRecord.select = "X" THEN
CALL ProcessSelection(changedRecord)
END IF
changedRecord = READ_CHANGED_SUBFILE_RECORD(screen, "SflRec")
END WHILERPG Cycle Handling (Legacy)
// Primary file drives RPG cycle
F DataFile IP E DISK
C READ DataFile
C DOW NOT %EOF(DataFile)
C *LOVAL SETLL DetailFile
C Key READE DetailFile
C DOW NOT %EOF(DetailFile)
C EXSR ProcessDetail
C Key READE DetailFile
C ENDDO
C READ DataFile
C ENDDO→
// Convert RPG cycle to explicit loop
PROCEDURE MainProcess()
BEGIN
dataRecord = READ_RECORD(dataFile)
WHILE NOT END_OF_FILE(dataFile) DO
// Level break logic would go here if present
CALL ProcessMasterRecord(dataRecord)
// Process matching detail records
POSITION_LOWER_LIMIT(detailFile, MIN_VALUE)
detailRecord = READ_EQUAL_KEY(detailFile, dataRecord.key)
WHILE NOT END_OF_FILE(detailFile) DO
CALL ProcessDetail(detailRecord)
detailRecord = READ_EQUAL_KEY(detailFile, dataRecord.key)
END WHILE
dataRecord = READ_RECORD(dataFile)
END WHILE
END PROCEDUREArray Initialization and Processing
D Months S 3 DIM(12) CTDATA PERRCD(4)
D MonthIdx S 3 0
C FOR MonthIdx = 1 TO 12
C EVAL Display = Months(MonthIdx)
C ENDFOR
**CTDATA Months
JanFebMarApr
MayJunJulAug
SepOctNovDec→
// Compile-time data array
months: ARRAY[12] OF STRING[3] = [
"Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec"
]
FOR monthIdx FROM 1 TO 12 DO
display = months[monthIdx]
END FORDynamic Array Sorting
C SORTA %SUBARR(Array:1:Count)→
SORT_ARRAY(array, 1, count, ASCENDING)Program Initialization and Termination
C *INZSR BEGSR
C EVAL StartTime = %TIME()
C EXSR LoadConfig
C ENDSR
C *INLR IFEQ *ON
C EVAL EndTime = %TIME()
C EXSR Cleanup
C ENDIF→
PROCEDURE Initialize()
BEGIN
startTime = CURRENT_TIME()
CALL LoadConfig()
END PROCEDURE
PROCEDURE Terminate()
BEGIN
endTime = CURRENT_TIME()
CALL Cleanup()
lastRecord = TRUE // *INLR equivalent
END PROCEDURE
// Main program flow
BEGIN PROGRAM
CALL Initialize()
CALL MainProcessing()
CALL Terminate()
END PROGRAMMessage Handling
D Msg S 52
D MsgKey S 4
C CALL 'QMHSNDPM'
C PARM MsgId
C PARM MsgFile
C PARM MsgData
C PARM MsgLen
C PARM '*INFO' MsgType
C PARM '*' CallStk
C PARM 0 StkCntr
C PARM MsgKey→
PROCEDURE SendProgramMessage(
msgId: STRING,
msgFile: STRING,
msgData: STRING,
msgType: STRING
) RETURNS STRING
BEGIN
msgKey: STRING[4]
// Call system API to send program message
CALL_SYSTEM_API("QMHSNDPM", [
msgId, msgFile, msgData, LENGTH(msgData),
msgType, "*", 0, msgKey
])
RETURN msgKey
END PROCEDUREData Queue Operations
D DtaQ S 10 INZ('MYDTAQ')
D DtaQLib S 10 INZ('MYLIB')
D DtaQData S 100
D DtaQLen S 5 0
C CALL 'QSNDDTAQ'
C PARM DtaQ
C PARM DtaQLib
C PARM 100 DtaQLen
C PARM DtaQData→
STRUCTURE DataQueue:
name: STRING[10]
library: STRING[10]
END STRUCTURE
PROCEDURE SendToDataQueue(
queue: DataQueue,
data: STRING,
length: INTEGER
)
BEGIN
CALL_SYSTEM_API("QSNDDTAQ", [
queue.name, queue.library, length, data
])
END PROCEDURE
PROCEDURE ReceiveFromDataQueue(
queue: DataQueue,
waitTime: INTEGER
) RETURNS STRING
BEGIN
data: STRING[100]
dataLength: INTEGER
CALL_SYSTEM_API("QRCVDTAQ", [
queue.name, queue.library, dataLength, data, waitTime
])
RETURN SUBSTRING(data, 1, dataLength)
END PROCEDURECommon Pitfalls to Avoid
1. Losing precision: Always check packed decimal field sizes (nP m) 2. Ignoring indicators: Named booleans must have meaningful names 3. MOVE/MOVEL: Remember these are position-based, not simple assignments 4. Array indexing: RPG uses 1-based indexing, adjust for target language 5. %FOUND vs %EOF: Use correct check after different file operations 6. Date formats: RPG date formats vary (ISO,USA, EUR,JIS, MDY, etc.) 7. String position: RPG %SUBST uses 1-based positions 8. Half-adjust: Don't forget (H) extender implies ROUND with HALF_UP 9. File scope: RPG files are global; modern code may need different scope 10. Indicator arrays: IN(01) array syntax vs individualIN01 11. EVAL optional: Free format may omit EVAL but it's an assignment 12. Procedure naming: BEGSR names are local; P-spec procs may be exported 13. Data structure arrays: Multiple occurrence DS != modern arrays 14. LR indicator: Setting INLR=ON closes files and frees resources 15. Factor 1 and 2: Some ops use Factor 1 as control (LOKUP, SCAN old style) 16. Subfile operations: Preserve subfile load/display/read patterns 17. READC operation: Convert to "read changed records" pattern 18. RPG cycle: Convert to explicit loops with clear control flow 19. CTDATA arrays: Extract to initialization code or configuration 20. F-spec prefixes: Apply field name prefixes consistently 21. USROPN files: Ensure explicit OPEN/CLOSE operations 22. Overflow indicators: Convert to page management logic 23. System APIs*: Document external dependencies clearly
Critical Rules
1. Indicators: Convert IN01-IN99 to named booleans with descriptive names 2. Packed decimal (P): MUST preserve precision using DECIMAL(n,m) 3. %ERROR: Convert to TRY-CATCH blocks 4. %FOUND: Check after CHAIN/SETLL/READE operations 5. Half-adjust (H): Use ROUND(expr, decimals) with HALF_UP rounding 6. MONITOR/ON-ERROR: Convert to modern TRY-CATCH-FINALLY blocks 7. Multiple occurrence DS: Convert to arrays with explicit indexing 8. OVERLAY: Document shared memory with clear comments 9. Data areas: Convert to persistent storage or configuration 10. Commitment control: Preserve transaction boundaries 11. Pointers: Use safe abstractions where possible 12. Special values: Convert to language-appropriate equivalents
---
Next: See pseudocode-rpg-advanced.md for advanced RPG features.
RPG Translation Rules - Index
This is the main index for RPG to pseudocode translation rules. The rules have been organized into focused, topic-specific files for easier navigation and context loading by AI agents.
---
Quick Start
New to RPG migration? Start here:
1. Read pseudocode-common-rules.md for basic pseudocode syntax 2. Read pseudocode-rpg-core-rules.md for fundamental RPG translations 3. Consult other files as needed for specific features
---
File Guide
📘 pseudocode-rpg-core-rules.md
Size: ~400 lines Use for: Basic RPG syntax, data types, and operations
Contains:
- Specification mapping (H-spec, F-spec, D-spec, C-spec, P-spec)
- F-spec file declarations (Database, Display, Printer)
- Core data types (Packed, Zoned, Character, Date/Time, Indicators)
- RPG special values (BLANK,ZERO, HIVAL,LOVAL, etc.)
- Basic operations (arithmetic, data movement, string ops)
- Control flow (IF, DOW, DOU, FOR, SELECT/WHEN)
- File operations (READ, WRITE, CHAIN, UPDATE, SETLL)
- Data validation operations
When to use: Start here for any RPG translation. This file contains the foundational rules needed for basic RPG programs.
---
🔧 pseudocode-rpg-functions.md
Size: ~130 lines Use for: RPG built-in functions (BIFs)
Contains:
- String functions (%SUBST, %TRIM, %SCAN, %REPLACE, %XLATE)
- Conversion functions (%CHAR, %INT, %DEC, %EDITC, %EDITW)
- Date/Time functions (%DATE, %TIME, %TIMESTAMP, %DIFF, durations)
- File status functions (%EOF, %FOUND, %EQUAL, %ERROR)
- Array/DS functions (%ELEM, %OCCUR, %ADDR, %LOOKUP)
- Arithmetic functions (%ABS, %DIV, %REM, %SQRT)
- Conditional functions (%NULLIND, %PARMS)
When to use: Reference this when encountering RPG BIFs (functions starting with %).
---
📦 pseudocode-rpg-data-structures.md
Size: ~200 lines Use for: Complex data structure patterns
Contains:
- Simple data structures
- Qualified data structures
- LIKEDS (structure references)
- OVERLAY (shared memory patterns)
- Multiple occurrence data structures
- I-spec (Input specification) patterns for fixed-format input
- O-spec (Output specification) patterns for reports
When to use: When translating programs with complex data structures, especially those using QUALIFIED, LIKEDS, OVERLAY, or legacy I/O specs.
---
🎯 pseudocode-rpg-patterns.md
Size: ~700 lines Use for: Common RPG idioms and translation patterns
Contains:
- Indicator → Boolean conversion
- Subroutine → Procedure patterns
- File loop patterns (CHAIN, READ, SETLL/READE)
- Update/Write record patterns
- Error handling (MONITOR, %ERROR)
- Procedure with parameters
- String manipulation patterns
- Date arithmetic
- SELECT/WHEN patterns
- Subfile operations (interactive programs)
- RPG cycle handling
- Array initialization (CTDATA)
- Program initialization/termination
- Message handling
- Data queue operations
- Common Pitfalls (23 gotchas to avoid)
- Critical Rules (12 must-follow rules for accurate migration)
When to use: This is your primary reference for everyday RPG patterns. Consult frequently during translation to avoid common mistakes.
---
🚀 pseudocode-rpg-advanced.md
Size: ~1,300 lines Use for: Modern ILE RPG features and integrations
Contains:
- File/Program status data structures (INFDS, PSDS)
- Data area operations
- Commitment control
- API call patterns
- Service programs and binding (NOMAIN, EXPORT)
- Parameter passing options (CONST, NOPASS,OMIT, *VARSIZE)
- Return values vs output parameters
- Performance optimization patterns
- Activation group management
- Embedded SQL (cursors, INSERT/UPDATE/DELETE, dynamic SQL, stored procedures)
- IFS Operations (file I/O, directory operations, file stats)
- XML and JSON (parsing and generation)
- HTTP/Web Services (REST, SOAP)
- Advanced file locking patterns
- User space operations
- Multiple threading (job submission, data queues)
- External program calls
- Object-oriented features (procedure pointers, factory pattern)
When to use: Reference when encountering ILE RPG features, SQL, web services, or modern RPG capabilities.
---
📋 pseudocode-rpg-migration-guide.md
Size: ~530 lines Use for: Migration planning and best practices
Contains:
- Advanced refactoring patterns
- Translation workflow (7-step process)
- Migration Best Practices:
- Decimal precision strategy
- Indicator refactoring (4 phases)
- File I/O abstraction layers
- Error handling modernization
- Transaction patterns
- Testing strategy with examples
- Performance considerations
- Common Migration Challenges:
- MOVE/MOVEL operations
- Overlay and shared memory
- Multiple occurrence data structures
- Special value comparisons
- Documentation template
When to use: Use this for project planning, establishing migration strategies, and understanding how to modernize legacy RPG patterns.
---
Translation Workflow
For a complete RPG program migration:
1. Analyze with pseudocode-rpg-core-rules.md
- Map specifications (H/F/D/C/P)
- Identify data types and file declarations
2. Convert Data with pseudocode-rpg-data-structures.md
- Transform DS patterns
- Handle special cases (OVERLAY, LIKEDS, multiple occurrence)
3. Translate Logic with pseudocode-rpg-patterns.md
- Apply common translation patterns
- Follow critical rules
- Avoid documented pitfalls
4. Handle Functions with pseudocode-rpg-functions.md
- Convert BIFs to pseudocode equivalents
5. Address Advanced Features with pseudocode-rpg-advanced.md
- Translate SQL, web services, IFS operations
- Handle service programs and binding
6. Apply Best Practices with pseudocode-rpg-migration-guide.md
- Refactor indicators and error handling
- Establish testing strategy
- Document migration decisions
---
Quick Reference
Critical Data Type Rules
- Packed Decimal (nP m) →
DECIMAL(n,m)- NEVER USE FLOAT FOR MONEY! - *Indicators (INxx)** → Named
BOOLEANvariables - Arrays → 1-based indexing in RPG, adjust for target language
- Dates → Handle format differences (ISO,USA, EUR,MDY, etc.)
Most Common Patterns
- File loop:
READinDOW NOT %EOFloop - Key processing:
SETLL+READEin loop - Update:
CHAIN+ check%FOUND+UPDATE - Error handling:
MONITOR/ON-ERROR→ TRY/CATCH
Files Referenced
- pseudocode-common-rules.md - Base pseudocode syntax
- testing-strategy.md - Unit testing approaches
- transaction-handling.md - Transaction patterns
- performance-patterns.md - Optimization techniques
- messaging-integration.md - Message queue integration
---
File Statistics
| File | Lines | Focus | Complexity |
|---|---|---|---|
| pseudocode-rpg-core-rules.md | ~400 | Foundation | Basic |
| pseudocode-rpg-functions.md | ~130 | BIFs | Basic |
| pseudocode-rpg-data-structures.md | ~200 | Data Patterns | Intermediate |
| pseudocode-rpg-patterns.md | ~700 | Common Idioms | Intermediate |
| pseudocode-rpg-advanced.md | ~1,300 | ILE RPG, SQL, Web | Advanced |
| pseudocode-rpg-migration-guide.md | ~530 | Best Practices | Strategic |
| Total | ~3,260 | Complete Coverage | All Levels |
---
Version History
v2.0 - Split Architecture (2026-01-20)
- Breaking Change: Split single 2,993-line file into 6 focused files
- Benefit: Improved Agent Skills compliance (<500 line recommendation)
- Benefit: Easier context loading for AI agents
- Benefit: Progressive disclosure of complexity
v1.0 - Monolithic (Original)
- Single comprehensive file
- 2,993 lines total
- Complete but difficult to navigate
---
Reference: IBM RPG IV Reference, ILE RPG Programmer's Guide
Related skills
FAQ
Which RPG dialects does it cover?
RPG III, RPG IV, and ILE, including H/F/D/C/P specs, indicators, and built-in functions described in the skill workflow.
What Java mappings does it emphasize?
Packed and zoned decimals to BigDecimal, dates to java.time types, D-specs to POJOs, and file operations to JPA or JDBC patterns.