class Z80::Label

Z80 Label

myloop  inc [hl]
        inc hl
        djnz myloop

A label in a Z80::Program represents an address, a number, an expression or an index to another label. Instead of using numbers, provide a name and define its value above or below. Labels have three properties assotiated with them: an address, a type (which influences its size) and the property indicating if the label is absolute or relative to the code or a field of a structure.

Labels are being lazy evaluated when a program is being compiled. Labels as well as integers may be used in expressions. Currently there are lazy evaluated expression functions available:

-x      - a negative x
x + y   - a result of y added to x
x - y   - a result of y subtracted from x
x * y   - a result of x times y
x / y   - a quotient of an euclidean division of x by y
x % y   - a remainder of an euclidean division of x by y
x << y  - a bitwise left shifted x by y bits
x >> y  - a bitwise right shifted x by y bits
~x      - a bitwise negated x
x ^ y   - a bitwise "exclusive or" of x and y
x | y   - a bitwise "or" of x and y
x & y   - a bitwise "and" of x and y
+z      - a byte size of a type of z (in this instance z must not be an expression)

Where x and y represents labels or indexes to labels or expressions; y may also be an integer; z may only be a label or an index.

Labels may be nested, that is each label may contain named members. There are two types of such members:

Members are being accessed by indexes. An index is being formed by either:

Indices as integers or expressions offset labels' addresses by their type's size. Indices as names access members or sub-labels.

A data structure is a ruby class inherited from Label.

# a data structure, also a new type: Sprite
class Sprite < Label
# name    type[, count]
  x       byte, 1
  y       byte # , 1 is default
  data_pl byte
  data_ph byte
  size    word
# alias   orig.   type
  data_p  data_pl word
end

class SpritePool < Label
  numspr  byte
  sprites Sprite, 2
end

Fields are being formed by labeling data types and providing its count as a second, optional argument. The only basic data types are: Label.byte and Label.word. A data structure may also be used as a data type. In the above example data_p and data_pl are aliases (unions) which means that data_p represents the same offset as data_pl but has different size.

Labels that are not part of a structure may be absolute or relative to the program code. Absolute labels resolve always to the same value so they are also called “immediate” as we don't need to know the program origin to evaluate them. See Program.immediate?.

Note

Labels are being evaluated by calling Label#to_i method on them. Normally you don't need to know this, but in some corner cases you may want to take advantage of that. However be warned that labels are lazy by its nature and may be defined in the future. Labels that are part of an expression but are not defined yet are called “dummy”. See: Label.dummy?. Calling to_i on a “dummy” label results in an error. An expression containing at least one “dummy” label is also a “dummy” one.

Allocating, that is assigning values to labels may be done in several ways:

Using Program.alias_label you can anchor labels:

ns :inner do
  # inner.foo will reference some.other.label
  foo    as  some.other.label
  # inner.bar will reference (baz * 3) aligned to 16 bytes.
  bar    as  baz * 3, align: 16
end
Examples

To access data_p field from a second Sprite in the spritep:

ld  hl, [spritep.sprites[1].data_p]

or

ld  l, [spritep.sprites[1].data_pl]
ld  h, [spritep.sprites[1].data_ph]

To set register pointing to data_p from second sprite:

ld  hl, spritep.sprites[1].data_p
ld  e, [hl]
inc hl
ld  d, [hl]

or

ld  ix, sprite
ld  b, [ix + spritep.numspr]
ld  l, [ix + spritep.sprites[1].data_pl]
ld  h, [ix + spritep.sprites[1].data_ph]

Access a size of a label

ld  bc, +spritep # lazy evaluates to +SpritePool to 13

Access a size of a field

ld  bc, +spritep.sprites       # lazy evaluates to +Sprite to 6
ld  b, +spritep.sprites.size   # lazy evaluates to 2

Label names

