Skip to content

Commit

Permalink
factor out SExpr/SExprWriter classes out of smtlib backend, and also …
Browse files Browse the repository at this point in the history
…tidy them up/document them
  • Loading branch information
aiju committed Jul 11, 2024
1 parent e786722 commit ccb95b3
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 161 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@ $(eval $(call add_include_file,kernel/register.h))
$(eval $(call add_include_file,kernel/rtlil.h))
$(eval $(call add_include_file,kernel/satgen.h))
$(eval $(call add_include_file,kernel/scopeinfo.h))
$(eval $(call add_include_file,kernel/sexpr.h))
$(eval $(call add_include_file,kernel/sigtools.h))
$(eval $(call add_include_file,kernel/timinginfo.h))
$(eval $(call add_include_file,kernel/utils.h))
Expand All @@ -647,7 +648,7 @@ $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h))

OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o
OBJS += kernel/binding.o
OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o
OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o kernel/sexpr.o
OBJS += kernel/drivertools.o kernel/functionalir.o
ifeq ($(ENABLE_ZLIB),1)
OBJS += kernel/fstdata.o
Expand Down
163 changes: 3 additions & 160 deletions backends/functional/smtlib.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@

#include "kernel/functionalir.h"
#include "kernel/yosys.h"
#include "kernel/sexpr.h"
#include <ctype.h>

USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN

using SExprUtil::list;

const char *reserved_keywords[] = {
"BINARY", "DECIMAL", "HEXADECIMAL", "NUMERAL", "STRING", "_", "!", "as", "let", "exists", "forall", "match", "par",
"assert", "check-sat", "check-sat-assuming", "declare-const", "declare-datatype", "declare-datatypes",
Expand All @@ -47,166 +50,6 @@ struct SmtScope : public FunctionalTools::Scope<int> {
}
};

class SExpr {
public:
std::variant<std::vector<SExpr>, std::string> _v;
public:
SExpr(std::string a) : _v(std::move(a)) {}
SExpr(const char *a) : _v(a) {}
SExpr(int n) : _v(std::to_string(n)) {}
SExpr(std::vector<SExpr> const &l) : _v(l) {}
SExpr(std::vector<SExpr> &&l) : _v(std::move(l)) {}
bool is_atom() const { return std::holds_alternative<std::string>(_v); }
std::string const &atom() const { return std::get<std::string>(_v); }
bool is_list() const { return std::holds_alternative<std::vector<SExpr>>(_v); }
std::vector<SExpr> const &list() const { return std::get<std::vector<SExpr>>(_v); }
friend std::ostream &operator<<(std::ostream &os, SExpr const &sexpr) {
if(sexpr.is_atom())
os << sexpr.atom();
else if(sexpr.is_list()){
os << "(";
auto l = sexpr.list();
for(size_t i = 0; i < l.size(); i++) {
if(i > 0) os << " ";
os << l[i];
}
os << ")";
}else
os << "<invalid>";
return os;
}
std::string to_string() const {
std::stringstream ss;
ss << *this;
return ss.str();
}
};
template<typename... Args> SExpr list(Args&&... args) {
return SExpr(std::vector<SExpr>{std::forward<Args>(args)...});
}

class SExprWriter {
std::ostream &os;
int _max_line_width;
int _indent = 0;
int _pos = 0;
bool _pending_nl = false;
vector<bool> _unclosed;
vector<size_t> _unclosed_stack;
void nl_if_pending() {
if(_pending_nl) {
os << '\n';
_pos = 0;
_pending_nl = false;
}
}
void puts(std::string const &s) {
if(s.empty()) return;
nl_if_pending();
for(auto c : s) {
if(c == '\n') {
os << c;
_pos = 0;
} else {
if(_pos == 0) {
for(int i = 0; i < _indent; i++)
os << " ";
_pos = 2 * _indent;
}
os << c;
_pos++;
}
}
}
int width(SExpr const &sexpr) {
if(sexpr.is_atom())
return sexpr.atom().size();
else if(sexpr.is_list()) {
int w = 2;
for(auto arg : sexpr.list())
w += width(arg);
if(sexpr.list().size() > 1)
w += sexpr.list().size() - 1;
return w;
} else
return 0;
}
void print(SExpr const &sexpr, bool close = true, bool indent_rest = true) {
if(sexpr.is_atom())
puts(sexpr.atom());
else if(sexpr.is_list()) {
auto args = sexpr.list();
puts("(");
bool vertical = args.size() > 1 && _pos + width(sexpr) > _max_line_width;
if(vertical) _indent++;
for(size_t i = 0; i < args.size(); i++) {
if(i > 0) puts(vertical ? "\n" : " ");
print(args[i]);
}
_indent += (!close && indent_rest) - vertical;
if(close)
puts(")");
else {
_unclosed.push_back(indent_rest);
_pending_nl = true;
}
}else
log_error("shouldn't happen in SExprWriter::print");
}
public:
SExprWriter(std::ostream &os, int max_line_width = 80)
: os(os)
, _max_line_width(max_line_width)
{}
void open(SExpr const &sexpr, bool indent_rest = true) {
log_assert(sexpr.is_list());
print(sexpr, false, indent_rest);
}
void close(size_t n = 1) {
log_assert(_unclosed.size() - (_unclosed_stack.empty() ? 0 : _unclosed_stack.back()) >= n);
while(n-- > 0) {
bool indented = _unclosed[_unclosed.size() - 1];
_unclosed.pop_back();
_pending_nl = _pos >= _max_line_width;
if(indented)
_indent--;
puts(")");
_pending_nl = true;
}
}
void push() {
_unclosed_stack.push_back(_unclosed.size());
}
void pop() {
auto t = _unclosed_stack.back();
log_assert(_unclosed.size() >= t);
close(_unclosed.size() - t);
_unclosed_stack.pop_back();
}
SExprWriter &operator <<(SExpr const &sexpr) {
print(sexpr);
_pending_nl = true;
return *this;
}
void comment(std::string const &str, bool hanging = false) {
if(hanging) {
if(_pending_nl) {
_pending_nl = false;
puts(" ");
}
}
puts("; ");
puts(str);
puts("\n");
}
~SExprWriter() {
while(!_unclosed_stack.empty())
pop();
close(_unclosed.size());
nl_if_pending();
}
};

