dfhack/library/xml/Common.pm

263 lines
6.2 KiB
Perl

package Common;
use utf8;
use strict;
use warnings;
BEGIN {
use Exporter ();
our $VERSION = 1.00;
our @ISA = qw(Exporter);
our @EXPORT = qw(
$main_namespace $export_prefix
%types %type_files *typename *filename
&parse_address &check_bad_attrs &check_name
&is_attr_true &type_header_def &add_type_to_hash
*lines *indentation &with_emit &emit &indent &outdent &emit_block
&is_primitive_type &primitive_type_name &get_primitive_base
*weak_refs *strong_refs &register_ref &decode_type_name_ref
%static_lines %static_includes &with_emit_static
&ensure_name &with_anon
);
our %EXPORT_TAGS = ( ); # eg: TAG => [ qw!name1 name2! ],
our @EXPORT_OK = qw( );
}
END { }
use XML::LibXML;
our $main_namespace = '';
our $export_prefix = '';
our %types;
our %type_files;
# Misc XML analysis
our $typename;
our $filename;
sub parse_address($;$) {
my ($str,$in_bits) = @_;
return undef unless defined $str;
# Parse the format used by offset attributes in xml
$str =~ /^0x([0-9a-f]+)(?:\.([0-7]))?$/
or die "Invalid address syntax: $str\n";
my ($full, $bv) = ($1, $2);
die "Bits not allowed: $str\n" unless $in_bits;
return $in_bits ? (hex($full)*8 + ($bv||0)) : hex($full);
}
sub check_bad_attrs($;$$) {
my ($tag, $allow_size, $allow_align) = @_;
die "Cannot use size, alignment or offset for ".$tag->nodeName."\n"
if ((!$allow_size && defined $tag->getAttribute('size')) ||
defined $tag->getAttribute('offset') ||
(!$allow_align && defined $tag->getAttribute('alignment')));
}
sub check_name($) {
my ($name) = @_;
$name =~ /^[_a-zA-Z][_a-zA-Z0-9]*$/
or die "Invalid identifier: $name\n";
return $name;
}
sub is_attr_true($$) {
my ($tag, $name) = @_;
return ($tag->getAttribute($name)||'') eq 'true';
}
sub type_header_def($) {
my ($name) = @_;
return uc($main_namespace).'_'.uc($name).'_H';
}
sub add_type_to_hash($) {
my ($type) = @_;
my $name = $type->getAttribute('type-name')
or die "Type without a name in $filename\n";
die "Duplicate definition of $name in $filename\n" if $types{$name};
local $typename = $name;
check_bad_attrs $type;
$types{$name} = $type;
$type_files{$name} = $filename;
}
# Text generation with indentation
our @lines;
our $indentation = 0;
sub with_emit(&;$) {
# Executes the code block, and returns emitted lines
my ($blk, $start_indent) = @_;
local @lines;
local $indentation = ($start_indent||0);
$blk->();
return @lines;
}
sub emit(@) {
# Emit an indented line to be returned from with_emit
my $line = join('',map { defined($_) ? $_ : '' } @_);
$line = (' 'x$indentation).$line unless length($line) == 0;
push @lines, $line;
}
sub indent(&) {
# Indent lines emitted from the block by one step
my ($blk) = @_;
local $indentation = $indentation+2;
$blk->();
}
sub outdent(&) {
# Unindent lines emitted from the block by one step
my ($blk) = @_;
local $indentation = ($indentation >= 2 ? $indentation-2 : 0);
$blk->();
}
sub emit_block(&;$$%) {
# Emit a full {...} block with indentation
my ($blk, $prefix, $suffix, %flags) = @_;
my @inner = &with_emit($blk,$indentation+2);
return if $flags{-auto} && !@inner;
$prefix ||= '';
$suffix ||= '';
emit $prefix,'{';
push @lines, @inner;
emit '}',$suffix;
}
# Primitive types
my @primitive_type_list =
qw(int8_t uint8_t int16_t uint16_t
int32_t uint32_t int64_t uint64_t
s-float
bool flag-bit
padding static-string);
my %primitive_aliases = (
's-float' => 'float',
'static-string' => 'char',
'flag-bit' => 'void',
'padding' => 'void',
);
my %primitive_types;
$primitive_types{$_}++ for @primitive_type_list;
sub is_primitive_type($) {
return $primitive_types{$_[0]};
}
sub primitive_type_name($) {
my ($tag_name) = @_;
$primitive_types{$tag_name}
or die "Not primitive: $tag_name\n";
return $primitive_aliases{$tag_name} || $tag_name;
}
sub get_primitive_base($;$) {
my ($tag, $default) = @_;
my $base = $tag->getAttribute('base-type') || $default || 'uint32_t';
$primitive_types{$base} or die "Must be primitive: $base\n";
return $base;
}
# Type references
our %weak_refs;
our %strong_refs;
sub register_ref($;$) {
# Register a reference to another type.
# Strong ones require the type to be included.
my ($ref, $is_strong) = @_;
if ($ref) {
my $type = $types{$ref}
or die "Unknown type $ref referenced.\n";
if ($is_strong) {
$strong_refs{$ref}++;
} else {
$weak_refs{$ref}++;
}
}
}
sub decode_type_name_ref($;%) {
# Interpret the type-name field of a tag
my ($tag,%flags) = @_;
my $force_type = $flags{-force_type};
my $attr = $flags{-attr_name} || 'type-name';
my $tname = $tag->getAttribute($attr) or return undef;
if ($primitive_types{$tname}) {
die "Cannot use type $tname as $attr here: $tag\n"
if ($force_type && $force_type ne 'primitive');
return primitive_type_name($tname);
} else {
register_ref $tname, !$flags{-weak};
die "Cannot use type $tname as $attr here: $tag\n"
if ($force_type && $force_type ne $types{$tname}->getAttribute('ld:meta'));
return $main_namespace.'::'.$tname;
}
}
# Static file output
our %static_lines;
our %static_includes;
sub with_emit_static(&;$) {
my ($blk, $tag) = @_;
my @inner = &with_emit($blk,2) or return;
$tag ||= '';
$static_includes{$tag}{$typename}++;
push @{$static_lines{$tag}}, @inner;
}
# Anonymous variable names
our $anon_id = 0;
our $anon_prefix;
sub ensure_name($) {
# If the name is empty, assign an auto-generated one
my ($name) = @_;
unless ($name) {
$name = $anon_prefix.(($anon_id == 0) ? '' : '_'.$anon_id);
$anon_id++;
}
return check_name($name);
}
sub with_anon(&;$) {
# Establish a new anonymous namespace
my ($blk,$stem) = @_;
local $anon_id = $stem ? 0 : 1;
local $anon_prefix = ($stem||'anon');
$blk->();
}
1;