Label name may be a valid ruby method name except singleton method names of your program and any existing ruby class method name:

  • ruby statements

  • ruby core Class.methods: ! != !~ < <= <=> == === =~ > >= __id__ __send__ allocate ancestors autoload autoload? class class_eval class_exec class_variable_defined? class_variable_get class_variable_set class_variables clone const_defined? const_get const_missing const_set constants define_singleton_method display dup enum_for eql? equal? extend freeze frozen? hash include? included_modules initialize_clone initialize_dup inspect instance_eval instance_exec instance_method instance_methods instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? kind_of? method method_defined? methods module_eval module_exec name nil? object_id private_class_method private_instance_methods private_method_defined? private_methods protected_instance_methods protected_method_defined? protected_methods public_class_method public_instance_method public_instance_methods public_method public_method_defined? public_methods public_send remove_class_variable respond_to? respond_to_missing? send singleton_class singleton_methods superclass taint tainted? tap to_enum to_s trust untaint untrust untrusted?

  • Z80::Program.instance_methods: [] a adc add addr af anda b bc bc_ bit bytes c call ccf code cp cpd cpdr cpi cpir cpl d daa data de de_ debug dec di djnz e ei ex export exx h halt hl hl_ hlt i im0 im01 im1 im2 import import_file inc ind indr ini inir inp ix ix_ ixh ixl iy iy_ iyh iyl jp jr l label labels ld ldd lddr ldi ldir macro method_missing neg new nop ns ora org otdr otir out outd outi pc pop push r reloc res ret reti retn rl rla rlc rlca rld r rra rrc rrca rrd rst sbc scf set sl1 sla sll sp sp_ sra srl sub union words xor

  • and macros defined in your program.

Pro tip

Use namespaces: Program.ns extensively in your program.

Be carefull with loops, as loop is a ruby statement.

Constants

Member

A class representing members of a data structure.

Public Class Methods

+@() click to toggle source

Returns a lazy evaluated size of a data structure. Better for debugging than Label.to_i.

# File lib/z80/labels.rb, line 1057
def +@
        if defined?(@struct_size)
                +(self.new(0).tap{|l|l.name = self.name}.to_alloc)
        else
                raise Syntax, "Label has no size"
        end
end
byte(size = 1) click to toggle source

A data structure's field type.

# File lib/z80/labels.rb, line 952
def byte(size = 1)
        1*size
end
dummy(name = nil) click to toggle source

Creates a dummy label. Should not be used directly in programs. This is called by Program.method_missing when referenced label has not been defined yet.

# File lib/z80/labels.rb, line 764
def self.dummy(name = nil)
        n = new(0, nil)
        n.name = name if name
        n
end
members_of_struct() click to toggle source

Returns a hash containing structure members as instances of a Member class.

# File lib/z80/labels.rb, line 1065
def members_of_struct; @members && Hash[@members]; end
method_missing(m, struct=nil, count=1) click to toggle source

Any other method is being used as a label to a member of a data structure.

Calls superclass method
# File lib/z80/labels.rb, line 963
def method_missing(m, struct=nil, count=1)
        if struct.nil? || !defined?(@struct_size) || !defined?(@members)
                super
        else
                n = m.to_s
                if struct.is_a? Member
                        struct.name = n
                        raise "#{self.name} has already a member: #{n}" if @members.assoc(n)
                        @members << [n, struct]
                        nil
                else
                        tsize = struct.is_a?(Integer) ? struct : struct.to_i
                        _, mem = @members.assoc(n)
                        if mem
                                Member.new(nil, mem.offset, struct, count, true)
                        else
                                @members << [n, Member.new(n, @struct_size, struct, count, false)]
                                @struct_size+= tsize*count
                                nil
                        end
                end
        end
end
new(addr, type = 1, reloc = nil, members = nil) click to toggle source

Creates an instance of a label. Do not use it directly in programs. Instead use Program.data, Program.label, Program.addr, Program.union or prepend any instruction with a name instead. Some instructions like Program.ns can create named labels if given a symbol.

