/* REXX */
/* ========================================================================== */
/* BXC - Benford Analysis Tool (Classic REXX for ArcaOS 5.1.1) */
/* Original BASIC version by Jason S. Page */
/* Enhanced Classic REXX version for ArcaOS */
/* ========================================================================== */
/* -------------------------------------------------------------------------- */
/* Robust RexxUtil Loading for ArcaOS 5.1.1 */
/* -------------------------------------------------------------------------- */
call LoadRexxUtil
/* -------------------------------------------------------------------------- */
/* Initialize Benford's Law Constants */
/* -------------------------------------------------------------------------- */
numeric digits 12
benford.0 = 9
benford.1 = 30.1
benford.2 = 17.6
benford.3 = 12.5
benford.4 = 9.7
benford.5 = 7.9
benford.6 = 6.7
benford.7 = 5.8
benford.8 = 5.1
benford.9 = 4.6
/* -------------------------------------------------------------------------- */
/* Initialize Global Variables */
/* -------------------------------------------------------------------------- */
toke = 0
animate = 0
animate_interval = 100
total_processed = 0
block_char = 'DB'x
/* -------------------------------------------------------------------------- */
/* Parse Command Line Arguments */
/* -------------------------------------------------------------------------- */
parse arg params
load1 = ""
a12 = ""
prog = ""
col = ""
anim = ""
call ParseFlag "-f", params
load1 = result
call ParseFlag "-d", params
a12 = result
call ParseFlag "-l", params
prog = result
call ParseFlag "-c", params
col = result
call ParseFlag "-a", params
anim = result
/* -------------------------------------------------------------------------- */
/* Show Help if Requested */
/* -------------------------------------------------------------------------- */
if pos("-h", params) > 0 | pos("--help", params) > 0 then do
call ShowHelp
exit
end
/* -------------------------------------------------------------------------- */
/* Handle Animation Flag */
/* -------------------------------------------------------------------------- */
if length(anim) > 0 then do
animate = 1
if datatype(anim, 'W') then animate_interval = anim
end
/* -------------------------------------------------------------------------- */
/* Interactive Mode if Missing Required Arguments */
/* -------------------------------------------------------------------------- */
if length(load1) = 0 | length(a12) = 0 | length(prog) = 0 | length(col) = 0 then do
say "Error: Missing required flags."
say ""
say "Interactive mode:"
call SysFileTree "*.dat", "fileList", "FO"
do i = 1 to fileList.0
say "Found: " || fileList.i
end
call charout , "Load file: "
parse pull load1
call charout , "[A]ll Digits or [1]st Digit: "
parse pull a12
call charout , "Capture Average Every (default: 10000) Points: "
parse pull prog
call charout , "Which Column Number (0 = single column): "
parse pull col
call charout , "Enable animation? [y/n]: "
parse pull anim_choice
if translate(left(anim_choice, 1)) = "Y" then do
animate = 1
call charout , "Update interval (default: 100): "
parse pull anim_input
if datatype(anim_input, 'W') then animate_interval = anim_input
end
end
/* -------------------------------------------------------------------------- */
/* Set Defaults */
/* -------------------------------------------------------------------------- */
if length(prog) = 0 then prog = "10000"
if length(col) = 0 then col = "0"
if col = "0" then col = "1"
/* -------------------------------------------------------------------------- */
/* Display Configuration */
/* -------------------------------------------------------------------------- */
say ""
say "Configuration:"
say " File: " || load1
say " Digits: " || a12
say " Sample size: " || prog
say " Column: " || col
if animate = 1 then
say " Animation: Enabled (update every " || animate_interval || " records)"
else
say " Animation: Disabled"
say ""
/* -------------------------------------------------------------------------- */
/* Download File if URL */
/* -------------------------------------------------------------------------- */
if left(translate(load1), 3) = "FTP" | left(translate(load1), 4) = "HTTP" then do
say "Downloading file..."
address CMD "wget -c " || load1 || " 2>&1"
last_slash = lastpos('/', load1)
if last_slash > 0 then load1 = substr(load1, last_slash + 1)
say "Assuming downloaded file is: " || load1
end
/* -------------------------------------------------------------------------- */
/* Verify File Exists */
/* -------------------------------------------------------------------------- */
if \SysFileExists(load1) then do
say "Error: File '" || load1 || "' not found!"
exit 1
end
/* -------------------------------------------------------------------------- */
/* Create Log Files */
/* -------------------------------------------------------------------------- */
log_values = load1 || "_" || a12 || "-" || prog || "-.log"
log_percent = load1 || "_" || a12 || "-" || prog || "_.log"
call lineout log_values, , 1
call lineout log_percent, , 1
/* -------------------------------------------------------------------------- */
/* Initialize Counters */
/* -------------------------------------------------------------------------- */
c. = 0
count_total = 0
/* -------------------------------------------------------------------------- */
/* Display Header */
/* -------------------------------------------------------------------------- */
say ""
say "========================================================================"
say "Benford X-C Forensics Digital Analysis Tool (ArcaOS 5.1.1)"
say "========================================================================"
say ""
if animate = 0 then do
say " Record, 1, 2, 3, 4, 5, 6, 7, 8, 9"
end
/* -------------------------------------------------------------------------- */
/* Main Processing Loop */
/* -------------------------------------------------------------------------- */
infile = load1
do while lines(infile) > 0
line = linein(infile)
toke = toke + 1
total_processed = total_processed + 1
if length(line) = 0 then iterate
ot = line
/* Extract column if needed */
if col > 1 then do
call ExtractColumn line, col
ot = result
end
/* Clean to digits only */
call ExtractDigits ot
ot = result
if length(ot) = 0 then iterate
/* Determine position based on digit mode */
if translate(left(a12, 1)) = "A" then
position = length(ot)
else
position = 1
/* Count digits */
do i = 1 to position
digit = substr(ot, i, 1)
if digit >= 1 & digit <= 9 then do
count_total = count_total + 1
c.digit = c.digit + 1
end
end
/* Animate if enabled */
if animate = 1 & (total_processed // animate_interval = 0) & count_total > 0 then do
call DrawAnimatedChart count_total, total_processed, prog
end
/* Write sample when reaching sample size */
if count_total >= prog then do
p. = 0
do i = 1 to 9
if count_total > 0 then
p.i = format((c.i * 100) / count_total, 2, 2)
else
p.i = 0
end
cc1 = ot || "," || toke || "," || c.1 || "," || c.2 || "," || c.3 || "," || c.4 || "," || c.5 || "," || c.6 || "," || c.7 || "," || c.8 || "," || c.9
cc2 = ot || "," || toke || "," || p.1 || "," || p.2 || "," || p.3 || "," || p.4 || "," || p.5 || "," || p.6 || "," || p.7 || "," || p.8 || "," || p.9
if animate = 0 then do
say "#: " || cc1
say "%: " || cc2
end
call lineout log_values, cc1
call lineout log_percent, cc2
/* Reset counters */
count_total = 0
do i = 1 to 9
c.i = 0
end
end
end
/* -------------------------------------------------------------------------- */
/* Close Files */
/* -------------------------------------------------------------------------- */
call stream infile, 'C', 'CLOSE'
call lineout log_values
call lineout log_percent
if animate = 1 then say ""
/* -------------------------------------------------------------------------- */
/* Display Completion Message */
/* -------------------------------------------------------------------------- */
say ""
say "========================================================================"
say "Analysis Complete"
say "========================================================================"
say "Total records processed: " || toke
say ""
say "Calculating final statistics and chi-squared test..."
call GenerateFinalReport log_percent, load1, a12, prog
say ""
say "Output files:"
say " Values log: " || log_values
say " Percent log: " || log_percent
say ""
exit 0
/* ========================================================================== */
/* Subroutines */
/* ========================================================================== */
/* -------------------------------------------------------------------------- */
/* ShowHelp - Display usage information */
/* -------------------------------------------------------------------------- */
ShowHelp:
say "=================================="
say "bxc - Benford Analysis Tool (ArcaOS)"
say "=================================="
say "Usage: bxc -f [file] -d [1|all] -l [length] -c [column] -a [interval]"
say ""
say "Required flags:"
say " -f [file] Data file to analyze (or URL)"
say " -d [1|all] Analyze first digit (1) or all digits (all)"
say " -l [number] Sample pool length (default: 10000)"
say " -c [number] Column number (0 for single column)"
say ""
say "Optional flags:"
say " -a [interval] Enable animated graph (default: 100)"
say " -h, --help Show this help message"
say ""
say "Example:"
say " bxc -f data.dat -d 1 -l 10000 -c 1"
return
/* -------------------------------------------------------------------------- */
/* ParseFlag - Extract flag value from command line */
/* -------------------------------------------------------------------------- */
ParseFlag: procedure
parse arg flag, cmdline
result = ""
ppos = pos(flag, cmdline)
if ppos > 0 then do
start = ppos + length(flag) + 1
rest = substr(cmdline, start)
parse var rest val " -" rest_ignored
result = strip(val)
end
return result
/* -------------------------------------------------------------------------- */
/* ExtractColumn - Extract specified column from CSV line */
/* -------------------------------------------------------------------------- */
ExtractColumn: procedure
parse arg line, col_num
if col_num <= 1 then return line
loop_col = 1
temp_line = line
found_val = ""
do while loop_col <= col_num
parse var temp_line this_val "," temp_line
if loop_col = col_num then found_val = this_val
loop_col = loop_col + 1
end
return found_val
/* -------------------------------------------------------------------------- */
/* ExtractDigits - Extract only numeric digits from string */
/* -------------------------------------------------------------------------- */
ExtractDigits: procedure
parse arg text
clean = ""
do i = 1 to length(text)
char = substr(text, i, 1)
if datatype(char, 'N') then clean = clean || char
end
return clean
/* -------------------------------------------------------------------------- */
/* DrawAnimatedChart - Display live animated chart */
/* -------------------------------------------------------------------------- */
DrawAnimatedChart: procedure expose c. benford. block_char
parse arg total_seg, total_proc, sample_size
call SysCls
say "========================================================================"
say "Benford X-C Live Analysis - Animated View"
say "========================================================================"
say "Processing: " || total_proc || " records | Sample pool: " || sample_size || " | Current: " || total_seg
say "------------------------------------------------------------------------"
say ""
say "Digit Actual Expected Deviation Chart"
say "----- ------- -------- --------- ---------------------------------"
do i = 1 to 9
if total_seg > 0 then
pct = (c.i * 100) / total_seg
else
pct = 0
call PrintDigitRow i, pct, benford.i
end
say "========================================================================"
return
/* -------------------------------------------------------------------------- */
/* PrintDigitRow - Display single digit row in chart */
/* -------------------------------------------------------------------------- */
PrintDigitRow: procedure expose block_char
parse arg digit, actual, expected
deviation = actual - expected
bar_len = actual / 2
if bar_len > 50 then bar_len = 50
bar = copies(block_char, trunc(bar_len))
s_actual = format(actual, 2, 1) || "%"
s_expect = format(expected, 2, 1) || "%"
sign = " "
if deviation >= 0 then sign = "+"
s_dev = sign || format(deviation, 2, 1) || "%"
say " " || digit || " " || left(s_actual, 7) || " " || left(s_expect, 8) || " " || left(s_dev, 9) || " " || bar
return
/* -------------------------------------------------------------------------- */
/* GenerateFinalReport - Create final statistical report */
/* -------------------------------------------------------------------------- */
GenerateFinalReport: procedure expose benford. block_char
parse arg percent_file, data_file, mode, samp
sum. = 0
count = 0
/* Check if file exists and has content */
if \SysFileExists(percent_file) then return
/* Read and calculate averages */
call stream percent_file, 'C', 'CLOSE'
do while lines(percent_file) > 0
line = linein(percent_file)
if length(line) = 0 then iterate
parse var line toss "," recd "," p.1 "," p.2 "," p.3 "," p.4 "," p.5 "," p.6 "," p.7 "," p.8 "," p.9
do i = 1 to 9
sum.i = sum.i + p.i
end
count = count + 1
end
call stream percent_file, 'C', 'CLOSE'
if count > 0 then do
chi_squared = 0
say "Final Statistics (Averaged across all samples):"
say "------------------------------------------------"
say "Digit Actual Expected Deviation"
say "----- ------- -------- ---------"
do i = 1 to 9
avg = sum.i / count
dev = avg - benford.i
chi_part = (dev * dev) / benford.i
chi_squared = chi_squared + chi_part
s_avg = format(avg, 2, 1) || "%"
s_exp = format(benford.i, 2, 1) || "%"
s_dev = format(dev, 2, 1) || "%"
say " " || i || " " || left(s_avg, 7) || " " || left(s_exp, 8) || " " || s_dev
end
say ""
say "Chi-Squared Statistic: " || format(chi_squared, 4, 4)
say ""
/* Interpret results */
if chi_squared < 15.51 then do
say "Result: Data FITS Benford's Law (95% confidence)"
say " No significant deviation detected."
end
else do
say "Result: Data DOES NOT fit Benford's Law (95% confidence)"
say " Significant deviation detected - possible fraud indicator!"
end
say ""
say "Generating ASCII chart..."
call GenerateChart percent_file
end
return
/* -------------------------------------------------------------------------- */
/* GenerateChart - Create ASCII chart output file */
/* -------------------------------------------------------------------------- */
GenerateChart: procedure expose block_char
parse arg percent_file
chart_file = "chart_" || percent_file || ".txt"
call lineout chart_file, , 1
call stream percent_file, 'C', 'CLOSE'
sample_count = 0
do while lines(percent_file) > 0
line = linein(percent_file)
if length(line) = 0 then iterate
parse var line toss "," recd "," p.1 "," p.2 "," p.3 "," p.4 "," p.5 "," p.6 "," p.7 "," p.8 "," p.9
sample_count = sample_count + 1
call lineout chart_file, "========================================================================"
call lineout chart_file, "Sample #" || sample_count || " | Record: " || recd || " | Value: " || toss
call lineout chart_file, "------------------------------------------------------------------------"
do i = 1 to 9
call WriteChartLine chart_file, i, p.i
end
call lineout chart_file, ""
end
call stream percent_file, 'C', 'CLOSE'
call stream chart_file, 'C', 'CLOSE'
say "ASCII chart saved to: " || chart_file
say ""
say "Preview (first sample):"
call DisplayHead chart_file, 15
say ""
say "Preview (last sample):"
call DisplayTail chart_file, 12
return
/* -------------------------------------------------------------------------- */
/* WriteChartLine - Write single chart line to file */
/* -------------------------------------------------------------------------- */
WriteChartLine: procedure expose block_char
parse arg file_name, digit, percentage
bar_len = percentage
if bar_len > 100 then bar_len = 100
bar = copies(digit, trunc(bar_len))
line = " " || digit || " | " || format(percentage, 2, 1) || "% | " || bar
call lineout file_name, line
return
/* -------------------------------------------------------------------------- */
/* DisplayHead - Display first N lines of file */
/* -------------------------------------------------------------------------- */
DisplayHead: procedure
parse arg file, num_lines
cnt = 0
call stream file, 'C', 'CLOSE'
do while lines(file) > 0 & cnt < num_lines
say linein(file)
cnt = cnt + 1
end
call stream file, 'C', 'CLOSE'
return
/* -------------------------------------------------------------------------- */
/* DisplayTail - Display last N lines of file */
/* -------------------------------------------------------------------------- */
DisplayTail: procedure
parse arg file, num_lines
/* Read entire file into stem */
call stream file, 'C', 'CLOSE'
fileContent.0 = 0
do while lines(file) > 0
idx = fileContent.0 + 1
fileContent.idx = linein(file)
fileContent.0 = idx
end
call stream file, 'C', 'CLOSE'
/* Display last N lines */
start_line = fileContent.0 - num_lines + 1
if start_line < 1 then start_line = 1
do i = start_line to fileContent.0
say fileContent.i
end
return
/* -------------------------------------------------------------------------- */
/* LoadRexxUtil - Load RexxUtil functions for ArcaOS 5.1.1 */
/* -------------------------------------------------------------------------- */
LoadRexxUtil:
/* Try different RexxUtil loading methods for ArcaOS compatibility */
/* Method 1: Check if already loaded (ArcaOS often preloads) */
if RxFuncQuery('SysFileExists') = 0 then do
/* Already loaded */
return
end
/* Method 2: Try loading from REXXUTIL (uppercase) */
signal on syntax name RexxUtilError
call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs'
call SysLoadFuncs
signal off syntax
/* Verify it loaded */
if RxFuncQuery('SysFileExists') = 0 then return
/* Method 3: Try loading from RexxUtil (mixed case) */
signal on syntax name RexxUtilError
call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
call SysLoadFuncs
signal off syntax
/* Verify it loaded */
if RxFuncQuery('SysFileExists') = 0 then return
/* Method 4: Try loading from rexxutil (lowercase) - ArcaOS 5.1.1 */
signal on syntax name RexxUtilError
call RxFuncAdd 'SysLoadFuncs', 'rexxutil', 'SysLoadFuncs'
call SysLoadFuncs
signal off syntax
/* Verify it loaded */
if RxFuncQuery('SysFileExists') = 0 then return
/* If we get here, loading failed */
say ""
say "========================================================================"
say "ERROR: Could not load RexxUtil functions"
say "========================================================================"
say ""
say "This script requires RexxUtil (rexxutil.dll) to be available."
say ""
say "Troubleshooting steps:"
say " 1. Verify rexxutil.dll exists in your LIBPATH"
say " 2. Check: DIR rexxutil.dll in your OS/2 directory"
say " 3. On ArcaOS 5.1.1, this should be in C:\OS2\DLL"
say " 4. Try: SET LIBPATH | FIND /I \"DLL\""
say ""
say "Alternative: Use bxc_benford_minimal.cmd which doesn't need RexxUtil"
say ""
exit 99
RexxUtilError:
/* Silently continue to next method */
signal off syntax
return