Advertisement
plirof2

zmakebas_v--2.php

Jan 5th, 2025
123
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 53.01 KB | None | 0 0
  1. <?php
  2.  
  3. if (defined('__TURBOC__') && !defined('MSDOS')) {
  4.     define('MSDOS', true);
  5. }
  6.  
  7. define('VERSION', '1.8.2');
  8. define('DEFAULT_OUTPUT', 'out.tap');
  9. define('REM_TOKEN_NUM', 234);
  10. define('PEEK_TOKEN_NUM', 190); // Added ZX Spectrum PEEK token code (v1.5.2)
  11. define('BIN_TOKEN_NUM', 196);
  12. define('DEFFN_TOKEN_NUM', 206);
  13. define('VAL_TOKEN_NUM', 176); // Added ZX Spectrum VAL token code (v1.7.2)
  14. define('ON_TOKEN_NUM', 144);
  15. define('ON_ERR_TOKEN_NUM', 123);
  16.  
  17. define('ERR_TOKEN', 'err ');
  18. define('ERR_TOKEN_LEN', strlen(ERR_TOKEN) - 1);
  19.  
  20. // tokens are stored (and looked for) in reverse speccy-char-set order
  21. // to avoid def fn/fn and go to/to screwups. There are two entries for each token.
  22. $tokens = [
  23.     "%listen #", "%listen#",
  24.     "%accept #", "%accept#",
  25.     "%close #", "%close#",
  26.     "%fopen #", "%fopen#",
  27.     "%oneof", "",
  28.     "%mount", "",
  29.     "%umount", "",
  30.     "%cat", "",
  31.     "%cd", "",
  32.     "%fs", "",
  33.     "%load", "",
  34.     "%save", "",
  35.     "%aload", "",
  36.     "%asave", "",
  37.     "%tapein", "",
  38.     "%loadsnap", "",
  39.     "%connect", "",
  40.     "%opendir", "",
  41.     "%reclaim", "",
  42.     "%control", "",
  43.     "-----", "",
  44.     "copy", "",
  45.     "return", "",
  46.     "clear", "",
  47.     "draw", "",
  48.     "cls", "",
  49.     "if", "",
  50.     "randomize", "randomise",
  51.     "save", "",
  52.     "run", "",
  53.     "plot", "",
  54.     "print", "",
  55.     "poke", "",
  56.     "next", "",
  57.     "pause", "",
  58.     "let", "",
  59.     "list", "",
  60.     "load", "",
  61.     "input", "",
  62.     "go sub", "gosub",
  63.     "go to", "goto",
  64.     "for", "",
  65.     "rem", "",
  66.     "dim", "",
  67.     "continue", "",
  68.     "border", "",
  69.     "new", "",
  70.     "restore", "",
  71.     "data", "",
  72.     "read", "",
  73.     "stop", "",
  74.     "llist", "",
  75.     "lprint", "",
  76.     "out", "",
  77.     "over", "",
  78.     "inverse", "",
  79.     "bright", "",
  80.     "flash", "",
  81.     "paper", "",
  82.     "ink", "",
  83.     "circle", "",
  84.     "beep", "",
  85.     "verify", "",
  86.     "merge", "",
  87.     "close #", "close#",
  88.     "open #", "open#",
  89.     "erase", "",
  90.     "move", "",
  91.     "format", "",
  92.     "cat", "",
  93.     "def fn", "deffn",
  94.     "step", "",
  95.     "to", "",
  96.     "then", "",
  97.     "line", "",
  98.     "<>", "",
  99.     ">=", "",
  100.     "<=", "",
  101.     "and", "",
  102.     "or", "",
  103.     "bin", "",
  104.     "not", "",
  105.     "chr$", "",
  106.     "str$", "",
  107.     "usr", "",
  108.     "in", "",
  109.     "peek", "",
  110.     "abs", "",
  111.     "sgn", "",
  112.     "sqr", "",
  113.     "int", "",
  114.     "exp", "",
  115.     "ln", "",
  116.     "atn", "",
  117.     "acs", "",
  118.     "asn", "",
  119.     "tan", "",
  120.     "cos", "",
  121.     "sin", "",
  122.     "len", "",
  123.     "val", "",
  124.     "code", "",
  125.     "val$", "",
  126.     "tab", "",
  127.     "at", "",
  128.     "attr", "",
  129.     "screen$", "",
  130.     "point", "",
  131.     "fn", "",
  132.     "pi", "",
  133.     "inkey$", "",
  134.     "rnd", "",
  135.     "play", "",
  136.     "spectrum", "",
  137.     "rmdir", "", // Added ZX Spectrum Next keywords (v1.5.2)
  138.     "mkdir", "",
  139.     "cd", "",
  140.     "pwd", "",
  141.     "sprite", "",
  142.     "palette", "",
  143.     "layer", "",
  144.     "tile", "",
  145.     "bank", "",
  146.     "remount", "",
  147.     "else", "",
  148.     "repeat", "",
  149.     "while", "",
  150.     "driver", "",
  151.     "local", "",
  152.     "proc", "",
  153.     "end proc", "endproc",
  154.     "def proc", "defproc",
  155.     "on", "",
  156.     "error", "",
  157.     "until", "",
  158.     ">>", "",
  159.     "<<", "",
  160.     "mod", "",
  161.     "dpeek", "",
  162.     "dpoke", "",
  163.     "reg", "",
  164.     "peek$", "",
  165.     "", "",
  166.     "", "",
  167.     "", "",
  168.     "", "",
  169.     "", "",
  170.     "", "",
  171.     "reset", "",
  172.     "free", "",
  173.     "sound", "",
  174.     "stick", "",
  175.     "onerr", "",
  176.     NULL
  177. ];
  178.  
  179. $special_tokens = [NULL];
  180.  
  181. $tokens81 = [
  182.     "copy", "",
  183.     "return", "",
  184.     "clear", "",
  185.     "unplot", "",
  186.     "cls", "",
  187.     "if", "",
  188.     "rand", "",
  189.     "save", "",
  190.     "run", "",
  191.     "plot", "",
  192.     "print", "",
  193.     "poke", "",
  194.     "next", "",
  195.     "pause", "",
  196.     "let", "",
  197.     "list", "",
  198.     "load", "",
  199.     "input", "",
  200.     "go sub", "gosub",
  201.     "go to", "goto",
  202.     "for", "",
  203.     "rem", "",
  204.     "dim", "",
  205.     "cont", "",
  206.     "scroll", "",
  207.     "new", "",
  208.     "fast", "",
  209.     "slow", "",
  210.     "stop", "",
  211.     "llist", "",
  212.     "lprint", "",
  213.     "step", "",
  214.     "to", "",
  215.     "then", "",
  216.     "<>", "",
  217.     ">=", "",
  218.     "<=", "",
  219.     "and", "",
  220.     "or", "",
  221.     "**", "",
  222.     "not", "",
  223.     "chr$", "",
  224.     "str$", "",
  225.     "usr", "",
  226.     "peek", "",
  227.     "abs", "",
  228.     "sgn", "",
  229.     "sqr", "",
  230.     "int", "",
  231.     "exp", "",
  232.     "ln", "",
  233.     "atn", "",
  234.     "acs", "",
  235.     "asn", "",
  236.     "tan", "",
  237.     "cos", "",
  238.     "sin", "",
  239.     "len", "",
  240.     "val", "",
  241.     "code", "",
  242.     "", "",
  243.     "tab", "",
  244.     "at", "",
  245.     "", "",
  246.     "pi", "",
  247.     "inkey$", "",
  248.     "rnd", "",
  249.     NULL
  250. ];
  251.  
  252. // The raw BASIC file is written to filebuf; no output is generated
  253. // until the whole program has been converted. This is to allow a TAP
  254. // to be output on a non-seekable file (stdout).
  255. if (defined('MSDOS')) {
  256.     $MAX_LABELS = 500;
  257.     $filebuf = array_fill(0, 32768, 0); // Adjust size accordingly
  258.     $infile = '';
  259.     $outfile = '';
  260. } else {
  261.     $MAX_LABELS = 2000;
  262.     $filebuf = array_fill(0, 49152, 0); // Adjust size accordingly
  263.     $infile = '';
  264.     $outfile = '';
  265. }
  266.  
  267. //define('MAX_LABEL_LEN', 16);
  268.  
  269. const MAX_LABEL_LEN=10;
  270.  
  271. // This is needed for tap and +3 files too
  272. $headerbuf = array_fill(0, 0x80, 0);
  273.  
  274. $output_tape = 1;
  275. $output_dos = 0;
  276. $use_labels = 0;
  277. $zx81mode = 0;
  278. $startline = 0x8000;
  279. $autostart = 10;
  280. $autoincr = 2;
  281. $autoincr_opt = 2;
  282. $speccy_filename = '';
  283.  
  284. $labelend = 0;
  285. $labels = array_fill(0, $MAX_LABELS, array_fill(0, MAX_LABEL_LEN + 1, 0));
  286. $label_lines = array_fill(0, $MAX_LABELS, 0);
  287.  
  288. $startlabel = array_fill(0, MAX_LABEL_LEN + 1, 0);
  289.  
  290.  
  291.  
  292. // Simulating the getopt function in PHP
  293. if (!defined('HAVE_GETOPT')) {
  294.  
  295.     // Variables that would normally be used in the C version
  296.     $optopt = 0;
  297.     $opterr = 0;
  298.     $optind = 1;
  299.     $optarg = null;
  300.  
  301.     // Holds the offset in current argv[] value
  302.     $optpos = 1;
  303.  
  304.     /**
  305.      * Simulated getopt function in PHP.
  306.      * This function assumes the caller uses it correctly and does not pass invalid optstring or varying argc/argv.
  307.      */
  308.     function getopt111($argc, $argv, $optstring) {
  309.         global $optopt, $opterr, $optind, $optarg, $optpos;
  310.  
  311.         // Check for the end of the argument list
  312.         if ($optind == $argc || $argv[$optind][0] != '-' || strlen($argv[$optind]) <= 1) {
  313.             return -1; // No more options or invalid option
  314.         }
  315.  
  316.         // Check if the option exists in the optstring
  317.         $ptr = strpos($optstring, $argv[$optind][$optpos]);
  318.         if ($ptr === false) {
  319.             return '?'; // Error: unknown option
  320.         } else {
  321.             $optopt = $argv[$optind][$optpos];
  322.             if ($ptr + 1 < strlen($optstring) && $optstring[$ptr + 1] == ':') {
  323.                 // Option expects an argument
  324.                 if ($optind == $argc - 1) {
  325.                     return ':'; // Error: missing argument for the option
  326.                 }
  327.                 $optarg = $argv[$optind + 1];
  328.                 $optpos = 1;
  329.                 $optind += 2;
  330.                 return $optopt; // Return early, avoiding the normal increment
  331.             }
  332.         }
  333.  
  334.         // Now increment the position ready for the next time.
  335.         $optpos++;
  336.         if ($optpos >= strlen($argv[$optind])) {
  337.             $optpos = 1;
  338.             $optind++;
  339.         }
  340.  
  341.         return $optopt; // Return the found option
  342.     }
  343. }
  344.  
  345.  
  346.  
  347. /**
  348.  * This routine converts normal ASCII code to special code used in ZX81.
  349.  */
  350. function memcpycnv(&$dst, $src) {
  351.     $num = strlen($src); // Get the length of the source string
  352.    
  353.     $dst = ''; // Initialize the destination as an empty string
  354.  
  355.     for ($i = 0; $i < $num; $i++) {
  356.         $in = ord($src[$i]); // Get ASCII value of the character
  357.  
  358.         if ($in >= ord('0') && $in <= ord('9')) {
  359.             $dst .= chr($in - 20);
  360.         } elseif ($in >= ord('A') && $in <= ord('Z')) {
  361.             $dst .= chr($in - 27);
  362.         } elseif ($in >= ord('a') && $in <= ord('z')) {
  363.             $dst .= chr($in + 69);
  364.         } else {
  365.             switch ($in) {
  366.                 case 0x0d: $dst .= chr(0x76); break; // enter
  367.                 case 0x20: $dst .= chr(0x00); break; // space
  368.                 case 0x22: $dst .= chr(0x0b); break; // "
  369.                 case 0x24: $dst .= chr(0x0d); break; // $
  370.                 case 0x28: $dst .= chr(0x10); break; // (
  371.                 case 0x29: $dst .= chr(0x11); break; // )
  372.                 case 0x2a: $dst .= chr(0x17); break; // *
  373.                 case 0x2b: $dst .= chr(0x15); break; // +
  374.                 case 0x2c: $dst .= chr(0x1a); break; // ,
  375.                 case 0x2d: $dst .= chr(0x16); break; // -
  376.                 case 0x2e: $dst .= chr(0x1b); break; // .
  377.                 case 0x2f: $dst .= chr(0x18); break; // /
  378.                 case 0x3a: $dst .= chr(0x0e); break; // :
  379.                 case 0x3b: $dst .= chr(0x19); break; // ;
  380.                 case 0x3c: $dst .= chr(0x13); break; // <
  381.                 case 0x3d: $dst .= chr(0x14); break; // =
  382.                 case 0x3e: $dst .= chr(0x12); break; // >
  383.                 case 0x3f: $dst .= chr(0x0f); break; // ?
  384.                 case 0x60: $dst .= chr(0xc0); break; // Backtick (`) to Quote Image
  385.                 case 0x7c: $dst .= chr(0x41); break; // INKEY$
  386.                 case 0x7d: $dst .= chr(0x40); break; // RND
  387.                 case 0x7e:
  388.                     $dst .= chr($in); // number
  389.                     // Append next 5 characters
  390.                     $dst .= substr($src, $i + 1, 5);
  391.                     $i += 5; // Skip the 5 characters we just added
  392.                     break;
  393.                 case 0x7f: $dst .= chr(0x42); break; // PI
  394.                 default: $dst .= chr($in); // Default, just append the character
  395.             }
  396.         }
  397.     }
  398. }
  399.  
  400. // Example usage:
  401. $src = "Hello, world! 12345";
  402. $dst = '';
  403. memcpycnv($dst, $src);
  404. echo "Converted string: " . $dst . "\n";
  405.  
  406.  
  407.  
  408.  
  409.  
  410. ?>
  411. <?php
  412.  
  413. /**
  414.  * dbl2spec converts a double to an inline-basic-style speccy FP number.
  415.  *
  416.  * num is the double to convert.
  417.  * $pexp is the variable where the exponent byte will be returned.
  418.  * $pman is the variable where the 4 mantissa bytes will be returned.
  419.  * Bit 31 is bit 7 of the 0th (first) mantissa byte, and bit 0 is bit 0 of the 3rd (last).
  420.  * As such, the unsigned long returned *must* be written in big-endian format to make sense on a ZX81.
  421.  *
  422.  * Returns 1 if successful, 0 if exponent is too large.
  423.  */
  424. function dbl2spec($num, &$pexp, &$pman) {
  425.     $exp = 0;
  426.     $man = 0;
  427.  
  428.     // Check for small integers
  429.     if ($num == (int)$num && $num >= -65535.0 && $num <= 65535.0) {
  430.         // Ignores sign, as specified in the comment (applies to ints too).
  431.         $tmp = abs((int)$num);
  432.  
  433.         $exp = 0;
  434.         $man = (($tmp % 256) << 16) | (($tmp >> 8) << 8);
  435.     } else {
  436.         // Ignore the sign of the number (ZX81 uses '-' to determine negativity)
  437.         $num = abs($num);
  438.  
  439.         // Binary standard form goes from 0.50000... to 0.9999...(decimal), similar to how numbers are represented in decimal (0.10000... to 0.9999...)
  440.         // Adjust the number based on its value
  441.         while ($num >= 1.0) {
  442.             $num /= 2.0;
  443.             $exp++;
  444.         }
  445.  
  446.         while ($num != 0 && $num < 0.5) {
  447.             $num *= 2.0;
  448.             $exp--;
  449.         }
  450.  
  451.         // Now, the number is in binary standard form in $exp and $num.
  452.         // Check the range of $exp (-128 <= $exp <= 127). If it's out of range, return error (0).
  453.         if ($exp < -128 || $exp > 127) {
  454.             return 0;
  455.         }
  456.  
  457.         if ($num != 0) {
  458.             $exp = 128 + $exp;  // Adjust exponent by adding 128
  459.         }
  460.  
  461.         // Now roll the bits off the mantissa
  462.         $num *= 2.0;  // Make it so the 0.5ths bit is the integer part, and the rest is fractional.
  463.         $man = 0;
  464.         for ($f = 0; $f < 32; $f++) {
  465.             $man <<= 1;
  466.             $man |= (int)$num;
  467.             $num -= (int)$num;
  468.             $num *= 2.0;
  469.         }
  470.  
  471.         // Round up if the integer part of $num is non-zero, but prevent overflow in mantissa.
  472.         if ((int)$num && $man != 0xFFFFFFFF) {
  473.             $man++;
  474.         }
  475.  
  476.         // Finally, zero out the top bit (bit 31).
  477.         $man &= 0x7FFFFFFF;
  478.     }
  479.  
  480.     // Assign the calculated exponent and mantissa
  481.     $pexp = $exp;
  482.     $pman = $man;
  483.     return 1;
  484. }
  485.  
  486. // Example usage:
  487. $num = 1234.5678;
  488. $pexp = 0;
  489. $pman = 0;
  490.  
  491. if (dbl2spec($num, $pexp, $pman)) {
  492.     echo "Exponent: " . $pexp . "\n";
  493.     echo "Mantissa: " . dechex($pman) . "\n";
  494. } else {
  495.     echo "Exponent too large.\n";
  496. }
  497. ?>
  498. <?php
  499.  
  500. /**
  501.  * grok_hex converts a hexadecimal string (starting with "0x") into an unsigned long.
  502.  *
  503.  * @param string &$ptrp A reference to the string that contains the hexadecimal number.
  504.  * @param int $textlinenum The line number to be used in error messages.
  505.  *
  506.  * @return int The unsigned long value of the hexadecimal number.
  507.  */
  508. function grok_hex(&$ptrp, $textlinenum) {
  509.     $hexits = "0123456789abcdefABCDEF";  // Valid hexadecimal characters
  510.     $ptr = &$ptrp;  // Pointer to the current position in the string
  511.     $v = 0;  // Initialize the value to 0
  512.  
  513.     // We know the number starts with "0x", so skip the first 2 characters
  514.     $ptr = substr($ptr, 2);
  515.     if (strpos($hexits, $ptr[0]) === false) {
  516.         // If the next character is not a valid hex character, print an error and exit
  517.         echo "line $textlinenum: bad BIN 0x... number\n";
  518.         exit(1);
  519.     }
  520.  
  521.     // Process the rest of the string
  522.     while (strlen($ptr) > 0 && strpos($hexits, $ptr[0]) !== false) {
  523.         $n = strpos($hexits, $ptr[0]);
  524.  
  525.         // Adjust the value of n if it's a letter (A-F or a-f)
  526.         if ($n > 15) {
  527.             $n -= 6;
  528.         }
  529.  
  530.         // Update the value of v based on the hex character
  531.         $v = $v * 16 + $n;
  532.         $ptr = substr($ptr, 1);  // Move the pointer to the next character
  533.     }
  534.  
  535.     // Update the pointer reference with the new position in the string
  536.     $ptrp = $ptr;
  537.    
  538.     // Return the calculated unsigned long value
  539.     return $v;
  540. }
  541.  
  542. // Example usage:
  543. $hexString = "0x1f4e";  // Hexadecimal string with the "0x" prefix
  544. $textlinenum = 10;       // Example line number for error reporting
  545.  
  546. $value = grok_hex($hexString, $textlinenum);
  547. echo "The unsigned long value is: $value\n";
  548.  
  549. ?>
  550. <?php
  551.  
  552. /**
  553.  * grok_binary converts a binary string (starting with "0" or "1") to an unsigned long.
  554.  * If the binary string is prefixed with "0x" or "0X", it converts it as a hexadecimal number.
  555.  *
  556.  * @param string &$ptrp A reference to the string containing the binary number.
  557.  * @param int $textlinenum The line number for error reporting.
  558.  *
  559.  * @return int The unsigned long value of the binary number.
  560.  */
  561. function grok_binary(&$ptrp, $textlinenum) {
  562.     $v = 0;  // Initialize the result value to 0
  563.     $ptr = &$ptrp;  // Pointer to the current position in the string
  564.  
  565.     // Skip any whitespace characters at the beginning
  566.     $ptr = ltrim($ptr);
  567.  
  568.     // Check if the first character is '0' or '1'
  569.     if ($ptr[0] !== '0' && $ptr[0] !== '1') {
  570.         // If it's not, print an error message and exit
  571.         echo "line $textlinenum: bad BIN number\n";
  572.         exit(1);
  573.     }
  574.  
  575.     // Check for hexadecimal prefix "0x" or "0X"
  576.     if (isset($ptr[1]) && ($ptr[1] === 'x' || $ptr[1] === 'X')) {
  577.         // If it's hexadecimal, call the grok_hex function to process it
  578.         return grok_hex($ptrp, $textlinenum);
  579.     }
  580.  
  581.     // Process the binary number
  582.     while ($ptr[0] === '0' || $ptr[0] === '1') {
  583.         $v *= 2;  // Shift the current value by one bit to the left
  584.         $v += $ptr[0] === '1' ? 1 : 0;  // Add 1 if the current bit is '1'
  585.         $ptr = substr($ptr, 1);  // Move to the next character
  586.     }
  587.  
  588.     // Update the reference with the new pointer position
  589.     $ptrp = $ptr;
  590.    
  591.     // Return the resulting unsigned long value
  592.     return $v;
  593. }
  594.  
  595.  
  596.  
  597.  
  598.  
  599. ?>
  600. <?php
  601.  
  602. /**
  603.  * Displays the usage help for the zmakebas program.
  604.  */
  605. function usage_help() {
  606.     // Print general information about the program
  607.     echo "zmakebas - public domain by Russell Marks.\n\n";
  608.  
  609.     // Print usage instruction
  610.     echo "usage: zmakebas [-hlpr3v] [-a line] [-i incr] [-n speccy_filename]\n";
  611.     echo "                [-o output_file] [-s line] [input_file]\n\n";
  612.  
  613.     // List of options with descriptions
  614.     echo "        -v      output version number.\n";
  615.     echo "        -a      set auto-start line of basic file (default none).\n";
  616.     echo "        -h      give this usage help.\n";
  617.     echo "        -i      in labels mode, set line number incr. (default 2).\n";
  618.     echo "        -l      use labels rather than line numbers.\n";
  619.     echo "        -n      set Spectrum filename (to be given in tape header).\n";
  620.     echo "        -o      specify output file (default 'out.tap').\n";  // Replace with the default output
  621.     echo "        -p      output .p instead (set ZX81 mode).\n";
  622.     echo "        -r      output raw headerless file (default is .tap file).\n";
  623.     echo "        -3      output a +3DOS compatible file (default is .tap file).\n";
  624.     echo "        -s      in labels mode, set starting line number (default 10).\n";
  625. }
  626.  
  627.  
  628.  
  629. ?>
  630.  
  631. <?php
  632.  
  633. //define('MAX_LABEL_LEN', 10);
  634. const MAX_LABEL_LEN=10;
  635. define('VERSION', '1.0'); // Replace with your actual version
  636. $zx81mode = 0;
  637. $startlabel = '';
  638. $startline = 0;
  639. $autoincr_opt = 0;
  640. $use_labels = 0;
  641. $speccy_filename = '';
  642. $outfile = 'out.tap'; // Default output file
  643. $output_tape = 1;
  644. $output_dos = 0;
  645. $autostart = 0;
  646. $infile = '';
  647.  
  648. // Simulate the getopt function, similar to how it's used in C
  649. function parse_options($argc, $argv) {
  650.     global $zx81mode, $startlabel, $startline, $autoincr_opt, $use_labels, $speccy_filename, $outfile, $output_tape, $output_dos, $autostart, $infile;
  651.  
  652.     $options = getopt("pa:hi:ln:o:r3s:v");
  653.    
  654.     if (isset($options['p'])) {
  655.         $zx81mode = 1;
  656.     }
  657.    
  658.     if (isset($options['a'])) {
  659.         $optarg = $options['a'];
  660.         if ($optarg[0] === '@') {
  661.             if (strlen($optarg) - 1 > MAX_LABEL_LEN) {
  662.                 echo "Auto-start label too long\n";
  663.                 exit(1);
  664.             }
  665.             $startlabel = substr($optarg, 1);
  666.         } else {
  667.             $startline = (int)$optarg;
  668.             if ($startline > 9999) {
  669.                 echo "Auto-start line must be in the range 0 to 9999.\n";
  670.                 exit(1);
  671.             }
  672.         }
  673.     }
  674.    
  675.     if (isset($options['v'])) {
  676.         echo VERSION . "\n";
  677.         exit(1);
  678.     }
  679.    
  680.     if (isset($options['h'])) { // usage help
  681.         usage_help();
  682.         exit(1);
  683.     }
  684.  
  685.     if (isset($options['i'])) {
  686.         $autoincr_opt = (int)$options['i'];
  687.         if ($autoincr_opt < 1 || $autoincr_opt > 1000) {
  688.             echo "Label line incr. must be in the range 1 to 1000.\n";
  689.             exit(1);
  690.         }
  691.     }
  692.  
  693.     if (isset($options['l'])) {
  694.         $use_labels = 1;
  695.     }
  696.  
  697.     if (isset($options['n'])) {
  698.         $speccy_filename = substr($options['n'], 0, 10);
  699.     }
  700.  
  701.     if (isset($options['o'])) {
  702.         $outfile = $options['o'];
  703.     }
  704.  
  705.     if (isset($options['r'])) { // output raw file
  706.         $output_tape = 0;
  707.     }
  708.  
  709.     if (isset($options['3'])) { // output PLUS3DOS file
  710.         $output_tape = 0;
  711.         $output_dos = 1;
  712.     }
  713.  
  714.     if (isset($options['s'])) {
  715.         $autostart = (int)$options['s'];
  716.         if ($autostart < 0 || $autostart > 9999) {
  717.             echo "Label start line must be in the range 0 to 9999.\n";
  718.             exit(1);
  719.         }
  720.     }
  721.  
  722.     // Handle missing arguments (for example, --a needs a value)
  723.     foreach ($options as $key => $value) {
  724.         if ($value === false) {
  725.             switch ($key) {
  726.                 case 'a':
  727.                     echo "The `a' option takes a line number arg.\n";
  728.                     break;
  729.                 case 'i':
  730.                     echo "The `i' option takes a line incr. arg.\n";
  731.                     break;
  732.                 case 'n':
  733.                     echo "The `n' option takes a Spectrum filename arg.\n";
  734.                     break;
  735.                 case 'o':
  736.                     echo "The `o' option takes a filename arg.\n";
  737.                     break;
  738.                 case 's':
  739.                     echo "The `s' option takes a line number arg.\n";
  740.                     break;
  741.                 default:
  742.                     echo "Option `$key' not recognised.\n";
  743.             }
  744.             exit(1);
  745.         }
  746.     }
  747.  
  748.     // Check if there are any extra arguments
  749.     if ($argc > count($options) + 1) {
  750.         usage_help();
  751.         exit(1);
  752.     }
  753.  
  754.     // Check for input file (last argument)
  755.     if ($argc == count($options) + 1) {
  756.         $infile = $argv[$argc - 1];
  757.     }
  758. }
  759.  
  760.  
  761.  
  762. ?>
  763. <?php
  764.  
  765. function grok_block($ptr, $textlinenum) {
  766.     static $lookup = [
  767.         "  ", " '", "' ", "''", " .", " :", "'.", "':",
  768.         ". ", ".'", ": ", ":'", "..", ".:", ":.", "::",
  769.         NULL
  770.     ];
  771.    
  772.     static $lookup81 = [
  773.         "  ", "' ", " '", "''", ". ", ": ", ".'", ":'",
  774.         "::", ".:", ":.", "..", "':", " :", "'.", " .",
  775.         "!:", "!.", "!'", "|:", "|.", "|'",
  776.         NULL
  777.     ];
  778.  
  779.     $f = 0;
  780.     $v = -1;
  781.     $zx81mode = false; // Assume some flag indicating ZX81 mode
  782.    
  783.     // Choose the appropriate lookup table based on $zx81mode
  784.     $lookupTable = $zx81mode ? $lookup81 : $lookup;
  785.  
  786.     foreach ($lookupTable as $lptr) {
  787.         if ($lptr === NULL) break;
  788.  
  789.         if (strncmp(substr($ptr, 1), $lptr, 2) === 0) {
  790.             if ($zx81mode) {
  791.                 if ($f < 8) {
  792.                     $v = $f;
  793.                 } elseif ($f < 16) {
  794.                     $v = $f + 0x78;
  795.                 } elseif ($f < 19) {
  796.                     $v = $f - 8;
  797.                 } else {
  798.                     $v = $f + 117;
  799.                 }
  800.             } else {
  801.                 $v = $f + 128;
  802.             }
  803.             break;
  804.         }
  805.         $f++;
  806.     }
  807.  
  808.     if ($v === -1) {
  809.         fprintf(STDERR, "line %d: invalid block graphics escape\n", $textlinenum);
  810.         exit(1);
  811.     }
  812.  
  813.     return $v;
  814. }
  815.  
  816.  
  817.  
  818. ?>
  819.  
  820. <?php
  821.  
  822. // Simulate constants for platform detection
  823. define('MSDOS', false);  // Change this to true for MSDOS simulation
  824.  
  825. // Define the buffer sizes based on platform
  826. if (MSDOS) {
  827.     $buf = str_repeat("\0", 512);
  828.     $lcasebuf = str_repeat("\0", 512);
  829.     $outbuf = str_repeat("\0", 1024);
  830. } else {
  831.     $buf = str_repeat("\0", 2048);
  832.     $lcasebuf = str_repeat("\0", 2048);
  833.     $outbuf = str_repeat("\0", 4096);
  834. }
  835.  
  836. // Declare other variables
  837. $f = $toknum = $toklen = $linenum = $linelen = 0;
  838. $in_quotes = $in_rem = $in_deffn = $in_spec = $lastline = 0;
  839. $tarrptr = [];
  840. $ptr = $ptr2 = $linestart = $outptr = $remptr = $fileptr = $asciiptr = null;
  841. $num = $num_exp = 0;
  842. $num_mantissa = $num_ascii = 0;
  843. $chk = 0;
  844. $alttok = 0;
  845. $passnum = 1;
  846. $in = fopen("php://stdin", "r");
  847. $out = fopen("php://stdout", "w");
  848.  
  849. // Initialize variables for file names
  850. $speccy_filename = '';
  851. $infile = '-';
  852. $outfile = 'default_output.txt';  // Default output file
  853.  
  854. // Parse the options (a function to handle command-line arguments)
  855. parse_options($argc, $argv);
  856. ?>
  857. <?php
  858.  
  859. // Example setup (you should define these variables or get them from the program's context)
  860. $zx81mode = true;  // Example flag, set to true or false based on your logic
  861. define('DEFAULT_OUTPUT', 'default_output.txt');
  862. $outfile = 'default_output.txt'; // The output file name
  863. $infile = '-'; // The input file name (could be '-' for stdin or a file path)
  864. $filebuf = ''; // The file buffer (you'll want to initialize this based on actual use)
  865. $fileptr = ''; // The file pointer
  866. $linenum = -1; // Line number initialization
  867.  
  868. // Check if in ZX81 mode and output file is the default
  869. if ($zx81mode && $outfile === DEFAULT_OUTPUT) {
  870.     // In C, this would set outfile[4] and outfile[5] to 'p' and 0, respectively
  871.     // In PHP, we manipulate the string directly
  872.     $outfile[4] = 'p';
  873.     $outfile[5] = '';
  874. }
  875.  
  876. // If infile is not '-' and we cannot open the file, show an error and exit
  877. if ($infile !== "-" && !($in = fopen($infile, "r"))) {
  878.     echo "Couldn't open input file.\n";
  879.     exit(1);
  880. }
  881.  
  882. // Initialize fileptr with the file buffer (this part depends on actual use of $filebuf)
  883. $fileptr = $filebuf;
  884.  
  885. // Set linenum to -1 to indicate the first line (as per your original C code)
  886. $linenum = -1; // to set lastline
  887.  
  888. // In the original code, there was a comment about making one pass for line numbers and two for labels
  889. // You would need to implement this logic here, depending on how you handle lines and labels in your code.
  890.  
  891. ?>
  892. <?php
  893.  
  894. // ... (other variables and functions from the original code) ...
  895.  
  896. do {
  897.     $autoincr = $autoincr_opt;
  898.     if ($use_labels) {
  899.         $linenum = $autostart - $autoincr;
  900.     }
  901.     $textlinenum = 0;
  902.  
  903.     if ($passnum > 1 && fseek($in, 0, SEEK_SET) !== 0) {
  904.         fprintf(STDERR, "Need seekable input for label support\n");
  905.         exit(1);
  906.     }
  907.  
  908.     while (($line = fgets($in)) !== false) {
  909.         $line = " " . $line; // Add space at beginning for ptr[-1]
  910.  
  911.         $textlinenum++;
  912.         $lastline = $linenum;
  913.  
  914.         // Remove trailing newline
  915.         $line = rtrim($line, "\n");
  916.  
  917.         // Ignore comments and blank lines
  918.         if (strlen($line) < 2 || $line[1] === '#' || $line[1] === 0) {
  919.             continue;
  920.         }
  921.  
  922.         // Handle line continuation
  923.         while (substr($line, -1) === '\\') {
  924.             $f = strlen($line) - 1;
  925.             $line = substr($line, 0, $f) . fgets($in);
  926.             $textlinenum++;
  927.             $line = rtrim($line, "\n");
  928.         }
  929.  
  930.         // Line too long for buffer
  931.         if (strlen($line) >= sizeof($buf) - $MAX_LABEL_LEN - 1) {
  932.             fprintf(STDERR, "line %d: line too big for input buffer\n", $textlinenum);
  933.             exit(1);
  934.         }
  935.  
  936.         // Get line number (or assign one)
  937.         if ($use_labels) {
  938.             $linestart = $line;
  939.             $linenum += $autoincr;
  940.             if ($linenum > 9999) {
  941.                 fprintf(STDERR, "Generated line number is >9999 - %s\n",
  942.                     ($autostart > 1 || $autoincr > 1) ? "try using `-s 1 -i 1'"
  943.                     : "too many lines!"
  944.                 );
  945.                 exit(1);
  946.             }
  947.         } else {
  948.             $ptr = $line;
  949.             while (ctype_space($ptr[0])) {
  950.                 $ptr++;
  951.             }
  952.             if (!is_numeric($ptr[0])) {
  953.                 fprintf(STDERR, "line %d: missing line number\n", $textlinenum);
  954.                 exit(1);
  955.             }
  956.             $linenum = (int) strtol($ptr, $linestart, 10);
  957.  
  958.             if ($linenum <= $lastline) {
  959.                 fprintf(STDERR, "line %d: line no. not greater than previous one\n",
  960.                     $textlinenum
  961.                 );
  962.                 exit(1);
  963.             }
  964.         }
  965.  
  966.         if ($linenum < 0 || $linenum > 9999) {
  967.             fprintf(STDERR, "line %d: line no. out of range\n", $textlinenum);
  968.             exit(1);
  969.         }
  970.  
  971.         // Skip remaining spaces
  972.         while (ctype_space($linestart[0])) {
  973.             $linestart++;
  974.         }
  975.  
  976.         // Check for line number in labels mode
  977.         if ($use_labels && is_numeric($linestart[0])) {
  978.             $linenum = (int) strtol($linestart, $linestart, 10);
  979.             if ($linenum <= $lastline) {
  980.                 fprintf(STDERR, "line %d: line no. %d not greater than previous one (%d)\n",
  981.                     $textlinenum, $linenum, $lastline
  982.                 );
  983.                 exit(1);
  984.             }
  985.             if ($linestart[0] === '+' && is_numeric($linestart[1])) {
  986.                 $linestart++;
  987.                 $autoincr = (int) strtol($linestart, $linestart, 10);
  988.                 $autoincr += $autoincr === 0;
  989.             }
  990.             // Skip remaining spaces
  991.             while (ctype_space($linestart[0])) {
  992.                 $linestart++;
  993.             }
  994.         }
  995.  
  996.         // Handle labels
  997.         if ($use_labels && $linestart[0] === '@') {
  998.             // Find the colon
  999.             $ptr = strchr($linestart, ':');
  1000.             if ($ptr === false) {
  1001.                 fprintf(STDERR, "line %d: incomplete token definition\n", $textlinenum);
  1002.                 exit(1);
  1003.             }
  1004.             $labelLength = $ptr - $linestart - 1;
  1005.             if ($labelLength > $MAX_LABEL_LEN) {
  1006.                 fprintf(STDERR, "line %d: token too long\n", $textlinenum);
  1007.                 exit(1);
  1008.             }
  1009.  
  1010.             if ($passnum === 1) {
  1011.                 $label = substr($linestart, 1, $labelLength);
  1012.                 $label_lines[$labelend] = $linenum;
  1013.                 $labels[$labelend++] = $label;
  1014.                 if ($labelend >= $MAX_LABELS) {
  1015.                     fprintf(STDERR, "line %d: too many labels\n", $textlinenum);
  1016.                     exit(1);
  1017.                 }
  1018.                 // Check for duplicate labels
  1019.                 for ($f = 0; $f < $labelend - 1; $f++) {
  1020.                     if ($labels[$f] === $label) {
  1021.                         fprintf(STDERR, "line %d: attempt to redefine label\n", $textlinenum);
  1022.                         exit(1);
  1023.                     }
  1024.                 }
  1025.             }
  1026.  
  1027.             $linestart = substr($linestart, $labelLength + 2);
  1028.             while (ctype_space($linestart[0])) {
  1029.                 $linestart++;
  1030.             }
  1031.  
  1032.             // Skip blank lines after labels
  1033.             if ($linestart === "") {
  1034.                 $linenum -= $autoincr;
  1035.                 continue;
  1036.             }
  1037.         }
  1038.  
  1039.         // Skip first pass for labels
  1040.         if ($use_labels && $passnum === 1) {
  1041.             continue;
  1042.         }
  1043.  
  1044.         // Convert line to lowercase and remove strings
  1045.         $ptr = $linestart;
  1046.         $in_quotes = false;
  1047.         $ptr2 = "";
  1048.         while ($ptr !== false) {
  1049.             if ($ptr[0] === '"') {
  1050.                 $in_quotes = !$in_quotes;
  1051.             }
  1052.             if ($in_quotes && $ptr[0] !== '"') {
  1053.                 $ptr2 .= " ";
  1054.             } else {
  1055.                 $ptr2 .= strtolower($ptr[0]);
  1056.             }
  1057.             $ptr = mb_substr($ptr, 1);
  1058.         }
  1059.  
  1060.         // Tokenize the line
  1061.         $remptr = null;
  1062.         if (($ptr = strpos($ptr2, "rem")) !== false &&
  1063.             !ctype_alpha($ptr2[$ptr - 1]) && !ctype_alpha($ptr2[$ptr + 3])) {
  1064.             $ptr2 = substr_replace($ptr2, "\0", $ptr, strlen("rem"));
  1065.             $remptr = $ptr;
  1066.             $linestart = substr_replace($linestart, "\0", $ptr, strlen("rem"));
  1067.             $linestart = substr_replace($linestart, chr(1), $ptr, 1);
  1068.             $linestart = substr_replace($linestart, chr(1), $ptr + 1, 1);
  1069.             $linestart = substr_replace($linestart, chr(1), $ptr + 2, 1);
  1070.             if ($ptr2[$ptr + 3] === ' ') {
  1071.                 $linestart = substr_replace($linestart, chr(1), $ptr + 3, 1);
  1072.             }
  1073.         }
  1074.  
  1075.         // Tokenize the line using standard tokens table
  1076.         $toknum = $zx81mode ? 256 : 151;
  1077.         $alttok = 1;
  1078.         foreach ($zx81mode ? $tokens81 : $tokens as $tarrptr) {
  1079.             if ($alttok) {
  1080.                 $toknum--;
  1081.             }
  1082.             $alttok = !$alttok;
  1083.             if ($tarrptr === "") {
  1084.                 continue;
  1085.             }
  1086.             $toklen = strlen($tarrptr);
  1087.             if ($tarrptr === "-----") {
  1088.                 $toknum = 256;
  1089.                 $alttok = 0;
  1090.                 continue;
  1091.             }
  1092.             $ptr = $ptr2;
  1093.             while (($ptr = strpos($ptr, $tarrptr)) !== false) {
  1094.                 // Skip special commands
  1095.                 if ($ptr2[$ptr] === '%' && $tarrptr[0] === '%') {
  1096.                     $ptr += $toklen;
  1097.                     continue;
  1098.                 }
  1099.                 if ($ptr2[$ptr - 1] === '%' && $tarrptr[0] !== '%') {
  1100.                     $ptr += $toklen;
  1101.                     continue;
  1102.                 }
  1103.  
  1104.                 // Check for valid token placement
  1105.                 if ($tarrptr[0] === '<' || $tarrptr[1] === '=' ||
  1106.                     ((!ctype_alpha($ptr2[$ptr - 1]) && !ctype_alpha($ptr2[$ptr + $toklen]) && !( !$zx81mode && (($toknum === $PEEK_TOKEN_NUM) || ($toknum === $VAL_TOKEN_NUM)) && ($ptr2[$ptr + $toklen] === '$'))) &&
  1107.                     $toknum >= 123)) {
  1108.                     // Handle ON keyword for both ZX Spectrum Next and T/S 2000
  1109.                     if (!$zx81mode && $toknum === $ON_TOKEN_NUM) {
  1110.                         $ptr2_temp = substr($ptr2, $ptr + $toklen);
  1111.                         while (ctype_space($ptr2_temp[0])) {
  1112.                             $ptr2_temp = substr($ptr2_temp, 1);
  1113.                         }
  1114.                         if (strncmp($ptr2_temp, $ERR_TOKEN, $ERR_TOKEN_LEN) === 0) {
  1115.                             $toknum = $ON_ERR_TOKEN_NUM;
  1116.                             $toklen = $ptr + $ERR_TOKEN_LEN - 1 - $ptr;
  1117.                         }
  1118.                     }
  1119.  
  1120.                     // Replace token with token number
  1121.                     if ($zx81mode && $toknum > 0xbc && $toknum < 0xc0) {
  1122.                         $linestart = substr_replace($linestart, chr($toknum === 0xbe ? 0x7c : $toknum - 0x40), $ptr, $toklen);
  1123.                     } else {
  1124.                         $linestart = substr_replace($linestart, chr($toknum), $ptr, $toklen);
  1125.                     }
  1126.                     // Replace token in the lowercase copy
  1127.                     $ptr2 = substr_replace($ptr2, chr(1), $ptr, $toklen);
  1128.                     // Skip spaces after token
  1129.                     $ptr += $toklen;
  1130.                     while (ctype_space($ptr2[$ptr])) {
  1131.                         $ptr2 = substr_replace($ptr2, chr(1), $ptr, 1);
  1132.                         $ptr++;
  1133.                     }
  1134.                     // Special case for BIN token
  1135.                     if (!$zx81mode && $toknum === $BIN_TOKEN_NUM) {
  1136.                         $linestart = substr_replace($linestart, chr(1), $ptr - 1, 1);
  1137.                         $linestart = substr_replace($linestart, chr($toknum), $ptr, 1);
  1138.                         $ptr2 = substr_replace($ptr2, chr(1), $ptr - 1, 1);
  1139.                         $ptr2 = substr_replace($ptr2, chr($toknum), $ptr, 1);
  1140.                     }
  1141.                 }
  1142.                 $ptr += $toklen;
  1143.             }
  1144.         }
  1145.  
  1146.         // Replace labels with line numbers
  1147.         if ($use_labels) {
  1148.             $ptr = $linestart;
  1149.             while (($ptr = strpos($ptr, '@')) !== false) {
  1150.                 if ($ptr2[$ptr - 1] === '\\') {
  1151.                     $ptr++;
  1152.                     continue;
  1153.                 }
  1154.                 // Find matching label
  1155.                 $ptr++;
  1156.                 for ($f = 0; $f < $labelend; $f++) {
  1157.                     $len = strlen($labels[$f]);
  1158.                     if (strncmp($labels[$f], substr($linestart, $ptr, $len), $len) === 0 &&
  1159.                         (ord(substr($linestart, $ptr + $len, 1)) < 33 || ord(substr($linestart, $ptr + $len, 1)) > 126 || ispunct(substr($linestart, $ptr + $len, 1)))) {
  1160.                         // Remove REM temporarily
  1161.                         if ($remptr) {
  1162.                             $linestart = substr_replace($linestart, chr(1), $remptr, 1);
  1163.                         }
  1164.                         // Replace label with line number
  1165.                         $linestart = substr_replace($linestart, strval($label_lines[$f]), $ptr - 1, $len + 1);
  1166.                         // Update $remptr if it exists
  1167.                         if ($remptr) {
  1168.                             $remptr = $remptr - ($len + 1) + strlen(strval($label_lines[$f]));
  1169.                             $linestart = substr_replace($linestart, "\0", $remptr, 1);
  1170.                         }
  1171.                         break;
  1172.                     }
  1173.                 }
  1174.                 if ($f === $labelend) {
  1175.                     fprintf(STDERR, "line %d: undefined label\n", $textlinenum);
  1176.                     exit(1);
  1177.                 }
  1178.                 $ptr += strlen(strval($label_lines[$f])); // Update $ptr to the new position
  1179.             }
  1180.         }
  1181.  
  1182.         // Replace REM token
  1183.         if ($remptr !== null) {
  1184.             $linestart = substr_replace($linestart, chr($REM_TOKEN_NUM), $remptr, 1);
  1185.         }
  1186.  
  1187.         // Convert the line to bytes, handle escapes, and add numbers
  1188.         $ptr = $linestart;
  1189.         $outptr = "";
  1190.         $in_rem = $in_deffn = $in_quotes = $in_spec = false;
  1191.  
  1192.         while ($ptr !== false) {
  1193.             if (strlen($outptr) > sizeof($outbuf) - 10) {
  1194.                 fprintf(STDERR, "line %d: line too big\n", $textlinenum);
  1195.                 exit(1);
  1196.             }
  1197.  
  1198.             if ($ptr[0] === '"') {
  1199.                 $in_quotes = !$in_quotes;
  1200.             } else if ($ptr[0] === chr(1) || $ptr[0] === ' ' || ($ptr[0] === ' ' && !$in_quotes && !$in_rem && !$in_spec)) {
  1201.                 // Skip spaces and 0x01 chars
  1202.                 $ptr = mb_substr($ptr, 1);
  1203.                 continue;
  1204.             } else if ($ptr[0] === chr($DEFFN_TOKEN_NUM)) {
  1205.                 $in_deffn = true;
  1206.             } else if ($ptr[0] === chr($REM_TOKEN_NUM)) {
  1207.                 $in_rem = true;
  1208.             } else if ($ptr[0] === '%' && !$in_rem && !$in_quotes) {
  1209.                 // Handle special commands
  1210.                 $in_spec = true;
  1211.                 if ($ptr === $linestart) {
  1212.                     $outptr .= " ";
  1213.                 }
  1214.             } else if ($ptr[0] === '\\') {
  1215.                 $ptr = mb_substr($ptr, 1);
  1216.                 if (ctype_alpha($ptr[0]) && !str_contains("VWXYZvwxyz", $ptr[0])) {
  1217.                     $outptr .= chr(144 + ord(strtolower($ptr[0])) - ord('a'));
  1218.                 } else if ($zx81mode) {
  1219.                     switch ($ptr[0]) {
  1220.                         case '0': case '1': case '2': case '3': case '4':
  1221.                         case '5': case '6': case '7': case '8': case '9':
  1222.                             $outptr .= chr(ord($ptr[0]) + 108);
  1223.                             break;
  1224.                         case '"':
  1225.                             $outptr .= chr(0x8b);
  1226.                             break;
  1227.                         case '$':
  1228.                             $outptr .= chr(0x8d);
  1229.                             break;
  1230.                         case ':':
  1231.                             if (!str_contains("'.: ", substr($ptr, 1, 1))) {
  1232.                                 $outptr .= chr(0x8e);
  1233.                             } else {
  1234.                                 $outptr .= chr(grok_block($ptr, $textlinenum));
  1235.                                 $ptr = mb_substr($ptr, 1);
  1236.                             }
  1237.                             break;
  1238.                         case '?':
  1239.                             $outptr .= chr(0x8f);
  1240.                             break;
  1241.                         case '(':
  1242.                             $outptr .= chr(0x90);
  1243.                             break;
  1244.                         case ')':
  1245.                             $outptr .= chr(0x91);
  1246.                             break;
  1247.                         case '>':
  1248.                             $outptr .= chr(0x92);
  1249.                             break;
  1250.                         case '<':
  1251.                             $outptr .= chr(0x93);
  1252.                             break;
  1253.                         case '=':
  1254.                             $outptr .= chr(0x94);
  1255.                             break;
  1256.                         case '+':
  1257.                             $outptr .= chr(0x95);
  1258.                             break;
  1259.                         case '-':
  1260.                             $outptr .= chr(0x96);
  1261.                             break;
  1262.                         case '*':
  1263.                             $outptr .= chr(0x97);
  1264.                             break;
  1265.                         case '/':
  1266.                             $outptr .= chr(0x98);
  1267.                             break;
  1268.                         case ';':
  1269.                             $outptr .= chr(0x99);
  1270.                             break;
  1271.                         case ',':
  1272.                             $outptr .= chr(0x9a);
  1273.                             break;
  1274.                         case '.':
  1275.                             if (!str_contains("'.: ", substr($ptr, 1, 1))) {
  1276.                                 $outptr .= chr(0x9b);
  1277.                             } else {
  1278.                                 $outptr .= chr(grok_block($ptr, $textlinenum));
  1279.                                 $ptr = mb_substr($ptr, 1);
  1280.                             }
  1281.                             break;
  1282.                         case '\\':
  1283.                             $outptr .= chr(0x0c); // Pound symbol
  1284.                             break;
  1285.                         case '@':
  1286.                             $outptr .= chr(0x8c); // Inverse pound symbol
  1287.                             break;
  1288.                         case '\'': case '!': case '|': case ' ':
  1289.                             $outptr .= chr(grok_block($ptr, $textlinenum));
  1290.                             $ptr = mb_substr($ptr, 1);
  1291.                             break;
  1292.                         case '{': // Directly specify output code
  1293.                             // Find end of number
  1294.                             $asciiptr = strrpos($ptr, '}');
  1295.                             if ($asciiptr === false) {
  1296.                                 fprintf(STDERR, "line %d: unclosed brace in eight-bit character code\n", $textlinenum);
  1297.                                 exit(1);
  1298.                             }
  1299.                             // Parse number in decimal, octal, or hex
  1300.                             $num_ascii = strtoul(substr($ptr, 1, $asciiptr - 1), null, 0);
  1301.                             if ($num_ascii > 255) {
  1302.                                 fprintf(STDERR, "line %d: eight-bit character code out of range\n", $textlinenum);
  1303.                                 exit(1);
  1304.                             }
  1305.                             $outptr .= chr($num_ascii);
  1306.                             $ptr = substr($ptr, $asciiptr); // Move $ptr to the end of the code
  1307.                             break;
  1308.                         default:
  1309.                             fprintf(STDERR, "line %d: warning: unknown escape `%c', inserting literally\n", $textlinenum, $ptr[0]);
  1310.                             $outptr .= $ptr[0];
  1311.                     }
  1312.                 } else {
  1313.                     switch ($ptr[0]) {
  1314.                         case '\\':
  1315.                             $outptr .= '\\';
  1316.                             break;
  1317.                         case '@':
  1318.                             $outptr .= '@';
  1319.                             break;
  1320.                         case '*':
  1321.                             $outptr .= chr(127); // Copyright symbol
  1322.                             break;
  1323.                         case '\'': case '.': case ':': case ' ': // Block graphics char
  1324.                             $outptr .= chr(grok_block($ptr, $textlinenum));
  1325.                             $ptr = mb_substr($ptr, 1);
  1326.                             break;
  1327.                         case '{': // Directly specify output code
  1328.                             // Find end of number
  1329.                             $asciiptr = strrpos($ptr, '}');
  1330.                             if ($asciiptr === false) {
  1331.                                 fprintf(STDERR, "line %d: unclosed brace in eight-bit character code\n", $textlinenum);
  1332.                                 exit(1);
  1333.                             }
  1334.                             // Parse number in decimal, octal, or hex
  1335.                             $num_ascii = strtoul(substr($ptr, 1, $asciiptr - 1), null, 0);
  1336.                             if ($num_ascii > 255) {
  1337.                                 fprintf(STDERR, "line %d: eight-bit character code out of range\n", $textlinenum);
  1338.                                 exit(1);
  1339.                             }
  1340.                             $outptr .= chr($num_ascii);
  1341.                             $ptr = substr($ptr, $asciiptr); // Move $ptr to the end of the code
  1342.                             break;
  1343.                         default:
  1344.                             fprintf(STDERR, "line %d: warning: unknown escape `%c', inserting literally\n", $textlinenum, $ptr[0]);
  1345.                             $outptr .= $ptr[0];
  1346.                     }
  1347.                 }
  1348.                 $ptr = mb_substr($ptr, 1); // Move $ptr to the next character
  1349.                 continue;
  1350.             }
  1351.  
  1352.             // Handle numbers
  1353.             if (!$in_rem && !$in_quotes && !ctype_alpha($ptr[0]) &&
  1354.                 (is_numeric($ptr[0]) ||
  1355.                 (($ptr[0] === '-' || $ptr[0] === '+' || $ptr[0] === '.') && is_numeric(substr($ptr, 1, 1))) ||
  1356.                 (($ptr[0] === '-' || $ptr[0] === '+') && substr($ptr, 1, 1) === '.' && is_numeric(substr($ptr, 2, 1))))) {
  1357.                 if ($zx81mode || $ptr2[$ptr - 1] !== chr($BIN_TOKEN_NUM)) {
  1358.                     // Parse number with strtod()
  1359.                     $num = (double) strtod($ptr, $ptr2);
  1360.                 } else {
  1361.                     // Parse binary number after BIN token
  1362.                     $ptr2 = $ptr;
  1363.                     $num = (double) grok_binary($ptr2, $textlinenum);
  1364.                 }
  1365.                 // Add number to output
  1366.                 $outptr .= substr($linestart, $ptr, $ptr2 - $ptr);
  1367.                 $outptr .= chr($zx81mode ? 0x7e : 0x0e);
  1368.                 // Convert number to inline FP representation
  1369.                 if (!dbl2spec($num, $num_exp, $num_mantissa)) {
  1370.                     fprintf(STDERR, "line %d: exponent out of range (number too big)\n", $textlinenum);
  1371.                     exit(1);
  1372.                 }
  1373.                 $outptr .= chr($num_exp);
  1374.                 $outptr .= chr($num_mantissa >> 24);
  1375.                 $outptr .= chr($num_mantissa >> 16);
  1376.                 $outptr .= chr($num_mantissa >> 8);
  1377.                 $outptr .= chr($num_mantissa & 255);
  1378.                 $ptr = $ptr2;
  1379.             } else {
  1380.                 // Handle def fn case
  1381.                 if ($in_deffn) {
  1382.                     if ($ptr[0] === '=') {
  1383.                         $in_deffn = false;
  1384.                     } else if ($ptr[0] === ',' || $ptr[0] === ')') {
  1385.                         $outptr .= chr(0x0e);
  1386.                         $outptr .= chr(0);
  1387.                         $outptr .= chr(0);
  1388.                         $outptr .= chr(0);
  1389.                         $outptr .= chr(0);
  1390.                         $outptr .= chr(0);
  1391.                         $outptr .= $ptr[0];
  1392.                         $ptr = mb_substr($ptr, 1);
  1393.                     }
  1394.                     if ($ptr[0] !== ' ') {
  1395.                         $outptr .= $ptr[0];
  1396.                         $ptr = mb_substr($ptr, 1);
  1397.                     }
  1398.                 } else {
  1399.                     // Add character to output
  1400.                     if ($zx81mode) {
  1401.                         $outptr .= mb_convert_encoding(mb_substr($ptr, 0, 1), 'CP437', 'UTF-8');
  1402.                     } else {
  1403.                         $outptr .= $ptr[0];
  1404.                     }
  1405.                     $ptr = mb_substr($ptr, 1);
  1406.                 }
  1407.             }
  1408.         }
  1409.  
  1410.         $outptr .= chr($zx81mode ? 0x76 : 0x0d); // Add terminating CR
  1411.  
  1412.         // Output line
  1413.         $linelen = strlen($outptr);
  1414.         if (strlen($filebuf) + 4 + $linelen >= sizeof($filebuf)) {
  1415.             fprintf(STDERR, "program too big!\n");
  1416.             exit(1);
  1417.         }
  1418.         // Handle ZX81 start line adjustment
  1419.         if ($zx81mode && $startline === $linenum) {
  1420.             $startline = strlen($filebuf) - $zx81mode++;
  1421.         }
  1422.         $filebuf .= chr($linenum >> 8);
  1423.         $filebuf .= chr($linenum & 255);
  1424.         $filebuf .= chr($linelen & 255);
  1425.         $filebuf .= chr($linelen >> 8);
  1426.         $filebuf .= $outptr;
  1427.     }
  1428.  
  1429.     $passnum++;
  1430. } while ($use_labels && $passnum <= 2);
  1431.  
  1432. if ($in !== STDIN) {
  1433.     fclose($in);
  1434. }
  1435.  
  1436. /* we only need to do this if outputting a .tap, but might as well do
  1437.  * it to check the label's ok anyway.
  1438.  */
  1439. if (!empty($startlabel)) {
  1440.     /* this is nearly a can't happen error, but FWIW... */
  1441.     if (!$use_labels) {
  1442.         fprintf(STDERR, "Auto-start label specified, but not using labels!\n");
  1443.         exit(1);
  1444.     }
  1445.  
  1446.     for ($f = 0; $f < $labelend; $f++) {
  1447.         if (strcmp($startlabel, $labels[$f]) === 0) {
  1448.             $startline = $label_lines[$f];
  1449.             break;
  1450.         }
  1451.     }
  1452.  
  1453.     if ($f === $labelend) {
  1454.         fprintf(STDERR, "Auto-start label is undefined\n");
  1455.         exit(1);
  1456.     }
  1457. }
  1458.  
  1459. /* write output file */
  1460.  
  1461. if (strcmp($outfile, "-") !== 0) {
  1462.     $out = fopen($outfile, "wb");
  1463.     if ($out === false) {
  1464.         fprintf(STDERR, "Couldn't open output file.\n");
  1465.         exit(1);
  1466.     }
  1467. }
  1468.  
  1469.  
  1470. if ($output_tape) {
  1471.     $siz = $fileptr - $filebuf;
  1472.  
  1473.     if ($zx81mode) {
  1474.         /* make header */
  1475.         // $headerbuf[0] = 0;                        // VERSN
  1476.         // *(short*)($headerbuf + 1) = 0;              // E_PPC
  1477.         $headerbuf[3] = $siz + 0x407d;       // D_FILE
  1478.         $headerbuf[5] = $siz + 0x407e;       // DF_CC
  1479.         $headerbuf[7] = $siz + 0x4096;       // VARS
  1480.         // *(short*)($headerbuf + 9) = 0;              // DEST
  1481.         $headerbuf[11] = $siz + 0x4097;      // E_LINE
  1482.         $headerbuf[13] = $siz + 0x4096;      // CH_ADD
  1483.         // *(short*)($headerbuf + 15) = 0;             // X_PTR
  1484.         $headerbuf[17] = $siz + 0x4097;      // STKBOT
  1485.         $headerbuf[19] = $siz + 0x4097;      // STKEND
  1486.         // $headerbuf[21] = 0;                       // BERG
  1487.         $headerbuf[22] = 0x405d;          // MEM
  1488.         // $headerbuf[24] = 0;                       // not used
  1489.         $headerbuf[25] = 2;                         // DF_SZ
  1490.         $headerbuf[26] = 1;                         // S_TOP
  1491.         $headerbuf[28] = -1;                        // LAST_K
  1492.         $headerbuf[29] = -1;
  1493.         $headerbuf[31] = 55;                        // MARGIN
  1494.         $headerbuf[32] = ($zx81mode - 2)       // NXTLIN
  1495.                           ? $siz + 0x407d
  1496.                           : $startline + 0x407e;
  1497.         // *(short*)($headerbuf + 34) = 0;             // OLDPPC
  1498.         // $headerbuf[36] = 0;                       // FLAGX
  1499.         // *(short*)($headerbuf + 37) = 0;             // STRLEN
  1500.         $headerbuf[39] = 0x0c8d;          // T_ADDR
  1501.         // *(short*)($headerbuf + 41) = 0;             // SEED
  1502.         $headerbuf[43] = -1;              // FRAMES
  1503.         // *(short*)($headerbuf + 45) = 0;             // COORDS
  1504.         $headerbuf[47] = 0xbc;                      // PR_CC
  1505.         $headerbuf[48] = 33;                        // S_POSN
  1506.         $headerbuf[49] = 24;
  1507.         $headerbuf[50] = 0x40;                // CDFLAG
  1508.  
  1509.         /* write header */
  1510.         fwrite($out, $headerbuf, 0x74);
  1511.     } else {
  1512.         /* make header */
  1513.         $headerbuf[0] = 0;
  1514.         for ($f = strlen($speccy_filename); $f < 10; $f++) {
  1515.             $speccy_filename[$f] = ' ';
  1516.         }
  1517.         strncpy($headerbuf + 1, $speccy_filename, 10);
  1518.         $headerbuf[11] = ($siz & 255);
  1519.         $headerbuf[12] = ($siz / 256);
  1520.         $headerbuf[13] = ($startline & 255);
  1521.         $headerbuf[14] = ($startline / 256);
  1522.         $headerbuf[15] = ($siz & 255);
  1523.         $headerbuf[16] = ($siz / 256);
  1524.  
  1525.         /* write header */
  1526.         fprintf($out, "%c%c%c", 19, 0, $chk = 0);
  1527.         for ($f = 0; $f < 17; $f++) {
  1528.             $chk ^= $headerbuf[$f];
  1529.         }
  1530.         fwrite($out, $headerbuf, 17);
  1531.         fputc($out, $chk);
  1532.  
  1533.         /* write (most of) tap bit for data block */
  1534.         fprintf($out, "%c%c%c", ($siz + 2) & 255, ($siz + 2) >> 8, $chk = 255);
  1535.         for ($f = 0; $f < $siz; $f++) {
  1536.             $chk ^= $filebuf[$f];
  1537.         }
  1538.     }
  1539. } elseif ($output_dos) {
  1540.     $siz = $fileptr - $filebuf;
  1541.     $dossiz = $siz + 128;
  1542.  
  1543.     /* +3DOS header */
  1544.     $headerbuf[0] = 'P';
  1545.     $headerbuf[1] = 'L';
  1546.     $headerbuf[2] = 'U';
  1547.     $headerbuf[3] = 'S';
  1548.     $headerbuf[4] = '3';
  1549.     $headerbuf[5] = 'D';
  1550.     $headerbuf[6] = 'O';
  1551.     $headerbuf[7] = 'S';                         // PLUS3DOS
  1552.     $headerbuf[8] = 0x1a;                        // Soft-EOF
  1553.     $headerbuf[9] = 0x01;                        // Issue number
  1554.     $headerbuf[10] = 0x00;                       // Version number
  1555.     $headerbuf[11] = ($dossiz & 255);             // File size
  1556.     $headerbuf[12] = ($dossiz / 256);             // including header
  1557.     $headerbuf[13] = 0;                          // Also size, but BASIC header
  1558.     $headerbuf[14] = 0;                          // only 16-bits so don't need these
  1559.  
  1560.     $headerbuf[15] = 0;                          // Program
  1561.     $headerbuf[16] = ($siz & 255);                // Size again
  1562.     $headerbuf[17] = ($siz / 256);                // but 16-bit
  1563.     $headerbuf[18] = ($startline & 255);          // Start line
  1564.     $headerbuf[19] = ($startline / 256);          //   "    "
  1565.     $headerbuf[20] = ($siz & 255);                // Offset to prog
  1566.     $headerbuf[21] = ($siz / 256);                // ZXOS puts the size in here again
  1567.     $headerbuf[22] = 0;                          // Not used
  1568.  
  1569.     for ($f = 23; $f < 127; $f++) {
  1570.         $headerbuf[$f] = 0x00;                // Not used
  1571.     }
  1572.  
  1573.     /* calculate checksum */
  1574.     $chk = 0;
  1575.     for ($f = 0; $f < 127; $f++) {
  1576.         $chk += $headerbuf[$f];
  1577.     }
  1578.     $headerbuf[127] = $chk % 256;
  1579.  
  1580.     fwrite($out, $headerbuf, 128);
  1581. }
  1582.  
  1583. fwrite($out, $filebuf, $fileptr - $filebuf);
  1584.  
  1585. if ($output_tape) {
  1586.     if ($zx81mode) {
  1587.         for ($i = 0; $i < 25; $i++) {
  1588.             fputc($out, 0x76);
  1589.         }
  1590.         fputc($out, 0x80);
  1591.     } else {
  1592.         fputc($out, $chk);
  1593.     }
  1594. }
  1595.  
  1596. if ($out !== STDOUT) {
  1597.     fclose($out);
  1598. }
  1599.  
  1600. exit(0);
  1601.  
  1602.  
  1603. ?>
  1604.  
  1605.  
  1606.  
  1607.  
  1608.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement