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:
-
Independent labels, living in a namespace, formed as a result of using:
Program.import
orProgram.ns
. -
Fields of a data structure; fields represents offsets relative to a parent label; fields may also contain members, but only as fields.
Members are being accessed by indexes. An index is being formed by either:
-
using [index] after a label name, see:
Label#[]
, e.g.:foo[:bar][2]['baz']
-
using an undefined method on a label, e.g.:
foo.bar[2].baz
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?
. Callingto_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:
- With
Program.addr
-
Creates an absolute (immediate) label “spritep”, with
value
=0x8888
and of typeSpritePool
:spritep addr 0x8888, SpritePool
- With
- With
Program.data
-
Creates a relative label “spritep”, with
value
=Program.pc
and of typeSpritePool
, fills SpritePool fields with provided data:spritep data SpritePool, [2, {x:0, y:0, size:12, data_p: sprite1_data}, {x:0, y:0, size:16, data_p: sprite2_data}]
- With
- With
Program.label
-
Creates a relative label “someprc”, with
value
=Program.pc
and of type 1:someprc label
- With
- With a mnemonic
-
Creates a relative label “someprc”, with
value
=Program.pc
and of type 1:someprc ld de, [foo.bar]
- With
Program.union
-
Creates a relative label “foo”, with
value
=bar
and of type 2:foo union bar, 2
- With
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 secondSprite
in thespritep
: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
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
A data structure's field type.
# File lib/z80/labels.rb, line 952 def byte(size = 1) 1*size end
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
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
Any other method is being used as a label to a member of a data structure.
# 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
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.
# 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
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
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
Returns a size of a data structure immediately.
# File lib/z80/labels.rb, line 1055 def to_i; @struct_size; end
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
A data structure's field type.
# File lib/z80/labels.rb, line 956 def word(size = 1) 2*size end
Public Instance Methods
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
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
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
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
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
Returns a lazy evaluated size of a type of a label.
# File lib/z80/labels.rb, line 815 def +@ +to_alloc end
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
Returns a lazy evaluated negative label.
# File lib/z80/labels.rb, line 819 def -@ -to_alloc end
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
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
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
Returns a lazy evaluated label offset by index
.
-
If
index
isnil
, 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
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
Checks if a label is an address label (lazy alias).
# File lib/z80/labels.rb, line 760 def alias?; false; end
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
Checks if a label is an expression.
# File lib/z80/labels.rb, line 744 def expression?; false; end
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
Returns true
if a lazy evaluated label can be offset by index
.
# File lib/z80/labels.rb, line 823 def indexable?; true; end
Any other method will lazy evaluate as an accessor to the member label of this label.
# 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
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
Checks if label is a pointer. Prefer using Program.pointer?
instead.
# File lib/z80/labels.rb, line 742 def pointer?; false; end
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
Checks if a label is a named sub-label access expression.
# File lib/z80/labels.rb, line 746 def sublabel_access_expression?; false; end
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
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
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
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
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
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
Returns a lazy evaluated bitwise negated label.
# File lib/z80/labels.rb, line 885 def ~ ~to_alloc end
Protected Instance Methods
# 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