#!/usr/bin/awk -f
#
# This script parses Vector CAN DBs (.dbc files), such as can be created
# using Vector CANdb++.
#
# A subset of the parsed information is output using a set of templates.
#
# @note
# Pipe the input through "iconv -f CP1252" so GNU AWK doesn't choke
# on non-UTF-8 characters in comments.
# @warning
# Templates are subject to change, which may break the output for
# your use case. To prevent this retain your own copy of the
# templates directory and set the \ref dbc2c_env_TEMPLTES variable.
# Old templates will continue working, though they might cause
# deprecation warnings.
#
# \section dbc2c_env Environment
#
# The script uses certain environment variables.
#
# \subsection dbc2c_env_DEBUG DEBUG
#
# | Value | Effect
# |---------------------|-----------------------------------------
# | 0, "" | Debugging output is deactivated
# | 1, any string != "" | Debugging output to stderr is activated
# | > 1 | Additionally any string read is output
#
# \subsection dbc2c_env_TEMPLTES TEMPLATES
#
# This variable can be used to pass the template directory to the script.
#
# If the \c LIBPROJDIR environment variable is set it defaults to
# ${LIBPROJDIR}/scripts/templates.dbc2c, otherwise it defaults
# to the relative path scripts/templates.dbc2c.
#
# \subsection dbc2c_env_DATE DATE
#
# This can be used to define the date string provided to header.tpl.
#
# It defaults to the output of the \c date command.
#
# \section dbc2c_vts Value Tables
#
# Since values in value tables only consist of a number and description,
# the first word of this description is used as a symbolic name for a given
# value.
#
# All non-alhpanumeric characters of this first word will be converted to
# underscores. Redundancies will be resolved by appending the value to the
# word that signifies the name.
#
# This functionality is implemented in the function getUniqueEnum().
#
# \section dbc2c_templates Templates
#
# This section describes the templates that are used by the script
# and the arguments passed to them. Templates are listed in the
# chronological order of use.
#
# \subsection dbc2c_templates_attributes Special Attributes
#
# Some of the arguments provided depend on custom attributes:
# | Template | Argument | Attribute | Object
# |-------------|----------|---------------------|--------------------------
# | sig.tpl | start | GenSigStartValue | Signal
# | msg.tpl | fast | GenMsgCycleTimeFast | Message
# | msg.tpl | cycle | GenMsgCycleTime | Message
# | msg.tpl | delay | GenMsgDelayTime | Message
# | msg.tpl | send | GenMsgSendType | Message
# | timeout.tpl | timeout | GenSigTimeoutTime | Relation (ECU to Signal)
#
# These and more attributes are specified by the
# Vector Interaction Layer.
#
# \subsection dbc2c_templates_data Inserting Data
#
# Templates are arbitrary text files that are provided with a set of
# arguments. Arguments have a symbolic name through which they can be used.
# In the following sections they are called fields, because they are provided
# to the template() function in an associative array.
#
# Inserting data into a template is simple:
#
# <:name:>
#
# The previous example adds the data in the field \c name into the file.
# It can be surrounded by additional context:
#
# #define <:name:> <:value:>
#
# If \c name is "FOO_BAR" and \c value is 1337, this line would be resolved
# to:
#
# #define FOO_BAR 1337
#
# It may be desired to reformat some of those values. A number of special
# filters (see filter()) as well es printf(3) style formatting is available.
# E.g. \c name can be converted to camel case and \c value to hex:
#
# #define <:name:camel:%-16s:> <:value:%#x:>
#
# The output would look like this:
#
# #define fooBar 0x539
#
# An important property of templates is that arguments may contain multiple
# lines. In that case the surrounding text is preserved for every line, which
# is useful to format multiline text or lists. This can be used to create
# lists or provide visual sugar around text:
#
# +----[ <:title:%-50s:> ]----+
# | <:text:%-60s:> |
# +--------------------------------------------------------------+
#
# Output could look like this:
#
# +----[ Racecar by Matt Brown ]----+
# | 'Racecar - Searching for the Limit in Formula SAE' |
# | is available for download from: |
# | http://www.superfastmatt.com/2011/11/book.html |
# +--------------------------------------------------------------+
#
# Multi line data is treated as an array of individual lines. Besides
# descriptions in DBC files multiline data can also originate from lists
# provided by this script in order to allow describing the relations
# between ECUs, messages, signals etc..
#
# In some cases it is prudent to print lines conditionally. For that
# conditionals are provided:
#
#
#
# If the reverenced field evaluates to \c true, the conditional is removed
# from the line. If it evaluates to \c false, the entire template line is
# omitted.
#
# \subsection dbc2c_templates_header header.tpl
#
# Used once with the following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | date | string | The current date
# | db | string[] | A list of identifiers for the parsed DBCs
#
# \subsection dbc2c_templates_file file.tpl
#
# Used for each input file with the following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | db | string | An identifier for this input file
# | file | string | The file name
# | comment | string[] | The comment text for this CANdb
# | ecu | string[] | A list of ECUs provided with this file
#
# \subsection dbc2c_templates_sigid sigid.tpl
#
# This template should only contain a single line that produces a unique
# identifier string for a signal, using the following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | msg | int | The message ID
# | msgname | string | The message name
# | sig | string | The signal name
#
# Signal names are not globally unique, thus an identifier must contain
# a message reference to avoid name collisions.
#
# \subsection dbc2c_templates_ecu ecu.tpl
#
# Used for each ECU with the following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | ecu | string | An identifier for the ECU
# | comment | string[] | The comment text for this ECU
# | db | string | The input file identifier
# | txid | int[] | A list of message IDs belonging to messages sent by this ECU
# | txname | string[] | A list of message names sent by this ECU
# | rx | string[] | A list of signals received by this ECU
# | rxid | string[] | A list of unique signal identifiers received by this ECU
#
# \subsection dbc2c_templates_msg msg.tpl
#
# Used for each message with the following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | msg | int | The message ID
# | name | string | The message name
# | comment | string[] | The comment text for this message
# | sig | string[] | A list of signal names contained in this message
# | sigid | string[] | A list of signal identifiers contained in this message
# | ecu | string | The ECU sending this message
# | ext | bool | Message ID is extended
# | dlc | int | The data length count
# | cycle | int | The cycle time of this message
# | fast | int | The fast cycle time of this message
# | delay | int | The minimum delay time between two transmissions
# | send | string | The send type (cyclic, spontaneous etc.)
# | sgid | string[] | A list of signal group ids
# | sgname | string[] | A list of signal group names
#
# \subsection dbc2c_templates_siggrp siggrp.tpl
#
# Used for each signal group with the following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | id | string | The ID of the signal group (created using sigid.tpl)
# | name | string | The name of the signal group
# | msg | int | The ID of the message containing this signal group
# | msgname | string | The name of the message containing this signal group
# | sig | string[] | A list of signals belonging to this signal group
# | sigid | string[] | A list of signal identifers belonging to this signal group
#
# \subsection dbc2c_templates_sig sig.tpl
#
# Used for each signal with the following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | name | string | The signal name
# | id | string | The unique signal identifier created with sigid.tpl
# | comment | string[] | The comment text for this signal
# | enum | bool | Indicates whether this signal has a value table
# | msg | int | The ID of the message sending this signal
# | sgid | string[] | The signal groups containing this signal
# | sgname | string[] | The names of the signal groups containing this signal
# | ecu | string[] | A list of the ECUs receiving this signal
# | intel | bool | Intel (little endian) style signal
# | motorola | bool | Motorola (big endian) style signal
# | signed | bool | The signal is signed
# | sbit | int | The start bit (meaning depends on endianess)
# | len | int | The signal length
# | start | int | The initial (default) signal value (raw)
# | calc16 | string[] | A rational conversion function (see \ref dbc2c_templates_sig_calc16)
# | min | int | The raw minimum value
# | max | int | The raw maximum value
# | off | int | The raw offset value
# | getbuf | string[] | The output of sig_getbuf.tpl
# | setbuf | string[] | The output of sig_setbuf.tpl
#
# \subsubsection dbc2c_templates_sig_calc16 calc16
#
# A rational conversion function for the raw signal value \c x
# and formatting factor \c fmt into a real value as defined
# by the linear factor and offset in the DBC, this function
# uses up to 16bit integers.
#
# \subsubsection dbc2c_templates_sig_buf sig_getbuf.tpl, sig_setbuf.tpl
#
# These templates can be used to construct static byte wise signal getters
# and setters.
#
# For signed signals sig_getbuf.tpl is first called with the
# following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | sign | string | "-"
# | byte | int | The byte containing the most significant bit
# | align | int | The position of the most significant bit in the byte
# | msk | int | 1
# | pos | int | The position in front of the entire read signal
# | int8 | bool | Indicates whether an 8 bit integer suffices to contain the signal
# | int16 | bool | Indicates whether a 16 bit integer suffices to contain the signal
# | int32 | bool | Indicates whether a 32 bit integer suffices to contain the signal
#
# These arguments can be used to duplicate the signed bit and shift it
# in front.
#
# Both templates are used for each touched signal byte with the following
# arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | sign | string | "+"
# | byte | int | The signal byte
# | align | int | The least significant bit within the byte belonging to the signal
# | msk | int | A bit mask to mask the aligned signal bits
# | pos | int | The position to shift the resulting bits to
# | int8 | bool | Indicates whether an 8 bit integer suffices to address the desired bit
# | int16 | bool | Indicates whether a 16 bit integer suffices to address the desired bit
# | int32 | bool | Indicates whether a 32 bit integer suffices to address the desired bit
#
# \subsubsection dbc2c_templates_sig_enum sig_enum.tpl, sig_enumval.tpl
#
# In case a value table is assigned to the signal, sig_enum.tpl is
# called with all the arguments provided to sig.tpl.
#
# For each entry in the value table sig_enumval.tpl is called
# with these additional arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | enumval | int | The value
# | enumname | string | The name of the value
# | comment | string[] | The comment part of the value description
#
# \subsection dbc2c_templates_timeout timeout.tpl
#
# Used for each timeout with the following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | ecu | string | The ECU that times out
# | sig | string | The signal that is expected by the ECU
# | sigid | string | The unique identifier for the expected signal
# | timeout | int | The timeout time
# | msg | int | The ID of the CAN message containing the signal
# | msgname | string | The name of the CAN message containing the signal
#
# \subsection dbc2c_templates_enum enum.tpl
#
# Invoked for every value table with the following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | enum | string | The name of the value table
# | db | string | The name of the CAN DB this enum was defined in
#
# \subsubsection dbc2c_templates_enumval enumval.tpl
#
# Invoked for every value defined in a value table. All the template arguments
# for \c enum.tpl are available in addition to the following arguments:
# | Field | Type | Description
# |----------|----------|-------------
# | val | int | The value
# | name | string | The symbolic name for the value
# | comment | string[] | The comment part of the value description
#
##
# Initialises globals.
#
BEGIN {
# Environment variables
DEBUG = (DEBUG ? DEBUG : ENVIRON["DEBUG"])
TEMPLATES = (TEMPLATES ? TEMPLATES : ENVIRON["TEMPLATES"])
DATE = (DATE ? DATE : ENVIRON["DATE"])
# Template directory
if (!TEMPLATES) {
path = ENVIRON["LIBPROJDIR"]
sub(/.+/, "&/", path)
TEMPLATES = path "scripts/templates.dbc2c"
}
sub(/\/?$/, "/", TEMPLATES)
# Generating date
if (!DATE) {
"date" | getline DATE
close("date")
}
FILENAME = "/dev/stdin"
# Regexes for different types of data
rLF = "\n"
rFLOAT = "-?[0-9]+(\\.[0-9]+)?([eE][-+]?[0-9]+)?"
rINT = "-?[0-9]+"
rID = "[0-9]+"
rDLC = "[0-9]+"
rSEP = "[:;,]"
rSYM = "[a-zA-Z0-9_]+"
rSYMS = "(" rSYM ",)*" rSYM
rSIG = "[0-9]+\\|[0-9]+@[0-9]+[-+]"
rVEC = "\\(" rFLOAT "," rFLOAT "\\)"
rBND = "\\[" rFLOAT "\\|" rFLOAT "\\]"
rSTR = "\"([^\"]|\\\\.)*\"" # CANdb++ does not support escaping
# characters like ", however it parses
# them just fine.
# Type strings
tDISCARD["BS_"]
t["SIG_ENUM"] = "VAL_"
t["ENUM"] = "VAL_TABLE_"
t["SYMBOLS"] = "NS_"
t["VER"] = "VERSION"
t["ECU"] = "BU_"
t["MSG"] = "BO_"
t["SIG"] = "SG_"
t["ENV"] = "EV_"
t["COM"] = "CM_"
t["ATTRDEFAULT"] = "BA_DEF_DEF_"
t["RELATTRDEFAULT"] = "BA_DEF_DEF_REL_"
t["ATTRRANGE"] = "BA_DEF_"
t["RELATTRRANGE"] = "BA_DEF_REL_"
t["ATTR"] = "BA_"
t["RELATTR"] = "BA_REL_"
t["EDLC"] = "ENVVAR_DATA_"
t["TX"] = "BO_TX_BU_"
t["SIG_GRP"] = "SIG_GROUP_"
# Relationship symbols
t["ECU_SIG"] = "BU_SG_REL_"
t["ECU_ENV"] = "BU_EV_REL_"
t["ECU_MSG"] = "BU_BO_REL_"
# Not implemented!
# This is marked by the symbol being indexed with its literal name.
t["NS_DESC_"] = "NS_DESC_"
t["CAT_DEF_"] = "CAT_DEF_"
t["CAT_"] = "CAT_"
t["FILTER"] = "FILTER"
t["EV_DATA_"] = "EV_DATA_"
t["SGTYPE_"] = "SGTYPE_"
t["SGTYPE_VAL_"] = "SGTYPE_VAL_"
t["BA_DEF_SGTYPE_"] = "BA_DEF_SGTYPE_"
t["BA_SGTYPE_"] = "BA_SGTYPE_"
t["SIG_TYPE_REF_"] = "SIG_TYPE_REF_"
t["SIG_VALTYPE_"] = "SIG_VALTYPE_"
t["SIGTYPE_VALTYPE_"] = "SIGTYPE_VALTYPE_"
t["SG_MUL_VAL_"] = "SG_MUL_VAL_"
# Attribute types
atSTR = "STRING"
atENUM = "ENUM"
atINT = "INT"; atNUM[atINT]
atFLOAT = "FLOAT"; atNUM[atFLOAT]
atHEX = "HEX"; atNUM[atHEX]
# Environment variable types
etINT = "INT"
etFLOAT = "FLOAT"
etDATA = "DATA"
eTYPE[0] = etINT
eTYPE[1] = etFLOAT
# Prominent attributes
aSTART = "GenSigStartValue"
aFCYCLE = "GenMsgCycleTimeFast"
aCYCLE = "GenMsgCycleTime"
aDELAY = "GenMsgDelayTime"
aSEND = "GenMsgSendType"
aTIMEOUT = "GenSigTimeoutTime"
# Global error indicator
errno = 0
}
##
# Strip DOS line endings and make sure there is a new line symbol at the
# end of the line, so multiline definitions can be parsed.
#
{
gsub(/[\r\n]*$/, "\n")
}
##
# Prints an error message on stderr and exits.
#
# @param no
# The number to set errno to
# @param msg
# The error message
#
function error(no, msg) {
errno = no
print "dbc2c.awk: ERROR: " FILENAME "(" NR "): " msg > "/dev/stderr"
exit
}
##
# Prints a warning message on stderr.
#
# @param msg
# The message to print
#
function warn(msg) {
print "dbc2c.awk: WARNING: " FILENAME "(" NR "): " msg > "/dev/stderr"
}
##
# Prints a debugging message on stderr.
#
# The debugging message is only printed if DEBUG is set.
#
# @param msg
# The message to print
#
function debug(msg) {
if (DEBUG) {
print "dbc2c.awk: " msg > "/dev/stderr"
}
}
##
# Makes sure $0 is not empty.
#
function buffer() {
sub(/^[ ]*/, "")
if (!$0) {
getline
gsub(/[\r\n]*$/, "\n")
sub(/^[ ]*/, "")
}
}
##
# Special function to fetch a string from the buffer.
#
# This is a special case, because strings may span multiple lines.
# This function supports strings with up to 256 lines.
#
# @return
# The fetched string
#
function fetchStr(dummy,
str,i) {
buffer()
if ($0 !~ /^"/) {
return ""
}
# Assume strings are no longer than 256 lines
while ($0 !~ "^(" rSTR ")" && i++ < 256) {
getline str
gsub(/[\r\n]*$/, "\n", str)
$0 = $0 str
}
return strip(fetch(rSTR))
}
##
# Fetch the next token from the input buffer, matching a given type.
#
# @param types
# A regular expression describing the type of data to be fetched
# @return
# The fetched string of data
#
function fetch(types,
str, re) {
buffer()
if (match($0, "^(" types ")")) {
str = substr($0, RSTART, RLENGTH)
# Cut str from the beginning of $0
$0 = substr($0, RSTART + RLENGTH)
}
if (DEBUG > 1 && str !~ /^[ \t\n]*$/) {
debug("fetch: " str)
}
if (str) {
fetch_loop_detect = 0
} else if (fetch_loop_detect++ >= 100) {
error(11, "infinite loop detected!")
}
return str
}
##
# Returns the expresion with ^ and $ at beginning and end to make ~ match
# entire strings only.
#
# @param re
# The expression to wrap
# @return
# An expression for matching entire strings
#
function whole(re) {
return "^(" re ")$"
}
##
# Remove quotes and escapes from strings.
#
# This function is used by fetchStr().
#
# @param str
# The string to unescape
# @return
# The litreal string
#
function strip(str) {
sub(/^"/, "", str)
sub(/"$/, "", str)
# Unescape "
gsub(/\\"/, "\"", str)
return str
}
##
# Returns the context type for a string.
#
# @param str
# The string to interpret
# @retval "sig"
# The context is a signal
# @retval "msg"
# The context is a message
# @retval "ecu"
# The context is an ECU
# @retval "env"
# The context is an environment variable
# @retval "db"
# The context is the DB
#
function getContext(str) {
if (str == t["SIG"]) {
return "sig"
}
if (str == t["MSG"]) {
return "msg"
}
if (str == t["ECU"]) {
return "ecu"
}
if (str == t["ENV"]) {
return "env"
}
if (str == "") {
return "db"
}
error(2, "unknown context " str " encountered")
}
##
# Generates a unique name for a value table entry.
#
# Updates:
# - obj_enum_count[enum, name] = (int)
#
# Sets the following fields in the given array:
# - name: A unique identifier
# - desc: The description
# - invalid: No valid identifier was in the description (bool)
# - duplicate: The identifier was already in use (bool)
#
# @param ret
# An array to return the data set in
# @param enum
# The identifier of the value table
# @param val
# The value
# @param desc
# The description string to fetch a name from
#
function getUniqueEnum(ret, enum, val, desc,
name) {
name = desc
ret["invalid"] = 0
ret["duplicate"] = 0
sub(/[ \t\r\n].*/, "", name)
if (name !~ /^[a-zA-Z0-9_]*$/) {
warn("Invalid identifier '" name "' for value " sprintf("%#x", val) " in table " enum)
gsub(/[^a-zA-Z0-9_]/, "_", name)
warn("Replaced with '" name "'")
ret["invalid"] = 1
} else {
# Name is valid, remove it from the description
sub(/^[^ \t\r\n]+[ \t\r\n]*/, "", desc)
}
while (obj_enum_count[enum, name]++) {
warn("Identifier '" name "' for value " sprintf("%#x", val) " in table " enum " already in use")
name = name "_" sprintf("%X", val)
warn("Replaced with '" name "'")
ret["duplicate"] = 1
}
ret["name"] = name
ret["desc"] = desc
}
##
# Discards buffered symbols until an empty line is encountered.
#
# This is used to skip the list of supported symbols at the beginning of a
# dbc file.
#
function fsm_discard() {
fetch(":")
fetch(rLF)
while(fetch(rSYM "|" rLF) !~ whole(rLF)) {
fetch(rLF)
}
}
##
# Parse an ECU definition.
#
# Token: BU_
#
# Creates:
# - * ind_ecu[cnt_ecu++] = ecu
# - * obj_ecu[ecu]
# - * obj_ecu_db[ecu] = FILENAME
# - * obj_db_ecu[FILENAME, p] = ecu
#
function fsm_ecu(dummy,
ecu, p) {
fetch(":")
ecu = fetch(rSYM "|" rLF)
while (ecu !~ whole(rLF)) {
ind_ecu[cnt_ecu++] = ecu
obj_ecu[ecu]
obj_ecu_db[ecu] = FILENAME
p = 0
while (obj_db_ecu[FILENAME, p++]);
obj_db_ecu[FILENAME, --p] = ecu
ecu = fetch(rSYM "|" rLF)
}
}
##
# Parse a value table.
#
# Token: VAL_TABLE_
#
# Creates:
# - 1 ind_enum[cnt_enum++] = enum
# - 1 obj_enum_db[enum] = FILENAME
# - * obj_enum_val[enum, i] = val
# - * obj_enum_name[enum, i] = name
# - * obj_enum_desc[enum, i] = desc
# - * obj_enum_invalid[enum, i] = (bool)
# - * obj_enum_duplicate[enum, i] = (bool)
#
function fsm_enum(dummy,
enum,
val, a,
i) {
enum = fetch(rSYM)
ind_enum[cnt_enum++] = enum
obj_enum_db[enum] = FILENAME
val = fetch(rINT "|;")
i = 0
while (val != ";") {
obj_enum_val[enum, i] = val
delete a
getUniqueEnum(a, enum, val, fetchStr())
obj_enum_name[enum, i] = a["name"]
obj_enum_desc[enum, i] = a["desc"]
obj_enum_invalid[enum, i] = a["invalid"]
obj_enum_duplicate[enum, i] = a["duplicate"]
++i
val = fetch(rINT "|;")
}
fetch(rLF)
}
##
# Parse a value table bound to a signal.
#
# Token: VAL_
#
# Creates:
# - 1 obj_sig_enum[msgid, sig]
# - * obj_sig_enum_val[msgid, sig, i] = val
# - * obj_sig_enum_name[msgid, sig, i] = name
# - * obj_sig_enum_desc[enum, i] = desc
# - * obj_sig_enum_invalid[enum, i] = (bool)
# - * obj_sig_enum_duplicate[enum, i] = (bool)
#
function fsm_sig_enum(dummy,
msgid, sig,
val, a,
i) {
msgid = fetch(rID)
sig = fetch(rSYM)
obj_sig_enum[msgid, sig]
val = fetch(rINT "|;")
i = 0
while (val != ";") {
obj_sig_enum_val[msgid, sig, i] = val
delete a
getUniqueEnum(a, msgid SUBSEP sig, val, fetchStr())
obj_sig_enum_name[msgid, sig, i] = a["name"]
obj_sig_enum_desc[msgid, sig, i] = a["desc"]
obj_sig_enum_invalid[msgid, sig, i] = a["invalid"]
obj_sig_enum_duplicate[msgid, sig, i] = a["duplicate"]
++i
val = fetch(rINT "|;")
}
fetch(rLF)
}
##
# Parse an environment variable.
#
# Token: EV_
#
# Creates:
# - 1 ind_env[cnt_env++] = name
# - 1 obj_env[name] = val
# - 1 obj_env_type[name] = ("INT"|"FLOAT"|"DATA")
# - 1 obj_env_min[name] = (float)
# - 1 obj_env_max[name] = (float)
# - 1 obj_env_unit[name] = (string)
#
function fsm_env(dummy,
name, a) {
name = fetch(rSYM)
debug("obj_env[" name "]")
ind_env[cnt_env++] = name
fetch(":")
obj_env_type[name] = eTYPE[fetch(rID)]
split(fetch(rBND), a, /[][|]/)
obj_env_min[name] = a[2]
obj_env_max[name] = a[3]
obj_env_unit[name] = fetchStr()
if (obj_env_type[name] == etINT) {
obj_env[name] = int(fetch(rFLOAT))
} else if (obj_env_type[name] == etFLOAT) {
obj_env[name] = fetch(rFLOAT)
} else {
error(3, "environment variable type '" obj_env_type[name] "' not implemented!")
}
fetch(rID) # Just a counter of environment variables
fetch(rSYM) # DUMMY_NODE_VECTOR0
fetch(rSYM) # Vector__XXX TODO add support for ECUs
fetch(";")
}
##
# Parse the data length count of DATA type environment variables.
#
# Token: ENVVAR_DATA_
#
# Creates:
# - 1 obj_env_dlc[name] = (int)
#
function fsm_env_data(dummy,
name) {
name = fetch(rSYM)
debug("obj_env_dlc[" name "]")
fetch(":")
obj_env_dlc[name] = int(fetch(rDLC))
fetch(";")
}
##
# Parse a message definition.
#
# Token: BO_
#
# Creates:
# - 1 ind_msg[cnt_msg++] = id
# - 1 obj_msg_name[id] = name
# - 1 obj_msg_dlc[id] = dlc
# - 1 obj_msg_tx[id] = ecu
# - 1 obj_ecu_tx[ecu, i] = id
#
function fsm_msg(dummy,
id,
name,
dlc,
ecu,
i) {
id = fetch(rID) # Do not cast to int to preserve full value
debug("obj_msg[" id "]")
name = fetch(rSYM)
fetch(":")
dlc = int(fetch(rDLC))
ecu = fetch(rSYM)
fetch(rLF)
ind_msg[cnt_msg++] = id
obj_msg_name[id] = name
obj_msg_dlc[id] = dlc
if (ecu in obj_ecu) {
obj_msg_tx[id] = ecu
while (obj_ecu_tx[ecu, i++]);
obj_ecu_tx[ecu, --i] = id
}
while (fetch(rSYM) == t["SIG"]) {
fsm_sig(id)
}
}
##
# Parse a signal definition.
#
# Token: SG_
#
# Creates:
# - 1 ind_sig[cnt_sig++] = msgid, name
# - 1 obj_sig_name[msgid, name] = name
# - 1 obj_sig_msgid[msgid, name] = msgid
# - 1 obj_sig_multiplexor[msgid, name] = (bool)
# - 1 obj_sig_multiplexed[msgid, name] = (int)
# - 1 obj_sig_sbit[msgid, name] = (uint)
# - 1 obj_sig_len[msgid, name] = (uint)
# - 1 obj_sig_intel[msgid, name] = (bool)
# - 1 obj_sig_signed[msgid, name] = (bool)
# - 1 obj_sig_fac[msgid, name] = (float)
# - 1 obj_sig_off[msgid, name] = (float)
# - 1 obj_sig_min[msgid, name] = (float)
# - 1 obj_sig_max[msgid, name] = (float)
# - 1 obj_sig_unit[msgid, name] = (string)
# - * obj_sig_rx[msgid, name, i] = ecu
# - * obj_ecu_rx[ecu, p] = msgid, name
# - * obj_msg_sig[msgid, p] = msgid, name
#
function fsm_sig(msgid,
name,
multiplexing,
a,
ecu, count, i, p) {
name = fetch(rSYM)
debug("obj_sig[" msgid ", " name "]")
ind_sig[cnt_sig++] = msgid SUBSEP name
obj_sig_name[msgid, name] = name
obj_sig_msgid[msgid, name] = msgid
while (obj_msg_sig[msgid, p++]);
obj_msg_sig[msgid, --p] = msgid SUBSEP name
multiplexing = fetch("m[0-9]+|M")
obj_sig_multiplexor[msgid, name] = (multiplexing == "M")
gsub(/[mM]/, "", multiplexing)
obj_sig_multiplexed[msgid, name] = multiplexing
fetch(":")
split(fetch(rSIG), a, /[|@]/)
obj_sig_sbit[msgid, name] = a[1]
obj_sig_len[msgid, name] = a[2]
obj_sig_intel[msgid, name] = (a[3] ~ /^1/)
obj_sig_signed[msgid, name] = (a[3] ~ /-$/)
split(fetch(rVEC), a, /[(),]/)
obj_sig_fac[msgid, name] = a[2]
obj_sig_off[msgid, name] = a[3]
split(fetch(rBND), a, /[][|]/)
obj_sig_min[msgid, name] = a[2]
obj_sig_max[msgid, name] = a[3]
obj_sig_unit[msgid, name] = fetchStr()
count = split(fetch(rSYMS), a, /,/)
for (ecu = 1; ecu <= count; ++ecu) {
if (a[ecu] in obj_ecu) {
obj_sig_rx[msgid, name, i++] = a[ecu]
p = 0
while (obj_ecu_rx[a[ecu], p++]);
obj_ecu_rx[a[ecu], --p] = msgid SUBSEP name
}
}
fetch(rLF)
}
##
# Parse comments.
#
# Token: CM_
#
# Creates one of:
# - 1 obj_db_comment[FILENAME]
# - 1 obj_ecu_comment[name]
# - 1 obj_env_comment[name]
# - 1 obj_msg_comment[msgid]
# - 1 obj_sig_comment[msgid, name]
#
function fsm_comment(dummy,
context, name, msgid, str) {
context = getContext(fetch(rSYM))
if (context == "db") {
obj_db_comment[FILENAME] = fetchStr()
} else if (context == "env") {
name = fetch(rSYM)
obj_env_comment[name] = fetchStr()
} else if (context == "ecu") {
name = fetch(rSYM)
obj_ecu_comment[name] = fetchStr()
} else if (context == "msg") {
msgid = fetch(rID)
obj_msg_comment[msgid] = fetchStr()
} else if (context == "sig") {
msgid = fetch(rID)
name = fetch(rSYM)
obj_sig_comment[msgid, name] = fetchStr()
} else {
error(10, "comment for '" context "' not implemented!")
}
fetch(";")
fetch(rLF)
}
##
# Parse a custom attribute definition.
#
# Token: BA_DEF_
#
# Creates:
# - 1 ind_attr[cnt_attr++] = name
# - 1 obj_attr_context[name] = ("sig"|"msg"|"ecu"|"env"|"db")
# - 1 obj_attr_type[name] = ("INT"|"ENUM"|"STRING")
# - ? obj_attr_min[name] = (float)
# - ? obj_attr_max[name] = (float)
# - * obj_attr_enum[name, i] = (string)
# - ? obj_attr_str[name] = (string)
#
function fsm_attrrange(dummy,
context,
name,
type,
val,
i) {
context = getContext(fetch(rSYM))
name = fetchStr()
obj_attr_context[name] = context
ind_attr[cnt_attr++] = name
type = fetch(rSYM)
obj_attr_type[name] = type
if (type in atNUM) {
obj_attr_min[name] = fetch(rFLOAT)
obj_attr_max[name] = fetch(rFLOAT)
} else if (type == atENUM) {
val = fetchStr()
while (val != "") {
obj_attr_enum[name, ++i] = val
fetch(",")
val = fetchStr()
}
} else if (type == atSTR) {
obj_attr_str[name] = fetchStr()
}
fetch(";")
}
##
# Parse a custom relation attribute definition.
#
# Token: BA_DEF_REL_
#
# Creates:
# - 1 ind_attr[cnt_attr++] = name
# - 1 obj_attr_context[name] = "rel"
# - 1 obj_attr_from[name] = ("sig"|"msg"|"ecu"|"env"|"db")
# - 1 obj_attr_to[name] = ("sig"|"msg"|"ecu"|"env"|"db")
# - 1 obj_attr_type[name] = ("INT"|"ENUM"|"STRING")
# - ? obj_attr_min[name] = (float)
# - ? obj_attr_max[name] = (float)
# - * obj_attr_enum[name, i] = (string)
# - ? obj_attr_str[name] = (string)
#
function fsm_relattrrange(dummy,
context, relation,
name,
type,
val,
i) {
relation = fetch(rSYM)
name = fetchStr()
obj_attr_context[name] = "rel"
ind_attr[cnt_attr++] = name
sub(/REL_$/, "", relation)
context = relation
sub(/[A-Z]+_$/, "", context)
obj_attr_from[name] = getContext(context)
context = relation
sub(/^[A-Z]+_/, "", context)
obj_attr_to[name] = getContext(context)
type = fetch(rSYM)
obj_attr_type[name] = type
if (type in atNUM) {
obj_attr_min[name] = fetch(rFLOAT)
obj_attr_max[name] = fetch(rFLOAT)
} else if (type == atENUM) {
val = fetchStr()
while (val != "") {
obj_attr_enum[name, ++i] = val
fetch(",")
val = fetchStr()
}
} else if (type == atSTR) {
obj_attr_str[name] = fetchStr()
}
fetch(";")
}
##
# Parse attribute default value.
#
# Token: BA_DEF_DEF_
#
# Creates:
# - 1 obj_attr_default[name] = value
# - * obj_msg_attr[msgid, name]
# - * obj_sig_attr[msgid, signame, name]
# - * obj_db_attr[FILENAME, name]
#
function fsm_attrdefault(dummy,
name,
value,
i, ix) {
name = fetchStr()
if (obj_attr_type[name] == atSTR) {
obj_attr_default[name] = fetchStr()
} else if (obj_attr_type[name] == atENUM) {
value = fetchStr()
while (obj_attr_enum[name, ++i] != value);
obj_attr_default[name] = i
} else if (obj_attr_type[name] in atNUM) {
obj_attr_default[name] = fetch(rFLOAT)
} else {
error(4, "attribute type '" obj_attr_type[name] "' not implemented!")
}
debug("obj_attr_default[" name "] = " obj_attr_default[name])
fetch(";")
# Poplulate objects with defaults
if (obj_attr_context[name] == "msg") {
for (i = ind_msg[ix = 0]; ix < cnt_msg; i = ind_msg[++ix]) {
obj_msg_attr[i, name] = obj_attr_default[name]
}
} else if (obj_attr_context[name] == "sig") {
for (i = ind_sig[ix = 0]; ix < cnt_sig; i = ind_sig[++ix]) {
obj_sig_attr[i, name] = obj_attr_default[name]
}
} else if (obj_attr_context[name] == "db") {
for (i = ind_db[ix = 0]; ix < cnt_db; i = ind_db[++ix]) {
obj_db_attr[i, name] = obj_attr_default[name]
}
}
}
##
# Fetches an attribute value of a given type from the read buffer.
#
# @param attribute
# The attribute type identifier
# @return
# The value of the chosen type
#
function fetch_attrval(attribute,
val) {
if (obj_attr_type[attribute] == atSTR) {
return fetchStr()
} else if (obj_attr_type[attribute] == atENUM) {
val = fetch(rFLOAT)
if (val != int(val)) {
warn("An enum value is expected but the floating point value " \
val " was provided, converting to " int(val))
}
return int(val)
} else if (obj_attr_type[attribute] == atINT) {
val = fetch(rFLOAT)
if (val != int(val) && DEBUG) {
# This warning is so frequent, that it should
# not be printed unless in DEBUG mode.
warn("An integer value is expected but the floating point value " \
val " was provided, converting to " int(val))
}
return int(val)
}
return fetch(rFLOAT)
}
##
# Parse an attribute value.
#
# Token: BA_
#
# Creates one of:
# - 1 obj_sig_attr[msgid, signame, name] = value
# - 1 obj_msg_attr[msgid, name] = value
# - 1 obj_ecu_attr[ecu, name] = value
# - 1 obj_db_attr[FILENAME, name] = value
#
function fsm_attr(dummy,
name,
id, sig) {
name = fetchStr()
if (obj_attr_context[name] == "sig") {
fetch(t["SIG"])
id = fetch(rID) # Fetch message ID
sig = fetch(rSYM)
obj_sig_attr[id, sig, name] = fetch_attrval(name)
debug("obj_sig_attr[" id ", " sig ", " name "] = " obj_sig_attr[id, sig, name])
}
else if (obj_attr_context[name] == "msg") {
fetch(t["MSG"])
id = fetch(rID)
obj_msg_attr[id, name] = fetch_attrval(name)
debug("obj_msg_attr[" id "," name "] = " obj_msg_attr[id, name])
}
else if (obj_attr_context[name] == "ecu") {
fetch(t["ECU"])
id = fetch(rSYM)
obj_ecu_attr[id, name] = fetch_attrval(name)
}
else if (obj_attr_context[name] == "db") {
obj_db_attr[FILENAME, name] = fetch_attrval(name)
debug("obj_db_attr[" FILENAME "," name "] = " obj_db_attr[FILENAME, name])
}
else {
error(6, "attributes for " obj_attr_context[name] " not implemented!")
}
fetch(";")
}
##
# Parse a relation attribute value.
#
# Token: BA_REL_
#
# Creates:
# - 1 ind_rel_attr[cnt_rel_attr++] = name, from, to
# - 1 obj_rel_attr[name, from, to] = value
# - 1 obj_rel_attr_name[name, from, to] = name
# - 1 obj_rel_attr_from[name, from, to] = from
# - 1 obj_rel_attr_to[name, from, to] = to
#
# The types of to and from are recorded in:
# - obj_attr_from[name]
# - obj_attr_to[name]
#
function fsm_relattr(dummy,
name,
from, to) {
name = fetchStr()
fetch(rSYM) # Fetch the type of the relation, this is already known
if (obj_attr_from[name] == "sig") {
from = fetch(rID) # Fetch message ID
from = from SUBSEP fetch(rSYM)
}
else if (obj_attr_from[name] == "msg") {
from = fetch(rID)
}
else if (obj_attr_from[name] == "ecu") {
from = fetch(rSYM)
}
else if (obj_attr_from[name] == "db") {
from = FILENAME
}
else {
error(7, "relation attributes for " obj_attr_from[name] " not implemented!")
}
fetch(rSYM) # Fetch the type of the related object
if (obj_attr_to[name] == "sig") {
to = fetch(rID) # Fetch message ID
to = to SUBSEP fetch(rSYM)
}
else if (obj_attr_to[name] == "msg") {
to = fetch(rID)
}
else if (obj_attr_to[name] == "ecu") {
to = fetch(rSYM)
}
else if (obj_attr_to[name] == "db") {
to = FILENAME
}
else {
error(8, "relation attributes for " obj_attr_to[name] " not implemented!")
}
ind_rel_attr[cnt_rel_attr++] = name SUBSEP from SUBSEP to
obj_rel_attr[name, from, to] = fetch_attrval(name)
debug("obj_rel_attr[" name ", " from ", " to "] = " obj_rel_attr[name, from, to])
obj_rel_attr_name[name, from, to] = name
obj_rel_attr_from[name, from, to] = from
obj_rel_attr_to[name, from, to] = to
fetch(";")
}
##
# Parse the symbol table at the beginning of a .dbc file, bail if
# unsupported symbols are encountered.
#
# Token: NS_
#
function fsm_symbols(dummy,
sym, i) {
fetch(":")
fetch(rLF)
while (sym = fetch(rSYM)) {
fetch(rLF)
for (i in t) {
if (t[i] == sym) {
break
}
}
if (t[i] != sym) {
error(5, "unknown symbol " sym " encountered")
}
}
}
##
# Gets a list of ECUs that transmit a certain message.
#
# This may appear when several device options are available.
#
# Token: BO_TX_BU_
#
# Creates:
# - 1 obj_ecu_tx[ecu, i] = msgid
#
function fsm_tx(dummy,
msgid, ecus, i, p, present) {
msgid = fetch(rID)
fetch(":")
split(fetch(rSYMS), ecus, /,/)
for (i in ecus) {
p = 0
present = 0
while (obj_ecu_tx[ecus[i], p]) {
if (obj_ecu_tx[ecus[i], p++] == msgid) {
present = 1
break
}
}
if (!present) {
obj_ecu_tx[ecus[i], p++] = msgid
}
}
fetch(";")
}
##
# Gets a signal group.
#
# Token: SIG_GROUP_
#
# Creates:
# - 1 ind_siggrp[cnt_siggrp++] = msgid, name
# - 1 obj_siggrp[msgid, name] = name
# - 1 obj_siggrp_msg[msgid, name] = msgid
# - * obj_siggrp_sig[msgid, name, i] = sig
# - * obj_sig_grp[msgid, sig, p] = msgid, name
# - * obj_msg_grp[msgid, p] = msgid, name
#
function fsm_siggrp(dummy,
i, msgid, name, sig, p) {
msgid = fetch(rID)
name = fetch(rSYM)
ind_siggrp[cnt_siggrp++] = msgid SUBSEP name
obj_siggrp[msgid, name] = name
debug("obj_siggrp[" msgid ", " name "] = " obj_siggrp[msgid, name])
i = fetch(rID) # Seems to always be 1
if (i != 1) {
warn("Signal group " msgid ", " name " has unknown properties")
}
obj_siggrp_msg[msgid, name] = msgid
fetch(":")
i = 0
while (!fetch(";")) {
sig = fetch(rSYM)
obj_siggrp_sig[msgid, name, i++] = msgid SUBSEP sig
p = 0
while (obj_sig_grp[msgid, sig, p++]);
obj_sig_grp[msgid, sig, --p] = msgid SUBSEP name
}
p = 0
while (obj_msg_grp[msgid, p++]);
obj_msg_grp[msgid, --p] = msgid SUBSEP name
}
##
# Pick tokens from the input buffer and call the respective parsing
# functions.
#
# Creates:
# - 1 ind_db[cnt_db++] = FILENAME
# - 1 obj_db[FILENAME]
#
function fsm_start(dummy,
sym) {
sym = fetch(rSYM "|" rLF)
if (!(FILENAME in obj_db)) {
ind_db[cnt_db++] = FILENAME
obj_db[FILENAME]
}
# Check whether symbol is known but not supported
if (t[sym]) {
error(9, "unsupported symbol " sym " encountered")
}
# Check known symbols
if (sym == t["SYMBOLS"]) {
fsm_symbols()
}
# Discard version
else if (sym == t["VER"]) {
fetchStr()
}
# Discard not required symbols
else if (sym in tDISCARD) {
fsm_discard()
}
# Environment variables
else if (sym == t["ENV"]) {
fsm_env()
}
# Get ECUs
else if (sym == t["ECU"]) {
fsm_ecu()
}
# Get signal enums
else if (sym == t["SIG_ENUM"]) {
fsm_sig_enum()
}
# Get enums
else if (sym == t["ENUM"]) {
fsm_enum()
}
# Get message objects
else if (sym == t["MSG"]) {
fsm_msg()
}
# Get comments
else if (sym == t["COM"]) {
fsm_comment()
}
# Get attribute ranges
else if (sym == t["ATTRRANGE"]) {
fsm_attrrange()
}
# Get relation attribute ranges
else if (sym == t["RELATTRRANGE"]) {
fsm_relattrrange()
}
# Get attribute defaults
else if (sym == t["ATTRDEFAULT"]) {
fsm_attrdefault()
}
# Get relation attribute defaults
else if (sym == t["RELATTRDEFAULT"]) {
fsm_attrdefault()
}
# Get attributes
else if (sym == t["ATTR"]) {
fsm_attr()
}
# Get relational attributes
else if (sym == t["RELATTR"]) {
fsm_relattr()
}
# Get the DLC of environment variables of type DATA
else if (sym == t["EDLC"]) {
fsm_env_data()
}
# Get a list of TX ECUs for a message
else if (sym == t["TX"]) {
fsm_tx()
}
# Get a signal group
else if (sym == t["SIG_GRP"]) {
fsm_siggrp()
}
}
##
# This starts the line wise parsing of the DBC file.
#
{
while ($0) {
fsm_start()
}
}
#
# End of the parser section, the following code deals with output.
#
##
# Returns the greatest common divider (GCD).
#
# @param a
# An integer
# @param b
# An integer
# @return
# The greatest common divider of a and b
#
function euclid(a, b,
tmp) {
while (b != 0) {
tmp = a
a = b
b = tmp % b
}
return a
}
##
# Returns a compact string representation of a rational number.
#
# @param n
# The numerator
# @param d
# The denominator
# @return
# The given rational number as a string
#
function rationalFmt(n, d) {
# Zero always wins
if (n == 0) {
return "* 0"
}
# The given rational is an integer
if (d == 1) {
if (n > 0) {
return sprintf("* %d", n)
}
return sprintf("* (%d)", n)
}
# There only is a division factor
if (n == 1) {
if (d > 0) {
return sprintf("/ %d", d)
}
return sprintf("/ (%d)", d)
}
# Both values are positive
if (n > 0 && d > 0) {
return sprintf("* %d / %d", n, d)
}
# One is always supposed to be positive
if (n > 0) {
return sprintf("* %d / (%d)", n, d)
}
if (d > 0) {
return sprintf("* (%d) / %d", n, d)
}
return sprintf("* (%d) / (%d)", n, d)
}
##
# Returns a rational string representation of a real value.
#
# This function builds the value around the numerator.
#
# @param val
# The real value to return as a rational
# @param base
# The logical number base to generate the rational from
# @param precision
# The maximum number of bits for either rational component
# @return
# A rational string representation of the given value
#
function rationalN(val, base, precision,
div, gcd) {
div = 1
while((val % 1) != 0 && div < 2^precision) {
div *= base
val *= base
}
gcd = euclid(int(val), int(div))
val /= gcd
div /= gcd
while ((val > 0 ? val : -val) >= 2^precision || (div > 0 ? div : -div) >= 2^precision) {
val /= base
div /= base
}
return rationalFmt(int(val), int(div))
}
##
# Returns a rational string representation of a real value.
#
# This function builds the value around the denominator.
#
# @param val
# The real value to return as a rational
# @param base
# The logical number base to generate the rational from
# @param precision
# The maximum number of bits for either rational component
# @return
# A rational string representation of the given value
#
function rationalD(val, base, precision,
div, gcd) {
if (val == 0) {
return rationalFmt(0)
}
div = 1 / val
val = 1
while((div % 1) != 0 && val < 2^precision) {
div *= base
val *= base
}
gcd = euclid(int(val), int(div))
val /= gcd
div /= gcd
while ((val > 0 ? val : -val) >= 2^precision || (div > 0 ? div : -div) >= 2^precision) {
val /= base
div /= base
}
return rationalFmt(int(val), int(div))
}
##
# Returns a rational string representation of a real value.
#
# This uses the different rational*() functions to find a minimal
# representation of the value.
#
# @param val
# The real value to return as a rational
# @param precision
# The maximum number of bits for either rational component
# @return
# A rational string representation of the given value
#
function rational(val, precision,
a, b) {
a = rationalN(val, 2, precision)
b = rationalD(val, 2, precision)
a = (length(b) < length(a) ? b : a)
b = rationalN(val, 10, precision)
a = (length(b) < length(a) ? b : a)
b = rationalD(val, 10, precision)
a = (length(b) < length(a) ? b : a)
return a
}
##
# Applies filter chains to a given string.
#
# Filters are a colon separated lists of the following filter commands:
# | Command | Effect
# |---------|----------------------------------------
# | low | Convert to lower case
# | up | Convert to upper case
# | camel | Convert to camel case
# | uncamel | Convert camel case to _ separated
# | %... | A printf(3) style format specification
#
# @param str
# The string to apply the filters to
# @param filters
# The list of filters
# @param template
# The name of the current template
# @return
# The converted string
#
function filter(str, filters, template,
cmds, count, i) {
count = split(filters, cmds, /:/)
for (i = 1; i <= count; ++i) {
if (cmds[i] == "low") {
str = tolower(str)
continue
}
if (cmds[i] == "up") {
str = toupper(str)
continue
}
if (cmds[i] == "camel") {
str = tolower(str)
while (match(str, /_./)) {
str = substr(str, 1, RSTART - 1) \
toupper(substr(str, RSTART + 1, 1)) \
substr(str, RSTART + 2)
}
continue
}
if (cmds[i] == "uncamel") {
while (match(str, /[A-Z]+/)) {
str = substr(str, 1, RSTART - 1) "_" \
tolower(substr(str, RSTART, RLENGTH)) \
substr(str, RSTART + RLENGTH)
}
continue
}
if (cmds[i] ~ /^%[-#+ 0]*[0-9]*\.?[0-9]*[diouxXfFeEgGaAcsb]$/) {
str = sprintf(cmds[i], str)
continue
}
if (!UNKNOWN[template, cmds[i]]++) {
warn(template ": Unknown filter '" cmds[i] "' ignored")
}
}
return str
}
##
# Populates a template line with data.
#
# Multiline data in a template needs to be in its own line.
#
# Lines with empty data fields are removed.
#
# Identifiers in templates have the following shape:
# "<:" name ":>"
#
# Additionally boolean filters can be installed:
# "" name "?>"
#
# If the variable addressed in the filter evaluetes to true, the filter
# is removed, otherwise the entire line is removed.
#
# @param data
# The array containing field data
# @param line
# The line to perform substitutions in
# @param template
# The name of the template this line comes from, this is used to warn
# about deprecated arguments
# @return
# The line(s) with performed substitutions
#
function tpl_line(data, line, template,
name, filters, pre, post, count, array, i) {
# Filter lines with boolean checks
while(match(line, /<\?[a-zA-Z0-9]+\?>/)) {
name = substr(line, RSTART + 2, RLENGTH - 4)
if (!data[name]) {
return ""
}
line = substr(line, 1, RSTART - 1) substr(line, RSTART + RLENGTH)
}
# Insert data
while(match(line, /<:[a-zA-Z0-9]+(:[^>:][^:]*)*:>/)) {
filters = name = substr(line, RSTART + 2, RLENGTH - 4)
sub(/:.*/, "", name)
sub(/^[^:]*:?/, "", filters)
pre = substr(line, 1, RSTART - 1)
post = substr(line, RSTART + RLENGTH)
# Warn when using deprecated symbols
if (data["#" name] && !DEPRECATED[template, name]++) {
warn(template ": <:" name ":> is deprecated in favour of <:" data["#" name] ":>")
}
# Output the template line once for each data value
count = split(data[name], array, RS)
while (count && array[count] ~ /^[ \t\r\n]*$/ && --count);
if (!count) {
return ""
}
line = pre filter(array[1], filters, template) post
for (i = 2; i <= count; i++) {
line = line ORS pre filter(array[i], filters, template) post
}
}
return line ORS
}
##
# Reads a template, substitutes place holders with data from a given
# array and returns it.
#
# @param data
# The array to take data from
# @param name
# The name of the template file
# @return
# The filled up template
#
function template(data, name,
buf, line) {
# Read template
name = TEMPLATES name
# Warn about missing templates, but do not terminate
if ((getline line < name) < 0) {
if (!MISSING[name]++) {
warn("Template " name " is missing")
}
return
}
buf = tpl_line(data, line, name)
while (getline line < name) {
buf = buf tpl_line(data, line, name)
}
close(name)
return buf
}
##
# Set the necessary type to be able to shift something to the given bit.
#
# Creates the entries int8, int16 and int32 in the given arrays, with the
# fitting type set to the value 1 and the others to 0.
#
# @param array
# The array put the entries into
# @param bitpos
# The bit that needs to be addressable
#
function setTypes(array, bitpos) {
array["int32"] = (bitpos >= 16 ? 1 : 0)
array["int16"] = (!array["int32"] && bitpos >= 8 ? 1 : 0)
array["int8"] = (bitpos < 8 ? 1 : 0)
}
##
# Returns a unique signal identifier using the sigident.tpl file.
#
# Returns an empty string if the template is missing.
#
# @param sig
# The signal reference consisting of message and signal name
# @return
# A unique signal identifier
#
function sigident(sig,
tpl) {
tpl["sig"] = obj_sig_name[sig]
tpl["msgid"] = sprintf("%x", obj_sig_msgid[sig])
tpl["#msgid"] = "msg"
tpl["msg"] = obj_sig_msgid[sig]
tpl["msgname"] = obj_msg_name[obj_sig_msgid[sig]]
sig = template(tpl, "sigid.tpl")
sub(ORS "$", "", sig)
return sig
}
##
# Returns a unique signal group identifier using the sigident.tpl file.
#
# Returns an empty string if the template is missing.
#
# @param sg
# The signal group reference consisting of message and signal name
# @return
# A unique signal identifier
#
function siggrpident(sg,
tpl) {
tpl["sig"] = obj_siggrp[sg]
tpl["msgid"] = sprintf("%x", obj_siggrp_msg[sg])
tpl["#msgid"] = "msg"
tpl["msg"] = obj_siggrp_msg[sg]
tpl["msgname"] = obj_msg_name[obj_siggrp_msg[sg]]
sg = template(tpl, "sigid.tpl")
sub(ORS "$", "", sg)
return sg
}
##
# Generates a printable message id be removing the extended bit.
#
# @param id
# The message id to return
# @return
# The message id without the extended bit
#
function msgid(id) {
return sprintf("%d", (msgidext(id) ? id - 2^31 : id))
}
##
# Tests a message id for the extended bit.
#
# @param id
# The message id to check
# @retval 1
# The message is extended
# @retval 0
# The message is not extended
#
function msgidext(id) {
return int(id) >= 2^31
}
##
# Print the DBC files to stdout.
#
END {
# Do not produce output on error
if (errno) {
exit errno
}
# Headers
tpl["date"] = DATE
dbs =""
for (db = ind_db[ix = 0]; ix < cnt_db; db = ind_db[++ix]) {
sub(/.*\//, "", db)
sub(/\.[^\.]*$/, "", db)
dbs = dbs db RS
}
tpl["db"] = dbs
printf("%s", template(tpl, "header.tpl"))
# Introduce the DB files
for (file = ind_db[ix = 0]; ix < cnt_db; file = ind_db[++ix]) {
delete tpl
tpl["db"] = file
sub(/.*\//, "", tpl["db"])
sub(/\.[^\.]*$/, "", tpl["db"])
tpl["file"] = file
tpl["comment"] = obj_db_comment[file]
ecus = ""
p = 0
while (obj_db_ecu[file, p]) {
ecus = ecus obj_db_ecu[file, p++] RS
}
tpl["ecu"] = ecus
printf("%s", template(tpl, "file.tpl"))
}
# Introduce the ECUs
for (ecu = ind_ecu[ix = 0]; ix < cnt_ecu; ecu = ind_ecu[++ix]) {
delete tpl
tpl["ecu"] = ecu
tpl["comment"] = obj_ecu_comment[ecu]
# DB
tpl["db"] = obj_ecu_db[ecu]
sub(/.*\//, "", tpl["db"])
sub(/\.[^\.]*$/, "", tpl["db"])
# TX messages
p = 0
tx = txid = txname = ""
while (obj_ecu_tx[ecu, p]) {
txname = txname obj_msg_name[obj_ecu_tx[ecu, p]] RS
txid = txid msgid(obj_ecu_tx[ecu, p]) RS
tx = tx sprintf("%x", msgid(obj_ecu_tx[ecu, p++])) RS
}
tpl["txname"] = txname
tpl["txid"] = txid
tpl["tx"] = tx
tpl["#tx"] = "txid"
# RX signals
p = 0
rxid = rx = ""
while (obj_ecu_rx[ecu, p]) {
rxid = rxid sigident(obj_ecu_rx[ecu, p]) RS
rx = rx obj_sig_name[obj_ecu_rx[ecu, p++]] RS
}
tpl["rxid"] = rxid
tpl["rx"] = rx
# Load template
printf("%s", template(tpl, "ecu.tpl"))
}
# Introduce the Messages
for (msg = ind_msg[ix = 0]; ix < cnt_msg; msg = ind_msg[++ix]) {
delete tpl
tpl["id"] = sprintf("%x", msgid(msg))
tpl["msg"] = msgid(msg)
tpl["#id"] = "msg"
tpl["name"] = obj_msg_name[msg]
tpl["comment"] = obj_msg_comment[msg]
tpl["ecu"] = obj_msg_tx[msg]
tpl["ext"] = msgidext(msg)
tpl["dlc"] = obj_msg_dlc[msg]
tpl["cycle"] = 0 + obj_msg_attr[msg, aCYCLE]
tpl["fast"] = 0 + obj_msg_attr[msg, aFCYCLE]
tpl["delay"] = 0 + obj_msg_attr[msg, aDELAY]
tpl["send"] = obj_msg_attr[msg, aSEND]
# Get signal list
i = 0
sigids = sigs = ""
while (obj_msg_sig[msg, i]) {
sigids = sigids sigident(obj_msg_sig[msg, i]) RS
sigs = sigs obj_sig_name[obj_msg_sig[msg, i++]] RS
}
tpl["sigid"] = sigids
tpl["sig"] = sigs
# Get signal group list
i = 0
sgids = sgnames = ""
while (obj_msg_grp[msg, i]) {
sgids = sgids siggrpident(obj_msg_grp[msg, i]) RS
sgnames = sgnames obj_siggrp[obj_msg_grp[msg, i++]] RS
}
tpl["sgid"] = sgids
tpl["sgname"] = sgnames
# Load template
printf("%s", template(tpl, "msg.tpl"))
}
# Introduce signal groups
for (id = ind_siggrp[ix = 0]; ix < cnt_siggrp; id = ind_siggrp[++ix]) {
delete tpl
tpl["id"] = siggrpident(id)
tpl["name"] = obj_siggrp[id]
tpl["msgid"] = sprintf("%x", msgid(obj_siggrp_msg[id]))
tpl["msg"] = msgid(obj_siggrp_msg[id])
tpl["#msgid"] = "msg"
tpl["msgname"] = obj_msg_name[obj_siggrp_msg[id]]
sub(/.*\//, "", tpl["db"])
sub(/\.[^\.]*$/, "", tpl["db"])
# Signals
sigids = sigs = ""
p = 0
while (obj_siggrp_sig[id, p]) {
sigids = sigids sigident(obj_siggrp_sig[id, p]) RS
sigs = sigs obj_sig_name[obj_siggrp_sig[id, p++]] RS
}
tpl["sigid"] = sigids
tpl["sig"] = sigs
# Load template
printf("%s", template(tpl, "siggrp.tpl"))
}
# Types of integer bits
delete inttypes
inttypes[8]
inttypes[16]
inttypes[32]
# Introduce the Signals
for (sig = ind_sig[ix = 0]; ix < cnt_sig; sig = ind_sig[++ix]) {
delete tpl
tpl["name"] = obj_sig_name[sig]
tpl["id"] = sigident(sig)
tpl["comment"] = obj_sig_comment[sig]
# Reference the message this signal belongs to
tpl["msgid"] = sprintf("%x", msgid(obj_sig_msgid[sig]))
tpl["msg"] = msgid(obj_sig_msgid[sig])
tpl["#msgid"] = "msg"
tpl["msgname"] = obj_msg_name[obj_sig_msgid[sig]]
# Reference the signal groups this message belongs to
i = 0
grpids = grps = ""
while (obj_sig_grp[sig, i]) {
grpids = grpids siggrpident(obj_sig_grp[sig, i]) RS
grps = grps obj_siggrp[obj_sig_grp[sig, i++]] RS
}
tpl["sgid"] = grpids
tpl["sgname"] = grps
# Reference the RX ECUs
i = 0
ecus = ""
while (obj_sig_rx[sig, i]) {
ecus = ecus obj_sig_rx[sig, i++] RS
}
tpl["ecu"] = ecus
# Has value table?
tpl["enum"] = (sig in obj_sig_enum)
# Tuple
tpl["intel"] = obj_sig_intel[sig]
tpl["motorola"] = !obj_sig_intel[sig]
tpl["signed"] = obj_sig_signed[sig]
tpl["sbit"] = obj_sig_sbit[sig]
tpl["len"] = obj_sig_len[sig]
# Setup/Start
tpl["start"] = 0 + obj_sig_attr[sig, aSTART]
# Calc
fac = rational(obj_sig_fac[sig], 16)
off = rational(obj_sig_off[sig], 16)
tpl["calc16"] = "(x) * fmt"
if (fac != "* 1") {
tpl["calc16"] = tpl["calc16"] " " fac
}
if (off != "* 0") {
tpl["calc16"] = tpl["calc16"] " + fmt " off
}
# Min, max, offset
tpl["min"] = sprintf("%.0f", (obj_sig_min[sig] - obj_sig_off[sig]) / obj_sig_fac[sig])
tpl["max"] = sprintf("%.0f", (obj_sig_max[sig] - obj_sig_off[sig]) / obj_sig_fac[sig])
tpl["off"] = sprintf("%.0f", obj_sig_off[sig] / obj_sig_fac[sig])
# Getter and setter
bits = obj_sig_len[sig]
bpos = obj_sig_sbit[sig]
if (obj_sig_intel[sig]) {
# Intel style signals
pos = 0
tpl["getbuf"] = ""
tpl["setbuf"] = ""
if (obj_sig_signed[sig] && !(bits in inttypes)) {
delete sbits
sbits["sign"] = "-"
sbits["byte"] = int((bpos + bits - 1) / 8)
sbits["align"] = (bpos + bits - 1) % 8
sbits["mask"] = "0x01"
sbits["msk"] = 1
sbits["#mask"] = "msk"
sbits["pos"] = bits
setTypes(sbits, sbits["pos"])
tpl["getbuf"] = template(sbits, "sig_getbuf.tpl")
}
while (bits > 0) {
delete sbits
sbits["sign"] = "+"
sbits["byte"] = int(bpos / 8)
sbits["align"] = bpos % 8
shift = 8 - sbits["align"]
if (shift > bits) {
shift = bits
}
sbits["mask"] = sprintf("%#04x", 2^shift - 1)
sbits["msk"] = 2^shift - 1
sbits["#mask"] = "msk"
sbits["pos"] = pos
setTypes(sbits, sbits["pos"] + shift - 1)
tpl["getbuf"] = tpl["getbuf"] \
template(sbits, "sig_getbuf.tpl")
tpl["setbuf"] = tpl["setbuf"] \
template(sbits, "sig_setbuf.tpl")
pos += shift
bpos += shift
bits -= shift
}
} else {
# Motorola signals
tpl["getbuf"] = ""
tpl["setbuf"] = ""
if (obj_sig_signed[sig] && !(bits in inttypes)) {
delete sbits
sbits["sign"] = "-"
sbits["byte"] = int(bpos / 8)
sbits["align"] = bpos % 8
sbits["mask"] = "0x01"
sbits["msk"] = 1
sbits["#mask"] = "msk"
sbits["pos"] = bits
setTypes(sbits, sbits["pos"])
tpl["getbuf"] = template(sbits, "sig_getbuf.tpl")
}
while (bits > 0) {
delete sbits
sbits["sign"] = "+"
sbits["byte"] = int(bpos / 8)
slice = bpos % 8 + 1
if (slice > bits) {
slice = bits
}
sbits["align"] = bpos % 8 + 1 - slice
sbits["mask"] = sprintf("%#04x", 2^slice - 1)
sbits["msk"] = 2^slice - 1
sbits["#mask"] = "msk"
sbits["pos"] = bits - slice
setTypes(sbits, sbits["pos"] + slice - 1)
tpl["getbuf"] = tpl["getbuf"] \
template(sbits, "sig_getbuf.tpl")
tpl["setbuf"] = tpl["setbuf"] \
template(sbits, "sig_setbuf.tpl")
bpos = (sbits["byte"] + 1) * 8 + 7
bits -= slice
}
}
# Load template
printf("%s", template(tpl, "sig.tpl"))
# Check for value table
if (!(sig in obj_sig_enum)) {
continue
}
# Load template
printf("%s", template(tpl, "sig_enum.tpl"))
# Print values
i = 0
while ((sig SUBSEP i) in obj_sig_enum_val) {
tpl["enumval"] = obj_sig_enum_val[sig, i]
tpl["enumname"] = obj_sig_enum_name[sig, i]
tpl["comment"] = obj_sig_enum_desc[sig, i++]
# Load template
printf("%s", template(tpl, "sig_enumval.tpl"))
}
}
# Introduce timeouts
for (rel = ind_rel_attr[ix = 0]; ix < cnt_rel_attr; rel = ind_rel_attr[++ix]) {
if (obj_rel_attr_name[rel] != aTIMEOUT) {
# Not a timeout
continue
}
if (obj_attr_from[aTIMEOUT] != "ecu") {
warn(aTIMEOUT " attribute discarded, because it is not a \"Node - Mapped RX Signal\" (ECU to signal) relation attribute")
continue
}
if (obj_attr_to[aTIMEOUT] != "sig") {
warn(aTIMEOUT " attribute discarded, because it is not a \"Node - Mapped RX Signal\" (ECU to signal) relation attribute")
continue
}
delete tpl
tpl["ecu"] = obj_rel_attr_from[rel]
tpl["sig"] = obj_sig_name[obj_rel_attr_to[rel]]
tpl["sigid"] = sigident(obj_rel_attr_to[rel])
tpl["timeout"] = obj_rel_attr[rel]
tpl["value"] = tpl["timeout"] # For compatibility with
tpl["#value"] = "timeout" # older 3rd party templates
tpl["msgid"] = sprintf("%x", msgid(obj_sig_msgid[obj_rel_attr_to[rel]]))
tpl["msg"] = msgid(obj_sig_msgid[obj_rel_attr_to[rel]])
tpl["#msgid"] = "msg"
tpl["msgname"] = obj_msg_name[obj_sig_msgid[obj_rel_attr_to[rel]]]
# Load template
printf("%s", template(tpl, "timeout.tpl"))
}
# List enum
for (enum = ind_enum[ix = 0]; ix < cnt_enum; enum = ind_enum[++ix]) {
delete tpl
tpl["enum"] = enum
tpl["db"] = obj_enum_db[enum]
sub(/.*\//, "", tpl["db"])
sub(/\.[^\.]*$/, "", tpl["db"])
# Load template
printf("%s", template(tpl, "enum.tpl"))
# Print values
i = 0
while ((enum SUBSEP i) in obj_enum_val) {
tpl["val"] = obj_enum_val[enum, i]
tpl["name"] = obj_enum_name[enum, i]
tpl["comment"] = obj_enum_desc[enum, i++]
# Load template
printf("%s", template(tpl, "enumval.tpl"))
}
}
}