class ZXLib::Basic::Program
Represents a ZX Basic
program in a semi-parsed form.
Attributes
An array of Basic::Line
instances representing this Basic
program body.
The optional starting line of a Basic
program as an integer.
An instance of Basic::Vars
containing program's run-time variables.
Public Class Methods
# 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
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
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
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
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
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
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