struct SmtSort {
FunctionalIR::Sort sort;
SmtSort(FunctionalIR::Sort sort) : sort(sort) {}
Expand Down
163 changes: 163 additions & 0 deletions kernel/sexpr.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2024 Emily Schmidt <[email protected]>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/

#include "sexpr.h"

YOSYS_NAMESPACE_BEGIN

std::ostream &operator<<(std::ostream &os, SExpr const &sexpr) {
if(sexpr.is_atom())
os << sexpr.atom();
else if(sexpr.is_list()){
os << "(";
auto l = sexpr.list();
for(size_t i = 0; i < l.size(); i++) {
if(i > 0) os << " ";
os << l[i];
}
os << ")";
}else
os << "<invalid>";
return os;
}

std::string SExpr::to_string() const {
std::stringstream ss;
ss << *this;
return ss.str();
}

void SExprWriter::nl_if_pending() {
if(_pending_nl) {
os << '\n';
_pos = 0;
_pending_nl = false;
}
}

void SExprWriter::puts(std::string_view s) {
if(s.empty()) return;
nl_if_pending();
for(auto c : s) {
if(c == '\n') {
os << c;
_pos = 0;
} else {
if(_pos == 0) {
for(int i = 0; i < _indent; i++)
os << " ";
_pos = 2 * _indent;
}
os << c;
_pos++;
}
}
}


// Calculate how much space would be left if expression was written
// out in full horizontally. Returns any negative value if it doesn't fit.
//
// (Ideally we would avoid recalculating the widths of subexpression,
// but I can't figure out how to store the widths. As an alternative,
// we bail out of the calculation as soon as we can tell the expression
// doesn't fit in the available space.)
int SExprWriter::check_fit(SExpr const &sexpr, int space) {
if(sexpr.is_atom())
return space - sexpr.atom().size();
else if(sexpr.is_list()) {
space -= 2;
if(sexpr.list().size() > 1)
space -= sexpr.list().size() - 1;
for(auto arg : sexpr.list()) {
if(space < 0) break;
space = check_fit(arg, space);
}
return space;
} else
return -1;
}

void SExprWriter::print(SExpr const &sexpr, bool close, bool indent_rest) {
if(sexpr.is_atom())
puts(sexpr.atom());
else if(sexpr.is_list()) {
auto args = sexpr.list();
puts("(");
// Expressions are printed horizontally if they fit on the line.
// We do the check *after* puts("(") to make sure that _pos is accurate.
// (Otherwise there could be a pending newline + indentation)
bool vertical = args.size() > 1 && check_fit(sexpr, _max_line_width - _pos + 1) < 0;
if(vertical) _indent++;
for(size_t i = 0; i < args.size(); i++) {
if(i > 0) puts(vertical ? "\n" : " ");
print(args[i]);
}
// Any remaining arguments are currently always printed vertically,
// but are not indented if indent_rest = false.
_indent += (!close && indent_rest) - vertical;
if(close)
puts(")");
else {
_unclosed.push_back(indent_rest);
_pending_nl = true;
}
}else
log_error("shouldn't happen: SExpr '%s' is neither an atom nor a list", sexpr.to_string().c_str());
}

void SExprWriter::close(size_t n) {
log_assert(_unclosed.size() - (_unclosed_stack.empty() ? 0 : _unclosed_stack.back()) >= n);
while(n-- > 0) {
bool indented = _unclosed[_unclosed.size() - 1];
_unclosed.pop_back();
// Only print ) on the same line if it fits.
_pending_nl = _pos >= _max_line_width;
if(indented)
_indent--;
puts(")");
_pending_nl = true;
}
}

void SExprWriter::comment(std::string const &str, bool hanging) {
if(hanging) {
if(_pending_nl) {
_pending_nl = false;
puts(" ");
}
}
size_t i = 0, e;
do{
e = str.find('\n', i);
puts("; ");
puts(std::string_view(str).substr(i, e - i));
puts("\n");
i = e + 1;
}while(e != std::string::npos);
}

SExprWriter::~SExprWriter() {
while(!_unclosed_stack.empty())
pop();
close(_unclosed.size());
nl_if_pending();
}

YOSYS_NAMESPACE_END
Loading

0 comments on commit ccb95b3

Please sign in to comment.