module ZXLib::Basic
A module with ZX Spectrum's BASIC program utilities.
SE BASIC extensions are supported.
See: ZXLib::Basic::Program
, ZXLib::Basic::Vars
, ZXLib::Basic::Variable
Example:
require 'zxlib/basic' include ZXLib program = Basic.parse_source <<-EOB 10 LET a$="Hello World!" 20 PRINT a$ EOB program.start = 10 program.save_tap 'helloworld.tap' program = Basic.parse_source '10 PRINT s$' program.start = 10 program.vars << Basic::Variable.new_string('s$', '`FLASH 1`""`INK 1`Hello World!`INK 0`""`FLASH 0`') program.save_tap 'helloworld.tap', append: true puts program.vars chunk = Z80::TAP.read_chunk('helloworld.tap') program = Basic.from_tap_chunk(chunk) puts program source = program.to_source
Constants
- ARROWS
- CHAR_CODES
- CHAR_TABLE
- COLOR_CTRL
- CTRL_CODES
- CURSOR_CTRL
- DEF_FN_VAR_PLACEHOLDER
- ESCAPE_CODES
- ESCAPE_CODES_ASCII_ONLY
- KEYWORDS
- KEYWORD_CODES
- KEYWORD_START_CODE
- NON_ASCII_ESCAPE_TOKENS
- PRINTABLE_CHARS
- SE_ALIASES
- SE_KEYWORDS
- SE_NEW_KEYWORDS
- SE_NEW_KEYWORDS_END
- STATEMENTS_AS_EXPRESSIONS_CODES
- STATEMENT_START_CODE
Public Class Methods
Creates a Basic::Program
instance from a ZX Spectrum's raw binary data.
The binary data may be a snapshot of ZX Spectrum's memory (PROG + VARS) or taken from a TAP chunk.
Provide program data
as a binary (ASCII-8BIT) string. Provide prog_length
argument to indicate the size of the program itself. Any data after prog_length
bytes will be interpreted as program variables.
Additionally :start
argument may be provided to indicate the starting line of a program. This information will be used when saving program as a TAP file.
# File lib/zxlib/basic.rb, line 456 def from_program_data(data, prog_length=nil, start:nil) if prog_length.nil? prog_length = data.bytesize else raise "program size must not exceed data size" if prog_length > data.bytesize end prog, vars = data.unpack("a#{prog_length}a*") lines = [] while true no, size, prog = prog.unpack("S>S<a*") break if no.nil? body, eol, prog = prog.unpack("a#{size - 1}Ca*") raise "Invalid program line" unless eol == 13 lines << Line.new(no, body) end Program.new lines, vars, start end
Creates a Basic::Program
or a Basic::Variable
depending on the type of the chunk. The chunk should be a Z80::TAP::HeaderBody
instance obtained from e.g. Z80::TAP.read_chunk
.
# File lib/zxlib/basic.rb, line 476 def from_tap_chunk(chunk) if chunk.program? from_program_data chunk.body.data, chunk.header.p2, start: chunk.header.p1 elsif chunk.array? data = [chunk.header.array_head, chunk.header.length, chunk.body.data].pack('Cva*') Variable.from_data data else raise "expected a program, a character array or a number array chunk" end end
Creates a Basic::Program
from a BASIC program text.
The source
should be an UTF-8 encoded string.
Each new line separator terminates each BASIC line. The line may start with a decimal indicating the line number but doesn't have to. In this instance the last line number + 1 will be used. The line numbers must be ascending, must not be negative or larger than 9999.
The text is being decoded according to special character conversion rules. For details please consult Basic::Program#to_source
.
Note:¶ ↑
This method doesn't interpret the BASIC program. A lot of nonsense will be accepted. However the program text is interpreted using a Basic::Tokenizer
and some simple heuristics, mainly to ensure a proper syntax of strings, numbers and some specific expressions:
-
All numbers outside of string literals are followed by a character code 14 and 5 bytes of their internal representation in the FP format (see
ZXLib::Math
). This also applies to SE BASIC hexadecimal and octal literals. -
Literal strings are being tracked, ensuring they are properly closed.
-
Opened and closed parentheses are counted ensuring they are properly balanced.
-
A statement keyword is expected (except spaces, control characters and colons) at the beginning of each line, after the colon character or after a
THEN
keyword. In these instances only the statement keywords are being accepted. -
Inside parentheses the colon character or a
THEN
keyword is forbidden. -
If a statement keyword is not expected only the keywords that may be used in expression context are converted to ZX BASIC keywords.
-
After every argument of a DEF FN header list a character code 14 and 5 placeholder bytes are being added.
-
Argument after the
BIN
keyword will be interpreted as a binary number. -
After the
REM
statement most of the rules are being relaxed until the end of the line. -
A literal
7
or8
after the SE BASIC'sDIR
statement will be interpreted as a single character argument and not a number. -
In addition to all ZX Spectrum BASIC keywords, the SE BASIC keywords will be also recognized and parsed accordingly. See
Basic::Program#to_source
for the list of those keywords.
Additionally :start
argument may be provided to indicate a starting line of a program. This information will be used when saving program as a TAP file.
# File lib/zxlib/basic.rb, line 533 def parse_source(source, start:nil) unless source.force_encoding(Encoding::UTF_8).valid_encoding? raise "invalid program source encoding, expecting: UTF-8" end last_line_no = -1 lines = [] source.each_line.with_index do |line_text, line_index| line_text.chomp! line = Line.parse_source_line(line_text, last_line_no, line_index + 1) last_line_no = line.line_no lines << line end Program.new(lines, Vars.new, start) end
Creates a Basic::Program
from a BASIC text file.
See parse_source
for details.
# File lib/zxlib/basic.rb, line 551 def read_source(filename, **opts) parse_source IO.read(filename, encoding: Encoding::UTF_8), **opts end
Creates a Basic::Program
or a Basic::Variable
from a TAP file.
See Z80::TAP.read_chunk
for arguments description.
# File lib/zxlib/basic.rb, line 490 def read_tap(filename, **opts) Z80::TAP.read_chunk(filename, **opts) { |chunk| from_tap_chunk(chunk) } end