Calls superclass method
# File lib/z80/labels.rb, line 1070
def new(addr, type = 1, reloc = nil, members = nil)
        if members.nil?
                if defined?(@struct_size)
                        members = Hash[@members.map do |_, m|
                                l = if m.type.is_a?(Class) && m.type.respond_to?(:to_data)
                                        m.type.new(m.offset, 1, :parent)
                                else
                                        Label.new(m.offset, m.type, :parent)
                                end
                                l.name = m.name
                                [m.name, l]
                        end]
                        Label.new(addr, self, reloc, members)
                elsif type.is_a?(Class) && type.ancestors.include?(self)
                        type.new(addr, type.to_i, reloc)
                end
        end || (addr.is_a?(self) ? addr : super)
end
offset_of_(name) click to toggle source

Returns a lazy evaluated, debug visible, byte offset of a struct member. Returns nil if self is not a struct or a member does not exist.

# File lib/z80/labels.rb, line 1046
def offset_of_(name)
        if @members
                name = name.to_s
                if @members.any? {|m| m[0] == name }
                        self.new(0).tap{|l|l.name = self.name}.to_alloc.send(name)
                end
        end
end
to_data(prog, offset, data) click to toggle source

Used by Program.data. Do not use it directly in programs. data must be a Hash, Struct, Array, String or a convertible Object (with a to_z80bin method).

# File lib/z80/labels.rb, line 988
def to_data(prog, offset, data)
        unless defined?(@struct_size) && defined?(@members)
                raise Syntax, "Label is not a data strucutre"
        end
        data = data.to_h if data.is_a?(::Struct)
        if data.is_a?(Hash)
                res = "\x0"*@struct_size
                @members.each do |n, m|
                        n = n.to_sym
                        if data.key?(n)
                                item = data[n]
                                items = if item.is_a?(Hash) || item.is_a?(::Struct) then [item] else Array(item) end
                                item_offset = m.offset
                                m.count.times do |index|
                                        s = member_item_to_data(prog, m, offset + item_offset, items[index])
                                        size = s.bytesize
                                        res[item_offset, size] = s
                                        item_offset += size
                                end
                        end
                end
        elsif data.respond_to? :to_z80bin
                res = data.to_z80bin[0,@struct_size].ljust(@struct_size, "\x0")
        elsif data.is_a?(String)
                res = data.dup.force_encoding(Encoding::ASCII_8BIT)[0,@struct_size].ljust(@struct_size, "\x0")
        else
                data = Array(data)
                res = ''
                index = 0
                @members.reject {|_, m| m.alias}.each do |_, m|
                        m.count.times do
                                s = member_item_to_data(prog, m, offset, data[index])
                                offset += s.bytesize
                                index += 1
                                res << s
                        end
                end
        end
        res
end
to_i() click to toggle source

Returns a size of a data structure immediately.

# File lib/z80/labels.rb, line 1055
def to_i; @struct_size; end
to_struct() click to toggle source

Returns a new Ruby Struct from members defined in a data structure.

Instances of such a Struct are suitable for passing as arguments to Program.data instead of e.g. Hash instances.

Member aliases are being ignored when creating a Struct.

# File lib/z80/labels.rb, line 947
def to_struct
        raise Syntax, "Label is not a data strucutre" unless defined?(@members)
        ::Struct.new *@members.reject {|_, m| m.alias}.map{|n, _| n.to_sym}
end
word(size = 1) click to toggle source

A data structure's field type.

# File lib/z80/labels.rb, line 956
def word(size = 1)
        2*size
end

Public Instance Methods

%(other) click to toggle source

Returns a lazy evaluated remainder of a label divided by an other label or an integer.

# File lib/z80/labels.rb, line 861
def %(other)
        to_alloc % other
end
&(m) click to toggle source

Returns a lazy evaluated bitwise “and” of a label and an other label or an integer.

# File lib/z80/labels.rb, line 881
def &(m)
        to_alloc & m
end
*(other) click to toggle source

Returns a lazy evaluated label multiplied by an other label or an integer.

# File lib/z80/labels.rb, line 853
def *(other)
        to_alloc * other
end
**(name) click to toggle source

Returns a member by its name as a separate label. This is used internally. Use Label#[] and Label#method_missing instead.

# File lib/z80/labels.rb, line 789
def **(name)
        @members[name]
end
+(other) click to toggle source

Returns a lazy evaluated label offset by an other label or an integer.

