Index: src/AST/Bitfield.hpp
===================================================================
--- src/AST/Bitfield.hpp	(revision 20de6fb5d9d2cbba5d2d6bf78abd05bfdb13c59d)
+++ src/AST/Bitfield.hpp	(revision 9d23c2d1e4db73b3db4065d5a6f012923edd07ff)
@@ -57,19 +57,13 @@
 };
 
-/// Adds default printing operator to a bitfield type.
-/// Include in definition to add print function, requires other bitfield operators.
-/// @param N  Number of bits in bitfield
-#define MakeBitfieldPrint( N ) \
-	static const char* Names[]; \
- \
-	void print( std::ostream & os ) const { \
-		if ( (*this).any() ) { \
-			for ( unsigned int i = 0; i < N; i += 1 ) { \
-				if ( (*this)[i] ) { \
-					os << Names[i] << ' '; \
-				} \
-			} \
-		} \
-	}
+template<typename T>
+inline bool operator== ( const bitfield<T> & a, const bitfield<T> & b ) {
+	return a.val == b.val;
+}
+
+template<typename T>
+inline bool operator!= ( const bitfield<T> & a, const bitfield<T> & b ) {
+	return !(a == b);
+}
 
 // Local Variables: //
Index: src/AST/Node.cpp
===================================================================
--- src/AST/Node.cpp	(revision 20de6fb5d9d2cbba5d2d6bf78abd05bfdb13c59d)
+++ src/AST/Node.cpp	(revision 9d23c2d1e4db73b3db4065d5a6f012923edd07ff)
@@ -26,4 +26,6 @@
 #include "Type.hpp"
 #include "TypeSubstitution.hpp"
+
+#include "Print.hpp"
 
 template< typename node_t, enum ast::Node::ref_type ref_t >
@@ -47,7 +49,5 @@
 
 std::ostream & ast::operator<< ( std::ostream & out, const ast::Node * node ) {
-	(void)node;
-	#warning unimplemented
-	assertf(false, "Unimplemented");
+	print(out, node);
 	return out;
 }
