Headalyzer is a tool for generating header files (or equivalent) in several
languages. This is useful when you have shared constants that you wish to use
across several languages. This is especially handy when you need to share
information in projects that use FFI.
In a nutshell:
$ cat sample.star
# "symbol components" like "version", "major" will be "cased" appropriately
# according to the target language's conventions
declare(["version", "major"], "int", 2)
declare(["version", "minor"], "int", 7)
declare(["version", "patch"], "int", 1)
# If a symbol only has one component, the brackets can be omitted
declare("build", "string", "git")
# Target-language-specific "hints" can be defined, say width in bits for
# SystemVerilog
declare("bufsize", "int", 128)
hint("bufsize", "systemverilog", "width", 10)
# Generators can be configured too, the C generator uses CAPITAL_SNAKE_CASE
# by default, but we can ask it to use UpperCamelCase instead...
option("c", "case", "UpperCamelCase")
$ hdr describe sample.star
Description of file 'sample.star'
Options:
{c case UpperCamelCase}
Constants:
<Constant type=int val='2'>
<Constant type=int val='7'>
<Constant type=int val='1'>
<Constant type=string val='git'>
<Constant type=int val='128'>
Hints:
{{[bufsize]} {systemverilog width 10}}
$ hdr generate -l c sample.star
#ifndef HEADALYZER_GENERATED_H
#define HEADALYZER_GENERATED_H
#define VersionMajor 2
#define VersionMinor 7
#define VersionPatch 1
#define Build "git"
#define Bufsize 128
#endif /* HEADALYZER_GENERATED_H */
$ hdr generate -l shell sample.star
VERSION_MAJOR='2'
VERSION_MINOR='7'
VERSION_PATCH='1'
BUILD='git'
BUFSIZE='128'
$ hdr generate -l systemverilog sample.star
`ifndef HEADALYZER_GENERATED_H
`define HEADALYZER_GENERATED_H
`define VERSION_MAJOR 2
`define VERSION_MINOR 7
`define VERSION_PATCH 1
`define BUILD "git"
`define BUFSIZE 10'd128
`endif /* HEADALYZER_GENERATED_H */
How to Install
Due to this issue with Starlib,
it's not currently possible to install this program in the usual way. Instead:
This will no longer be needed once the upstream issue is resolved.
Once Upstream is Fixed
Motivation
My motivation behind writing Headalyzer was needing to share certain constants
between some C++ and SystemVerilog code in a
Verilator project. I've run into
these types of troubles in the past, so I decided to finally solve this problem
in the general case.
How It Works
Headalyzer is built on top of
google/starlark-go. It implements three special functions,
declare(symbol, type, value)
, option(section, key, value)
, and
hint(symbol, section, key, value)
which is uses to build an in-memory list of
constants, options, and hints.
A constant is what Headalzer is outputting when you run hdr generate
, it
associates a symbol with a type and a value. The type is used to influence
how the output is generated.
A symbol is a collection of string components. A symbol is how a
particular constant is referred to. This approach was chosen because it enabled
Headalyzer to automatically convert symbols into the appropriate casing style
for the target language. For example, when targeting Go you might want to case
like VersionMajor
, but for C you might prefer VERSION_MAJOR
.
A generator is a library under Headalyzer that contains methods to generate
output targeting a specific language.
An option associates a section, key, and value. By convention,
the section will be the name of a generator (e.g. "c" or "shell"). A
key is a configuration value associated with that generator. The value
is stored as a string, but the generator may internally case it to a different
type, and may throw an appropriate error if this fails. Generators will almost
always have default values and not require you to explicitly specify any
options.
A hint associates an option with a symbol, it can be used to express
metadata about a single specific constant, rather than configuring a generator
at large. An example of where this is useful is that in SystemVerilog, integer
constants can optionally have an explicit bit width associated with them, which
wouldn't be meaningful in say, C.
When you run hdr generate
, the input file is run to build an in-memory list
of these constants, options, and hints, then the appropriate Generate
function is called (based on the -l
flag).
Starlark Extensions
To make it easier to compute constants,
starlib is included, and starlib modules
can be loaded, which may be useful for calculating floating point constants
with starlib's math library.
Further considering the intended use of Headalyzer and the lack of logarithm
functions in Starlib's math library, wrappers around Golang's math.Log()
,
math.Log2()
, and math.Log10()
as the Starlark functions log()
, log2()
,
and log10()
.
Supported Languages
To find out what languages your copy of hdr
supports, run hdr list
.
Supported Types
Contributing
If you have a question, comment, or patch, you should send it in via my public
inbox.
Adding A New Language
To support a new language, you should create a new package in
./pkg/headalyzer
named after your desired output
language. The systemverilog
generator is a good example, as
it uses options and hints.
Once you have added the appropraite package, update
./cmd/hdr/main.go
to list the newly added language, and
to support using it with -l
.
Adding A New Type
Presently, types are defined in
./pkg/headalyzer/headalyzer.go
. There is a
big list of constant type names where you may wish to add your type, if it
isn't there already. You need to implement the functions of the Constant
interface for your new constant, then add it to the CreateConstant()
function. You should also add it to all of the generators as well.
Keep in mind that Constant.String()
is intended to return a human-readable
string, while Constant.Text()
is intended to return the way it should be
represented by the generator during output. The generator will apply any
requisite quoting, semicolons, etc. so do not insert these in your Text()
function.
Status
Headalyzer is a very new project. The API and command syntax should not be
considered stable at this time, and breaking changes are possible. Once I have
used Headalyzer in a real project or two and am convinced it works the way it
aught to, I will cut a 1.0.0 release and switch to semantic versioning.
FAQ
Isn't This overkill?
Probably. But I'm tired of echo-ing things into header files from Makefiles.
Why Starlark?
I wanted to use an existing file format, not write a new one from scratch.
Starlark provides a lot of power, and is easy to bind functions into. I also
like the Python-like syntax more than Lua or JS (which are the other languages
I'm aware of that have Go-embeddable variants).
Can I Use Headalyzer As a Library?
Yes, that is specifically supported. However, as noted in the Status,
Headalyzer probably should not be considered Stable.
License
BSD 3-Clause. See ./LICENSE
.