# File lib/z80/labels.rb, line 845
def +(other)
        to_alloc + other
end
+@() click to toggle source

Returns a lazy evaluated size of a type of a label.

# File lib/z80/labels.rb, line 815
def +@
        +to_alloc
end
-(other) click to toggle source

Returns a lazy evaluated label negatively offset by an other label or an integer.

# File lib/z80/labels.rb, line 849
def -(other)
        to_alloc - other
end
-@() click to toggle source

Returns a lazy evaluated negative label.

# File lib/z80/labels.rb, line 819
def -@
        -to_alloc
end
/(other) click to toggle source

Returns a lazy evaluated quotient of a label divided by an other label or an integer.

# File lib/z80/labels.rb, line 857
def /(other)
        to_alloc / other
end
<<(m) click to toggle source

Returns a lazy evaluated label left shifted by a number of bits as an other label or an integer.

# File lib/z80/labels.rb, line 869
def <<(m)
        to_alloc << m
end
>>(m) click to toggle source

Returns a lazy evaluated label right shifted by a number of bits as an other label or an integer.

# File lib/z80/labels.rb, line 865
def >>(m)
        to_alloc >> m
end
[](index = nil) click to toggle source

Returns a lazy evaluated label offset by index.

  • If index is nil, returns a pointer label instead.

  • If index is a number or an expression the offset is multiplied by a size of a label's type.

  • If index is a symbol or a string an accessor to the member of this label will be created. See: Label#method_missing.

e.g.:

foo addr 0x1234, 2
ld  hl, foo[7]   # loads 0x1234+14 into hl
ld  hl, foo[-42] # loads 0x1234-84 into hl
                 # pointer conversion (2nd form)
ld  hl, foo[]    # loads a byte from memory pointed at 0x1234 into l
                 # and a byte pointed at 0x1235 into h
For clarity don't use the pointer form directly in your programs.

Instead prefer to use one-element array wrapped around a label, integer or a Register, like this:

ld  hl, [foo]
# File lib/z80/labels.rb, line 840
def [](index = nil)
        to_alloc[index]
end
^(m) click to toggle source

Returns a lazy evaluated bitwise “exclusive or” of a label and an other label or an integer.

# File lib/z80/labels.rb, line 873
def ^(m)
        to_alloc ^ m
end
alias?() click to toggle source

Checks if a label is an address label (lazy alias).

# File lib/z80/labels.rb, line 760
def alias?; false; end
dummy?() click to toggle source

Checks if a label is not yet given value and type (in-the-future a.k.a. a dummy label).

# File lib/z80/labels.rb, line 756
def dummy?
        @type.nil?
end
expression?() click to toggle source

Checks if a label is an expression.

# File lib/z80/labels.rb, line 744
def expression?; false; end
immediate?() click to toggle source

Checks if a label is defined and absolute: true or not (relative or dummy): (false). Prefer using Program.immediate? instead.

# File lib/z80/labels.rb, line 748
def immediate?
        !dummy? and !@reloc
end
indexable?() click to toggle source

Returns true if a lazy evaluated label can be offset by index.

# File lib/z80/labels.rb, line 823
def indexable?; true; end
method_missing(m) click to toggle source

Any other method will lazy evaluate as an accessor to the member label of this label.

Calls superclass method
# File lib/z80/labels.rb, line 924
def method_missing(m)
        if m == :to_ary || m == :to_a || m == :to_hash || m == :to_h
                super
        else
                to_alloc.send m
        end
end
name=(value) click to toggle source

Gives a name to a no-named label. Should not be used directly in programs.

# File lib/z80/labels.rb, line 895
def name=(value)
        value = value.to_s
        raise Syntax, "Invalid label name: #{value.inspect}" if value.empty?
        raise Syntax, "Can't rename already named label: #{@name} != #{value}" if @name and @name != value
        @name = value
end
pointer?() click to toggle source

Checks if label is a pointer. Prefer using Program.pointer? instead.

# File lib/z80/labels.rb, line 742
def pointer?; false; end
sublabel?() click to toggle source

Checks if a label is a member of a struct or a stand-alone label.

