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

code[R]

A raw, not relocated code.

debug[R]

A raw, debug information.

exports[R]

Exportable labels.

reloc[R]

A relocation table.

Public Instance Methods

[](label) click to toggle source

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
addr(address, type = 1, align: 1, offset: 0) click to toggle source

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
address?(arg) click to toggle source

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
alias_label(address, align: 1, offset: 0) click to toggle source

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
Also aliased as: as
as(address, align: 1, offset: 0)
Alias for: alias_label
bytes count click to toggle source
bytes count, byte_integer, ...
bytes count, [byte_integer, ...]
bytes [byte_integer, ...]

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
data(type = 1) click to toggle source
data(type, size = nil)
data(type, size, *data)
data(type, *data)
data(str[, size = str.bytesize])

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 a jr instruction.

  • :self

    A data label will be evaluated relatively to self, like an offset of an address using ix/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
db *byte_integers click to toggle source

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
dc!(text=''.freeze)
Alias for: debug_comment
debug_comment(text=''.freeze) click to toggle source

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
Also aliased as: dc!
define_label(name, label=nil) click to toggle source

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
direct_address?(arg) click to toggle source

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
direct_label?(arg) click to toggle source

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
dw *word_integers click to toggle source

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
export label_name click to toggle source
export :auto
export :noauto

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
immediate?(arg) click to toggle source

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
import(program, name=nil, labels:true, code:true, macros:false, override:{}, args:[]) click to toggle source

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 with Program.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
import_file(file, type = :any, size = nil, pipe:nil, check_size:nil, data_type:nil, **args) click to toggle source

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
isolate **opts {|eoc| ... } click to toggle source
isolate name, **opts {|eoc| ... }

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
label(type = 1, align: nil, offset: 0) click to toggle source

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
label?(arg) click to toggle source

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
label_defined?(name) click to toggle source

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
label_immediate?(arg) click to toggle source

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
label_import(program, name = nil, labels:true, macros:false) click to toggle source

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
macro_import(program) click to toggle source

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
method_missing(m, label = nil) click to toggle source

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
Calls superclass method
# 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
new(start = 0x0000, *args, override:{}) click to toggle source

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.

Calls superclass method
# 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
ns **opts {|eoc| ... } click to toggle source
ns name, **opts {|eoc| ... }

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 an eoc 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
org(address = pc, pad = 0, align: 1, offset: 0) click to toggle source

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
pc() click to toggle source

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
pointer?(arg) click to toggle source

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
register?(arg) click to toggle source

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
select(*args, &test) click to toggle source

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
union(label, type, align: nil, offset: 0) click to toggle source

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
unwrap_pointer(arg) click to toggle source

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
words count click to toggle source
words count, word_integer, ...
words count, [word_integer, ...]
words [word_integer, ...]

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