From 88cd5c97dc6500c4f1b5daa229d6f5a215b0023a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@nocternity.net>
Date: Tue, 3 Oct 2017 13:53:50 +0200
Subject: [PATCH] Shader preprocessor - input

---
 Makefile     |   1 +
 externals.hh |   1 +
 shaders.cc   | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++
 shaders.hh   |  56 ++++++++++++++++++
 4 files changed, 221 insertions(+)
 create mode 100644 shaders.cc
 create mode 100644 shaders.hh

diff --git a/Makefile b/Makefile
index 52312da..35bc677 100644
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,7 @@ DEMO = \
 	 texture.cc \
 	 rendertarget.cc \
 	 programs.cc \
+	 shaders.cc \
 	 camera.cc \
 	 demo.cc \
 	 \
diff --git a/externals.hh b/externals.hh
index ff917fa..6eb23dd 100644
--- a/externals.hh
+++ b/externals.hh
@@ -19,6 +19,7 @@
 #include <memory>
 #include <fstream>
 #include <map>
+#include <regex>
 
 // ImGui
 #include <imgui.h>
diff --git a/shaders.cc b/shaders.cc
new file mode 100644
index 0000000..7f00584
--- /dev/null
+++ b/shaders.cc
@@ -0,0 +1,163 @@
+#include "externals.hh"
+#include "shaders.hh"
+
+
+namespace {
+	const std::regex PreprocDirective_( "^\\s*//!\\s*([a-z]+(\\s+([^\\s]+))*)\\s*$" );
+
+	const std::map< std::string , E_ShaderInput > InputTypes_( ([] {
+		std::map< std::string , E_ShaderInput > t;
+		t.emplace( "chunk" , E_ShaderInput::CHUNK );
+		t.emplace( "library" , E_ShaderInput::LIBRARY );
+		t.emplace( "lib" , E_ShaderInput::LIBRARY );
+		t.emplace( "vertex" , E_ShaderInput::VERTEX );
+		t.emplace( "fragment" , E_ShaderInput::FRAGMENT );
+		return t;
+	})());
+
+
+	struct T_InputReader_
+	{
+		using T_Tokens_ = std::vector< std::string >;
+
+		FILE* const file;
+		T_ShaderInput& input;
+		uint32_t line{ 0 };
+
+		char* buffer{ nullptr };
+		ssize_t readCount;
+
+		std::string accumulator{ };
+		uint32_t accumLines{ 0 };
+
+		T_InputReader_( __rd__ FILE* const file ,
+				__rw__ T_ShaderInput& input )
+			: file( file ) , input( input )
+		{ }
+		~T_InputReader_( );
+
+		void read( );
+		void addAccumulated( );
+		void handleDirective(
+				__rd__ T_Tokens_ const& tokens );
+		void error( __rd__ std::string const& err );
+	};
+
+	T_InputReader_::~T_InputReader_( )
+	{
+		if ( buffer ) {
+			free( buffer );
+		}
+		fclose( file );
+	}
+
+	void T_InputReader_::read( )
+	{
+		size_t bsz( 0 );
+		while ( ( readCount = getline( &buffer , &bsz , file ) ) != -1 ) {
+			line ++;
+			std::cmatch match;
+			if ( std::regex_match( buffer , match , PreprocDirective_ ) ) {
+				const T_Tokens_ tokens( ([](std::string const& a) {
+					using stri = std::istream_iterator< std::string >;
+					std::istringstream iss( a );
+					return T_Tokens_{ stri( iss ) , stri( ) };
+				})( match[ 1 ].str( ) ) );
+				assert( tokens.size( ) >= 1 );
+				handleDirective( tokens );
+			} else {
+				accumulator += buffer;
+				accumLines ++;
+			}
+		}
+		addAccumulated( );
+	}
+
+	void T_InputReader_::addAccumulated( )
+	{
+		if ( accumLines ) {
+			auto& ck( input.chunks );
+			ck.emplace( ck.end( ) , E_ShaderInputChunk::CODE ,
+					accumulator , accumLines );
+			accumulator = {};
+			accumLines = 0;
+		}
+	}
+
+	void T_InputReader_::handleDirective(
+			__rd__ T_Tokens_ const& tokens )
+	{
+		auto const& directive( tokens[ 0 ] );
+
+		if ( directive == "include" ) {
+			if ( tokens.size( ) != 2 ) {
+				error( "invalid arguments" );
+				return;
+			}
+			auto& ck( input.chunks );
+			ck.emplace( ck.end( ) , E_ShaderInputChunk::INCLUDE , tokens[ 1 ] , 1 );
+
+		} else if ( directive == "type" ) {
+			if ( tokens.size( ) != 2 ) {
+				error( "invalid arguments" );
+				return;
+			}
+			auto pos( InputTypes_.find( tokens[ 1 ] ) );
+			if ( pos == InputTypes_.end( ) ) {
+				error( "unknown type" );
+			} else {
+				input.type = pos->second;
+			}
+
+		} else {
+			error( "unknown directive" );
+		}
+	}
+
+	void T_InputReader_::error(
+			__rd__ std::string const& err )
+	{
+		input.errors.push_back( T_ShaderInputError{
+				line , err } );
+	}
+
+} // namespace
+
+
+bool T_ShaderInput::load(
+		__rd__ std::string const& path )
+{
+	type = E_ShaderInput::CHUNK;
+	chunks.clear( );
+	errors.clear( );
+
+	FILE* const file{ fopen( path.c_str( ) , "r" ) };
+	if ( !file ) {
+		errors.push_back( T_ShaderInputError{ 0 , "unable to open" } );
+		return false;
+	}
+
+	T_InputReader_ reader( file , *this );
+	reader.read( );
+
+	return true;
+}
+
+
+#if 0
+void testLoadShaderFile( )
+{
+	const std::string source( "test-loader.glsl" );
+	const std::string sPath( "shaders/" + source );
+
+	T_ShaderInput input;
+	const bool ok( input.load( sPath ) );
+	printf( "OK? %s\n" , ok ? "yes" : "no" );
+	printf( "type: %d\n" , int( input.type ) );
+	printf( "chunks: %zu\n" , input.chunks.size( ) );
+	printf( "errors: %zu\n" , input.errors.size( ) );
+	for ( auto const& error : input.errors ) {
+		printf( "\tl%d: %s\n" , error.line , error.error.c_str( ) );
+	}
+}
+#endif
diff --git a/shaders.hh b/shaders.hh
new file mode 100644
index 0000000..d778e6a
--- /dev/null
+++ b/shaders.hh
@@ -0,0 +1,56 @@
+#pragma once
+#include "utilities.hh"
+
+
+/*= INPUT FILES ==============================================================*/
+
+// Type of input chunk
+enum class E_ShaderInputChunk {
+	CODE ,
+	INCLUDE
+};
+
+// Input chunk data
+struct T_ShaderInputChunk
+{
+	E_ShaderInputChunk type;
+	std::string text;
+	uint32_t lines;
+
+	T_ShaderInputChunk( ) = default;
+	T_ShaderInputChunk(
+			__rd__ const E_ShaderInputChunk type ,
+			__rd__ std::string text ,
+			__rd__ const uint32_t lines )
+		: type( type ) , text( std::move( text ) ) , lines( lines )
+	{ }
+};
+
+// Input file type
+enum class E_ShaderInput {
+	CHUNK ,			// Chunk that may be repeated
+	LIBRARY ,		// Library (will only be loaded once)
+
+	// "Main" shader source files
+	VERTEX , FRAGMENT ,
+};
+
+// Preprocessing errors
+struct T_ShaderInputError
+{
+	uint32_t line;
+	std::string error;
+};
+
+// Source file
+struct T_ShaderInput
+{
+	E_ShaderInput type = E_ShaderInput::CHUNK;
+	std::vector< T_ShaderInputChunk > chunks;
+	std::vector< T_ShaderInputError > errors;
+
+	bool load( __rd__ std::string const& path );
+};
+
+
+//void testLoadShaderFile( );