# File lib/z80/labels.rb, line 752
def sublabel?
        @reloc == :parent
end
sublabel_access_expression?() click to toggle source

Checks if a label is a named sub-label access expression.

# File lib/z80/labels.rb, line 746
def sublabel_access_expression?; false; end
to_aliased_name(start) click to toggle source

Returns an abbreviated string information about a label for aliased targets.

# File lib/z80/labels.rb, line 913
def to_aliased_name(start)
        return @name if @name
        return (@address + if @reloc == :code then start else 0 end).to_s(16)
end
to_alloc() click to toggle source

Returns a lazy evaluated label as an instance of Alloc class. Use one of the lazy operators directly on a label instead.

# File lib/z80/labels.rb, line 811
def to_alloc
        Alloc.new(self)
end
to_i(start = 0, rel_to = nil, override:nil, prefix:''.freeze, size_of:false) click to toggle source

Evaluates a label. This method is being used during program compilation.

  • start

    An absolute address to offset a label if it's relative to the code base.

  • rel_to

    An absolute address to subtract from a label value or :self (used by ix/iy offset addressing).

  • override

    A Hash containing a possibly nested label names and override values for label overriding.

  • prefix

    A prefix of a nested label used for label overriding.

  • size_of

    If true returns a size of a label's type instead.

# File lib/z80/labels.rb, line 724
def to_i(start = 0, rel_to = nil, override:nil, prefix:''.freeze, size_of:false)
        raise Syntax, "a label `#{@name}' can't be coerced to a Integer before it's defined" if dummy?

        return @size if size_of

        if rel_to == :self
                0
        else
                if @name
                        fullname = prefix + @name
                        if (override_value = override && override[fullname])
                                return override_value - rel_to.to_i
                        end
                end
                @address - rel_to.to_i + (@reloc == :code ? start : 0)
        end
end
to_label(program) click to toggle source

Should return a Label or an Alloc. This method's existence indicates that something quacks like a label. The only argument is a program class on which the label will be used.

# File lib/z80/labels.rb, line 893
def to_label(_); self; end
to_name(info=false) click to toggle source

Returns this label's name as string or nil.

info enables returning made up name if this label is anonymous.

# File lib/z80/labels.rb, line 905
def to_name(info=false)
        return @name if @name
        if info
                return '$'.freeze if @reloc == :code
                return @address.to_s(16)
        end
end
to_s()
Alias for: to_str
to_str() click to toggle source

Returns an abbreviated string information about a label, mostly used in error messages.

# File lib/z80/labels.rb, line 918
def to_str; "`#{@name}':#{'%04X' % @address}:#{@size} #{@reloc}#{dummy? ? '?':''}"; end
Also aliased as: to_s
|(m) click to toggle source

Returns a lazy evaluated bitwise “or” of a label and an other label or an integer.

# File lib/z80/labels.rb, line 877
def |(m)
        to_alloc | m
end
~() click to toggle source

Returns a lazy evaluated bitwise negated label.

# File lib/z80/labels.rb, line 885
def ~
        ~to_alloc
end

Protected Instance Methods

initialize(address, type = 1, reloc = nil, members = nil) click to toggle source
# File lib/z80/labels.rb, line 770
def initialize(address, type = 1, reloc = nil, members = nil) # :notnew:
        # an absolute or relative address
        @address = address.to_i
        # size in bytes
        @size = type.to_i
        # a dummy label has @type == nil, usually 1 or 2 or a class inherited from the Label
        if type.nil? and (!(members.nil? or members.empty?) or !reloc.nil? or address != 0)
                raise Syntax, "not a really dummy label: #{self.inspect} reloc: #{reloc.inspect} address: #{address.inspect} members: #{members.inspect}"
        end
        @type = type
        # nil, :code or :parent
        raise Syntax, "reloc should be nil, :code or :parent, got: #{reloc.inspect}" unless reloc.nil? or reloc == :code or reloc == :parent
        @reloc = reloc
        # a hash with members (struct base members have reloc == :parent)
        @members = {}.update(members || {})
        # optional name, assigned later
        @name = nil
end