class ZXLib::Basic::Program

Represents a ZX Basic program in a semi-parsed form.

Attributes

lines[RW]

An array of Basic::Line instances representing this Basic program body.

start[RW]

The optional starting line of a Basic program as an integer.

vars[R]

An instance of Basic::Vars containing program's run-time variables.

Public Class Methods

new(lines, vars = nil, start = nil) click to toggle source
# File lib/zxlib/basic.rb, line 48
def initialize(lines, vars = nil, start = nil)
        @lines = lines
        @vars = Vars.new(vars)
        @start = start
end

Public Instance Methods

[](index) click to toggle source

Returns a Basic::Line at index or an array of lines if Range is given.

# File lib/zxlib/basic.rb, line 211
def [](index)
        lines[index]
end
code() click to toggle source

Returns the raw byte representation of the whole ZX Basic program as a binary string.

# File lib/zxlib/basic.rb, line 216
def code
        res = ''
        lines.each do |line|
                body = line.body
                res << [line.line_no, body.bytesize + 1, body, 13].pack('S>S<a*C')
        end
        res
end
line_index(line_no) click to toggle source

Returns index in lines of a Basic line number equal or greater than line_no.

# File lib/zxlib/basic.rb, line 189
def line_index(line_no)
        lines.index{|l| l.line_no >= line_no}
end
list(line_no) click to toggle source

Returns a new Basic::Program instance with the subset of its lines according to the line_no argument.

line_no may be an integer or a Range. The integer indicates the first line to be included. The Range selects a range of lines to be included. line_no relates to the Basic line number.

# File lib/zxlib/basic.rb, line 198
def list(line_no)
        if Integer === line_no
                if index = line_index(line_no)
                        Program.new lines[index..-1], vars
                else
                        Program.new [], vars
                end
        elsif line_no.respond_to? :===
                Program.new lines.select{|l| line_no === l.line_no}, vars
        end
end
to_s(escape_keywords:false, ascii_only:false, se:false)
Alias for: to_source
to_source(escape_keywords:false, ascii_only:false, se:false) click to toggle source

Creates the textual representation of a ZX Basic::Program.

Returns an UTF-8 encoded string.

The conversion is done as follows:

  • Each character in the ASCII printable range 32..126 except the £ (pound, code 96) is left unmodified.

  • The £ (pound, code 96) and the © (code 127) characters are converted to a U+00A3 and U+00A9 accordingly.

  • Raw FP numbers beginning with a character code 14 are being stripped outside of literal strings.

  • A comma control character (code 6) is encoded as \t (TABULATION U+0009) character.

  • Control characters 8..11 are encoded as Unicode ARROWS (see below).

  • The remaining control characters in the code range 0..31 are encoded using escape sequences.

  • The block characters in the code range 128..143 are converted to Unicode BLOCK elements (see below).

  • The characters in the UDG code range 144..164 are converted to CIRCLED LATIN CAPITAL LETTERs.

  • Keywords in the code range 165..255 are either encoded as escaped keywords (e.g. `PRINT`) when found inside literal strings or just as sequences of its constituent characters.

Note:

The last rule may lead to some disambiguities. Consider a line:

PRINT RND

The RND keyword in this case may be a variable name consisting of 3 capital letters R N D or a RND function. The ZX BASIC knows the difference because the keyword RND is encoded as a single code point: 165. However when presented as text you can't really tell the difference. This may lead to errors when trying to parse such a text back to the ZX Spectrum's binary program format.

To disambiguate keywords from regular characters in strings they are being encoded as escape sequences, e.g. `GO SUB`, `RND`, `OPEN #`. Pass true to the :escape_keywords option to enforce keywords to be always escaped.

