diff --git a/.gitignore b/.gitignore
index 87289b07f..2ec020514 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,7 @@ spec/nim/bin
# Rust compiled code, cargo and rls data
spec/rust/target/
+
+src/bin
+.vscode
+spec/c/test_*
diff --git a/builder/c_builder.rb b/builder/c_builder.rb
new file mode 100644
index 000000000..bcf8d60be
--- /dev/null
+++ b/builder/c_builder.rb
@@ -0,0 +1,27 @@
+require 'fileutils'
+
+require_relative 'cpp_builder'
+
+class CBuilder < CppBuilder
+ attr_accessor :mode
+
+ def initialize(src_dir, cpp_spec_dir, cpp_test_out_dir)
+ super(src_dir, cpp_spec_dir, cpp_test_out_dir)
+ end
+
+ def list_disposable_files
+ list = Dir.glob("#{@cpp_spec_dir}/**/*.c") + Dir.glob("#{@src_dir}/*.c") + Dir.glob("#{@cpp_spec_dir}/**/*.cpp") + Dir.glob("#{@src_dir}/*.cpp")
+ list.map { |x|
+ r = File.absolute_path(x)
+
+ # On Windows, filesystem is case insensitive, but our Set
+ # implementation is not. So we proactively normalize everything we
+ # can to lower case.
+ if @mode == :msbuild_windows
+ r.downcase!
+ end
+
+ r
+ }
+ end
+end
diff --git a/ci-c b/ci-c
new file mode 100755
index 000000000..dfffa4994
--- /dev/null
+++ b/ci-c
@@ -0,0 +1,23 @@
+#!/bin/sh -ef
+
+. ./config
+
+SRC_DIR="$(pwd)/compiled/c"
+C_SPEC_DIR="$(pwd)/spec/c"
+C_TEST_OUT_DIR="$TEST_OUT_DIR/c"
+
+./run-c "$SRC_DIR" "$C_SPEC_DIR" "$C_TEST_OUT_DIR"
+
+# Run C valgrind check, generates Valgrind XML report
+echo 'Running Valgrind checks...'
+if valgrind --version; then
+ ./valgrind-c "$SRC_DIR" "$C_TEST_OUT_DIR" || :
+else
+ echo 'Valgrind not found :-('
+
+ # Generate empty valgrind output - this is normal and it should not stop `ci.json` generation
+ echo '' >"$C_TEST_OUT_DIR/valgrind.xml"
+fi
+
+./kst-adoption-report $(basename "$SRC_DIR")
+aggregate/convert_to_json cpp_stl "$C_TEST_OUT_DIR" "$C_TEST_OUT_DIR/ci.json"
diff --git a/kst-adoption-report b/kst-adoption-report
index 571869ae0..d00460cb1 100755
--- a/kst-adoption-report
+++ b/kst-adoption-report
@@ -34,6 +34,8 @@ def glob_spec_files(lang)
Dir.glob('spec/construct/**/test_*.py')
when 'ruby'
Dir.glob('spec/ruby/**/*_spec.rb')
+ when 'c'
+ Dir.glob('spec/c/**/test_*.cpp')
else
raise "Unable to handle language #{@lang.inspect}"
end
@@ -74,6 +76,9 @@ def spec_file_to_test_name(lang, fn)
when 'ruby'
raise "Unable to extract test name from #{fn.inspect}" unless fn =~ /^(.*?)_spec\.rb$/
underscore_to_ucamelcase($1)
+ when 'c'
+ raise "Unable to extract test name from #{fn.inspect}" unless fn =~ /^test_(.*?)\.cpp$/
+ underscore_to_ucamelcase($1)
else
raise "Unable to handle language #{lang.inspect}"
end
diff --git a/run-c b/run-c
new file mode 100755
index 000000000..048b010a3
--- /dev/null
+++ b/run-c
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+require_relative 'builder/c_builder'
+CBuilder.new(ARGV[0], ARGV[1], ARGV[2]).command_line(ARGV[3..-1])
diff --git a/spec/c/.gitignore b/spec/c/.gitignore
new file mode 100644
index 000000000..5d19f2f70
--- /dev/null
+++ b/spec/c/.gitignore
@@ -0,0 +1 @@
+tests
diff --git a/spec/c/CMakeLists.txt b/spec/c/CMakeLists.txt
new file mode 100644
index 000000000..121df6b86
--- /dev/null
+++ b/spec/c/CMakeLists.txt
@@ -0,0 +1,57 @@
+cmake_minimum_required(VERSION 3.1)
+
+project(KS_TEST_C)
+
+set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
+
+find_package(Boost COMPONENTS unit_test_framework REQUIRED)
+find_package(ZLIB REQUIRED)
+find_package(Iconv REQUIRED)
+
+# Enforce UTF-8 source files encoding for MSVC
+add_compile_options("$<$:/utf-8>")
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror ")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wno-unused-function -std=c89 -Wdeclaration-after-statement -Wformat=2")
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+set(RUNTIME_SRC_PATH ../../../runtime/c)
+set(PREREQ_PATH prereq)
+
+include(${INC_PATH})
+
+set(PREREQ_SOURCES
+ ${PREREQ_PATH}/custom.c
+)
+
+set(RUNTIME_SOURCES
+ ${RUNTIME_SRC_PATH}/kaitaistruct.c
+)
+
+add_executable (ks_tests
+ ${RUNTIME_SOURCES}
+ ${PREREQ_SOURCES}
+ ${DISPOSABLE_SOURCES}
+)
+
+include_directories(
+ "${PROJECT_BINARY_DIR}"
+ "${KS_PATH}"
+ "${RUNTIME_SRC_PATH}"
+ "${PREREQ_PATH}"
+ "${ZLIB_INCLUDE_DIRS}"
+)
+
+# NB: no quotes around variables here, as it messes up new "optimized
+# A debug B" syntax which gets generated on Windows platforms
+target_link_libraries(ks_tests
+ ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}
+ ${ZLIB_LIBRARIES}
+ ${Iconv_LIBRARIES}
+)
+
+add_test(ks_tests ks_tests)
+
+enable_testing()
diff --git a/spec/c/cmake/FindIconv.cmake b/spec/c/cmake/FindIconv.cmake
new file mode 100644
index 000000000..bf1c9e4c5
--- /dev/null
+++ b/spec/c/cmake/FindIconv.cmake
@@ -0,0 +1,143 @@
+# Part of modern CMake library (since 2017-11-29), not yet available in most
+# CMake distributions:
+#
+# https://gitlab.kitware.com/cmake/cmake/blob/master/Modules/FindIconv.cmake
+# https://github.com/Kitware/CMake/blob/master/Modules/FindIconv.cmake
+#
+# Minor modifications to allow it to run in standalone environment:
+# fix file includes from a current directory to module includes (no
+# dir, no .cmake extension).
+
+# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+# file Copyright.txt or https://cmake.org/licensing for details.
+
+#[=======================================================================[.rst:
+FindIconv
+---------
+
+This module finds the ``iconv()`` POSIX.1 functions on the system.
+These functions might be provided in the regular C library or externally
+in the form of an additional library.
+
+The following variables are provided to indicate iconv support:
+
+.. variable:: Iconv_FOUND
+
+ Variable indicating if the iconv support was found.
+
+.. variable:: Iconv_INCLUDE_DIRS
+
+ The directories containing the iconv headers.
+
+.. variable:: Iconv_LIBRARIES
+
+ The iconv libraries to be linked.
+
+.. variable:: Iconv_IS_BUILT_IN
+
+ A variable indicating whether iconv support is stemming from the
+ C library or not. Even if the C library provides `iconv()`, the presence of
+ an external `libiconv` implementation might lead to this being false.
+
+Additionally, the following :prop_tgt:`IMPORTED` target is being provided:
+
+.. variable:: Iconv::Iconv
+
+ Imported target for using iconv.
+
+The following cache variables may also be set:
+
+.. variable:: Iconv_INCLUDE_DIR
+
+ The directory containing the iconv headers.
+
+.. variable:: Iconv_LIBRARY
+
+ The iconv library (if not implicitly given in the C library).
+
+.. note::
+ On POSIX platforms, iconv might be part of the C library and the cache
+ variables ``Iconv_INCLUDE_DIR`` and ``Iconv_LIBRARY`` might be empty.
+
+#]=======================================================================]
+
+include(CMakePushCheckState)
+if(CMAKE_C_COMPILER_LOADED)
+ include(CheckCSourceCompiles)
+elseif(CMAKE_CXX_COMPILER_LOADED)
+ include(CheckCXXSourceCompiles)
+else()
+ # If neither C nor CXX are loaded, implicit iconv makes no sense.
+ set(Iconv_IS_BUILT_IN FALSE)
+endif()
+
+# iconv can only be provided in libc on a POSIX system.
+# If any cache variable is already set, we'll skip this test.
+if(NOT DEFINED Iconv_IS_BUILT_IN)
+ if(UNIX AND NOT DEFINED Iconv_INCLUDE_DIR AND NOT DEFINED Iconv_LIBRARY)
+ cmake_push_check_state(RESET)
+ # We always suppress the message here: Otherwise on supported systems
+ # not having iconv in their C library (e.g. those using libiconv)
+ # would always display a confusing "Looking for iconv - not found" message
+ set(CMAKE_FIND_QUIETLY TRUE)
+ # The following code will not work, but it's sufficient to see if it compiles.
+ # Note: libiconv will define the iconv functions as macros, so CheckSymbolExists
+ # will not yield correct results.
+ set(Iconv_IMPLICIT_TEST_CODE
+ "
+ #include
+ #include
+ int main() {
+ char *a, *b;
+ size_t i, j;
+ iconv_t ic;
+ ic = iconv_open(\"to\", \"from\");
+ iconv(ic, &a, &i, &b, &j);
+ iconv_close(ic);
+ }
+ "
+ )
+ if(CMAKE_C_COMPILER_LOADED)
+ check_c_source_compiles("${Iconv_IMPLICIT_TEST_CODE}" Iconv_IS_BUILT_IN)
+ else()
+ check_cxx_source_compiles("${Iconv_IMPLICIT_TEST_CODE}" Iconv_IS_BUILT_IN)
+ endif()
+ cmake_pop_check_state()
+ else()
+ set(Iconv_IS_BUILT_IN FALSE)
+ endif()
+endif()
+
+if(NOT Iconv_IS_BUILT_IN)
+ find_path(Iconv_INCLUDE_DIR
+ NAMES "iconv.h"
+ DOC "iconv include directory")
+ set(Iconv_LIBRARY_NAMES "iconv" "libiconv")
+else()
+ set(Iconv_INCLUDE_DIR "" CACHE FILEPATH "iconv include directory")
+ set(Iconv_LIBRARY_NAMES "c")
+endif()
+
+find_library(Iconv_LIBRARY
+ NAMES ${Iconv_LIBRARY_NAMES}
+ DOC "iconv library (potentially the C library)")
+
+mark_as_advanced(Iconv_INCLUDE_DIR)
+mark_as_advanced(Iconv_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+if(NOT Iconv_IS_BUILT_IN)
+ find_package_handle_standard_args(Iconv REQUIRED_VARS Iconv_LIBRARY Iconv_INCLUDE_DIR)
+else()
+ find_package_handle_standard_args(Iconv REQUIRED_VARS Iconv_LIBRARY)
+endif()
+
+if(Iconv_FOUND)
+ set(Iconv_INCLUDE_DIRS "${Iconv_INCLUDE_DIR}")
+ set(Iconv_LIBRARIES "${Iconv_LIBRARY}")
+ if(NOT TARGET Iconv::Iconv)
+ add_library(Iconv::Iconv INTERFACE IMPORTED)
+ endif()
+ set_property(TARGET Iconv::Iconv PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${Iconv_INCLUDE_DIRS}")
+ set_property(TARGET Iconv::Iconv PROPERTY INTERFACE_LINK_LIBRARIES "${Iconv_LIBRARIES}")
+endif()
diff --git a/spec/c/main.cpp b/spec/c/main.cpp
new file mode 100644
index 000000000..90d7ccdb1
--- /dev/null
+++ b/spec/c/main.cpp
@@ -0,0 +1,3 @@
+#define BOOST_TEST_MODULE "C test units for Kaitai Struct"
+#define BOOST_TEST_DYN_LINK
+#include
diff --git a/spec/c/prereq/custom.c b/spec/c/prereq/custom.c
new file mode 100644
index 000000000..8f42f61ba
--- /dev/null
+++ b/spec/c/prereq/custom.c
@@ -0,0 +1,68 @@
+#include
+
+static ks_bytes* decode1(void*userdata, ks_bytes* src)
+{
+ int length = ks_bytes_get_length(src) + 2;
+ char* data = calloc(1, length);
+ ks_bytes* ret;
+ ks_bytes_get_data(src, data + 1);
+
+ data[0] = '_';
+ data[length - 1] = '_';
+
+ ret = ks_bytes_recreate(src, data, length);
+ free(data);
+ return ret;
+}
+
+ks_custom_decoder custom_fx_no_args_create(void)
+{
+ ks_custom_decoder ret = {0};
+ ret.decode = decode1;
+ return ret;
+}
+
+void custom_fx_no_args_destroy(ks_custom_decoder decoder)
+{
+}
+
+ks_custom_decoder custom_fx_create(int p_key)
+{
+ ks_custom_decoder ret = {0};
+ ret.decode = decode1;
+ return ret;
+}
+
+void custom_fx_destroy(ks_custom_decoder decoder)
+{
+}
+
+static ks_bytes* decode2(void* userdata, ks_bytes* src) {
+ int i;
+ int key = *(int*)userdata;
+ int len = ks_bytes_get_length(src);
+ char* data = calloc(1, len);
+ ks_bytes* ret;
+ ks_bytes_get_data(src, data);
+
+ for (i = 0; i < len; i++)
+ data[i] = data[i] + key;
+
+ ret = ks_bytes_recreate(src, data, len);
+ free(data);
+ return ret;
+}
+
+ks_custom_decoder my_custom_fx_create(int p_key, int p_flag, ks_bytes* p_some_bytes)
+{
+ ks_custom_decoder ret = {0};
+ ret.userdata = malloc(sizeof(int));
+ *((int*)ret.userdata) = p_flag ? p_key : -p_key;
+ ret.decode = decode2;
+ return ret;
+}
+
+void my_custom_fx_destroy(ks_custom_decoder decoder)
+{
+ free(decoder.userdata);
+}
diff --git a/spec/c/prereq/custom_fx.h b/spec/c/prereq/custom_fx.h
new file mode 100644
index 000000000..c30c14d8f
--- /dev/null
+++ b/spec/c/prereq/custom_fx.h
@@ -0,0 +1,9 @@
+#ifndef CUSTOM_FX_H_
+#define CUSTOM_FX_H_
+
+#include
+
+ks_custom_decoder custom_fx_create(int p_key);
+void custom_fx_destroy(ks_custom_decoder decoder);
+
+#endif
diff --git a/spec/c/prereq/custom_fx_no_args.h b/spec/c/prereq/custom_fx_no_args.h
new file mode 100644
index 000000000..73b7effee
--- /dev/null
+++ b/spec/c/prereq/custom_fx_no_args.h
@@ -0,0 +1,9 @@
+#ifndef CUSTOM_FX_NO_ARGS_H_
+#define CUSTOM_FX_NO_ARGS_H_
+
+#include
+
+ks_custom_decoder custom_fx_no_args_create(void);
+void custom_fx_no_args_destroy(ks_custom_decoder decoder);
+
+#endif
diff --git a/spec/c/prereq/my_custom_fx.h b/spec/c/prereq/my_custom_fx.h
new file mode 100644
index 000000000..15f7049ac
--- /dev/null
+++ b/spec/c/prereq/my_custom_fx.h
@@ -0,0 +1,9 @@
+#ifndef MY_CUSTOM_FX_H_
+#define MY_CUSTOM_FX_H_
+
+#include
+
+ks_custom_decoder my_custom_fx_create(int p_key, int p_flag, ks_bytes* p_some_bytes);
+void my_custom_fx_destroy(ks_custom_decoder decoder);
+
+#endif
diff --git a/spec/c/prereq/operators.h b/spec/c/prereq/operators.h
new file mode 100644
index 000000000..3fc95d257
--- /dev/null
+++ b/spec/c/prereq/operators.h
@@ -0,0 +1,128 @@
+extern "C" {
+#include
+}
+#include
+#include
+
+class bytes_wrapper {
+public:
+ const ks_bytes* data;
+ bytes_wrapper(const ks_bytes* bytes) : data(bytes) {}
+};
+
+inline bool operator==(const ks_string s1, const char* s2)
+{
+ return strcmp(s1.data, s2) == 0;
+}
+
+inline bool operator==(const ks_string s1, const ks_string s2)
+{
+ return strcmp(s1.data, s2.data) == 0;
+}
+
+inline bool operator!=(const ks_string s1, const ks_string s2)
+{
+ return !(s1 == s2);
+}
+
+inline std::ostream& operator<<(std::ostream& out, const ks_string s)
+{
+ return out << s.data;
+}
+
+inline bool operator==(const bytes_wrapper b1, const bytes_wrapper b2)
+{
+ uint8_t* data1;
+ uint8_t* data2;
+ uint64_t length1, length2;
+
+ length1 = ks_bytes_get_length(b1.data);
+ length2 = ks_bytes_get_length(b2.data);
+
+ if (length1 != length2)
+ return false;
+
+ data1 = (uint8_t*)malloc(length1);
+ data2 = (uint8_t*)malloc(length2);
+ ks_bytes_get_data(b1.data, data1);
+ ks_bytes_get_data(b2.data, data2);
+
+ for (uint64_t i = 0; i < length1; i++)
+ {
+ if (data1[i] != data2[i])
+ {
+ free(data1);
+ free(data2);
+ return false;
+ }
+ }
+
+ free(data1);
+ free(data2);
+ return true;
+}
+
+inline std::ostream& operator<<(std::ostream& out, const bytes_wrapper b)
+{
+ uint8_t* data;
+ uint64_t length;
+ std::stringstream ss;
+
+ length = ks_bytes_get_length(b.data);
+ data = (uint8_t*)malloc(length);
+ ks_bytes_get_data(b.data, data);
+
+ bool first = true;
+ for (uint64_t i = 0; i < length; i++)
+ {
+ if (!first)
+ ss << ", ";
+ ss << std::hex << "0x" << (int)data[i];
+ first = false;
+ }
+ free(data);
+
+ return out << "[ " << ss.str() << " ]";
+}
+
+#define COMPARE_ARRAY(typ, act, ...) \
+ { \
+ typ temp1[] = {__VA_ARGS__}; \
+ std::vector temp2( \
+ temp1, \
+ temp1 + sizeof(temp1) / sizeof(typ) \
+ ); \
+ std::vector temp3( \
+ act->data, \
+ act->data + act->size \
+ ); \
+ BOOST_CHECK_EQUAL_COLLECTIONS( \
+ temp3.begin(), temp3.end(), \
+ temp2.begin(), temp2.end() \
+ ); \
+ }
+
+#define COMPARE_ARRAY_POINTER(typ, act, ...) \
+ { \
+ typ* temp1[] = {__VA_ARGS__}; \
+ std::vector temp2( \
+ temp1, \
+ temp1 + sizeof(temp1) / sizeof(typ*) \
+ ); \
+ std::vector temp3( \
+ act->data, \
+ act->data + act->size \
+ ); \
+ std::vector temp4; \
+ for (uint32_t i = 0; i < temp2.size(); i++) { \
+ temp4.push_back(*temp2[i]); \
+ } \
+ std::vector temp5; \
+ for (uint32_t i = 0; i < temp3.size(); i++) { \
+ temp5.push_back(*temp3[i]); \
+ } \
+ BOOST_CHECK_EQUAL_COLLECTIONS( \
+ temp5.begin(), temp5.end(), \
+ temp4.begin(), temp4.end() \
+ ); \
+ }
diff --git a/translator/src/main/scala/io/kaitai/struct/testtranslator/Main.scala b/translator/src/main/scala/io/kaitai/struct/testtranslator/Main.scala
index c5dfbdc10..6e44b908e 100644
--- a/translator/src/main/scala/io/kaitai/struct/testtranslator/Main.scala
+++ b/translator/src/main/scala/io/kaitai/struct/testtranslator/Main.scala
@@ -14,6 +14,7 @@ object Main extends App {
val importsDir = s"$baseDir/../formats"
val ALL_LANGS = List(
+ "c",
"construct",
"cpp_stl_98",
"cpp_stl_11",
diff --git a/translator/src/main/scala/io/kaitai/struct/testtranslator/TestTranslator.scala b/translator/src/main/scala/io/kaitai/struct/testtranslator/TestTranslator.scala
index 336bdff87..85fa1fa35 100644
--- a/translator/src/main/scala/io/kaitai/struct/testtranslator/TestTranslator.scala
+++ b/translator/src/main/scala/io/kaitai/struct/testtranslator/TestTranslator.scala
@@ -108,6 +108,7 @@ class TestTranslator(options: CLIOptions) {
}
def getSG(lang: String, testSpec: TestSpec, provider: ClassTypeProvider): BaseGenerator = lang match {
+ case "c" => new CSG(testSpec, provider)
case "construct" => new ConstructSG(testSpec, provider)
case "cpp_stl_98" => new CppStlSG(testSpec, provider, CppRuntimeConfig().copyAsCpp98())
case "cpp_stl_11" => new CppStlSG(testSpec, provider, CppRuntimeConfig().copyAsCpp11())
diff --git a/translator/src/main/scala/io/kaitai/struct/testtranslator/specgenerators/CSG.scala b/translator/src/main/scala/io/kaitai/struct/testtranslator/specgenerators/CSG.scala
new file mode 100644
index 000000000..34504410f
--- /dev/null
+++ b/translator/src/main/scala/io/kaitai/struct/testtranslator/specgenerators/CSG.scala
@@ -0,0 +1,125 @@
+package io.kaitai.struct.testtranslator.specgenerators
+
+import io.kaitai.struct.datatype.{DataType, KSError}
+import io.kaitai.struct.exprlang.Ast
+import io.kaitai.struct.languages.CCompiler
+import io.kaitai.struct.testtranslator.{Main, TestEquals, TestSpec}
+import io.kaitai.struct.datatype.DataType._
+import io.kaitai.struct.translators.CTranslator
+import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig}
+import io.kaitai.struct.languages.components.CppImportList
+
+class CSG(spec: TestSpec, provider: ClassTypeProvider) extends BaseGenerator(spec) {
+ val compiler = new CCompiler(provider, RuntimeConfig())
+ val className = spec.id.toLowerCase()
+ val cppImportList = new CppImportList
+ val translator = new CTranslator(provider, cppImportList, false)
+
+ override def fileName(name: String): String = s"test_$name.cpp"
+
+ cppImportList.addLocal("operators.h")
+ cppImportList.addSystem("boost/test/unit_test.hpp")
+ spec.extraImports.foreach(entry => cppImportList.addLocal(s"$entry.h"))
+ cppImportList.addSystem("stdio.h")
+
+ override def header() = {
+ out.puts("static void log(const char* text) {")
+ out.puts(" printf(text);")
+ out.puts("}")
+ out.puts
+ out.puts(s"BOOST_AUTO_TEST_CASE(test_${spec.id}) {")
+ out.inc
+ }
+
+ override def runParse(): Unit = {
+ runParseCommon1()
+ out.puts(s"BOOST_CHECK_EQUAL(error, 0);")
+ out.puts("if (error != 0) return;")
+ }
+
+ override def runParseExpectError(exception: KSError): Unit = {
+ runParseCommon1()
+ out.puts(s"BOOST_CHECK_EQUAL(error == 0, 0);")
+ }
+
+ def runParseCommon1(): Unit = {
+ out.puts(s"ksx_$className* data;")
+ out.puts("ks_stream* stream;")
+ out.puts("ks_config* config;")
+ out.puts("ks_error error;")
+ out.puts("config = ks_config_create(log);")
+ out.puts("FILE* file = fopen(\"src/" + spec.data + "\", \"r\");")
+ out.puts("BOOST_CHECK_EQUAL(file != 0, 1);")
+ out.puts("if (!file) return;")
+ out.puts("stream = ks_stream_create_from_file(file, config);")
+ out.puts(s"data = ksx_read_${className}_from_stream(stream, &error);")
+ out.puts(s"(void)data;")
+ }
+
+ override def footer() = {
+ out.puts
+ out.puts("ks_config_destroy(config);")
+ out.dec
+ out.puts("}")
+ }
+
+ override def simpleEquality(check: TestEquals): Unit = {
+ val ptr = translator.detectType(check.expected) match {
+ case t: StrType => "*"
+ case _ => ""
+ }
+ val actStr = translateAct(check.actual)
+ val expStr = translator.translate(check.expected)
+ translator.detectType(check.expected) match {
+ case t: BytesType =>
+ out.puts(s"BOOST_CHECK_EQUAL(bytes_wrapper($actStr), bytes_wrapper($expStr));")
+ case _ =>
+ out.puts(s"BOOST_CHECK_EQUAL($ptr$actStr, $ptr$expStr);")
+ }
+ }
+
+ override def floatEquality(check: TestEquals): Unit = {
+ val actStr = translateAct(check.actual)
+ val expStr = translator.translate(check.expected)
+ out.puts(s"BOOST_CHECK_CLOSE($actStr, $expStr, 1e-4);")
+ }
+
+ override def nullAssert(actual: Ast.expr): Unit = {
+ val nullCheckStr = actual match {
+ case Ast.expr.Attribute(x, Ast.identifier(attrName)) =>
+ fixup("!" + translator.anyField(x, s"_is_valid_$attrName"))
+ }
+ out.puts(s"BOOST_CHECK($nullCheckStr);")
+ }
+
+ override def trueArrayEquality(check: TestEquals, elType: DataType, elts: Seq[Ast.expr]): Unit = {
+ val elTypeName = CCompiler.kaitaiType2NativeType(elType)
+ val eltsStr = elts.map(translator.translate).mkString(", ")
+ val actStr = translateAct(check.actual)
+ elType match {
+ case t: StrType =>
+ out.puts(s"COMPARE_ARRAY_POINTER(ks_string, $actStr, $eltsStr);")
+ case _ =>
+ out.puts(s"COMPARE_ARRAY($elTypeName, $actStr, $eltsStr);")
+ }
+ }
+
+ override def indentStr: String = " "
+
+ override def results: String = {
+ "// " + AUTOGEN_COMMENT + "\n\n" +
+ "#define KS_USE_ZLIB\n" +
+ "#define KS_USE_ICONV\n" +
+ "extern \"C\" {\n" +
+ "#include \"" + spec.id + ".h\"\n" +
+ "}\n" +
+ cppImportList.result + "\n" +
+ out.result
+ }
+
+ def fixup(s: String) : String = s.replace("->" + Main.INIT_OBJ_NAME, "")
+
+ def translateAct(x: Ast.expr) = {
+ fixup(translator.translate(x))
+ }
+}
diff --git a/valgrind-c b/valgrind-c
new file mode 100755
index 000000000..983328e12
--- /dev/null
+++ b/valgrind-c
@@ -0,0 +1,15 @@
+#!/bin/sh -e
+
+. ./config
+
+if [ "$#" -ne 2 ]; then
+ echo "$0 "
+ exit 1
+fi
+
+SRC_DIR="$1"
+C_TEST_OUT_DIR="$2"
+
+OBJ_DIR="$SRC_DIR/bin"
+
+valgrind --leak-check=full --xml=yes --xml-file="$C_TEST_OUT_DIR/valgrind.xml" "$OBJ_DIR/ks_tests"