Index: src/AST/Print.cpp
===================================================================
--- src/AST/Print.cpp	(revision 9d23c2d1e4db73b3db4065d5a6f012923edd07ff)
+++ src/AST/Print.cpp	(revision 9d23c2d1e4db73b3db4065d5a6f012923edd07ff)
@@ -0,0 +1,513 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// Print.cpp --
+//
+// Author           : Thierry Delisle
+// Created On       : Tue May 21 16:20:15 2019
+// Last Modified By :
+// Last Modified On :
+// Update Count     :
+//
+
+#include "Print.hpp"
+
+#include "Decl.hpp"
+#include "Expr.hpp"
+#include "Stmt.hpp"
+#include "Type.hpp"
+#include "TypeSubstitution.hpp"
+
+
+namespace ast {
+
+template <typename C, typename... T>
+constexpr auto make_array(T&&... values) ->
+	std::array<C,sizeof...(T)>
+{
+	return std::array<C,sizeof...(T)>{
+		std::forward<T>(values)...
+	};
+}
+
+class Printer : public Visitor {
+public:
+	std::ostream & os;
+	Indenter indent;
+
+	Printer(std::ostream & os, Indenter indent) : os( os ), indent( indent ) {}
+
+private:
+	template< typename C >
+	void printAll( const C & c ) {
+		for ( const auto & i : c ) {
+			if ( i ) {
+				os << indent;
+				i->accept( *this );
+				// need an endl after each element because it's not
+				// easy to know when each individual item should end
+				os << std::endl;
+			} // if
+		} // for
+	}
+
+
+	static const char* Names[];
+
+	struct Names {
+		static constexpr auto FuncSpecifiers = make_array<const char*>(
+			"inline", "_Noreturn", "fortran"
+		);
+
+		static constexpr auto StorageClasses = make_array<const char*>(
+			"extern", "static", "auto", "register", "_Thread_local"
+		);
+
+		static constexpr auto Qualifiers = make_array<const char*>(
+			"const", "restrict", "volatile", "lvalue", "mutex", "_Atomic"
+		);
+	};
+
+	template<typename storage_t, size_t N>
+	void print(const storage_t & storage, const std::array<const char *, N> & Names ) {
+		if ( storage.any() ) {
+			for ( size_t i = 0; i < Names.size(); i += 1 ) {
+				if ( storage[i] ) {
+					os << Names[i] << ' ';
+				}
+			}
+		}
+	}
+
+	void print( const ast::Function::Specs & specs ) {
+		print(specs, Names::FuncSpecifiers);
+	}
+
+	void print( const ast::Storage::Classes & storage ) {
+		print(storage, Names::StorageClasses);
+	}
+
+	void print( const ast::CV::Qualifiers & qualifiers ) {
+		print(qualifiers, Names::Qualifiers);
+	}
+
+public:
+	virtual const ast::DeclWithType *     visit( const ast::ObjectDecl           * node ) {
+		if ( node->name != "" ) os << node->name << ": ";
+
+		if ( node->linkage != Linkage::Cforall ) {
+			os << Linkage::name( node->linkage ) << " ";
+		} // if
+
+		print( node->storage );
+
+		if ( node->type ) {
+			node->type->accept( *this );
+		} else {
+			os << " untyped entity ";
+		} // if
+
+		if ( node->init ) {
+			os << " with initializer (" << (
+				node->init->maybeConstructed
+					? "maybe constructed"
+					: "not constructed"
+				) << ")" << std::endl << indent+1;
+
+			++indent;
+			node->init->accept( *this );
+			--indent;
+			os << std::endl;
+		} // if
+
+		if ( ! node->attributes.empty() ) {
+			os << std::endl << indent << "... with attributes:" << std::endl;
+			++indent;
+			printAll( node->attributes );
+			--indent;
+		}
+
+		if ( node->bitfieldWidth ) {
+			os << indent << " with bitfield width ";
+			node->bitfieldWidth->accept( *this );
+		} // if
+		return node;
+	}
+
+	virtual const ast::DeclWithType *     visit( const ast::FunctionDecl         * node ) {
+		return node;
+	}
+
+	virtual const ast::Decl *             visit( const ast::StructDecl           * node ) {
+		return node;
+	}
+
+	virtual const ast::Decl *             visit( const ast::UnionDecl            * node ) {
+		return node;
+	}
+
+	virtual const ast::Decl *             visit( const ast::EnumDecl             * node ) {
+		return node;
+	}
+
+	virtual const ast::Decl *             visit( const ast::TraitDecl            * node ) {
+		return node;
+	}
+
+	virtual const ast::Decl *             visit( const ast::TypeDecl             * node ) {
+		return node;
+	}
+
+	virtual const ast::Decl *             visit( const ast::TypedefDecl          * node ) {
+		return node;
+	}
+
+	virtual const ast::AsmDecl *          visit( const ast::AsmDecl              * node ) {
+		return node;
+	}
+
+	virtual const ast::StaticAssertDecl * visit( const ast::StaticAssertDecl     * node ) {
+		return node;
+	}
+
+	virtual const ast::CompoundStmt *     visit( const ast::CompoundStmt         * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::ExprStmt             * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::AsmStmt              * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::DirectiveStmt        * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::IfStmt               * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::WhileStmt            * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::ForStmt              * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::SwitchStmt           * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::CaseStmt             * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::BranchStmt           * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::ReturnStmt           * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::ThrowStmt            * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::TryStmt              * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::CatchStmt            * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::FinallyStmt          * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::WaitForStmt          * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::WithStmt             * node ) {
+		return node;
+	}
+
+	virtual const ast::NullStmt *         visit( const ast::NullStmt             * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::DeclStmt             * node ) {
+		return node;
+	}
+
+	virtual const ast::Stmt *             visit( const ast::ImplicitCtorDtorStmt * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::ApplicationExpr      * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::UntypedExpr          * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::NameExpr             * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::AddressExpr          * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::LabelAddressExpr     * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::CastExpr             * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::KeywordCastExpr      * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::VirtualCastExpr      * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::UntypedMemberExpr    * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::MemberExpr           * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::VariableExpr         * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::ConstantExpr         * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::SizeofExpr           * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::AlignofExpr          * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::UntypedOffsetofExpr  * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::OffsetofExpr         * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::OffsetPackExpr       * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::LogicalExpr          * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::ConditionalExpr      * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::CommaExpr            * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::TypeExpr             * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::AsmExpr              * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::ImplicitCopyCtorExpr * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::ConstructorExpr      * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::CompoundLiteralExpr  * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::RangeExpr            * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::UntypedTupleExpr     * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::TupleExpr            * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::TupleIndexExpr       * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::TupleAssignExpr      * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::StmtExpr             * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::UniqueExpr           * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::UntypedInitExpr      * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::InitExpr             * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::DeletedExpr          * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::DefaultArgExpr       * node ) {
+		return node;
+	}
+
+	virtual const ast::Expr *             visit( const ast::GenericExpr          * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::VoidType             * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::BasicType            * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::PointerType          * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::ArrayType            * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::ReferenceType        * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::QualifiedType        * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::FunctionType         * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::StructInstType       * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::UnionInstType        * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::EnumInstType         * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::TraitInstType        * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::TypeInstType         * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::TupleType            * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::TypeofType           * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::VarArgsType          * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::ZeroType             * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::OneType              * node ) {
+		return node;
+	}
+
+	virtual const ast::Type *             visit( const ast::GlobalScopeType      * node ) {
+		return node;
+	}
+
+	virtual const ast::Designation *      visit( const ast::Designation          * node ) {
+		return node;
+	}
+
+	virtual const ast::Init *             visit( const ast::SingleInit           * node ) {
+		return node;
+	}
+
+	virtual const ast::Init *             visit( const ast::ListInit             * node ) {
+		return node;
+	}
+
+	virtual const ast::Init *             visit( const ast::ConstructorInit      * node ) {
+		return node;
+	}
+
+	virtual const ast::Attribute *        visit( const ast::Attribute            * node ) {
+		return node;
+	}
+
+	virtual const ast::TypeSubstitution * visit( const ast::TypeSubstitution     * node ) {
+		return node;
+	}
+
+};
+
+void print( std::ostream & os, const ast::Node * node, Indenter indent ) {
+	Printer printer { os, indent };
+	node->accept(printer);
+}
+
+// Annoyingly these needed to be defined out of line to avoid undefined references.
+// The size here needs to be explicit but at least the compiler will produce an error
+// if the wrong size is specified
+constexpr std::array<const char*, 3> Printer::Names::FuncSpecifiers;
+constexpr std::array<const char*, 5> Printer::Names::StorageClasses;
+constexpr std::array<const char*, 6> Printer::Names::Qualifiers;
+}
Index: src/AST/Print.hpp
===================================================================
--- src/AST/Print.hpp	(revision 9d23c2d1e4db73b3db4065d5a6f012923edd07ff)
+++ src/AST/Print.hpp	(revision 9d23c2d1e4db73b3db4065d5a6f012923edd07ff)
@@ -0,0 +1,31 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// Print.hpp --
+//
+// Author           : Thierry Delisle
+// Created On       : Tue May 21 16:20:15 2019
+// Last Modified By :
+// Last Modified On :
+// Update Count     :
+//
+
+#pragma once
+
+#include <iosfwd>
+
+#include "AST/Node.hpp"
+#include "Common/Indenter.h"
+
+namespace ast {
+
+void print( std::ostream & os, const ast::Node * node, Indenter indent = {} );
+
+inline void print( std::ostream & os, const ast::Node * node, unsigned int indent ) {
+    print( os, node, Indenter{ Indenter::tabsize, indent });
+}
+
+}
Index: src/AST/module.mk
===================================================================
--- src/AST/module.mk	(revision 20de6fb5d9d2cbba5d2d6bf78abd05bfdb13c59d)
+++ src/AST/module.mk	(revision 9d23c2d1e4db73b3db4065d5a6f012923edd07ff)
@@ -24,4 +24,5 @@
 	AST/LinkageSpec.cpp \
 	AST/Node.cpp \