Escape sequences are using GRAVE ACCENT ` (U+0060, also known as a backtick) as enclosing character because it's absent in the ZX Spectrum's character set.

The control characters are encoded as decimal code numbers, e.g: `12`. More codes can be put inside an escape sequence using whitespaces or commas as separators, e.g.: `0xff, 201, 0b01010001` stands for 3 bytes. Any ruby number literal is accepted: decimal, hexadecimal, octal, binary.

Color and cursor position control codes are multi-byte. There are special control escape sequences for them:

code seq. count  special escape sequence format
`16 n`    2      `INK n`
`17 n`    2      `PAPER n`
`18 n`    2      `FLASH n`
`19 n`    2      `BRIGHT n`
`20 n`    2      `INVERSE n`
`21 n`    2      `OVER n`
`22 y x`  3      `AT y,x`
`23 x x`  3      `TAB x`

Where n, x, y are decimal numbers representing the following character code and at the same time special control arguments.

Non-ASCII characters and alternative escape sequences:

code  escaped  unicode   description
   8  `<`      U+2190 ←  move left
   9  `>`      U+2192 →  move right
  10  `v`      U+2193 ↓  move down
  11  `^`      U+2191 ↑  move up
  96  `&`      U+00A3 £  a pound sign
 127  `(c)`    U+00A9 ©  a copyright sign
 128  `|8`     U+2591 ░  various block characters
 129  `|1`     U+259D ▝  
 130  `|2`     U+2598 ▘  
 131  `|3`     U+2580 ▀  
 132  `|4`     U+2597 ▗  
 133  `|5`     U+2590 ▐  
 134  `|6`     U+259A ▚  
 135  `|7`     U+259C ▜  
 136  `#7`     U+2596 ▖  
 137  `#6`     U+259E ▞  
 138  `#5`     U+258C ▌  
 139  `#4`     U+259B ▛  
 140  `#3`     U+2584 ▄  
 141  `#2`     U+259F ▟  
 142  `#1`     U+2599 ▙  
 143  `#8`     U+2588 █  
 144  `a`      U+24B6 Ⓐ  user defined graphics
 145  `b`      U+24B7 Ⓑ  
 146  `c`      U+24B8 Ⓒ  
 147  `d`      U+24B9 Ⓓ  
 148  `e`      U+24BA Ⓔ  
 149  `f`      U+24BB Ⓕ  
 150  `g`      U+24BC Ⓖ  
 151  `h`      U+24BD Ⓗ  
 152  `i`      U+24BE Ⓘ  
 153  `j`      U+24BF Ⓙ  
 154  `k`      U+24C0 Ⓚ  
 155  `l`      U+24C1 Ⓛ  
 156  `m`      U+24C2 Ⓜ  
 157  `n`      U+24C3 Ⓝ  
 158  `o`      U+24C4 Ⓞ  
 159  `p`      U+24C5 Ⓟ  
 160  `q`      U+24C6 Ⓠ  
 161  `r`      U+24C7 Ⓡ  
 162  `s`      U+24C8 Ⓢ  
 163  `t`      U+24C9 Ⓣ  
 164  `u`      U+24CA Ⓤ

The above escape sequences may be safely concatenated between a single pair of enclosing backticks, e.g.:

Unicode:    "£©░█ⒶⒷⒸⓊ←→↑↓"
Ascii only: "`&(c)|8#8abcu<>^v`"

Passing true to the :ascii_only option will render escape sequences instead of non-ascii characters.

SE BASIC support

Passing true to the :se option will render SE BASIC tokens.

In this instance the new SE BASIC keywords will be recognized:

DELETE
EDIT
RENUM
PALETTE
SOUND
ON ERROR

and the following keywords will be output instead:

ZX Spectrum Basic | SE BASIC
------------------+---------
             COPY | CALL
             INK  | PEN
             CAT  | DIR
# File lib/zxlib/basic.rb, line 181
def to_source(escape_keywords:false, ascii_only:false, se:false)
        lines.map { |line|
                line.to_s escape_keywords: escape_keywords, ascii_only: ascii_only, se: se
        }.join("\n")
end
Also aliased as: to_s
to_tap_chunk(name, line:nil) click to toggle source

Creates a Z80::TAP::HeaderBody instance from Basic::Program#code.

This method is provided for the included Z80::TAP#to_tap and Z80::TAP#save_tap methods.

# File lib/zxlib/basic.rb, line 228
def to_tap_chunk(name, line:nil)
        prog_length = code.bytesize
        line ||= start
        Z80::TAP::HeaderBody.new_program(name, code << vars.code, line: line, prog_length: prog_length)
end