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. The address can also be specified as a :next Symbol. In this instance the address will be the previously added label offset by its size.
Options:
:align-
Additionally aligns the
addressto the nearest multiple of:alignbytes.
:offset-
Added to the
addressafter alignment.
Example:
foo addr 0xffff bar addr 0x4000, 2 baz addr :next, 2 # 0x4002
# File lib/z80/labels.rb, line 266 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 address can also be specified as a :last Symbol. In this instance the address will be the previously added label.
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
addressto the nearest multiple of:alignbytes.
:offset-
Added to the
addressafter alignment.
Example:
baz as foo[2] bak as (baz + 1 / 10), align: 16, offset: -1
# File lib/z80/labels.rb, line 227 def alias_label(address, align: 1, offset: 0) if address == :last last_label = @labels.values.last raise Syntax, "There is no label added to the program yet." if last_label.nil? return alias_label(last_label, align:align, offset:offset) end 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 428 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
datalabel will be evaluated relatively to the program counter, e.g. an offset of a jump table.
:jr-
A
datalabel will be evaluated relatively to the program counter + 1, like an offset of ajrinstruction.
:self-
A
datalabel will be evaluated relatively to self, like an offset of an address usingix/iyregisters. 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_z80binwhich 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 365 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 435 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 473 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 453 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/falseor an absolute address (default:true).
:code-
true/falseor 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.nsor 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
procto 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
CompileErrorwill 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
:alignbytes 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/falseor 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 540 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
truenamespace can inherit absolute labels from parent namespaces; if a name, label or an Array of names is given - only specified labels are being inherited;falseby default.
:inherit-
An alias of
:use.
:isolate-
If
truecreates an isolated namespace; see:Program#isolate.
:merge-
If
truemerges labels from within a namespace with the current context; useful if you want to pass aneoclabel 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::Programhave absolute addresses.
Options:
:align-
Additionally aligns the
addressto the nearest multiple of:alignbytes (relative to the beginning of code).
:offset-
Added to the
addressafter 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
labelto the nearest multiple of:alignbytes applying a lazy evaluated expression to a label.
:offset-
Added to the
labelafter alignment.
Example:
foo label bar union foo, 2
# File lib/z80/labels.rb, line 294 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 446 def words(*args); data(2, *args); end