module Z80::Program
This module defines methods that become your program's class methods when you include the Z80
module. This includes all of Program::Macros
and Program::Mnemonics
methods as well as Macros
module defined in the including class.
Use these methods to build your Z80
assembler program or macros.
Constants
- VERSION
Attributes
A raw, not relocated code.
A raw, debug information.
Exportable labels.
A relocation table.
Public Instance Methods
Method used internally by mnemonics to make a pointer of a label or a Register
.
Example:
ld hl, [foo] # is equivalent to ld hl, foo[] # is equivalent to ld hl, self.[](foo) # or ld hl, [foo[5]] # is equivalent to ld hl, foo[5][] # is equivalent to ld hl, self.[](foo[5]) # or ld hl, [foo - 8] # is equivalent to ld hl, (foo - 8)[] # is equivalent to ld hl, self.[](foo - 8)
# File lib/z80/labels.rb, line 61 def [](label) label = label.first while label.is_a?(Array) if label.respond_to?(:to_label) || label.is_a?(Register) label[] elsif label.is_a?(Condition) raise Syntax, "Invalid pointer argument." else Label.new(label.to_i, 1)[] end end
Returns an unnamed, immediate label at an absolute address
of the optional type
.
type
can be an integer or a data structure (a class derived from Label
). The address
may be a number or another label or an immediate expression. It may also be a :next
symbol. In this instance the label address will be the previously added label address offset by its size.
Options:
:align
-
Additionally aligns the
address
to the nearest multiple of:align
bytes.
:offset
-
Added to the
address
after alignment.
Example:
foo addr 0xffff bar addr 0x4000, 2 baz addr :next, 2 # 0x4002
# File lib/z80/labels.rb, line 259 def addr(address, type = 1, align: 1, offset: 0) if address == :next last_label = @labels.values.last raise Syntax, "There is no label added to the program yet." if last_label.nil? addr last_label[1], type, align:align, offset:offset else address = address.to_i align = align.to_i raise ArgumentError, "align must be >= 1" if align < 1 address = (address + align - 1) / align * align + offset.to_i Label.new address, type end end
A convenient method for macros to check if an argument is a non-register address (direct or a pointer).
Returns true
for:
0x1234, foo, :foo, [0x1234], [foo], foo[10], [:foo], [foo + 10]
# File lib/z80/labels.rb, line 143 def address?(arg) arg = arg.first while arg.is_a?(Array) arg.is_a?(Integer) or arg.respond_to?(:to_label) end
Returns an alias of a label or an expression.
The address
must be a label or a label expression.
The returned label will eventually inherit the type from the given address
.
The returned label will be visible together with aliased target in debug listing at the code position when it was defined.
Options:
:align
-
Additionally aligns the
address
to the nearest multiple of:align
bytes.
:offset
-
Added to the
address
after alignment.
Example:
baz as foo[2] bak as (baz + 1 / 10), align: 16, offset: -1
# File lib/z80/labels.rb, line 225 def alias_label(address, align: 1, offset: 0) align = align.to_i offset = offset.to_i raise ArgumentError, "align must be >= 1" if align < 1 if address.respond_to?(:to_alloc) offset = offset.to_i address = (address + align - 1) / align * align if align > 1 address = address + offset unless offset.zero? Alloc.new(address, :alias).tap do |alc| @reloc << Relocation.new(pc, alc, :alias, nil) @debug << DebugInfo.new(pc, 0, '---> %04xH', [nil, :alias], @context_labels.dup << alc) end else raise ArgumentError, "address must be a label or a lazy expression" end end
Returns an unnamed label and allocates count
bytes with Program.data
. Optionally you can provide values for the allocated bytes.
See: Program.data
.
# File lib/z80/labels.rb, line 421 def bytes(*args); data(1, *args); end
Returns an unnamed, relative label and adds provided data to the Program.code
at Program.pc
.
The type
argument may be a number 1
to indicate bytes or 2
to indicate words. To store larger integers please consult Z80::MathInt::Macros.int
.
type
may also be a class derived from Label
, which represents a data structure with named fields. In this instance each data
argument must be an Array or a Hash containing data for each field in the structure. Please consult Label
for more information and examples.
type
may also be one of the following symbols - in this instance the type
will always be 1 (a byte):
:pc
-
A
data
label will be evaluated relatively to the program counter, e.g. an offset of a jump table.
:jr
-
A
data
label will be evaluated relatively to the program counter + 1, like an offset of ajr
instruction.
:self
-
A
data
label will be evaluated relatively to self, like an offset of an address usingix
/iy
registers. In this instance all labels but fields will evaluate to 0.
If the first argument is a string, a label is being created of the type
being equal to the string's byte size. It allows you to easily access string's size with unary + method on a returned label. The string is being added as a binary string to Program.code
at Program.pc
and may be limited (or extended) with a size
argument. Any other arguments are ignored in this form.
The data
argument must be one of the following:
-
A string: it will be added as a binary string.
-
A convertible
Object
(with method:to_z80bin
which should return a binary string). -
An integer (starting from 3rd argument), a label or a lazy evaluated expression will be added as a byte or a word (2-bytes, LSB) depending on the
type
. -
An array of integers, strings, convertible objects or labels (the array will be flattened), will be added consecutively to the
Program.code
.
If the size
is specified as a second argument, data
will be padded with zeroes or cut according to size
* type.to_i
.
Examples:
# Creates "foo" label of type 2 and fills 10 bytes of code (5 words) with data from array. foo data 2, [0, 2, 4, label1, label2] # Creates "bar" label and fills 10 bytes of code with 0s. bar data 1, 10 # Creates "bar" label and fills 2 words of code with data from an array and the rest (3 words) with 0s. baz data 2, 5, [1, 2] baz data 2, 5, 1, 2 # Creates "mystr" label and fills 12 bytes of code with bytes from a string, adds a byte +10+ at the end. mystr data 1, "Hello World!", 10 # Creates "mystr" label and fills 12 bytes of code with bytes from a string, adds a word +4242+ at the end # and fills additional 14 bytes of code with 0s. mystr data 2, 20, "Hello World!", 4242 # Creates "hello" label which addresses the following string and +hello resolves to its length # which is 12 in this instance. hello data "Hello World!"
See also: Label
for additional examples on how to use labels in a more advanced way.
# File lib/z80/labels.rb, line 358 def data(type = 1, size = nil, *args) res = '' from = 0 case type when :jr, :pc, :self from = type type = 1 end if type.respond_to? :to_data unless Integer === size args.unshift size size = args.length end type_size = type.to_i size.times do |i| res << type.to_data(self, i*type_size, args.shift) end size = nil elsif type.is_a?(String) res << type type = Integer === size ? size : res.bytesize else bsize = type.to_i raise Syntax, "Invalid data type" unless bsize == 1 || bsize == 2 if Integer === size size *= bsize else args.unshift size size = nil end pack_string = bsize == 1 ? 'c' : 's<' args.flatten.each_with_index do |data, index| res << if data.respond_to? :to_label case bsize when 1 then Z80::add_reloc(self, data, 1, index, from) when 2 then Z80::add_reloc(self, data, 2, index*2) end elsif data.respond_to? :to_z80bin data.to_z80bin elsif Integer === data [data].pack(pack_string) else data.to_s end end end if size res = res.ljust(size, "\x0") if res.bytesize < size res.slice!(size..-1) end Z80::add_code(self, res.force_encoding(Encoding::ASCII_8BIT), type) end
Returns an unnamed label and adds the provided integers to Program.code
as bytes.
See: Program.data
.
# File lib/z80/labels.rb, line 428 def db(*args); data(1, args); end
Appends user comment to the debug listing.
The comment will be visible as text in the listing at the current Program.pc
address.
Example:
dc! dc!"*********************************************" dc!"*** HELLO Z80 ***" dc!"*********************************************"
# File lib/z80.rb, line 676 def debug_comment(text=''.freeze) @debug << DebugInfo.new(pc, 0, text.to_s, nil, [Label.dummy]) end
Defines a label with the given name in the current namespace's context. Returns a named label.
A label
, if provided, may be an integer, an instance of an unnamed label, or a lazy evaluated expression. If label
has already some different name an error will be raised.
If label
argument is missing the “dummy” label (a label that is being referenced before being given value and type) is being created instead. See Label
and Label.dummy?
.
This method exists for ability to create an arbitrary named label without any constraint on its name. However the way one should normally define labels is via: method_missing
Example:.
define_label :loop, label # ... some code djnz define_label :loop
See method_missing
for more examples.
# File lib/z80/labels.rb, line 466 def define_label(name, label=nil) raise "there is no context for a label" unless (ct = @contexts.last) name = name.to_s if label label = if label.respond_to?(:to_label) label.to_label self else Label.new label.to_i, 1 end dummies_deleted = @dummies.reject! do |n, lbl, *cts| if n == name and cts.include? ct.object_id lbl.reinitialize label label.name = name true else false end end # make sure aliased labels (allocs) gets undummied too unless dummies_deleted.nil? @dummies.delete_if {|_, lbl, *_| !lbl.dummy? } end if ct.has_key? name ct[name].reinitialize label label.name = name else label.name = name ct[name] = label end if @autoexport and @contexts.length == 1 export ct[name] else ct[name] end else ct[name]||= Label.dummy(name).to_alloc end.tap do |label| label.name = name @labels.delete name # move label to last position @labels[name] = label end end
A convenient method for macros to check if an argument is a non-register direct address (not a pointer).
Returns true
for:
0x1234, foo, :foo, foo[10]
# File lib/z80/labels.rb, line 152 def direct_address?(arg) arg.is_a?(Integer) or direct_label?(arg) end
A convenient method for macros to check if an argument is a direct label (not a pointer).
Returns true
for:
foo, :foo, foo[10]
# File lib/z80/labels.rb, line 119 def direct_label?(arg) arg.respond_to?(:to_label) and !pointer?(arg) end
Returns an unnamed label and adds the provided integers to Program.code
as words.
See: Program.data
.
# File lib/z80/labels.rb, line 446 def dw(*args); data(2, args); end
Marks label_name
as exportable. Programs may import labels from another programs with Program.import
. Only exportable labels will be imported into the parent program. Imported labels retain all their members.
Alternatively pass :auto
symbol to make all subsequent top level labels exportable or :noauto
to stop auto-exporting.
Only top level labels may be exported this way.
# File lib/z80/labels.rb, line 27 def export(label) raise Syntax, "Only labels on the top level may be exported" if @contexts.length != 1 case label when :auto @autoexport = true when :noauto, :no_auto @autoexport = false else name = label.to_name raise Syntax, "No label name for export: #{label.inspect}." if name.nil? || name.empty? @exports[name.to_s] = label end end
A convenient method for macros to check if an argument is an immediate label or an integer.
Returns true
for:
foo addr 0x1234 0x1234, foo, :foo, [0x1234], [foo], foo[10], [:foo], [foo + 10]
# File lib/z80/labels.rb, line 175 def immediate?(arg) arg = arg.first while arg.is_a?(Array) label_immediate?(arg) or arg.is_a?(Integer) end
Imports code, labels or macros from another program
class. Give an optional name
to create a namespace for the imported labels. With no name
given, imported labels will be defined in the current context. Pass a class of a program (not an instance!).
Options to choose what to import:
:labels
-
true/false
or an absolute address (default:true
).
:code
-
true/false
or an absolute address (default:true
).
:macros
-
true/false
(default:false
).
:override
-
A flat hash containing names with labels to be replaced.
:args
-
Initialize arguments for an imported program.
Only labels marked with Program.export
are being imported. If :labels
is an address, all relative labels being imported will be converted to absolute labels and will be offset by the given value.
If :code
is an address, the imported code will be always compiled at the given address.
To be able to import macros, create a module named Macros
inside your Program
class and put macro methods there.
- NOTE
-
When creating macro methods remember to wrap the generated code in a namespace with
Program.ns
or better yet withProgram.isolate
. The best practice is to return such a namespace, so it can be named later by the code invoking the macro.
Returns a label that points to the beginning of the imported code and its size is equal to the imported code size. If the name is given, the returned label will hold all the imported labels as sublabels.
# File lib/z80.rb, line 554 def import(program, name=nil, labels:true, code:true, macros:false, override:{}, args:[]) if program.is_a?(Symbol) program, name = name, program end raise Syntax, "modules may not be imported from under namespaces" if @contexts.length != 1 addr = pc raise ArgumentError, "override should be a map of override names to labels" unless override.respond_to?(:map) override = override.map do |n,v| v = case v when Integer Label.new(v, 0) when Label, Alloc v else raise ArgumentError, "override: #{n} is not a label or an address" end [n.to_s, v] end.to_h code_addr = if code.respond_to?(:to_i) code.to_i end if macros self.extend program::Macros if defined?(program::Macros) end if labels label_addr, absolute = if labels.respond_to?(:to_i) [labels.to_i, true] else [addr, false] end members = Hash[program.exports.map {|n, l| raise Syntax, "only named labels may be exported" if l.to_name.nil? [n, l.deep_clone_with_relocation(label_addr, absolute, override)] }] end type = code ? program.code.bytesize : 0 if name plabel = Label.new(addr, type, :code, members) self.define_label name, plabel else members.each {|n, m| self.define_label n, m} if members plabel = Label.new addr, type, :code end if code and !program.code.bytesize.zero? @debug << DebugInfo.new(addr, 0, nil, nil, @context_labels.dup << plabel) @debug << @imports.size @imports << [addr, type, program, code_addr, args, name, override] @code << program.code program.code.freeze end program.freeze if labels or code plabel end
Imports a binary file.
file
-
A file name.
type
-
A format of a binary file (as a symbol), if
:any
-> format will be determined by file name's extension.
size
-
A size in bytes to which imported data will be cropped or extended.
Options:
pipe
-
A
proc
to postprocess binary data with (e.g. compress it).
check_size
-
A byte size to check the size (before pipe) of the imported data. If the sizes don't match a
CompileError
will be raised.
data_type
-
A returned label's type.
Any additional options are being passed to read_data
method of the format handler.
Currently only :tap or :tzx format is supported and only, if you include Z80::TAP
in your program.
If format is not known, a file is being imported as a blob.
Returns an unnamed label that points to the beginning of imported data.
# File lib/z80.rb, line 637 def import_file(file, type = :any, size = nil, pipe:nil, check_size:nil, data_type:nil, **args) type = type.to_s.upcase.to_sym if type == :ANY type = File.extname(file).gsub(/^\./,'').upcase.to_sym end data = if Z80.constants.include?(type) and (handler = Z80.const_get(type)) and (handler.respond_to? :read_data) $stderr.puts "Importing #{type} file: `#{file}'." handler.read_data(file, args) else $stderr.puts "Importing binary file: `#{file}'." File.open(file, 'rb') {|f| f.read} end if Integer === check_size raise CompileError, "size does not match the imported file size: #{check_size} != #{data.bytesize}" if check_size != data.bytesize end unless pipe.nil? raise ArgumentError, "pipe should be a proc" unless pipe.respond_to? :call data = pipe.call data end if data_type.nil? data_type = size || data.bytesize end Z80::add_code self, if size data[0, size].ljust(size, "\x0") else data end, data_type end
Returns a relative label, as an isolated namespace, holding labels defined by the code created with block
as sub-labels. Appends the created code to the Program.code
.
In an isolated namespace you can't reference labels defined outside of it unless explicitly indicated with the :use
option.
See: Program#ns
.
# File lib/z80.rb, line 331 def isolate(name = nil, **opts, &block) ns(name, **opts, isolate: true, &block) end
Returns an unnamed, relative label at Program.pc
of the optional type
.
type
can be an integer or a data structure (a class derived from Label
).
Options:
:align
-
Additionally lazily aligns a label to the nearest multiple of
:align
bytes applying a lazy evaluated expression to a label.
:offset
-
Added to a label after alignment.
Example:
foo label bar label 2
# File lib/z80/labels.rb, line 193 def label(type = 1, align: nil, offset: 0) lbl = Label.new pc, type, :code if align.nil? and offset == 0 @debug << DebugInfo.new(pc, 0, nil, nil, @context_labels.dup << lbl) else if align align = align.to_i raise ArgumentError, "align must be >= 1" if align < 1 lbl = (lbl + align - 1) / align * align end lbl = lbl + offset unless offset == 0 end lbl end
A convenient method for macros to check if an argument is label-like.
Returns true
for:
foo, :foo, foo[10], [foo], [foo + 10], [:foo], [foo[10]]
# File lib/z80/labels.rb, line 110 def label?(arg) arg = arg.first while arg.is_a?(Array) arg.respond_to?(:to_label) end
True if a label with a name
is defined in the current context.
# File lib/z80/labels.rb, line 93 def label_defined?(name) @labels.has_key? name.to_s end
A convenient method for macros to check if an argument is an immediate label.
Returns true
for:
foo addr 0x1234 foo, :foo, [foo], foo[10], [:foo], [foo + 10]
# File lib/z80/labels.rb, line 161 def label_immediate?(arg) arg = arg.first while arg.is_a?(Array) if arg.respond_to?(:immediate?) arg.immediate? else label?(arg) and arg.to_label(self).immediate? end end
Imports labels from another program
class. Optionally imports macros.
A sugar for:
import program, code: false, labels: true, macros: false
See: Program.import
.
Options:
:labels
-
true/false
or an absolute address (default:true
).
:macros
-
true/false
(default:false
).
# File lib/z80.rb, line 522 def label_import(program, name = nil, labels:true, macros:false) import program, name, code: false, labels: labels, macros: macros end
Imports macros from another program
class.
A sugar for:
import program, code: false, macros: true, labels: false
See: Program.import
.
# File lib/z80.rb, line 507 def macro_import(program) import program, code: false, macros: true, labels: false end
If no singleton method m
is defined, assume m
is a label name to define. Returns a named label.
A label
, if provided, may be an integer, an instance of an unnamed label, or a lazy evaluated expression. If label
has already some other name an error will be raised.
If label
argument is missing the “dummy” label is being created instead. See Label
and Label.dummy?
.
To create a label with the name of the existing singleton method or a ruby keyword, see: define_label
Example:.
mylabel 0x0123 mylabel addr 0x0123, 2
This example gives a name “mylabel” to a label produced by an ld
instruction and later references it:
mylabel ld a, [hl] inc hl djnz mylabel
This example creates a dummy label “skipret” and assings a value to it later:
jp Z, skipret ret skipret label
# File lib/z80/labels.rb, line 533 def method_missing(m, label = nil) if m == :to_ary || m == :to_a || m == :to_hash || m == :to_h super else define_label(m, label) end end
Compiles a program at the start
address passing *args to initialize(). Returns a compiled instance of a program.
:override
-
A flat hash containing names of labels with addresses to be overwritten.
# File lib/z80.rb, line 198 def new(start = 0x0000, *args, override:{}) raise ArgumentError, "override should be a map of override names to labels" unless override.respond_to?(:to_h) override = Alloc.compile(override, 0, include_sizes:false) unless @dummies.empty? and !@contexts.last.any? {|_,v| v.dummy?} dummies = @dummies.map {|d| d[0..1]} + @contexts.last.select {|_,v| v.dummy?}.map {|d| d[0..1]} dummies.reject! {|n,_| override.has_key?(n) } unless dummies.empty? raise CompileError, "Labels referenced but not defined in #{self}: #{dummies.inspect}" end end prog = super(*args) code = @code.dup imports = Hash[@imports.map do |addr, size, program, code_addr, arguments, name, import_override| merged_override = Alloc.compile(import_override, start, override, include_sizes:false) if name.nil? merged_override.merge!(override) else prefix = name.to_s + '.'.freeze merged_override.merge!(override. select{ |k,| k.start_with?(prefix) }. map{ |k,v| [k.slice(prefix.length..-1), v] }. to_h) end code_addr = addr + start if code_addr.nil? ip = program.new(code_addr, *arguments, override:merged_override) raise CompileError, "Imported program #{program} has been modified." unless ip.code.bytesize == size code[addr, size] = ip.code [name.nil? ? addr + start : name.to_sym, ip] end] labels = Alloc.compile(@labels, start, override) reloc = @reloc.dup debug = @debug.dup @conditional_blocks.each do |cndblk| raise CompileError, "Invalid conditional block program" unless cndblk.program.equal?(self) ConditionalBlock.compile(cndblk, code, reloc, debug, start, override) end aliases = {} reloc.each do |r| case r.size when :alias aliases[r.alloc] = r.alloc.to_i(start, override:override) when 0 raise CompileError, "Absolute labels need relocation sizes for the overrides" # OBSOLETE: ignore, this is an absolute address but we need relocation info for debug when 1 addr = case r.from when Integer r.from when :jr, nil r.addr + 1 + start when :pc r.addr + start when :self :self else raise CompileError, "Unknown from relocation parameter: #{r.inspect}" end i = r.alloc.to_i(start, addr, override:override) if (r.from.nil? or r.from == :jr) and !(-128..127).include?(i) raise CompileError, "Relative relocation out of range at 0x#{'%04x' % r.addr} -> #{i} #{r.inspect}" end if r.from == :pc and !(0..255).include?(i) raise CompileError, "Jump table relocation out of range at 0x#{'%04x' % r.addr} -> #{i} #{r.inspect}" end code[r.addr] = [i].pack('c') when 2 code[r.addr, 2] = [r.alloc.to_i(start, override:override)].pack('S<') else code[r.addr, r.size] = [r.alloc.to_i(start, override:override)].pack('Q<')[0, r.size].ljust(r.size, "\0") end end ['@code', code, '@org', start, '@debug', nil, '@labels', labels, '@imports', imports, '@reloc_info', reloc, '@debug_info', debug, '@alias_info', aliases, ].each_slice(2) do |n,v| prog.instance_variable_set n,v end prog end
Returns a relative label, as a namespace, holding labels defined by the code created with block
as sub-labels. Appends the created code to the Program.code
.
This function requires a block which may generate Z80
code containing labels or possibly other namespaces (namespaces can be nested). Every label defined within this block will become a member of the created namespace.
An optional name
may be provided, as a string or a symbol. In this instance the returned label will already have a name. Otherwise an unnamed label is being returned.
Options:
:use
-
If
true
namespace can inherit absolute labels from parent namespaces; if a name, label or an Array of names is given - only specified labels are being inherited;false
by default.
:inherit
-
An alias of
:use
.
:isolate
-
If
true
creates an isolated namespace; see:Program#isolate
.
:merge
-
If
true
merges labels from within a namespace with the current context; useful if you want to pass aneoc
label to some block of code and don't need a namespace.
Given block
receives one argument: eoc
which is a relative label that will address the end of the namespaced code and may be referenced from within the block
.
Labels created within the block
has higher priority than labels with the same name created outside of it, when referenced from within the block
.
All labels created outside of the namespace scope and not indicated with :use
can only be referenced lazily. If you really need to gain an immediate access to absolute labels (e.g. coerce its address or size to an integer), provide :use
option with their names or true
for all absolute labels.
If :isolate
option is true
no label created outside of the block
can be referenced unless explicitly indicated with :use
.
Example:
ns :foo do |eoc| loop1 add a, a jr C, eoc djnz loop1 end
Returns a label that points to the beginning of the block
of code and its size is equal to the created code size.
# File lib/z80.rb, line 382 def ns(name = nil, **opts) raise ArgumentError, "no block given to ns" unless block_given? # Save parent labels labels = @labels @labels = labels.dup # Normalize inherit option inherit = opts[:inherit] || opts[:inherit_absolute] || opts[:inherit_labels] || opts[:use] inherit = [inherit] if !inherit.is_a?(Array) && (inherit.is_a?(Symbol) || inherit.is_a?(String) || label?(inherit)) # Looks for labels in every context find_1st_defined_label_in_contexts = ->(name) do ct = @contexts.reverse_each.find do |ct| l = ct[name] l and !l.dummy? end ct and ct[name] end # Handle inherit option @contexts << if inherit.is_a?(Array) inherit.map do |name| name = if name.respond_to?(:to_name) name.to_name else name.to_s end labl = labels[name] if labl and labl.dummy? labl = find_1st_defined_label_in_contexts[name] end raise CompileError, "Label: `#{name}' not found" if labl.nil? [name, labl] end.to_h elsif inherit == true labels.select do |name,label| if label.dummy? label = find_1st_defined_label_in_contexts[name] end label && label.immediate? end elsif !inherit {} else raise ArgumentError, "ns :inherit option must be a boolean, name or array of names" end # Prepare top and eoc labels addr = pc top = Label.dummy eoc = Label.dummy 'EOC' # Prepare debug info for a namespace begin_reloc_index = @reloc.length begin_debug = DebugInfo.new(addr, 0, '--- begin ---', nil, @context_labels.dup << top) @debug << begin_debug @context_labels << top unless opts[:merge] # Execute block yield eoc # Check if eoc was used and modify debug info accordingly if @reloc[begin_reloc_index...@reloc.length].any? {|r| Alloc.include?(r.alloc, eoc) } @debug << DebugInfo.new(pc, 0, '--- end ---', nil, @context_labels.dup << eoc) else begin_debug.text = nil end # Finally define eoc label eoc.reinitialize(pc, 1, :code) # Restore parent labels @labels = labels # Get our context's id context_id = @contexts.last.object_id # Partition labels created in this context members, dummies = @contexts.pop.partition do |_, l| if l.dummy? false else true end end # Handle dummies left by inner namespaces @dummies.delete_if do |name, label, *cts| if cts.include?(context_id) and @labels.has_key?(name) and !@labels[name].dummy? label.reinitialize @labels[name] true else false end end # Prepare contexts array for dummies contexts = @contexts.map(&:object_id) # Handle isolate option if opts[:isolate] unless dummies.empty? dummies = dummies.map {|d| d[0..1]} raise CompileError, "Undefined labels referenced in isolated namespace: #{dummies.inspect}" end else @dummies+= dummies.map do |name, label| if @labels.has_key?(name) and !@labels[name].dummy? label.reinitialize @labels[name] nil else # register dummy alias labels members << [name, label] if label.alias? [name, label] + contexts end end.compact end # Handle merge option if opts[:merge] members.each {|n, l| self.define_label(n, l) } top.reinitialize addr, pc - addr, :code else # Remove our context from debug info @context_labels.pop top.reinitialize addr, pc - addr, :code, Hash[members] end # Optionally give name to top label top = self.define_label name, top if name top end
Returns an unnamed, relative label that points to the beginning of padded space. The space is being padded with pad
byte. The address
should be relative to the beginning of the Program.code
and must be equal to or greater than Program.pc
.
- Note
-
Do not confuse it with assembler directive ORG which sets absolute address of a program. Only instances of
Z80::Program
have absolute addresses.
Options:
:align
-
Additionally aligns the
address
to the nearest multiple of:align
bytes (relative to the beginning of code).
:offset
-
Added to the
address
after alignment.
# File lib/z80.rb, line 311 def org(address = pc, pad = 0, align: 1, offset: 0) address = address.to_i align = align.to_i raise ArgumentError, "align must be >= 1" if align < 1 address = (address + align - 1) / align * align + offset.to_i raise Syntax, "The current code pointer: #{pc.to_s 16} is exceeding: #{address.to_i.to_s 16} " if pc > address raise Syntax, "The current code pointer is exceeding 64k address range: #{address.to_i.to_s 16} " if address > 0x10000 Z80::add_code self, [pad].pack('c')*(address - pc) end
Returns the current byte offset from the beginning of the Program.code
(a program counter relative to 0). To create a label at pc
use Program.label
instead.
# File lib/z80.rb, line 293 def pc @code.bytesize end
A convenient method for macros to check if an argument is pointer-like.
Returns true
for:
[foo], [:foo], [foo + 10], [foo[10]], foo[], foo[10][], [0x1234], [hl], [ix + 6], iy[7]
# File lib/z80/labels.rb, line 127 def pointer?(arg) if arg.is_a?(Array) true elsif arg.respond_to?(:pointer?) arg.pointer? elsif label?(arg) arg.to_label(self).pointer? else false end end
A convenient method for macros to check if an argument is a Register
.
Returns true
for:
hl, a, [hl], [iy + 6]
# File lib/z80/labels.rb, line 101 def register?(arg) arg = arg.first while arg.is_a?(Array) arg.is_a?(Register) end
Creates a conditional block that creates alternative code based on the lazy evaluated boolean condition.
Returns an instance of ConditionalBlock
.
Each argument should be a label or a label expression.
Provide a block of code that computes a boolean value based on the evaluated label expressions provided as block's arguments.
- NOTE
-
Currently code produced by each variant must have the same number of bytes. Labels defined inside a variant are not accessible from outside of the conditional block.
Example:
select((io.ay_out ^ io.ay_sel) & 0xFF00, &:zero?).then do |eoc| ld b, io.ay_out >> 8 end.else_select((io.ay_out ^ io.ay_sel) & 0x00FF, &:zero?).then do |eoc| ld c, io.ay_out end.else do raise ArgumentError, "ay_out and ay_sel should differ only on either 8-bit lsb or msb" end # validate memory alignment condition select((label + some_size) & 0xFF){|n| n >= some_size }.else do raise ArgumentError, "data must fit on a single 256-byte page of memory" end
# File lib/z80/select.rb, line 176 def select(*args, &test) ConditionalBlock.new(self, *args, &test).tap {|cndblk| @conditional_blocks << cndblk } end
Returns a new, unnamed label addressed by label
, but of different type
. type
can be an integer or a data structure (a class derived from Label
). If label
was relative the returned label will also be relative. If label
was absolute the returned label will also be absolute.
Options:
:align
-
Additionally aligns the
label
to the nearest multiple of:align
bytes applying a lazy evaluated expression to a label.
:offset
-
Added to the
label
after alignment.
Example:
foo label bar union foo, 2
# File lib/z80/labels.rb, line 287 def union(label, type, align: nil, offset: 0) unless label.respond_to?(:to_label) and !label.sublabel? and !label.dummy? and !type.nil? raise Syntax, "Invalid union argument." end lbl = Label.new label.to_i, type, label.immediate? ? nil : :code if align align = align.to_i raise ArgumentError, "align must be >= 1" if align < 1 lbl = (lbl + align - 1) / align * align end lbl = lbl + offset unless offset == 0 lbl end
Returns a normalized pointer label, Register
or an integer. Otherwise pass-through.
Convenient method for checking arguments in macros.
The following example arguments will be unwrapped as pointer labels:
[0x1234], [foo], [:bar]
The following example arguments will be unwrapped as pointer registers:
[hl], [de], [ix]
The following example arguments will pass unmodified:
a, bc, :foo, bar
# File lib/z80/labels.rb, line 83 def unwrap_pointer(arg) if arg.is_a?(Array) self.[](arg) else arg end end
Returns an unnamed label and allocates count
words with Program.data
. Optionally you can provide values for the allocated words.
See: Program.data
.
# File lib/z80/labels.rb, line 439 def words(*args); data(2, *args); end