+	AST/Print.cpp \
 	AST/Stmt.cpp \
 	AST/Type.cpp \
Index: src/Makefile.in
===================================================================
--- src/Makefile.in	(revision 20de6fb5d9d2cbba5d2d6bf78abd05bfdb13c59d)
+++ src/Makefile.in	(revision 9d23c2d1e4db73b3db4065d5a6f012923edd07ff)
@@ -169,5 +169,5 @@
 	AST/Expr.$(OBJEXT) AST/Init.$(OBJEXT) \
 	AST/LinkageSpec.$(OBJEXT) AST/Node.$(OBJEXT) \
-	AST/Stmt.$(OBJEXT) AST/Type.$(OBJEXT) \
+	AST/Print.$(OBJEXT) AST/Stmt.$(OBJEXT) AST/Type.$(OBJEXT) \
 	AST/TypeSubstitution.$(OBJEXT)
 am__objects_2 = CodeGen/CodeGenerator.$(OBJEXT) \
@@ -577,4 +577,5 @@
 	AST/LinkageSpec.cpp \
 	AST/Node.cpp \
+	AST/Print.cpp \
 	AST/Stmt.cpp \
 	AST/Type.cpp \
@@ -741,4 +742,5 @@
 	AST/$(DEPDIR)/$(am__dirstamp)
 AST/Node.$(OBJEXT): AST/$(am__dirstamp) AST/$(DEPDIR)/$(am__dirstamp)
+AST/Print.$(OBJEXT): AST/$(am__dirstamp) AST/$(DEPDIR)/$(am__dirstamp)
 AST/Stmt.$(OBJEXT): AST/$(am__dirstamp) AST/$(DEPDIR)/$(am__dirstamp)
 AST/Type.$(OBJEXT): AST/$(am__dirstamp) AST/$(DEPDIR)/$(am__dirstamp)
@@ -1172,4 +1174,5 @@
 @AMDEP_TRUE@@am__include@ @am__quote@AST/$(DEPDIR)/LinkageSpec.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@AST/$(DEPDIR)/Node.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@AST/$(DEPDIR)/Print.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@AST/$(DEPDIR)/Stmt.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@AST/$(DEPDIR)/Type.Po@am__quote@
