mirror of https://github.com/llvm/circt.git
200 lines
7.2 KiB
C++
200 lines
7.2 KiB
C++
//===- FormatStrings.cpp - Verilog format string conversion ---------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "ImportVerilogInternals.h"
|
|
#include "slang/text/SFormat.h"
|
|
|
|
using namespace mlir;
|
|
using namespace circt;
|
|
using namespace ImportVerilog;
|
|
using moore::IntAlign;
|
|
using moore::IntFormat;
|
|
using moore::IntPadding;
|
|
using slang::SFormat::FormatOptions;
|
|
|
|
namespace {
|
|
struct FormatStringParser {
|
|
Context &context;
|
|
OpBuilder &builder;
|
|
/// The remaining arguments to be parsed.
|
|
ArrayRef<const slang::ast::Expression *> arguments;
|
|
/// The current location to use for ops and diagnostics.
|
|
Location loc;
|
|
/// The default format for integer arguments not covered by a format string
|
|
/// literal.
|
|
IntFormat defaultFormat;
|
|
/// The interpolated string fragments that will be concatenated using a
|
|
/// `moore.fmt.concat` op.
|
|
SmallVector<Value> fragments;
|
|
|
|
FormatStringParser(Context &context,
|
|
ArrayRef<const slang::ast::Expression *> arguments,
|
|
Location loc, IntFormat defaultFormat)
|
|
: context(context), builder(context.builder), arguments(arguments),
|
|
loc(loc), defaultFormat(defaultFormat) {}
|
|
|
|
/// Entry point to the format string parser.
|
|
FailureOr<Value> parse(bool appendNewline) {
|
|
while (!arguments.empty()) {
|
|
const auto &arg = *arguments[0];
|
|
arguments = arguments.drop_front();
|
|
if (arg.kind == slang::ast::ExpressionKind::EmptyArgument)
|
|
continue;
|
|
loc = context.convertLocation(arg.sourceRange);
|
|
if (auto *lit = arg.as_if<slang::ast::StringLiteral>()) {
|
|
if (failed(parseFormat(lit->getValue())))
|
|
return failure();
|
|
} else {
|
|
if (failed(emitDefault(arg)))
|
|
return failure();
|
|
}
|
|
}
|
|
|
|
// Append the optional newline.
|
|
if (appendNewline)
|
|
emitLiteral("\n");
|
|
|
|
// Concatenate all string fragments into one formatted string, or return an
|
|
// empty literal if no fragments were generated.
|
|
if (fragments.empty())
|
|
return Value{};
|
|
if (fragments.size() == 1)
|
|
return fragments[0];
|
|
return moore::FormatConcatOp::create(builder, loc, fragments).getResult();
|
|
}
|
|
|
|
/// Parse a format string literal and consume and format the arguments
|
|
/// corresponding to the format specifiers it contains.
|
|
LogicalResult parseFormat(StringRef format) {
|
|
bool anyFailure = false;
|
|
auto onText = [&](auto text) {
|
|
if (anyFailure)
|
|
return;
|
|
emitLiteral(text);
|
|
};
|
|
auto onArg = [&](auto specifier, auto offset, auto len,
|
|
const auto &options) {
|
|
if (anyFailure)
|
|
return;
|
|
if (failed(emitArgument(specifier, format.substr(offset, len), options)))
|
|
anyFailure = true;
|
|
};
|
|
auto onError = [&](auto, auto, auto, auto) {
|
|
assert(false && "Slang should have already reported all errors");
|
|
};
|
|
slang::SFormat::parse(format, onText, onArg, onError);
|
|
return failure(anyFailure);
|
|
}
|
|
|
|
/// Emit a string literal that requires no additional formatting.
|
|
void emitLiteral(StringRef literal) {
|
|
fragments.push_back(moore::FormatLiteralOp::create(builder, loc, literal));
|
|
}
|
|
|
|
/// Consume the next argument from the list and emit it according to the given
|
|
/// format specifier.
|
|
LogicalResult emitArgument(char specifier, StringRef fullSpecifier,
|
|
const FormatOptions &options) {
|
|
auto specifierLower = std::tolower(specifier);
|
|
|
|
// Special handling for format specifiers that consume no argument.
|
|
if (specifierLower == 'm' || specifierLower == 'l')
|
|
return mlir::emitError(loc)
|
|
<< "unsupported format specifier `" << fullSpecifier << "`";
|
|
|
|
// Consume the next argument, which will provide the value to be
|
|
// formatted.
|
|
assert(!arguments.empty() && "Slang guarantees correct arg count");
|
|
const auto &arg = *arguments[0];
|
|
arguments = arguments.drop_front();
|
|
auto argLoc = context.convertLocation(arg.sourceRange);
|
|
|
|
// Handle the different formatting options.
|
|
// See IEEE 1800-2017 § 21.2.1.2 "Format specifications".
|
|
switch (specifierLower) {
|
|
case 'b':
|
|
return emitInteger(arg, options, IntFormat::Binary);
|
|
case 'o':
|
|
return emitInteger(arg, options, IntFormat::Octal);
|
|
case 'd':
|
|
return emitInteger(arg, options, IntFormat::Decimal);
|
|
case 'h':
|
|
case 'x':
|
|
return emitInteger(arg, options,
|
|
std::isupper(specifier) ? IntFormat::HexUpper
|
|
: IntFormat::HexLower);
|
|
|
|
case 's':
|
|
// Simplified handling for literals.
|
|
if (auto *lit = arg.as_if<slang::ast::StringLiteral>()) {
|
|
if (options.width)
|
|
return mlir::emitError(loc)
|
|
<< "string format specifier with width not supported";
|
|
emitLiteral(lit->getValue());
|
|
return success();
|
|
}
|
|
return mlir::emitError(argLoc)
|
|
<< "expression cannot be formatted as string";
|
|
|
|
default:
|
|
return mlir::emitError(loc)
|
|
<< "unsupported format specifier `" << fullSpecifier << "`";
|
|
}
|
|
}
|
|
|
|
/// Emit an integer value with the given format.
|
|
LogicalResult emitInteger(const slang::ast::Expression &arg,
|
|
const FormatOptions &options, IntFormat format) {
|
|
auto value =
|
|
context.convertToSimpleBitVector(context.convertRvalueExpression(arg));
|
|
if (!value)
|
|
return failure();
|
|
|
|
// Determine the width to which the formatted integer should be padded.
|
|
unsigned width;
|
|
if (options.width) {
|
|
width = *options.width;
|
|
} else {
|
|
width = cast<moore::IntType>(value.getType()).getWidth();
|
|
if (format == IntFormat::Octal)
|
|
// 3 bits per octal digit
|
|
width = (width + 2) / 3;
|
|
else if (format == IntFormat::HexLower || format == IntFormat::HexUpper)
|
|
// 4 bits per hex digit
|
|
width = (width + 3) / 4;
|
|
else if (format == IntFormat::Decimal)
|
|
// ca. 3.322 bits per decimal digit (ln(10)/ln(2))
|
|
width = std::ceil(width * std::log(2) / std::log(10));
|
|
}
|
|
|
|
// Determine the alignment and padding.
|
|
auto alignment = options.leftJustify ? IntAlign::Left : IntAlign::Right;
|
|
auto padding =
|
|
format == IntFormat::Decimal ? IntPadding::Space : IntPadding::Zero;
|
|
|
|
fragments.push_back(moore::FormatIntOp::create(builder, loc, value, format,
|
|
width, alignment, padding));
|
|
return success();
|
|
}
|
|
|
|
/// Emit an expression argument with the appropriate default formatting.
|
|
LogicalResult emitDefault(const slang::ast::Expression &expr) {
|
|
FormatOptions options;
|
|
return emitInteger(expr, options, defaultFormat);
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
FailureOr<Value> Context::convertFormatString(
|
|
slang::span<const slang::ast::Expression *const> arguments, Location loc,
|
|
IntFormat defaultFormat, bool appendNewline) {
|
|
FormatStringParser parser(*this, ArrayRef(arguments.data(), arguments.size()),
|
|
loc, defaultFormat);
|
|
return parser.parse(appendNewline);
|
|
}
|