* [PATCH olang v2 0/2] Add integration tests
@ 2024-02-16 2:58 Carlos Maniero
2024-02-16 2:58 ` [PATCH olang v2 1/2] tests: add munit testing framework file Carlos Maniero
` (2 more replies)
0 siblings, 3 replies; 40+ messages in thread
From: Carlos Maniero @ 2024-02-16 2:58 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
This patch is divided by two commits, the first is a copy of the
customer version of munit cloned from pipalang. The second is a
integration test setup that ensures the 0c binary is there and is
executable.
This patchset apply the changes suggested by johnny@johnnyrichard.com on
20240215162146.847336-1-carlos@maniero.me.
v2:
- uses perror and exit instead of assert
- replace make with $(MAKE) on Makefile
- create a check target on make to run all tests
- make linter and linter-fix to lint all files
- Although I still delegating the linter stuff to integration's
Makefile the reason is because I have to remove munit files from
the list to be linted since these files takes a long time to be
processed and I don't wanna leak this detail to the global
Makefile.
Carlos Maniero (2):
tests: add munit testing framework file
tests: add integration test setup
.build.yml | 4 +
Makefile | 12 +
tests/integration/Makefile | 27 +
tests/integration/cli_runner.c | 77 ++
tests/integration/cli_runner.h | 27 +
tests/integration/cli_test.c | 39 +
tests/integration/munit.c | 2057 ++++++++++++++++++++++++++++++++
tests/integration/munit.h | 536 +++++++++
8 files changed, 2779 insertions(+)
create mode 100644 tests/integration/Makefile
create mode 100644 tests/integration/cli_runner.c
create mode 100644 tests/integration/cli_runner.h
create mode 100644 tests/integration/cli_test.c
create mode 100644 tests/integration/munit.c
create mode 100644 tests/integration/munit.h
--
2.34.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v2 1/2] tests: add munit testing framework file
2024-02-16 2:58 [PATCH olang v2 0/2] Add integration tests Carlos Maniero
@ 2024-02-16 2:58 ` Carlos Maniero
2024-02-16 2:58 ` [PATCH olang v2 2/2] tests: add integration test setup Carlos Maniero
2024-02-16 3:15 ` [PATCH olang v2 0/2] Add integration tests Carlos Maniero
2 siblings, 0 replies; 40+ messages in thread
From: Carlos Maniero @ 2024-02-16 2:58 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
You can learn more about it on https://nemequ.github.io/munit/
Although, this is the pipa's version:
- https://git.sr.ht/~johnnyrichard/pipac/blob/master/test/munit.c
- https://git.sr.ht/~johnnyrichard/pipac/blob/master/test/munit.h
The only change from the original project is that pipa's version
prints errors in a way vim can interprets the test file that fails.
Signed-off-by: Carlos Maniero <carlos@maniero.me>
---
tests/integration/munit.c | 2057 +++++++++++++++++++++++++++++++++++++
tests/integration/munit.h | 536 ++++++++++
2 files changed, 2593 insertions(+)
create mode 100644 tests/integration/munit.c
create mode 100644 tests/integration/munit.h
diff --git a/tests/integration/munit.c b/tests/integration/munit.c
new file mode 100644
index 0000000..43e8e13
--- /dev/null
+++ b/tests/integration/munit.c
@@ -0,0 +1,2057 @@
+// clang-format off
+/* Copyright (c) 2013-2018 Evan Nemerson <evan@nemerson.com>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*** Configuration ***/
+
+/* This is just where the output from the test goes. It's really just
+ * meant to let you choose stdout or stderr, but if anyone really want
+ * to direct it to a file let me know, it would be fairly easy to
+ * support. */
+#if !defined(MUNIT_OUTPUT_FILE)
+# define MUNIT_OUTPUT_FILE stdout
+#endif
+
+/* This is a bit more useful; it tells µnit how to format the seconds in
+ * timed tests. If your tests run for longer you might want to reduce
+ * it, and if your computer is really fast and your tests are tiny you
+ * can increase it. */
+#if !defined(MUNIT_TEST_TIME_FORMAT)
+# define MUNIT_TEST_TIME_FORMAT "0.8f"
+#endif
+
+/* If you have long test names you might want to consider bumping
+ * this. The result information takes 43 characters. */
+#if !defined(MUNIT_TEST_NAME_LEN)
+# define MUNIT_TEST_NAME_LEN 37
+#endif
+
+/* If you don't like the timing information, you can disable it by
+ * defining MUNIT_DISABLE_TIMING. */
+#if !defined(MUNIT_DISABLE_TIMING)
+# define MUNIT_ENABLE_TIMING
+#endif
+
+/*** End configuration ***/
+
+#if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200809L)
+# undef _POSIX_C_SOURCE
+#endif
+#if !defined(_POSIX_C_SOURCE)
+# define _POSIX_C_SOURCE 200809L
+#endif
+
+/* Solaris freaks out if you try to use a POSIX or SUS standard without
+ * the "right" C standard. */
+#if defined(_XOPEN_SOURCE)
+# undef _XOPEN_SOURCE
+#endif
+
+#if defined(__STDC_VERSION__)
+# if __STDC_VERSION__ >= 201112L
+# define _XOPEN_SOURCE 700
+# elif __STDC_VERSION__ >= 199901L
+# define _XOPEN_SOURCE 600
+# endif
+#endif
+
+/* Because, according to Microsoft, POSIX is deprecated. You've got
+ * to appreciate the chutzpah. */
+#if defined(_MSC_VER) && !defined(_CRT_NONSTDC_NO_DEPRECATE)
+# define _CRT_NONSTDC_NO_DEPRECATE
+#endif
+
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+# include <stdbool.h>
+#elif defined(_WIN32)
+/* https://msdn.microsoft.com/en-us/library/tf4dy80a.aspx */
+#endif
+
+#include <limits.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <setjmp.h>
+
+#if !defined(MUNIT_NO_NL_LANGINFO) && !defined(_WIN32)
+#define MUNIT_NL_LANGINFO
+#include <locale.h>
+#include <langinfo.h>
+#include <strings.h>
+#endif
+
+#if !defined(_WIN32)
+# include <unistd.h>
+# include <sys/types.h>
+# include <sys/wait.h>
+#else
+# include <windows.h>
+# include <io.h>
+# include <fcntl.h>
+# if !defined(STDERR_FILENO)
+# define STDERR_FILENO _fileno(stderr)
+# endif
+#endif
+
+#include "munit.h"
+
+#define MUNIT_STRINGIFY(x) #x
+#define MUNIT_XSTRINGIFY(x) MUNIT_STRINGIFY(x)
+
+#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__)
+# define MUNIT_THREAD_LOCAL __thread
+#elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) || defined(_Thread_local)
+# define MUNIT_THREAD_LOCAL _Thread_local
+#elif defined(_WIN32)
+# define MUNIT_THREAD_LOCAL __declspec(thread)
+#endif
+
+/* MSVC 12.0 will emit a warning at /W4 for code like 'do { ... }
+ * while (0)', or 'do { ... } while (1)'. I'm pretty sure nobody
+ * at Microsoft compiles with /W4. */
+#if defined(_MSC_VER) && (_MSC_VER <= 1800)
+#pragma warning(disable: 4127)
+#endif
+
+#if defined(_WIN32) || defined(__EMSCRIPTEN__)
+# define MUNIT_NO_FORK
+#endif
+
+#if defined(__EMSCRIPTEN__)
+# define MUNIT_NO_BUFFER
+#endif
+
+/*** Logging ***/
+
+static MunitLogLevel munit_log_level_visible = MUNIT_LOG_INFO;
+static MunitLogLevel munit_log_level_fatal = MUNIT_LOG_ERROR;
+
+#if defined(MUNIT_THREAD_LOCAL)
+static MUNIT_THREAD_LOCAL munit_bool munit_error_jmp_buf_valid = 0;
+static MUNIT_THREAD_LOCAL jmp_buf munit_error_jmp_buf;
+#endif
+
+/* At certain warning levels, mingw will trigger warnings about
+ * suggesting the format attribute, which we've explicity *not* set
+ * because it will then choke on our attempts to use the MS-specific
+ * I64 modifier for size_t (which we have to use since MSVC doesn't
+ * support the C99 z modifier). */
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
+#endif
+
+MUNIT_PRINTF(5,0)
+static void
+munit_logf_exv(MunitLogLevel level, FILE* fp, const char* filename, int line, const char* format, va_list ap) {
+ if (level < munit_log_level_visible)
+ return;
+
+ if (filename != NULL)
+ fprintf(fp, "%s:%d: ", filename, line);
+
+ switch (level) {
+ case MUNIT_LOG_DEBUG:
+ fputs("Debug", fp);
+ break;
+ case MUNIT_LOG_INFO:
+ fputs("Info", fp);
+ break;
+ case MUNIT_LOG_WARNING:
+ fputs("Warning", fp);
+ break;
+ case MUNIT_LOG_ERROR:
+ fputs("Error", fp);
+ break;
+ default:
+ munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Invalid log level (%d)", level);
+ return;
+ }
+
+ fputs(": ", fp);
+ vfprintf(fp, format, ap);
+ fputc('\n', fp);
+}
+
+MUNIT_PRINTF(3,4)
+static void
+munit_logf_internal(MunitLogLevel level, FILE* fp, const char* format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ munit_logf_exv(level, fp, NULL, 0, format, ap);
+ va_end(ap);
+}
+
+static void
+munit_log_internal(MunitLogLevel level, FILE* fp, const char* message) {
+ munit_logf_internal(level, fp, "%s", message);
+}
+
+void
+munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ munit_logf_exv(level, stderr, filename, line, format, ap);
+ va_end(ap);
+
+ if (level >= munit_log_level_fatal) {
+#if defined(MUNIT_THREAD_LOCAL)
+ if (munit_error_jmp_buf_valid)
+ longjmp(munit_error_jmp_buf, 1);
+#endif
+ abort();
+ }
+}
+
+void
+munit_errorf_ex(const char* filename, int line, const char* format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ munit_logf_exv(MUNIT_LOG_ERROR, stderr, filename, line, format, ap);
+ va_end(ap);
+
+#if defined(MUNIT_THREAD_LOCAL)
+ if (munit_error_jmp_buf_valid)
+ longjmp(munit_error_jmp_buf, 1);
+#endif
+ abort();
+}
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+#pragma GCC diagnostic pop
+#endif
+
+#if !defined(MUNIT_STRERROR_LEN)
+# define MUNIT_STRERROR_LEN 80
+#endif
+
+static void
+munit_log_errno(MunitLogLevel level, FILE* fp, const char* msg) {
+#if defined(MUNIT_NO_STRERROR_R) || (defined(__MINGW32__) && !defined(MINGW_HAS_SECURE_API))
+ munit_logf_internal(level, fp, "%s: %s (%d)", msg, strerror(errno), errno);
+#else
+ char munit_error_str[MUNIT_STRERROR_LEN];
+ munit_error_str[0] = '\0';
+
+#if !defined(_WIN32)
+ strerror_r(errno, munit_error_str, MUNIT_STRERROR_LEN);
+#else
+ strerror_s(munit_error_str, MUNIT_STRERROR_LEN, errno);
+#endif
+
+ munit_logf_internal(level, fp, "%s: %s (%d)", msg, munit_error_str, errno);
+#endif
+}
+
+/*** Memory allocation ***/
+
+void*
+munit_malloc_ex(const char* filename, int line, size_t size) {
+ void* ptr;
+
+ if (size == 0)
+ return NULL;
+
+ ptr = calloc(1, size);
+ if (MUNIT_UNLIKELY(ptr == NULL)) {
+ munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Failed to allocate %" MUNIT_SIZE_MODIFIER "u bytes.", size);
+ }
+
+ return ptr;
+}
+
+/*** Timer code ***/
+
+#if defined(MUNIT_ENABLE_TIMING)
+
+#define psnip_uint64_t munit_uint64_t
+#define psnip_uint32_t munit_uint32_t
+
+/* Code copied from portable-snippets
+ * <https://github.com/nemequ/portable-snippets/>. If you need to
+ * change something, please do it there so we can keep the code in
+ * sync. */
+
+/* Clocks (v1)
+ * Portable Snippets - https://gitub.com/nemequ/portable-snippets
+ * Created by Evan Nemerson <evan@nemerson.com>
+ *
+ * To the extent possible under law, the authors have waived all
+ * copyright and related or neighboring rights to this code. For
+ * details, see the Creative Commons Zero 1.0 Universal license at
+ * https://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#if !defined(PSNIP_CLOCK_H)
+#define PSNIP_CLOCK_H
+
+#if !defined(psnip_uint64_t)
+# include "../exact-int/exact-int.h"
+#endif
+
+#if !defined(PSNIP_CLOCK_STATIC_INLINE)
+# if defined(__GNUC__)
+# define PSNIP_CLOCK__COMPILER_ATTRIBUTES __attribute__((__unused__))
+# else
+# define PSNIP_CLOCK__COMPILER_ATTRIBUTES
+# endif
+
+# define PSNIP_CLOCK__FUNCTION PSNIP_CLOCK__COMPILER_ATTRIBUTES static
+#endif
+
+enum PsnipClockType {
+ /* This clock provides the current time, in units since 1970-01-01
+ * 00:00:00 UTC not including leap seconds. In other words, UNIX
+ * time. Keep in mind that this clock doesn't account for leap
+ * seconds, and can go backwards (think NTP adjustments). */
+ PSNIP_CLOCK_TYPE_WALL = 1,
+ /* The CPU time is a clock which increases only when the current
+ * process is active (i.e., it doesn't increment while blocking on
+ * I/O). */
+ PSNIP_CLOCK_TYPE_CPU = 2,
+ /* Monotonic time is always running (unlike CPU time), but it only
+ ever moves forward unless you reboot the system. Things like NTP
+ adjustments have no effect on this clock. */
+ PSNIP_CLOCK_TYPE_MONOTONIC = 3
+};
+
+struct PsnipClockTimespec {
+ psnip_uint64_t seconds;
+ psnip_uint64_t nanoseconds;
+};
+
+/* Methods we support: */
+
+#define PSNIP_CLOCK_METHOD_CLOCK_GETTIME 1
+#define PSNIP_CLOCK_METHOD_TIME 2
+#define PSNIP_CLOCK_METHOD_GETTIMEOFDAY 3
+#define PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER 4
+#define PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME 5
+#define PSNIP_CLOCK_METHOD_CLOCK 6
+#define PSNIP_CLOCK_METHOD_GETPROCESSTIMES 7
+#define PSNIP_CLOCK_METHOD_GETRUSAGE 8
+#define PSNIP_CLOCK_METHOD_GETSYSTEMTIMEPRECISEASFILETIME 9
+#define PSNIP_CLOCK_METHOD_GETTICKCOUNT64 10
+
+#include <assert.h>
+
+#if defined(HEDLEY_UNREACHABLE)
+# define PSNIP_CLOCK_UNREACHABLE() HEDLEY_UNREACHABLE()
+#else
+# define PSNIP_CLOCK_UNREACHABLE() assert(0)
+#endif
+
+/* Choose an implementation */
+
+/* #undef PSNIP_CLOCK_WALL_METHOD */
+/* #undef PSNIP_CLOCK_CPU_METHOD */
+/* #undef PSNIP_CLOCK_MONOTONIC_METHOD */
+
+/* We want to be able to detect the libc implementation, so we include
+ <limits.h> (<features.h> isn't available everywhere). */
+
+#if defined(__unix__) || defined(__unix) || defined(__linux__)
+# include <limits.h>
+# include <unistd.h>
+#endif
+
+#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
+/* These are known to work without librt. If you know of others
+ * please let us know so we can add them. */
+# if \
+ (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17))) || \
+ (defined(__FreeBSD__))
+# define PSNIP_CLOCK_HAVE_CLOCK_GETTIME
+# elif !defined(PSNIP_CLOCK_NO_LIBRT)
+# define PSNIP_CLOCK_HAVE_CLOCK_GETTIME
+# endif
+#endif
+
+#if defined(_WIN32)
+# if !defined(PSNIP_CLOCK_CPU_METHOD)
+# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_GETPROCESSTIMES
+# endif
+# if !defined(PSNIP_CLOCK_MONOTONIC_METHOD)
+# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER
+# endif
+#endif
+
+#if defined(__MACH__) && !defined(__gnu_hurd__)
+# if !defined(PSNIP_CLOCK_MONOTONIC_METHOD)
+# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME
+# endif
+#endif
+
+#if defined(PSNIP_CLOCK_HAVE_CLOCK_GETTIME)
+# include <time.h>
+# if !defined(PSNIP_CLOCK_WALL_METHOD)
+# if defined(CLOCK_REALTIME_PRECISE)
+# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+# define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME_PRECISE
+# elif !defined(__sun)
+# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+# define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME
+# endif
+# endif
+# if !defined(PSNIP_CLOCK_CPU_METHOD)
+# if defined(_POSIX_CPUTIME) || defined(CLOCK_PROCESS_CPUTIME_ID)
+# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+# define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_PROCESS_CPUTIME_ID
+# elif defined(CLOCK_VIRTUAL)
+# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+# define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_VIRTUAL
+# endif
+# endif
+# if !defined(PSNIP_CLOCK_MONOTONIC_METHOD)
+# if defined(CLOCK_MONOTONIC_RAW)
+# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+# define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC
+# elif defined(CLOCK_MONOTONIC_PRECISE)
+# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+# define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC_PRECISE
+# elif defined(_POSIX_MONOTONIC_CLOCK) || defined(CLOCK_MONOTONIC)
+# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+# define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC
+# endif
+# endif
+#endif
+
+#if defined(_POSIX_VERSION) && (_POSIX_VERSION >= 200112L)
+# if !defined(PSNIP_CLOCK_WALL_METHOD)
+# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_GETTIMEOFDAY
+# endif
+#endif
+
+#if !defined(PSNIP_CLOCK_WALL_METHOD)
+# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_TIME
+#endif
+
+#if !defined(PSNIP_CLOCK_CPU_METHOD)
+# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK
+#endif
+
+/* Primarily here for testing. */
+#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) && defined(PSNIP_CLOCK_REQUIRE_MONOTONIC)
+# error No monotonic clock found.
+#endif
+
+/* Implementations */
+
+#if \
+ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \
+ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \
+ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \
+ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \
+ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \
+ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \
+ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \
+ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \
+ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_TIME))
+# include <time.h>
+#endif
+
+#if \
+ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \
+ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \
+ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY))
+# include <sys/time.h>
+#endif
+
+#if \
+ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \
+ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \
+ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \
+ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \
+ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \
+ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64))
+# include <windows.h>
+#endif
+
+#if \
+ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \
+ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \
+ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE))
+# include <sys/resource.h>
+# include <sys/time.h>
+#endif
+
+#if \
+ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \
+ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \
+ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME))
+# include <CoreServices/CoreServices.h>
+# include <mach/mach.h>
+# include <mach/mach_time.h>
+#endif
+
+/*** Implementations ***/
+
+#define PSNIP_CLOCK_NSEC_PER_SEC ((psnip_uint32_t) (1000000000ULL))
+
+#if \
+ (defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \
+ (defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \
+ (defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME))
+PSNIP_CLOCK__FUNCTION psnip_uint32_t
+psnip_clock__clock_getres (clockid_t clk_id) {
+ struct timespec res;
+ int r;
+
+ r = clock_getres(clk_id, &res);
+ if (r != 0)
+ return 0;
+
+ return (psnip_uint32_t) (PSNIP_CLOCK_NSEC_PER_SEC / res.tv_nsec);
+}
+
+PSNIP_CLOCK__FUNCTION int
+psnip_clock__clock_gettime (clockid_t clk_id, struct PsnipClockTimespec* res) {
+ struct timespec ts;
+
+ if (clock_gettime(clk_id, &ts) != 0)
+ return -10;
+
+ res->seconds = (psnip_uint64_t) (ts.tv_sec);
+ res->nanoseconds = (psnip_uint64_t) (ts.tv_nsec);
+
+ return 0;
+}
+#endif
+
+PSNIP_CLOCK__FUNCTION psnip_uint32_t
+psnip_clock_wall_get_precision (void) {
+#if !defined(PSNIP_CLOCK_WALL_METHOD)
+ return 0;
+#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+ return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_WALL);
+#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY
+ return 1000000;
+#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+PSNIP_CLOCK__FUNCTION int
+psnip_clock_wall_get_time (struct PsnipClockTimespec* res) {
+ (void) res;
+
+#if !defined(PSNIP_CLOCK_WALL_METHOD)
+ return -2;
+#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+ return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_WALL, res);
+#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME
+ res->seconds = time(NULL);
+ res->nanoseconds = 0;
+#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY
+ struct timeval tv;
+
+ if (gettimeofday(&tv, NULL) != 0)
+ return -6;
+
+ res->seconds = tv.tv_sec;
+ res->nanoseconds = tv.tv_usec * 1000;
+#else
+ return -2;
+#endif
+
+ return 0;
+}
+
+PSNIP_CLOCK__FUNCTION psnip_uint32_t
+psnip_clock_cpu_get_precision (void) {
+#if !defined(PSNIP_CLOCK_CPU_METHOD)
+ return 0;
+#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+ return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_CPU);
+#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK
+ return CLOCKS_PER_SEC;
+#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES
+ return PSNIP_CLOCK_NSEC_PER_SEC / 100;
+#else
+ return 0;
+#endif
+}
+
+PSNIP_CLOCK__FUNCTION int
+psnip_clock_cpu_get_time (struct PsnipClockTimespec* res) {
+#if !defined(PSNIP_CLOCK_CPU_METHOD)
+ (void) res;
+ return -2;
+#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+ return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_CPU, res);
+#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK
+ clock_t t = clock();
+ if (t == ((clock_t) -1))
+ return -5;
+ res->seconds = t / CLOCKS_PER_SEC;
+ res->nanoseconds = (t % CLOCKS_PER_SEC) * (PSNIP_CLOCK_NSEC_PER_SEC / CLOCKS_PER_SEC);
+#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES
+ FILETIME CreationTime, ExitTime, KernelTime, UserTime;
+ LARGE_INTEGER date, adjust;
+
+ if (!GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime))
+ return -7;
+
+ /* http://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/ */
+ date.HighPart = UserTime.dwHighDateTime;
+ date.LowPart = UserTime.dwLowDateTime;
+ adjust.QuadPart = 11644473600000 * 10000;
+ date.QuadPart -= adjust.QuadPart;
+
+ res->seconds = date.QuadPart / 10000000;
+ res->nanoseconds = (date.QuadPart % 10000000) * (PSNIP_CLOCK_NSEC_PER_SEC / 100);
+#elif PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE
+ struct rusage usage;
+ if (getrusage(RUSAGE_SELF, &usage) != 0)
+ return -8;
+
+ res->seconds = usage.ru_utime.tv_sec;
+ res->nanoseconds = tv.tv_usec * 1000;
+#else
+ (void) res;
+ return -2;
+#endif
+
+ return 0;
+}
+
+PSNIP_CLOCK__FUNCTION psnip_uint32_t
+psnip_clock_monotonic_get_precision (void) {
+#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD)
+ return 0;
+#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+ return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC);
+#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME
+ static mach_timebase_info_data_t tbi = { 0, };
+ if (tbi.denom == 0)
+ mach_timebase_info(&tbi);
+ return (psnip_uint32_t) (tbi.numer / tbi.denom);
+#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64
+ return 1000;
+#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER
+ LARGE_INTEGER Frequency;
+ QueryPerformanceFrequency(&Frequency);
+ return (psnip_uint32_t) ((Frequency.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC) ? PSNIP_CLOCK_NSEC_PER_SEC : Frequency.QuadPart);
+#else
+ return 0;
+#endif
+}
+
+PSNIP_CLOCK__FUNCTION int
+psnip_clock_monotonic_get_time (struct PsnipClockTimespec* res) {
+#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD)
+ (void) res;
+ return -2;
+#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
+ return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC, res);
+#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME
+ psnip_uint64_t nsec = mach_absolute_time();
+ static mach_timebase_info_data_t tbi = { 0, };
+ if (tbi.denom == 0)
+ mach_timebase_info(&tbi);
+ nsec *= ((psnip_uint64_t) tbi.numer) / ((psnip_uint64_t) tbi.denom);
+ res->seconds = nsec / PSNIP_CLOCK_NSEC_PER_SEC;
+ res->nanoseconds = nsec % PSNIP_CLOCK_NSEC_PER_SEC;
+#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER
+ LARGE_INTEGER t, f;
+ if (QueryPerformanceCounter(&t) == 0)
+ return -12;
+
+ QueryPerformanceFrequency(&f);
+ res->seconds = t.QuadPart / f.QuadPart;
+ res->nanoseconds = t.QuadPart % f.QuadPart;
+ if (f.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC)
+ res->nanoseconds /= f.QuadPart / PSNIP_CLOCK_NSEC_PER_SEC;
+ else
+ res->nanoseconds *= PSNIP_CLOCK_NSEC_PER_SEC / f.QuadPart;
+#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64
+ const ULONGLONG msec = GetTickCount64();
+ res->seconds = msec / 1000;
+ res->nanoseconds = sec % 1000;
+#else
+ return -2;
+#endif
+
+ return 0;
+}
+
+/* Returns the number of ticks per second for the specified clock.
+ * For example, a clock with millisecond precision would return 1000,
+ * and a clock with 1 second (such as the time() function) would
+ * return 1.
+ *
+ * If the requested clock isn't available, it will return 0.
+ * Hopefully this will be rare, but if it happens to you please let us
+ * know so we can work on finding a way to support your system.
+ *
+ * Note that different clocks on the same system often have a
+ * different precisions.
+ */
+PSNIP_CLOCK__FUNCTION psnip_uint32_t
+psnip_clock_get_precision (enum PsnipClockType clock_type) {
+ switch (clock_type) {
+ case PSNIP_CLOCK_TYPE_MONOTONIC:
+ return psnip_clock_monotonic_get_precision ();
+ case PSNIP_CLOCK_TYPE_CPU:
+ return psnip_clock_cpu_get_precision ();
+ case PSNIP_CLOCK_TYPE_WALL:
+ return psnip_clock_wall_get_precision ();
+ }
+
+ PSNIP_CLOCK_UNREACHABLE();
+ return 0;
+}
+
+/* Set the provided timespec to the requested time. Returns 0 on
+ * success, or a negative value on failure. */
+PSNIP_CLOCK__FUNCTION int
+psnip_clock_get_time (enum PsnipClockType clock_type, struct PsnipClockTimespec* res) {
+ assert(res != NULL);
+
+ switch (clock_type) {
+ case PSNIP_CLOCK_TYPE_MONOTONIC:
+ return psnip_clock_monotonic_get_time (res);
+ case PSNIP_CLOCK_TYPE_CPU:
+ return psnip_clock_cpu_get_time (res);
+ case PSNIP_CLOCK_TYPE_WALL:
+ return psnip_clock_wall_get_time (res);
+ }
+
+ return -1;
+}
+
+#endif /* !defined(PSNIP_CLOCK_H) */
+
+static psnip_uint64_t
+munit_clock_get_elapsed(struct PsnipClockTimespec* start, struct PsnipClockTimespec* end) {
+ psnip_uint64_t r = (end->seconds - start->seconds) * PSNIP_CLOCK_NSEC_PER_SEC;
+ if (end->nanoseconds < start->nanoseconds) {
+ r -= (start->nanoseconds - end->nanoseconds);
+ } else {
+ r += (end->nanoseconds - start->nanoseconds);
+ }
+ return r;
+}
+
+#else
+# include <time.h>
+#endif /* defined(MUNIT_ENABLE_TIMING) */
+
+/*** PRNG stuff ***/
+
+/* This is (unless I screwed up, which is entirely possible) the
+ * version of PCG with 32-bit state. It was chosen because it has a
+ * small enough state that we should reliably be able to use CAS
+ * instead of requiring a lock for thread-safety.
+ *
+ * If I did screw up, I probably will not bother changing it unless
+ * there is a significant bias. It's really not important this be
+ * particularly strong, as long as it is fairly random it's much more
+ * important that it be reproducible, so bug reports have a better
+ * chance of being reproducible. */
+
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) && !defined(__EMSCRIPTEN__) && (!defined(__GNUC_MINOR__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ > 8))
+# define HAVE_STDATOMIC
+#elif defined(__clang__)
+# if __has_extension(c_atomic)
+# define HAVE_CLANG_ATOMICS
+# endif
+#endif
+
+/* Workaround for http://llvm.org/bugs/show_bug.cgi?id=26911 */
+#if defined(__clang__) && defined(_WIN32)
+# undef HAVE_STDATOMIC
+# if defined(__c2__)
+# undef HAVE_CLANG_ATOMICS
+# endif
+#endif
+
+#if defined(_OPENMP)
+# define ATOMIC_UINT32_T uint32_t
+# define ATOMIC_UINT32_INIT(x) (x)
+#elif defined(HAVE_STDATOMIC)
+# include <stdatomic.h>
+# define ATOMIC_UINT32_T _Atomic uint32_t
+# define ATOMIC_UINT32_INIT(x) ATOMIC_VAR_INIT(x)
+#elif defined(HAVE_CLANG_ATOMICS)
+# define ATOMIC_UINT32_T _Atomic uint32_t
+# define ATOMIC_UINT32_INIT(x) (x)
+#elif defined(_WIN32)
+# define ATOMIC_UINT32_T volatile LONG
+# define ATOMIC_UINT32_INIT(x) (x)
+#else
+# define ATOMIC_UINT32_T volatile uint32_t
+# define ATOMIC_UINT32_INIT(x) (x)
+#endif
+
+static ATOMIC_UINT32_T munit_rand_state = ATOMIC_UINT32_INIT(42);
+
+#if defined(_OPENMP)
+static inline void
+munit_atomic_store(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T value) {
+#pragma omp critical (munit_atomics)
+ *dest = value;
+}
+
+static inline uint32_t
+munit_atomic_load(ATOMIC_UINT32_T* src) {
+ int ret;
+#pragma omp critical (munit_atomics)
+ ret = *src;
+ return ret;
+}
+
+static inline uint32_t
+munit_atomic_cas(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T* expected, ATOMIC_UINT32_T desired) {
+ munit_bool ret;
+
+#pragma omp critical (munit_atomics)
+ {
+ if (*dest == *expected) {
+ *dest = desired;
+ ret = 1;
+ } else {
+ ret = 0;
+ }
+ }
+
+ return ret;
+}
+#elif defined(HAVE_STDATOMIC)
+# define munit_atomic_store(dest, value) atomic_store(dest, value)
+# define munit_atomic_load(src) atomic_load(src)
+# define munit_atomic_cas(dest, expected, value) atomic_compare_exchange_weak(dest, expected, value)
+#elif defined(HAVE_CLANG_ATOMICS)
+# define munit_atomic_store(dest, value) __c11_atomic_store(dest, value, __ATOMIC_SEQ_CST)
+# define munit_atomic_load(src) __c11_atomic_load(src, __ATOMIC_SEQ_CST)
+# define munit_atomic_cas(dest, expected, value) __c11_atomic_compare_exchange_weak(dest, expected, value, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
+#elif defined(__GNUC__) && (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
+# define munit_atomic_store(dest, value) __atomic_store_n(dest, value, __ATOMIC_SEQ_CST)
+# define munit_atomic_load(src) __atomic_load_n(src, __ATOMIC_SEQ_CST)
+# define munit_atomic_cas(dest, expected, value) __atomic_compare_exchange_n(dest, expected, value, 1, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
+#elif defined(__GNUC__) && (__GNUC__ >= 4)
+# define munit_atomic_store(dest,value) do { *(dest) = (value); } while (0)
+# define munit_atomic_load(src) (*(src))
+# define munit_atomic_cas(dest, expected, value) __sync_bool_compare_and_swap(dest, *expected, value)
+#elif defined(_WIN32) /* Untested */
+# define munit_atomic_store(dest,value) do { *(dest) = (value); } while (0)
+# define munit_atomic_load(src) (*(src))
+# define munit_atomic_cas(dest, expected, value) InterlockedCompareExchange((dest), (value), *(expected))
+#else
+# warning No atomic implementation, PRNG will not be thread-safe
+# define munit_atomic_store(dest, value) do { *(dest) = (value); } while (0)
+# define munit_atomic_load(src) (*(src))
+static inline munit_bool
+munit_atomic_cas(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T* expected, ATOMIC_UINT32_T desired) {
+ if (*dest == *expected) {
+ *dest = desired;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+#endif
+
+#define MUNIT_PRNG_MULTIPLIER (747796405U)
+#define MUNIT_PRNG_INCREMENT (1729U)
+
+static munit_uint32_t
+munit_rand_next_state(munit_uint32_t state) {
+ return state * MUNIT_PRNG_MULTIPLIER + MUNIT_PRNG_INCREMENT;
+}
+
+static munit_uint32_t
+munit_rand_from_state(munit_uint32_t state) {
+ munit_uint32_t res = ((state >> ((state >> 28) + 4)) ^ state) * (277803737U);
+ res ^= res >> 22;
+ return res;
+}
+
+void
+munit_rand_seed(munit_uint32_t seed) {
+ munit_uint32_t state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT);
+ munit_atomic_store(&munit_rand_state, state);
+}
+
+static munit_uint32_t
+munit_rand_generate_seed(void) {
+ munit_uint32_t seed, state;
+#if defined(MUNIT_ENABLE_TIMING)
+ struct PsnipClockTimespec wc = { 0, };
+
+ psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wc);
+ seed = (munit_uint32_t) wc.nanoseconds;
+#else
+ seed = (munit_uint32_t) time(NULL);
+#endif
+
+ state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT);
+ return munit_rand_from_state(state);
+}
+
+static munit_uint32_t
+munit_rand_state_uint32(munit_uint32_t* state) {
+ const munit_uint32_t old = *state;
+ *state = munit_rand_next_state(old);
+ return munit_rand_from_state(old);
+}
+
+munit_uint32_t
+munit_rand_uint32(void) {
+ munit_uint32_t old, state;
+
+ do {
+ old = munit_atomic_load(&munit_rand_state);
+ state = munit_rand_next_state(old);
+ } while (!munit_atomic_cas(&munit_rand_state, &old, state));
+
+ return munit_rand_from_state(old);
+}
+
+static void
+munit_rand_state_memory(munit_uint32_t* state, size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) {
+ size_t members_remaining = size / sizeof(munit_uint32_t);
+ size_t bytes_remaining = size % sizeof(munit_uint32_t);
+ munit_uint8_t* b = data;
+ munit_uint32_t rv;
+ while (members_remaining-- > 0) {
+ rv = munit_rand_state_uint32(state);
+ memcpy(b, &rv, sizeof(munit_uint32_t));
+ b += sizeof(munit_uint32_t);
+ }
+ if (bytes_remaining != 0) {
+ rv = munit_rand_state_uint32(state);
+ memcpy(b, &rv, bytes_remaining);
+ }
+}
+
+void
+munit_rand_memory(size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) {
+ munit_uint32_t old, state;
+
+ do {
+ state = old = munit_atomic_load(&munit_rand_state);
+ munit_rand_state_memory(&state, size, data);
+ } while (!munit_atomic_cas(&munit_rand_state, &old, state));
+}
+
+static munit_uint32_t
+munit_rand_state_at_most(munit_uint32_t* state, munit_uint32_t salt, munit_uint32_t max) {
+ /* We want (UINT32_MAX + 1) % max, which in unsigned arithmetic is the same
+ * as (UINT32_MAX + 1 - max) % max = -max % max. We compute -max using not
+ * to avoid compiler warnings.
+ */
+ const munit_uint32_t min = (~max + 1U) % max;
+ munit_uint32_t x;
+
+ if (max == (~((munit_uint32_t) 0U)))
+ return munit_rand_state_uint32(state) ^ salt;
+
+ max++;
+
+ do {
+ x = munit_rand_state_uint32(state) ^ salt;
+ } while (x < min);
+
+ return x % max;
+}
+
+static munit_uint32_t
+munit_rand_at_most(munit_uint32_t salt, munit_uint32_t max) {
+ munit_uint32_t old, state;
+ munit_uint32_t retval;
+
+ do {
+ state = old = munit_atomic_load(&munit_rand_state);
+ retval = munit_rand_state_at_most(&state, salt, max);
+ } while (!munit_atomic_cas(&munit_rand_state, &old, state));
+
+ return retval;
+}
+
+int
+munit_rand_int_range(int min, int max) {
+ munit_uint64_t range = (munit_uint64_t) max - (munit_uint64_t) min;
+
+ if (min > max)
+ return munit_rand_int_range(max, min);
+
+ if (range > (~((munit_uint32_t) 0U)))
+ range = (~((munit_uint32_t) 0U));
+
+ return min + munit_rand_at_most(0, (munit_uint32_t) range);
+}
+
+double
+munit_rand_double(void) {
+ munit_uint32_t old, state;
+ double retval = 0.0;
+
+ do {
+ state = old = munit_atomic_load(&munit_rand_state);
+
+ /* See http://mumble.net/~campbell/tmp/random_real.c for how to do
+ * this right. Patches welcome if you feel that this is too
+ * biased. */
+ retval = munit_rand_state_uint32(&state) / ((~((munit_uint32_t) 0U)) + 1.0);
+ } while (!munit_atomic_cas(&munit_rand_state, &old, state));
+
+ return retval;
+}
+
+/*** Test suite handling ***/
+
+typedef struct {
+ unsigned int successful;
+ unsigned int skipped;
+ unsigned int failed;
+ unsigned int errored;
+#if defined(MUNIT_ENABLE_TIMING)
+ munit_uint64_t cpu_clock;
+ munit_uint64_t wall_clock;
+#endif
+} MunitReport;
+
+typedef struct {
+ const char* prefix;
+ const MunitSuite* suite;
+ const char** tests;
+ munit_uint32_t seed;
+ unsigned int iterations;
+ MunitParameter* parameters;
+ munit_bool single_parameter_mode;
+ void* user_data;
+ MunitReport report;
+ munit_bool colorize;
+ munit_bool fork;
+ munit_bool show_stderr;
+ munit_bool fatal_failures;
+} MunitTestRunner;
+
+const char*
+munit_parameters_get(const MunitParameter params[], const char* key) {
+ const MunitParameter* param;
+
+ for (param = params ; param != NULL && param->name != NULL ; param++)
+ if (strcmp(param->name, key) == 0)
+ return param->value;
+ return NULL;
+}
+
+#if defined(MUNIT_ENABLE_TIMING)
+static void
+munit_print_time(FILE* fp, munit_uint64_t nanoseconds) {
+ fprintf(fp, "%" MUNIT_TEST_TIME_FORMAT, ((double) nanoseconds) / ((double) PSNIP_CLOCK_NSEC_PER_SEC));
+}
+#endif
+
+/* Add a paramter to an array of parameters. */
+static MunitResult
+munit_parameters_add(size_t* params_size, MunitParameter* params[MUNIT_ARRAY_PARAM(*params_size)], char* name, char* value) {
+ *params = realloc(*params, sizeof(MunitParameter) * (*params_size + 2));
+ if (*params == NULL)
+ return MUNIT_ERROR;
+
+ (*params)[*params_size].name = name;
+ (*params)[*params_size].value = value;
+ (*params_size)++;
+ (*params)[*params_size].name = NULL;
+ (*params)[*params_size].value = NULL;
+
+ return MUNIT_OK;
+}
+
+/* Concatenate two strings, but just return one of the components
+ * unaltered if the other is NULL or "". */
+static char*
+munit_maybe_concat(size_t* len, char* prefix, char* suffix) {
+ char* res;
+ size_t res_l;
+ const size_t prefix_l = prefix != NULL ? strlen(prefix) : 0;
+ const size_t suffix_l = suffix != NULL ? strlen(suffix) : 0;
+ if (prefix_l == 0 && suffix_l == 0) {
+ res = NULL;
+ res_l = 0;
+ } else if (prefix_l == 0 && suffix_l != 0) {
+ res = suffix;
+ res_l = suffix_l;
+ } else if (prefix_l != 0 && suffix_l == 0) {
+ res = prefix;
+ res_l = prefix_l;
+ } else {
+ res_l = prefix_l + suffix_l;
+ res = malloc(res_l + 1);
+ memcpy(res, prefix, prefix_l);
+ memcpy(res + prefix_l, suffix, suffix_l);
+ res[res_l] = 0;
+ }
+
+ if (len != NULL)
+ *len = res_l;
+
+ return res;
+}
+
+/* Possbily free a string returned by munit_maybe_concat. */
+static void
+munit_maybe_free_concat(char* s, const char* prefix, const char* suffix) {
+ if (prefix != s && suffix != s)
+ free(s);
+}
+
+/* Cheap string hash function, just used to salt the PRNG. */
+static munit_uint32_t
+munit_str_hash(const char* name) {
+ const char *p;
+ munit_uint32_t h = 5381U;
+
+ for (p = name; *p != '\0'; p++)
+ h = (h << 5) + h + *p;
+
+ return h;
+}
+
+static void
+munit_splice(int from, int to) {
+ munit_uint8_t buf[1024];
+#if !defined(_WIN32)
+ ssize_t len;
+ ssize_t bytes_written;
+ ssize_t write_res;
+#else
+ int len;
+ int bytes_written;
+ int write_res;
+#endif
+ do {
+ len = read(from, buf, sizeof(buf));
+ if (len > 0) {
+ bytes_written = 0;
+ do {
+ write_res = write(to, buf + bytes_written, len - bytes_written);
+ if (write_res < 0)
+ break;
+ bytes_written += write_res;
+ } while (bytes_written < len);
+ }
+ else
+ break;
+ } while (1);
+}
+
+/* This is the part that should be handled in the child process */
+static MunitResult
+munit_test_runner_exec(MunitTestRunner* runner, const MunitTest* test, const MunitParameter params[], MunitReport* report) {
+ unsigned int iterations = runner->iterations;
+ MunitResult result = MUNIT_FAIL;
+#if defined(MUNIT_ENABLE_TIMING)
+ struct PsnipClockTimespec wall_clock_begin = { 0, }, wall_clock_end = { 0, };
+ struct PsnipClockTimespec cpu_clock_begin = { 0, }, cpu_clock_end = { 0, };
+#endif
+ unsigned int i = 0;
+
+ if ((test->options & MUNIT_TEST_OPTION_SINGLE_ITERATION) == MUNIT_TEST_OPTION_SINGLE_ITERATION)
+ iterations = 1;
+ else if (iterations == 0)
+ iterations = runner->suite->iterations;
+
+ munit_rand_seed(runner->seed);
+
+ do {
+ void* data = (test->setup == NULL) ? runner->user_data : test->setup(params, runner->user_data);
+
+#if defined(MUNIT_ENABLE_TIMING)
+ psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_begin);
+ psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_begin);
+#endif
+
+ result = test->test(params, data);
+
+#if defined(MUNIT_ENABLE_TIMING)
+ psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_end);
+ psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_end);
+#endif
+
+ if (test->tear_down != NULL)
+ test->tear_down(data);
+
+ if (MUNIT_LIKELY(result == MUNIT_OK)) {
+ report->successful++;
+#if defined(MUNIT_ENABLE_TIMING)
+ report->wall_clock += munit_clock_get_elapsed(&wall_clock_begin, &wall_clock_end);
+ report->cpu_clock += munit_clock_get_elapsed(&cpu_clock_begin, &cpu_clock_end);
+#endif
+ } else {
+ switch ((int) result) {
+ case MUNIT_SKIP:
+ report->skipped++;
+ break;
+ case MUNIT_FAIL:
+ report->failed++;
+ break;
+ case MUNIT_ERROR:
+ report->errored++;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ } while (++i < iterations);
+
+ return result;
+}
+
+#if defined(MUNIT_EMOTICON)
+# define MUNIT_RESULT_STRING_OK ":)"
+# define MUNIT_RESULT_STRING_SKIP ":|"
+# define MUNIT_RESULT_STRING_FAIL ":("
+# define MUNIT_RESULT_STRING_ERROR ":o"
+# define MUNIT_RESULT_STRING_TODO ":/"
+#else
+# define MUNIT_RESULT_STRING_OK "OK "
+# define MUNIT_RESULT_STRING_SKIP "SKIP "
+# define MUNIT_RESULT_STRING_FAIL "FAIL "
+# define MUNIT_RESULT_STRING_ERROR "ERROR"
+# define MUNIT_RESULT_STRING_TODO "TODO "
+#endif
+
+static void
+munit_test_runner_print_color(const MunitTestRunner* runner, const char* string, char color) {
+ if (runner->colorize)
+ fprintf(MUNIT_OUTPUT_FILE, "\x1b[3%cm%s\x1b[39m", color, string);
+ else
+ fputs(string, MUNIT_OUTPUT_FILE);
+}
+
+#if !defined(MUNIT_NO_BUFFER)
+static int
+munit_replace_stderr(FILE* stderr_buf) {
+ if (stderr_buf != NULL) {
+ const int orig_stderr = dup(STDERR_FILENO);
+
+ int errfd = fileno(stderr_buf);
+ if (MUNIT_UNLIKELY(errfd == -1)) {
+ exit(EXIT_FAILURE);
+ }
+
+ dup2(errfd, STDERR_FILENO);
+
+ return orig_stderr;
+ }
+
+ return -1;
+}
+
+static void
+munit_restore_stderr(int orig_stderr) {
+ if (orig_stderr != -1) {
+ dup2(orig_stderr, STDERR_FILENO);
+ close(orig_stderr);
+ }
+}
+#endif /* !defined(MUNIT_NO_BUFFER) */
+
+/* Run a test with the specified parameters. */
+static void
+munit_test_runner_run_test_with_params(MunitTestRunner* runner, const MunitTest* test, const MunitParameter params[]) {
+ MunitResult result = MUNIT_OK;
+ MunitReport report = {
+ 0, 0, 0, 0,
+#if defined(MUNIT_ENABLE_TIMING)
+ 0, 0
+#endif
+ };
+ unsigned int output_l;
+ munit_bool first;
+ const MunitParameter* param;
+ FILE* stderr_buf;
+#if !defined(MUNIT_NO_FORK)
+ int pipefd[2];
+ pid_t fork_pid;
+ int orig_stderr;
+ ssize_t bytes_written = 0;
+ ssize_t write_res;
+ ssize_t bytes_read = 0;
+ ssize_t read_res;
+ int status = 0;
+ pid_t changed_pid;
+#endif
+
+ if (params != NULL) {
+ output_l = 2;
+ fputs(" ", MUNIT_OUTPUT_FILE);
+ first = 1;
+ for (param = params ; param != NULL && param->name != NULL ; param++) {
+ if (!first) {
+ fputs(", ", MUNIT_OUTPUT_FILE);
+ output_l += 2;
+ } else {
+ first = 0;
+ }
+
+ output_l += fprintf(MUNIT_OUTPUT_FILE, "%s=%s", param->name, param->value);
+ }
+ while (output_l++ < MUNIT_TEST_NAME_LEN) {
+ fputc(' ', MUNIT_OUTPUT_FILE);
+ }
+ }
+
+ fflush(MUNIT_OUTPUT_FILE);
+
+ stderr_buf = NULL;
+#if !defined(_WIN32) || defined(__MINGW32__)
+ stderr_buf = tmpfile();
+#else
+ tmpfile_s(&stderr_buf);
+#endif
+ if (stderr_buf == NULL) {
+ munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create buffer for stderr");
+ result = MUNIT_ERROR;
+ goto print_result;
+ }
+
+#if !defined(MUNIT_NO_FORK)
+ if (runner->fork) {
+ pipefd[0] = -1;
+ pipefd[1] = -1;
+ if (pipe(pipefd) != 0) {
+ munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create pipe");
+ result = MUNIT_ERROR;
+ goto print_result;
+ }
+
+ fork_pid = fork();
+ if (fork_pid == 0) {
+ close(pipefd[0]);
+
+ orig_stderr = munit_replace_stderr(stderr_buf);
+ munit_test_runner_exec(runner, test, params, &report);
+
+ /* Note that we don't restore stderr. This is so we can buffer
+ * things written to stderr later on (such as by
+ * asan/tsan/ubsan, valgrind, etc.) */
+ close(orig_stderr);
+
+ do {
+ write_res = write(pipefd[1], ((munit_uint8_t*) (&report)) + bytes_written, sizeof(report) - bytes_written);
+ if (write_res < 0) {
+ if (stderr_buf != NULL) {
+ munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to write to pipe");
+ }
+ exit(EXIT_FAILURE);
+ }
+ bytes_written += write_res;
+ } while ((size_t) bytes_written < sizeof(report));
+
+ if (stderr_buf != NULL)
+ fclose(stderr_buf);
+ close(pipefd[1]);
+
+ exit(EXIT_SUCCESS);
+ } else if (fork_pid == -1) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ if (stderr_buf != NULL) {
+ munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to fork");
+ }
+ report.errored++;
+ result = MUNIT_ERROR;
+ } else {
+ close(pipefd[1]);
+ do {
+ read_res = read(pipefd[0], ((munit_uint8_t*) (&report)) + bytes_read, sizeof(report) - bytes_read);
+ if (read_res < 1)
+ break;
+ bytes_read += read_res;
+ } while (bytes_read < (ssize_t) sizeof(report));
+
+ changed_pid = waitpid(fork_pid, &status, 0);
+
+ if (MUNIT_LIKELY(changed_pid == fork_pid) && MUNIT_LIKELY(WIFEXITED(status))) {
+ if (bytes_read != sizeof(report)) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child exited unexpectedly with status %d", WEXITSTATUS(status));
+ report.errored++;
+ } else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child exited with status %d", WEXITSTATUS(status));
+ report.errored++;
+ }
+ } else {
+ if (WIFSIGNALED(status)) {
+#if defined(_XOPEN_VERSION) && (_XOPEN_VERSION >= 700)
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child killed by signal %d (%s)", WTERMSIG(status), strsignal(WTERMSIG(status)));
+#else
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child killed by signal %d", WTERMSIG(status));
+#endif
+ } else if (WIFSTOPPED(status)) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child stopped by signal %d", WSTOPSIG(status));
+ }
+ report.errored++;
+ }
+
+ close(pipefd[0]);
+ waitpid(fork_pid, NULL, 0);
+ }
+ } else
+#endif
+ {
+#if !defined(MUNIT_NO_BUFFER)
+ const volatile int orig_stderr = munit_replace_stderr(stderr_buf);
+#endif
+
+#if defined(MUNIT_THREAD_LOCAL)
+ if (MUNIT_UNLIKELY(setjmp(munit_error_jmp_buf) != 0)) {
+ result = MUNIT_FAIL;
+ report.failed++;
+ } else {
+ munit_error_jmp_buf_valid = 1;
+ result = munit_test_runner_exec(runner, test, params, &report);
+ }
+#else
+ result = munit_test_runner_exec(runner, test, params, &report);
+#endif
+
+#if !defined(MUNIT_NO_BUFFER)
+ munit_restore_stderr(orig_stderr);
+#endif
+
+ /* Here just so that the label is used on Windows and we don't get
+ * a warning */
+ goto print_result;
+ }
+
+ print_result:
+
+ fputs("[ ", MUNIT_OUTPUT_FILE);
+ if ((test->options & MUNIT_TEST_OPTION_TODO) == MUNIT_TEST_OPTION_TODO) {
+ if (report.failed != 0 || report.errored != 0 || report.skipped != 0) {
+ munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_TODO, '3');
+ result = MUNIT_OK;
+ } else {
+ munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1');
+ if (MUNIT_LIKELY(stderr_buf != NULL))
+ munit_log_internal(MUNIT_LOG_ERROR, stderr_buf, "Test marked TODO, but was successful.");
+ runner->report.failed++;
+ result = MUNIT_ERROR;
+ }
+ } else if (report.failed > 0) {
+ munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_FAIL, '1');
+ runner->report.failed++;
+ result = MUNIT_FAIL;
+ } else if (report.errored > 0) {
+ munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1');
+ runner->report.errored++;
+ result = MUNIT_ERROR;
+ } else if (report.skipped > 0) {
+ munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_SKIP, '3');
+ runner->report.skipped++;
+ result = MUNIT_SKIP;
+ } else if (report.successful > 1) {
+ munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2');
+#if defined(MUNIT_ENABLE_TIMING)
+ fputs(" ] [ ", MUNIT_OUTPUT_FILE);
+ munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock / report.successful);
+ fputs(" / ", MUNIT_OUTPUT_FILE);
+ munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock / report.successful);
+ fprintf(MUNIT_OUTPUT_FILE, " CPU ]\n %-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s Total: [ ", "");
+ munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock);
+ fputs(" / ", MUNIT_OUTPUT_FILE);
+ munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock);
+ fputs(" CPU", MUNIT_OUTPUT_FILE);
+#endif
+ runner->report.successful++;
+ result = MUNIT_OK;
+ } else if (report.successful > 0) {
+ munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2');
+#if defined(MUNIT_ENABLE_TIMING)
+ fputs(" ] [ ", MUNIT_OUTPUT_FILE);
+ munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock);
+ fputs(" / ", MUNIT_OUTPUT_FILE);
+ munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock);
+ fputs(" CPU", MUNIT_OUTPUT_FILE);
+#endif
+ runner->report.successful++;
+ result = MUNIT_OK;
+ }
+ fputs(" ]\n", MUNIT_OUTPUT_FILE);
+
+ if (stderr_buf != NULL) {
+ if (result == MUNIT_FAIL || result == MUNIT_ERROR || runner->show_stderr) {
+ fflush(MUNIT_OUTPUT_FILE);
+
+ rewind(stderr_buf);
+ munit_splice(fileno(stderr_buf), STDERR_FILENO);
+
+ fflush(stderr);
+ }
+
+ fclose(stderr_buf);
+ }
+}
+
+static void
+munit_test_runner_run_test_wild(MunitTestRunner* runner,
+ const MunitTest* test,
+ const char* test_name,
+ MunitParameter* params,
+ MunitParameter* p) {
+ const MunitParameterEnum* pe;
+ char** values;
+ MunitParameter* next;
+
+ for (pe = test->parameters ; pe != NULL && pe->name != NULL ; pe++) {
+ if (p->name == pe->name)
+ break;
+ }
+
+ if (pe == NULL)
+ return;
+
+ for (values = pe->values ; *values != NULL ; values++) {
+ next = p + 1;
+ p->value = *values;
+ if (next->name == NULL) {
+ munit_test_runner_run_test_with_params(runner, test, params);
+ } else {
+ munit_test_runner_run_test_wild(runner, test, test_name, params, next);
+ }
+ if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0))
+ break;
+ }
+}
+
+/* Run a single test, with every combination of parameters
+ * requested. */
+static void
+munit_test_runner_run_test(MunitTestRunner* runner,
+ const MunitTest* test,
+ const char* prefix) {
+ char* test_name = munit_maybe_concat(NULL, (char*) prefix, (char*) test->name);
+ /* The array of parameters to pass to
+ * munit_test_runner_run_test_with_params */
+ MunitParameter* params = NULL;
+ size_t params_l = 0;
+ /* Wildcard parameters are parameters which have possible values
+ * specified in the test, but no specific value was passed to the
+ * CLI. That means we want to run the test once for every
+ * possible combination of parameter values or, if --single was
+ * passed to the CLI, a single time with a random set of
+ * parameters. */
+ MunitParameter* wild_params = NULL;
+ size_t wild_params_l = 0;
+ const MunitParameterEnum* pe;
+ const MunitParameter* cli_p;
+ munit_bool filled;
+ unsigned int possible;
+ char** vals;
+ size_t first_wild;
+ const MunitParameter* wp;
+ int pidx;
+
+ munit_rand_seed(runner->seed);
+
+ fprintf(MUNIT_OUTPUT_FILE, "%-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s", test_name);
+
+ if (test->parameters == NULL) {
+ /* No parameters. Simple, nice. */
+ munit_test_runner_run_test_with_params(runner, test, NULL);
+ } else {
+ fputc('\n', MUNIT_OUTPUT_FILE);
+
+ for (pe = test->parameters ; pe != NULL && pe->name != NULL ; pe++) {
+ /* Did we received a value for this parameter from the CLI? */
+ filled = 0;
+ for (cli_p = runner->parameters ; cli_p != NULL && cli_p->name != NULL ; cli_p++) {
+ if (strcmp(cli_p->name, pe->name) == 0) {
+ if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, cli_p->value) != MUNIT_OK))
+ goto cleanup;
+ filled = 1;
+ break;
+ }
+ }
+ if (filled)
+ continue;
+
+ /* Nothing from CLI, is the enum NULL/empty? We're not a
+ * fuzzer… */
+ if (pe->values == NULL || pe->values[0] == NULL)
+ continue;
+
+ /* If --single was passed to the CLI, choose a value from the
+ * list of possibilities randomly. */
+ if (runner->single_parameter_mode) {
+ possible = 0;
+ for (vals = pe->values ; *vals != NULL ; vals++)
+ possible++;
+ /* We want the tests to be reproducible, even if you're only
+ * running a single test, but we don't want every test with
+ * the same number of parameters to choose the same parameter
+ * number, so use the test name as a primitive salt. */
+ pidx = munit_rand_at_most(munit_str_hash(test_name), possible - 1);
+ if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, pe->values[pidx]) != MUNIT_OK))
+ goto cleanup;
+ } else {
+ /* We want to try every permutation. Put in a placeholder
+ * entry, we'll iterate through them later. */
+ if (MUNIT_UNLIKELY(munit_parameters_add(&wild_params_l, &wild_params, pe->name, NULL) != MUNIT_OK))
+ goto cleanup;
+ }
+ }
+
+ if (wild_params_l != 0) {
+ first_wild = params_l;
+ for (wp = wild_params ; wp != NULL && wp->name != NULL ; wp++) {
+ for (pe = test->parameters ; pe != NULL && pe->name != NULL && pe->values != NULL ; pe++) {
+ if (strcmp(wp->name, pe->name) == 0) {
+ if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, pe->values[0]) != MUNIT_OK))
+ goto cleanup;
+ }
+ }
+ }
+
+ munit_test_runner_run_test_wild(runner, test, test_name, params, params + first_wild);
+ } else {
+ munit_test_runner_run_test_with_params(runner, test, params);
+ }
+
+ cleanup:
+ free(params);
+ free(wild_params);
+ }
+
+ munit_maybe_free_concat(test_name, prefix, test->name);
+}
+
+/* Recurse through the suite and run all the tests. If a list of
+ * tests to run was provied on the command line, run only those
+ * tests. */
+static void
+munit_test_runner_run_suite(MunitTestRunner* runner,
+ const MunitSuite* suite,
+ const char* prefix) {
+ size_t pre_l;
+ char* pre = munit_maybe_concat(&pre_l, (char*) prefix, (char*) suite->prefix);
+ const MunitTest* test;
+ const char** test_name;
+ const MunitSuite* child_suite;
+
+ /* Run the tests. */
+ for (test = suite->tests ; test != NULL && test->test != NULL ; test++) {
+ if (runner->tests != NULL) { /* Specific tests were requested on the CLI */
+ for (test_name = runner->tests ; test_name != NULL && *test_name != NULL ; test_name++) {
+ if ((pre_l == 0 || strncmp(pre, *test_name, pre_l) == 0) &&
+ strncmp(test->name, *test_name + pre_l, strlen(*test_name + pre_l)) == 0) {
+ munit_test_runner_run_test(runner, test, pre);
+ if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0))
+ goto cleanup;
+ }
+ }
+ } else { /* Run all tests */
+ munit_test_runner_run_test(runner, test, pre);
+ }
+ }
+
+ if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0))
+ goto cleanup;
+
+ /* Run any child suites. */
+ for (child_suite = suite->suites ; child_suite != NULL && child_suite->prefix != NULL ; child_suite++) {
+ munit_test_runner_run_suite(runner, child_suite, pre);
+ }
+
+ cleanup:
+
+ munit_maybe_free_concat(pre, prefix, suite->prefix);
+}
+
+static void
+munit_test_runner_run(MunitTestRunner* runner) {
+ munit_test_runner_run_suite(runner, runner->suite, NULL);
+}
+
+static void
+munit_print_help(int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)], void* user_data, const MunitArgument arguments[]) {
+ const MunitArgument* arg;
+ (void) argc;
+
+ printf("USAGE: %s [OPTIONS...] [TEST...]\n\n", argv[0]);
+ puts(" --seed SEED\n"
+ " Value used to seed the PRNG. Must be a 32-bit integer in decimal\n"
+ " notation with no separators (commas, decimals, spaces, etc.), or\n"
+ " hexidecimal prefixed by \"0x\".\n"
+ " --iterations N\n"
+ " Run each test N times. 0 means the default number.\n"
+ " --param name value\n"
+ " A parameter key/value pair which will be passed to any test with\n"
+ " takes a parameter of that name. If not provided, the test will be\n"
+ " run once for each possible parameter value.\n"
+ " --list Write a list of all available tests.\n"
+ " --list-params\n"
+ " Write a list of all available tests and their possible parameters.\n"
+ " --single Run each parameterized test in a single configuration instead of\n"
+ " every possible combination\n"
+ " --log-visible debug|info|warning|error\n"
+ " --log-fatal debug|info|warning|error\n"
+ " Set the level at which messages of different severities are visible,\n"
+ " or cause the test to terminate.\n"
+#if !defined(MUNIT_NO_FORK)
+ " --no-fork Do not execute tests in a child process. If this option is supplied\n"
+ " and a test crashes (including by failing an assertion), no further\n"
+ " tests will be performed.\n"
+#endif
+ " --fatal-failures\n"
+ " Stop executing tests as soon as a failure is found.\n"
+ " --show-stderr\n"
+ " Show data written to stderr by the tests, even if the test succeeds.\n"
+ " --color auto|always|never\n"
+ " Colorize (or don't) the output.\n"
+ /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */
+ " --help Print this help message and exit.\n");
+#if defined(MUNIT_NL_LANGINFO)
+ setlocale(LC_ALL, "");
+ fputs((strcasecmp("UTF-8", nl_langinfo(CODESET)) == 0) ? "µnit" : "munit", stdout);
+#else
+ puts("munit");
+#endif
+ printf(" %d.%d.%d\n"
+ "Full documentation at: https://nemequ.github.io/munit/\n",
+ (MUNIT_CURRENT_VERSION >> 16) & 0xff,
+ (MUNIT_CURRENT_VERSION >> 8) & 0xff,
+ (MUNIT_CURRENT_VERSION >> 0) & 0xff);
+ for (arg = arguments ; arg != NULL && arg->name != NULL ; arg++)
+ arg->write_help(arg, user_data);
+}
+
+static const MunitArgument*
+munit_arguments_find(const MunitArgument arguments[], const char* name) {
+ const MunitArgument* arg;
+
+ for (arg = arguments ; arg != NULL && arg->name != NULL ; arg++)
+ if (strcmp(arg->name, name) == 0)
+ return arg;
+
+ return NULL;
+}
+
+static void
+munit_suite_list_tests(const MunitSuite* suite, munit_bool show_params, const char* prefix) {
+ size_t pre_l;
+ char* pre = munit_maybe_concat(&pre_l, (char*) prefix, (char*) suite->prefix);
+ const MunitTest* test;
+ const MunitParameterEnum* params;
+ munit_bool first;
+ char** val;
+ const MunitSuite* child_suite;
+
+ for (test = suite->tests ;
+ test != NULL && test->name != NULL ;
+ test++) {
+ if (pre != NULL)
+ fputs(pre, stdout);
+ puts(test->name);
+
+ if (show_params) {
+ for (params = test->parameters ;
+ params != NULL && params->name != NULL ;
+ params++) {
+ fprintf(stdout, " - %s: ", params->name);
+ if (params->values == NULL) {
+ puts("Any");
+ } else {
+ first = 1;
+ for (val = params->values ;
+ *val != NULL ;
+ val++ ) {
+ if(!first) {
+ fputs(", ", stdout);
+ } else {
+ first = 0;
+ }
+ fputs(*val, stdout);
+ }
+ putc('\n', stdout);
+ }
+ }
+ }
+ }
+
+ for (child_suite = suite->suites ; child_suite != NULL && child_suite->prefix != NULL ; child_suite++) {
+ munit_suite_list_tests(child_suite, show_params, pre);
+ }
+
+ munit_maybe_free_concat(pre, prefix, suite->prefix);
+}
+
+static munit_bool
+munit_stream_supports_ansi(FILE *stream) {
+#if !defined(_WIN32)
+ return isatty(fileno(stream));
+#else
+
+#if !defined(__MINGW32__)
+ size_t ansicon_size = 0;
+#endif
+
+ if (isatty(fileno(stream))) {
+#if !defined(__MINGW32__)
+ getenv_s(&ansicon_size, NULL, 0, "ANSICON");
+ return ansicon_size != 0;
+#else
+ return getenv("ANSICON") != NULL;
+#endif
+ }
+ return 0;
+#endif
+}
+
+int
+munit_suite_main_custom(const MunitSuite* suite, void* user_data,
+ int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)],
+ const MunitArgument arguments[]) {
+ int result = EXIT_FAILURE;
+ MunitTestRunner runner;
+ size_t parameters_size = 0;
+ size_t tests_size = 0;
+ int arg;
+
+ char* envptr;
+ unsigned long ts;
+ char* endptr;
+ unsigned long long iterations;
+ MunitLogLevel level;
+ const MunitArgument* argument;
+ const char** runner_tests;
+ unsigned int tests_run;
+ unsigned int tests_total;
+
+ runner.prefix = NULL;
+ runner.suite = NULL;
+ runner.tests = NULL;
+ runner.seed = 0;
+ runner.iterations = 0;
+ runner.parameters = NULL;
+ runner.single_parameter_mode = 0;
+ runner.user_data = NULL;
+
+ runner.report.successful = 0;
+ runner.report.skipped = 0;
+ runner.report.failed = 0;
+ runner.report.errored = 0;
+#if defined(MUNIT_ENABLE_TIMING)
+ runner.report.cpu_clock = 0;
+ runner.report.wall_clock = 0;
+#endif
+
+ runner.colorize = 0;
+#if !defined(_WIN32)
+ runner.fork = 1;
+#else
+ runner.fork = 0;
+#endif
+ runner.show_stderr = 0;
+ runner.fatal_failures = 0;
+ runner.suite = suite;
+ runner.user_data = user_data;
+ runner.seed = munit_rand_generate_seed();
+ runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE);
+
+ for (arg = 1 ; arg < argc ; arg++) {
+ if (strncmp("--", argv[arg], 2) == 0) {
+ if (strcmp("seed", argv[arg] + 2) == 0) {
+ if (arg + 1 >= argc) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]);
+ goto cleanup;
+ }
+
+ envptr = argv[arg + 1];
+ ts = strtoul(argv[arg + 1], &envptr, 0);
+ if (*envptr != '\0' || ts > (~((munit_uint32_t) 0U))) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]);
+ goto cleanup;
+ }
+ runner.seed = (munit_uint32_t) ts;
+
+ arg++;
+ } else if (strcmp("iterations", argv[arg] + 2) == 0) {
+ if (arg + 1 >= argc) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]);
+ goto cleanup;
+ }
+
+ endptr = argv[arg + 1];
+ iterations = strtoul(argv[arg + 1], &endptr, 0);
+ if (*endptr != '\0' || iterations > UINT_MAX) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]);
+ goto cleanup;
+ }
+
+ runner.iterations = (unsigned int) iterations;
+
+ arg++;
+ } else if (strcmp("param", argv[arg] + 2) == 0) {
+ if (arg + 2 >= argc) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires two arguments", argv[arg]);
+ goto cleanup;
+ }
+
+ runner.parameters = realloc(runner.parameters, sizeof(MunitParameter) * (parameters_size + 2));
+ if (runner.parameters == NULL) {
+ munit_log_internal(MUNIT_LOG_ERROR, stderr, "failed to allocate memory");
+ goto cleanup;
+ }
+ runner.parameters[parameters_size].name = (char*) argv[arg + 1];
+ runner.parameters[parameters_size].value = (char*) argv[arg + 2];
+ parameters_size++;
+ runner.parameters[parameters_size].name = NULL;
+ runner.parameters[parameters_size].value = NULL;
+ arg += 2;
+ } else if (strcmp("color", argv[arg] + 2) == 0) {
+ if (arg + 1 >= argc) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]);
+ goto cleanup;
+ }
+
+ if (strcmp(argv[arg + 1], "always") == 0)
+ runner.colorize = 1;
+ else if (strcmp(argv[arg + 1], "never") == 0)
+ runner.colorize = 0;
+ else if (strcmp(argv[arg + 1], "auto") == 0)
+ runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE);
+ else {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]);
+ goto cleanup;
+ }
+
+ arg++;
+ } else if (strcmp("help", argv[arg] + 2) == 0) {
+ munit_print_help(argc, argv, user_data, arguments);
+ result = EXIT_SUCCESS;
+ goto cleanup;
+ } else if (strcmp("single", argv[arg] + 2) == 0) {
+ runner.single_parameter_mode = 1;
+ } else if (strcmp("show-stderr", argv[arg] + 2) == 0) {
+ runner.show_stderr = 1;
+#if !defined(_WIN32)
+ } else if (strcmp("no-fork", argv[arg] + 2) == 0) {
+ runner.fork = 0;
+#endif
+ } else if (strcmp("fatal-failures", argv[arg] + 2) == 0) {
+ runner.fatal_failures = 1;
+ } else if (strcmp("log-visible", argv[arg] + 2) == 0 ||
+ strcmp("log-fatal", argv[arg] + 2) == 0) {
+ if (arg + 1 >= argc) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]);
+ goto cleanup;
+ }
+
+ if (strcmp(argv[arg + 1], "debug") == 0)
+ level = MUNIT_LOG_DEBUG;
+ else if (strcmp(argv[arg + 1], "info") == 0)
+ level = MUNIT_LOG_INFO;
+ else if (strcmp(argv[arg + 1], "warning") == 0)
+ level = MUNIT_LOG_WARNING;
+ else if (strcmp(argv[arg + 1], "error") == 0)
+ level = MUNIT_LOG_ERROR;
+ else {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]);
+ goto cleanup;
+ }
+
+ if (strcmp("log-visible", argv[arg] + 2) == 0)
+ munit_log_level_visible = level;
+ else
+ munit_log_level_fatal = level;
+
+ arg++;
+ } else if (strcmp("list", argv[arg] + 2) == 0) {
+ munit_suite_list_tests(suite, 0, NULL);
+ result = EXIT_SUCCESS;
+ goto cleanup;
+ } else if (strcmp("list-params", argv[arg] + 2) == 0) {
+ munit_suite_list_tests(suite, 1, NULL);
+ result = EXIT_SUCCESS;
+ goto cleanup;
+ } else {
+ argument = munit_arguments_find(arguments, argv[arg] + 2);
+ if (argument == NULL) {
+ munit_logf_internal(MUNIT_LOG_ERROR, stderr, "unknown argument ('%s')", argv[arg]);
+ goto cleanup;
+ }
+
+ if (!argument->parse_argument(suite, user_data, &arg, argc, argv))
+ goto cleanup;
+ }
+ } else {
+ runner_tests = realloc((void*) runner.tests, sizeof(char*) * (tests_size + 2));
+ if (runner_tests == NULL) {
+ munit_log_internal(MUNIT_LOG_ERROR, stderr, "failed to allocate memory");
+ goto cleanup;
+ }
+ runner.tests = runner_tests;
+ runner.tests[tests_size++] = argv[arg];
+ runner.tests[tests_size] = NULL;
+ }
+ }
+
+ fflush(stderr);
+ fprintf(MUNIT_OUTPUT_FILE, "Running test suite with seed 0x%08" PRIx32 "...\n", runner.seed);
+
+ munit_test_runner_run(&runner);
+
+ tests_run = runner.report.successful + runner.report.failed + runner.report.errored;
+ tests_total = tests_run + runner.report.skipped;
+ if (tests_run == 0) {
+ fprintf(stderr, "No tests run, %d (100%%) skipped.\n", runner.report.skipped);
+ } else {
+ fprintf(MUNIT_OUTPUT_FILE, "%d of %d (%0.0f%%) tests successful, %d (%0.0f%%) test skipped.\n",
+ runner.report.successful, tests_run,
+ (((double) runner.report.successful) / ((double) tests_run)) * 100.0,
+ runner.report.skipped,
+ (((double) runner.report.skipped) / ((double) tests_total)) * 100.0);
+ }
+
+ if (runner.report.failed == 0 && runner.report.errored == 0) {
+ result = EXIT_SUCCESS;
+ }
+
+ cleanup:
+ free(runner.parameters);
+ free((void*) runner.tests);
+
+ return result;
+}
+
+int
+munit_suite_main(const MunitSuite* suite, void* user_data,
+ int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]) {
+ return munit_suite_main_custom(suite, user_data, argc, argv, NULL);
+}
diff --git a/tests/integration/munit.h b/tests/integration/munit.h
new file mode 100644
index 0000000..c614c78
--- /dev/null
+++ b/tests/integration/munit.h
@@ -0,0 +1,536 @@
+// clang-format off
+/* µnit Testing Framework
+ * Copyright (c) 2013-2017 Evan Nemerson <evan@nemerson.com>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(MUNIT_H)
+#define MUNIT_H
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#define MUNIT_VERSION(major, minor, revision) \
+ (((major) << 16) | ((minor) << 8) | (revision))
+
+#define MUNIT_CURRENT_VERSION MUNIT_VERSION(0, 4, 1)
+
+#if defined(_MSC_VER) && (_MSC_VER < 1600)
+# define munit_int8_t __int8
+# define munit_uint8_t unsigned __int8
+# define munit_int16_t __int16
+# define munit_uint16_t unsigned __int16
+# define munit_int32_t __int32
+# define munit_uint32_t unsigned __int32
+# define munit_int64_t __int64
+# define munit_uint64_t unsigned __int64
+#else
+# include <stdint.h>
+# define munit_int8_t int8_t
+# define munit_uint8_t uint8_t
+# define munit_int16_t int16_t
+# define munit_uint16_t uint16_t
+# define munit_int32_t int32_t
+# define munit_uint32_t uint32_t
+# define munit_int64_t int64_t
+# define munit_uint64_t uint64_t
+#endif
+
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+# if !defined(PRIi8)
+# define PRIi8 "i"
+# endif
+# if !defined(PRIi16)
+# define PRIi16 "i"
+# endif
+# if !defined(PRIi32)
+# define PRIi32 "i"
+# endif
+# if !defined(PRIi64)
+# define PRIi64 "I64i"
+# endif
+# if !defined(PRId8)
+# define PRId8 "d"
+# endif
+# if !defined(PRId16)
+# define PRId16 "d"
+# endif
+# if !defined(PRId32)
+# define PRId32 "d"
+# endif
+# if !defined(PRId64)
+# define PRId64 "I64d"
+# endif
+# if !defined(PRIx8)
+# define PRIx8 "x"
+# endif
+# if !defined(PRIx16)
+# define PRIx16 "x"
+# endif
+# if !defined(PRIx32)
+# define PRIx32 "x"
+# endif
+# if !defined(PRIx64)
+# define PRIx64 "I64x"
+# endif
+# if !defined(PRIu8)
+# define PRIu8 "u"
+# endif
+# if !defined(PRIu16)
+# define PRIu16 "u"
+# endif
+# if !defined(PRIu32)
+# define PRIu32 "u"
+# endif
+# if !defined(PRIu64)
+# define PRIu64 "I64u"
+# endif
+#else
+# include <inttypes.h>
+#endif
+
+#if !defined(munit_bool)
+# if defined(bool)
+# define munit_bool bool
+# elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+# define munit_bool _Bool
+# else
+# define munit_bool int
+# endif
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(__GNUC__)
+# define MUNIT_LIKELY(expr) (__builtin_expect ((expr), 1))
+# define MUNIT_UNLIKELY(expr) (__builtin_expect ((expr), 0))
+# define MUNIT_UNUSED __attribute__((__unused__))
+#else
+# define MUNIT_LIKELY(expr) (expr)
+# define MUNIT_UNLIKELY(expr) (expr)
+# define MUNIT_UNUSED
+#endif
+
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__PGI)
+# define MUNIT_ARRAY_PARAM(name) name
+#else
+# define MUNIT_ARRAY_PARAM(name)
+#endif
+
+#if !defined(_WIN32)
+# define MUNIT_SIZE_MODIFIER "z"
+# define MUNIT_CHAR_MODIFIER "hh"
+# define MUNIT_SHORT_MODIFIER "h"
+#else
+# if defined(_M_X64) || defined(__amd64__)
+# define MUNIT_SIZE_MODIFIER "I64"
+# else
+# define MUNIT_SIZE_MODIFIER ""
+# endif
+# define MUNIT_CHAR_MODIFIER ""
+# define MUNIT_SHORT_MODIFIER ""
+#endif
+
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+# define MUNIT_NO_RETURN _Noreturn
+#elif defined(__GNUC__)
+# define MUNIT_NO_RETURN __attribute__((__noreturn__))
+#elif defined(_MSC_VER)
+# define MUNIT_NO_RETURN __declspec(noreturn)
+#else
+# define MUNIT_NO_RETURN
+#endif
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1500)
+# define MUNIT_PUSH_DISABLE_MSVC_C4127_ __pragma(warning(push)) __pragma(warning(disable:4127))
+# define MUNIT_POP_DISABLE_MSVC_C4127_ __pragma(warning(pop))
+#else
+# define MUNIT_PUSH_DISABLE_MSVC_C4127_
+# define MUNIT_POP_DISABLE_MSVC_C4127_
+#endif
+
+typedef enum {
+ MUNIT_LOG_DEBUG,
+ MUNIT_LOG_INFO,
+ MUNIT_LOG_WARNING,
+ MUNIT_LOG_ERROR
+} MunitLogLevel;
+
+#if defined(__GNUC__) && !defined(__MINGW32__)
+# define MUNIT_PRINTF(string_index, first_to_check) __attribute__((format (printf, string_index, first_to_check)))
+#else
+# define MUNIT_PRINTF(string_index, first_to_check)
+#endif
+
+MUNIT_PRINTF(4, 5)
+void munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...);
+
+#define munit_logf(level, format, ...) \
+ munit_logf_ex(level, __FILE__, __LINE__, format, __VA_ARGS__)
+
+#define munit_log(level, msg) \
+ munit_logf(level, "%s", msg)
+
+MUNIT_NO_RETURN
+MUNIT_PRINTF(3, 4)
+void munit_errorf_ex(const char* filename, int line, const char* format, ...);
+
+#define munit_errorf(format, ...) \
+ munit_errorf_ex(__FILE__, __LINE__, format, __VA_ARGS__)
+
+#define munit_error(msg) \
+ munit_errorf("%s", msg)
+
+#define munit_assert(expr) \
+ do { \
+ if (!MUNIT_LIKELY(expr)) { \
+ munit_error("assertion failed: " #expr); \
+ } \
+ MUNIT_PUSH_DISABLE_MSVC_C4127_ \
+ } while (0) \
+ MUNIT_POP_DISABLE_MSVC_C4127_
+
+#define munit_assert_true(expr) \
+ do { \
+ if (!MUNIT_LIKELY(expr)) { \
+ munit_error("assertion failed: " #expr " is not true"); \
+ } \
+ MUNIT_PUSH_DISABLE_MSVC_C4127_ \
+ } while (0) \
+ MUNIT_POP_DISABLE_MSVC_C4127_
+
+#define munit_assert_false(expr) \
+ do { \
+ if (!MUNIT_LIKELY(!(expr))) { \
+ munit_error("assertion failed: " #expr " is not false"); \
+ } \
+ MUNIT_PUSH_DISABLE_MSVC_C4127_ \
+ } while (0) \
+ MUNIT_POP_DISABLE_MSVC_C4127_
+
+#define munit_assert_type_full(prefix, suffix, T, fmt, a, op, b) \
+ do { \
+ T munit_tmp_a_ = (a); \
+ T munit_tmp_b_ = (b); \
+ if (!(munit_tmp_a_ op munit_tmp_b_)) { \
+ munit_errorf("assertion failed: %s %s %s (" prefix "%" fmt suffix " %s " prefix "%" fmt suffix ")", \
+ #a, #op, #b, munit_tmp_a_, #op, munit_tmp_b_); \
+ } \
+ MUNIT_PUSH_DISABLE_MSVC_C4127_ \
+ } while (0) \
+ MUNIT_POP_DISABLE_MSVC_C4127_
+
+#define munit_assert_type(T, fmt, a, op, b) \
+ munit_assert_type_full("", "", T, fmt, a, op, b)
+
+#define munit_assert_char(a, op, b) \
+ munit_assert_type_full("'\\x", "'", char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b)
+#define munit_assert_uchar(a, op, b) \
+ munit_assert_type_full("'\\x", "'", unsigned char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b)
+#define munit_assert_short(a, op, b) \
+ munit_assert_type(short, MUNIT_SHORT_MODIFIER "d", a, op, b)
+#define munit_assert_ushort(a, op, b) \
+ munit_assert_type(unsigned short, MUNIT_SHORT_MODIFIER "u", a, op, b)
+#define munit_assert_int(a, op, b) \
+ munit_assert_type(int, "d", a, op, b)
+#define munit_assert_uint(a, op, b) \
+ munit_assert_type(unsigned int, "u", a, op, b)
+#define munit_assert_long(a, op, b) \
+ munit_assert_type(long int, "ld", a, op, b)
+#define munit_assert_ulong(a, op, b) \
+ munit_assert_type(unsigned long int, "lu", a, op, b)
+#define munit_assert_llong(a, op, b) \
+ munit_assert_type(long long int, "lld", a, op, b)
+#define munit_assert_ullong(a, op, b) \
+ munit_assert_type(unsigned long long int, "llu", a, op, b)
+
+#define munit_assert_size(a, op, b) \
+ munit_assert_type(size_t, MUNIT_SIZE_MODIFIER "u", a, op, b)
+
+#define munit_assert_float(a, op, b) \
+ munit_assert_type(float, "f", a, op, b)
+#define munit_assert_double(a, op, b) \
+ munit_assert_type(double, "g", a, op, b)
+#define munit_assert_ptr(a, op, b) \
+ munit_assert_type(const void*, "p", a, op, b)
+
+#define munit_assert_int8(a, op, b) \
+ munit_assert_type(munit_int8_t, PRIi8, a, op, b)
+#define munit_assert_uint8(a, op, b) \
+ munit_assert_type(munit_uint8_t, PRIu8, a, op, b)
+#define munit_assert_int16(a, op, b) \
+ munit_assert_type(munit_int16_t, PRIi16, a, op, b)
+#define munit_assert_uint16(a, op, b) \
+ munit_assert_type(munit_uint16_t, PRIu16, a, op, b)
+#define munit_assert_int32(a, op, b) \
+ munit_assert_type(munit_int32_t, PRIi32, a, op, b)
+#define munit_assert_uint32(a, op, b) \
+ munit_assert_type(munit_uint32_t, PRIu32, a, op, b)
+#define munit_assert_int64(a, op, b) \
+ munit_assert_type(munit_int64_t, PRIi64, a, op, b)
+#define munit_assert_uint64(a, op, b) \
+ munit_assert_type(munit_uint64_t, PRIu64, a, op, b)
+
+#define munit_assert_double_equal(a, b, precision) \
+ do { \
+ const double munit_tmp_a_ = (a); \
+ const double munit_tmp_b_ = (b); \
+ const double munit_tmp_diff_ = ((munit_tmp_a_ - munit_tmp_b_) < 0) ? \
+ -(munit_tmp_a_ - munit_tmp_b_) : \
+ (munit_tmp_a_ - munit_tmp_b_); \
+ if (MUNIT_UNLIKELY(munit_tmp_diff_ > 1e-##precision)) { \
+ munit_errorf("assertion failed: %s == %s (%0." #precision "g == %0." #precision "g)", \
+ #a, #b, munit_tmp_a_, munit_tmp_b_); \
+ } \
+ MUNIT_PUSH_DISABLE_MSVC_C4127_ \
+ } while (0) \
+ MUNIT_POP_DISABLE_MSVC_C4127_
+
+#include <string.h>
+#define munit_assert_string_equal(a, b) \
+ do { \
+ const char* munit_tmp_a_ = a; \
+ const char* munit_tmp_b_ = b; \
+ if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) != 0)) { \
+ munit_errorf("assertion failed: string %s == %s (\"%s\" == \"%s\")", \
+ #a, #b, munit_tmp_a_, munit_tmp_b_); \
+ } \
+ MUNIT_PUSH_DISABLE_MSVC_C4127_ \
+ } while (0) \
+ MUNIT_POP_DISABLE_MSVC_C4127_
+
+#define munit_assert_string_not_equal(a, b) \
+ do { \
+ const char* munit_tmp_a_ = a; \
+ const char* munit_tmp_b_ = b; \
+ if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) == 0)) { \
+ munit_errorf("assertion failed: string %s != %s (\"%s\" == \"%s\")", \
+ #a, #b, munit_tmp_a_, munit_tmp_b_); \
+ } \
+ MUNIT_PUSH_DISABLE_MSVC_C4127_ \
+ } while (0) \
+ MUNIT_POP_DISABLE_MSVC_C4127_
+
+#define munit_assert_memory_equal(size, a, b) \
+ do { \
+ const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \
+ const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \
+ const size_t munit_tmp_size_ = (size); \
+ if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) != 0) { \
+ size_t munit_tmp_pos_; \
+ for (munit_tmp_pos_ = 0 ; munit_tmp_pos_ < munit_tmp_size_ ; munit_tmp_pos_++) { \
+ if (munit_tmp_a_[munit_tmp_pos_] != munit_tmp_b_[munit_tmp_pos_]) { \
+ munit_errorf("assertion failed: memory %s == %s, at offset %" MUNIT_SIZE_MODIFIER "u", \
+ #a, #b, munit_tmp_pos_); \
+ break; \
+ } \
+ } \
+ } \
+ MUNIT_PUSH_DISABLE_MSVC_C4127_ \
+ } while (0) \
+ MUNIT_POP_DISABLE_MSVC_C4127_
+
+#define munit_assert_memory_not_equal(size, a, b) \
+ do { \
+ const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \
+ const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \
+ const size_t munit_tmp_size_ = (size); \
+ if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) == 0) { \
+ munit_errorf("assertion failed: memory %s != %s (%zu bytes)", \
+ #a, #b, munit_tmp_size_); \
+ } \
+ MUNIT_PUSH_DISABLE_MSVC_C4127_ \
+ } while (0) \
+ MUNIT_POP_DISABLE_MSVC_C4127_
+
+#define munit_assert_ptr_equal(a, b) \
+ munit_assert_ptr(a, ==, b)
+#define munit_assert_ptr_not_equal(a, b) \
+ munit_assert_ptr(a, !=, b)
+#define munit_assert_null(ptr) \
+ munit_assert_ptr(ptr, ==, NULL)
+#define munit_assert_not_null(ptr) \
+ munit_assert_ptr(ptr, !=, NULL)
+#define munit_assert_ptr_null(ptr) \
+ munit_assert_ptr(ptr, ==, NULL)
+#define munit_assert_ptr_not_null(ptr) \
+ munit_assert_ptr(ptr, !=, NULL)
+
+/*** Memory allocation ***/
+
+void* munit_malloc_ex(const char* filename, int line, size_t size);
+
+#define munit_malloc(size) \
+ munit_malloc_ex(__FILE__, __LINE__, (size))
+
+#define munit_new(type) \
+ ((type*) munit_malloc(sizeof(type)))
+
+#define munit_calloc(nmemb, size) \
+ munit_malloc((nmemb) * (size))
+
+#define munit_newa(type, nmemb) \
+ ((type*) munit_calloc((nmemb), sizeof(type)))
+
+/*** Random number generation ***/
+
+void munit_rand_seed(munit_uint32_t seed);
+munit_uint32_t munit_rand_uint32(void);
+int munit_rand_int_range(int min, int max);
+double munit_rand_double(void);
+void munit_rand_memory(size_t size, munit_uint8_t buffer[MUNIT_ARRAY_PARAM(size)]);
+
+/*** Tests and Suites ***/
+
+typedef enum {
+ /* Test successful */
+ MUNIT_OK,
+ /* Test failed */
+ MUNIT_FAIL,
+ /* Test was skipped */
+ MUNIT_SKIP,
+ /* Test failed due to circumstances not intended to be tested
+ * (things like network errors, invalid parameter value, failure to
+ * allocate memory in the test harness, etc.). */
+ MUNIT_ERROR
+} MunitResult;
+
+typedef struct {
+ char* name;
+ char** values;
+} MunitParameterEnum;
+
+typedef struct {
+ char* name;
+ char* value;
+} MunitParameter;
+
+const char* munit_parameters_get(const MunitParameter params[], const char* key);
+
+typedef enum {
+ MUNIT_TEST_OPTION_NONE = 0,
+ MUNIT_TEST_OPTION_SINGLE_ITERATION = 1 << 0,
+ MUNIT_TEST_OPTION_TODO = 1 << 1
+} MunitTestOptions;
+
+typedef MunitResult (* MunitTestFunc)(const MunitParameter params[], void* user_data_or_fixture);
+typedef void* (* MunitTestSetup)(const MunitParameter params[], void* user_data);
+typedef void (* MunitTestTearDown)(void* fixture);
+
+typedef struct {
+ char* name;
+ MunitTestFunc test;
+ MunitTestSetup setup;
+ MunitTestTearDown tear_down;
+ MunitTestOptions options;
+ MunitParameterEnum* parameters;
+} MunitTest;
+
+typedef enum {
+ MUNIT_SUITE_OPTION_NONE = 0
+} MunitSuiteOptions;
+
+typedef struct MunitSuite_ MunitSuite;
+
+struct MunitSuite_ {
+ char* prefix;
+ MunitTest* tests;
+ MunitSuite* suites;
+ unsigned int iterations;
+ MunitSuiteOptions options;
+};
+
+int munit_suite_main(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]);
+
+/* Note: I'm not very happy with this API; it's likely to change if I
+ * figure out something better. Suggestions welcome. */
+
+typedef struct MunitArgument_ MunitArgument;
+
+struct MunitArgument_ {
+ char* name;
+ munit_bool (* parse_argument)(const MunitSuite* suite, void* user_data, int* arg, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)]);
+ void (* write_help)(const MunitArgument* argument, void* user_data);
+};
+
+int munit_suite_main_custom(const MunitSuite* suite,
+ void* user_data,
+ int argc, char* const argv[MUNIT_ARRAY_PARAM(argc + 1)],
+ const MunitArgument arguments[]);
+
+#if defined(MUNIT_ENABLE_ASSERT_ALIASES)
+
+#define assert_true(expr) munit_assert_true(expr)
+#define assert_false(expr) munit_assert_false(expr)
+#define assert_char(a, op, b) munit_assert_char(a, op, b)
+#define assert_uchar(a, op, b) munit_assert_uchar(a, op, b)
+#define assert_short(a, op, b) munit_assert_short(a, op, b)
+#define assert_ushort(a, op, b) munit_assert_ushort(a, op, b)
+#define assert_int(a, op, b) munit_assert_int(a, op, b)
+#define assert_uint(a, op, b) munit_assert_uint(a, op, b)
+#define assert_long(a, op, b) munit_assert_long(a, op, b)
+#define assert_ulong(a, op, b) munit_assert_ulong(a, op, b)
+#define assert_llong(a, op, b) munit_assert_llong(a, op, b)
+#define assert_ullong(a, op, b) munit_assert_ullong(a, op, b)
+#define assert_size(a, op, b) munit_assert_size(a, op, b)
+#define assert_float(a, op, b) munit_assert_float(a, op, b)
+#define assert_double(a, op, b) munit_assert_double(a, op, b)
+#define assert_ptr(a, op, b) munit_assert_ptr(a, op, b)
+
+#define assert_int8(a, op, b) munit_assert_int8(a, op, b)
+#define assert_uint8(a, op, b) munit_assert_uint8(a, op, b)
+#define assert_int16(a, op, b) munit_assert_int16(a, op, b)
+#define assert_uint16(a, op, b) munit_assert_uint16(a, op, b)
+#define assert_int32(a, op, b) munit_assert_int32(a, op, b)
+#define assert_uint32(a, op, b) munit_assert_uint32(a, op, b)
+#define assert_int64(a, op, b) munit_assert_int64(a, op, b)
+#define assert_uint64(a, op, b) munit_assert_uint64(a, op, b)
+
+#define assert_double_equal(a, b, precision) munit_assert_double_equal(a, b, precision)
+#define assert_string_equal(a, b) munit_assert_string_equal(a, b)
+#define assert_string_not_equal(a, b) munit_assert_string_not_equal(a, b)
+#define assert_memory_equal(size, a, b) munit_assert_memory_equal(size, a, b)
+#define assert_memory_not_equal(size, a, b) munit_assert_memory_not_equal(size, a, b)
+#define assert_ptr_equal(a, b) munit_assert_ptr_equal(a, b)
+#define assert_ptr_not_equal(a, b) munit_assert_ptr_not_equal(a, b)
+#define assert_ptr_null(ptr) munit_assert_null_equal(ptr)
+#define assert_ptr_not_null(ptr) munit_assert_not_null(ptr)
+
+#define assert_null(ptr) munit_assert_null(ptr)
+#define assert_not_null(ptr) munit_assert_not_null(ptr)
+
+#endif /* defined(MUNIT_ENABLE_ASSERT_ALIASES) */
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* !defined(MUNIT_H) */
+
+#if defined(MUNIT_ENABLE_ASSERT_ALIASES)
+# if defined(assert)
+# undef assert
+# endif
+# define assert(expr) munit_assert(expr)
+#endif
--
2.34.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v2 2/2] tests: add integration test setup
2024-02-16 2:58 [PATCH olang v2 0/2] Add integration tests Carlos Maniero
2024-02-16 2:58 ` [PATCH olang v2 1/2] tests: add munit testing framework file Carlos Maniero
@ 2024-02-16 2:58 ` Carlos Maniero
2024-02-16 3:03 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-16 3:15 ` [PATCH olang v2 0/2] Add integration tests Carlos Maniero
2 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-02-16 2:58 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
This is a basic setup for integration tests which includes:
- a *cli_runner* helper file to provide an interface to interact with
the CLI.
- a simple test that validates if the compiler is returning zero exit
code when a file is provided.
At this point the compiler still doing nothing making all this bootstrap
just a fancy way to check if the compiler was compiled properly.
Signed-off-by: Carlos Maniero <carlos@maniero.me>
---
.build.yml | 4 ++
Makefile | 12 ++++++
tests/integration/Makefile | 27 ++++++++++++
tests/integration/cli_runner.c | 77 ++++++++++++++++++++++++++++++++++
tests/integration/cli_runner.h | 27 ++++++++++++
tests/integration/cli_test.c | 39 +++++++++++++++++
6 files changed, 186 insertions(+)
create mode 100644 tests/integration/Makefile
create mode 100644 tests/integration/cli_runner.c
create mode 100644 tests/integration/cli_runner.h
create mode 100644 tests/integration/cli_test.c
diff --git a/.build.yml b/.build.yml
index 3aebfcf..b38efb3 100644
--- a/.build.yml
+++ b/.build.yml
@@ -12,3 +12,7 @@ tasks:
- build: |
cd olang
make
+ - check: |
+ cd olang
+ make check
+
diff --git a/Makefile b/Makefile
index 2a23b59..b13b41b 100644
--- a/Makefile
+++ b/Makefile
@@ -19,10 +19,22 @@ $(BUILD_DIR):
.PHONY: linter
linter: $(SRCS) $(HEADERS)
clang-format --dry-run --Werror $?
+ $(MAKE) -C tests/integration/ linter
+
.PHONY: linter-fix
linter-fix: $(SRCS) $(HEADERS)
clang-format -i $?
+ $(MAKE) -C tests/integration/ linter-fix
+
+.PHONY: integration-test
+integration-test:
+ $(MAKE)
+ $(MAKE) -C tests/integration/
+
+.PHONY: check
+check:
+ $(MAKE) integration-test
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@
diff --git a/tests/integration/Makefile b/tests/integration/Makefile
new file mode 100644
index 0000000..a42f787
--- /dev/null
+++ b/tests/integration/Makefile
@@ -0,0 +1,27 @@
+SRCS := $(wildcard *_test.c)
+TO_LINT := $(filter-out munit.c munit.h,$(wildcard *.c *.h))
+OBJS := $(patsubst %_test.c, %_test.o, $(SRCS))
+CFLAGS := -I../../src
+TESTS := $(patsubst %_test.c, %_test, $(SRCS))
+EXEC_TESTS := $(patsubst %_test, ./%_test, $(TESTS))
+
+.PHONY: all
+all: munit.o cli_runner.o $(TESTS)
+ @for file in $(EXEC_TESTS); do \
+ ./"$$file"; \
+ done
+
+.PHONY: clean
+clean:
+ $(RM) *.o *_test
+
+.PHONY: linter
+linter: $(TO_LINT)
+ clang-format --dry-run --Werror $?
+
+.PHONY: linter-fix
+linter-fix: $(TO_LINT)
+ clang-format -i $?
+
+cli_test: munit.o cli_runner.o cli_test.o
+ $(CC) $? $(CFLAGS) -o $@
diff --git a/tests/integration/cli_runner.c b/tests/integration/cli_runner.c
new file mode 100644
index 0000000..0ca0b37
--- /dev/null
+++ b/tests/integration/cli_runner.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#include "cli_runner.h"
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define OLANG_COMPILER_PATH "../../0c"
+
+static int compiler_exists_already_checked = 0;
+
+void
+static assert_compiler_exists()
+{
+ {
+ if (compiler_exists_already_checked == 1) {
+ return;
+ }
+
+ compiler_exists_already_checked = 1;
+ }
+
+ FILE *file = fopen(OLANG_COMPILER_PATH, "r");
+
+ if (file != NULL) {
+ fclose(file);
+ return;
+ }
+
+ perror("Build the compiler before executing tests");
+ exit(1);
+}
+
+void
+create_tmp_file_name(char *file_name)
+{
+ sprintf(file_name, "%s/olang_programXXXXXX", P_tmpdir);
+ int fd = mkstemp(file_name);
+
+ if (fd == -1) {
+ perror("Could not create a tmp file. Check your P_tmpdir permission.");
+ exit(1);
+ }
+ close(fd);
+}
+
+cli_result_t
+cli_runner_compile_file(char *src)
+{
+ assert_compiler_exists();
+
+ cli_result_t result;
+ create_tmp_file_name(result.program_path);
+
+ char command[1024];
+ sprintf(command, "%s -o %s %s", OLANG_COMPILER_PATH, result.program_path, src);
+
+ result.exit_code = system(command);
+ return result;
+}
diff --git a/tests/integration/cli_runner.h b/tests/integration/cli_runner.h
new file mode 100644
index 0000000..5caa319
--- /dev/null
+++ b/tests/integration/cli_runner.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#ifndef CLI_RUNNER_H
+#define CLI_RUNNER_H
+typedef struct cli_result_t
+{
+ int exit_code;
+ char program_path[255];
+} cli_result_t;
+
+cli_result_t
+cli_runner_compile_file(char *src);
+#endif
diff --git a/tests/integration/cli_test.c b/tests/integration/cli_test.c
new file mode 100644
index 0000000..c7a9557
--- /dev/null
+++ b/tests/integration/cli_test.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#define MUNIT_ENABLE_ASSERT_ALIASES
+#include "cli_runner.h"
+#include "munit.h"
+
+static MunitResult
+test_cli_hello_file(const MunitParameter params[], void *user_data_or_fixture)
+{
+ cli_result_t compilation_result = cli_runner_compile_file("../../examples/hello.olang");
+ munit_assert_int(compilation_result.exit_code, ==, 0);
+ return MUNIT_OK;
+}
+
+static MunitTest tests[] = { { "/test_cli_hello_file", test_cli_hello_file, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
+ { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } };
+
+static const MunitSuite suite = { "/cli_test", tests, NULL, 1, MUNIT_SUITE_OPTION_NONE };
+
+int
+main(int argc, char *argv[])
+{
+ return munit_suite_main(&suite, NULL, argc, argv);
+ return EXIT_SUCCESS;
+}
--
2.34.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [PATCH olang v2 0/2] Add integration tests
2024-02-16 2:58 [PATCH olang v2 0/2] Add integration tests Carlos Maniero
2024-02-16 2:58 ` [PATCH olang v2 1/2] tests: add munit testing framework file Carlos Maniero
2024-02-16 2:58 ` [PATCH olang v2 2/2] tests: add integration test setup Carlos Maniero
@ 2024-02-16 3:15 ` Carlos Maniero
2024-02-16 8:02 ` Johnny Richard
2 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-02-16 3:15 UTC (permalink / raw)
To: Carlos Maniero, ~johnnyrichard/olang-devel
Rejected in favor of v3.
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v1 1/1] codegen: x64: deref returns pointer value
@ 2024-10-17 2:48 Carlos Maniero
2024-10-17 2:49 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-10-17 2:48 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
Deref is context dependent, when doing an assignment:
*a = 1
It is expected for the deref codegen to return the pointer location, so
than the assignment binop would be able to assign a new value at that
location.
On another hand, when performing:
return *a
It is expected for deref to actually returns the pointer location value.
The codegen of both behaviors were defined in new functions to avoid
indiscriminately increase the codegen_x86_64_emit_expression
switch/case.
Register choice:
================
Since the operation *mov (%rax), %rax* is not possible, I arbitrarily
choose R10 as it is a caller-saved register.
Signed-off-by: Carlos Maniero <carlos@maniero.me>
---
src/codegen_x86_64.c | 61 +++++++++++++++++++++++++++++++++++++-------
1 file changed, 52 insertions(+), 9 deletions(-)
diff --git a/src/codegen_x86_64.c b/src/codegen_x86_64.c
index deb7e24..db1b77a 100644
--- a/src/codegen_x86_64.c
+++ b/src/codegen_x86_64.c
@@ -52,6 +52,8 @@ typedef enum x86_64_register_type
REG_R15
} x86_64_register_type_t;
+typedef size_t size_in_bytes_t;
+
/**
* Arch/ABI arg1 arg2 arg3 arg4 arg5 arg6 arg7 Notes
* ──────────────────────────────────────────────────────────────
@@ -76,6 +78,14 @@ codegen_x86_64_put_stack_offset(codegen_x86_64_t *codegen,
static size_t
codegen_x86_64_get_stack_offset(codegen_x86_64_t *codegen, symbol_t *symbol);
+static size_in_bytes_t
+codegen_x86_64_emit_unary_deref_address(codegen_x86_64_t *codegen,
+ ast_unary_op_t *unary_op);
+
+static size_in_bytes_t
+codegen_x86_64_emit_unary_deref_value(codegen_x86_64_t *codegen,
+ ast_unary_op_t *unary_op);
+
static size_t
type_to_bytes(type_t *type);
@@ -126,8 +136,6 @@ codegen_x86_64_get_next_label(codegen_x86_64_t *codegen)
return ++codegen->label_index;
}
-typedef size_t size_in_bytes_t;
-
static size_in_bytes_t
codegen_x86_64_emit_expression(codegen_x86_64_t *codegen, ast_node_t *expr_node)
{
@@ -619,7 +627,8 @@ codegen_x86_64_emit_expression(codegen_x86_64_t *codegen, ast_node_t *expr_node)
AST_UNARY_DEREFERENCE &&
"unsupported assignment lhs");
- codegen_x86_64_emit_expression(codegen, bin_op.lhs);
+ codegen_x86_64_emit_unary_deref_address(
+ codegen, &bin_op.lhs->as_unary_op);
fprintf(codegen->out, " push %%rax\n");
@@ -679,12 +688,8 @@ codegen_x86_64_emit_expression(codegen_x86_64_t *codegen, ast_node_t *expr_node)
return 8;
}
case AST_UNARY_DEREFERENCE: {
- // FIXME: support dereference of dereference (**)
- assert(unary_op.expr->kind == AST_NODE_REF &&
- "unsupported unary expression for dereference (*)");
-
- return codegen_x86_64_emit_expression(codegen,
- unary_op.expr);
+ return codegen_x86_64_emit_unary_deref_value(
+ codegen, &unary_op);
}
default: {
assert(0 && "unsupported unary operation");
@@ -829,6 +834,44 @@ codegen_x86_64_emit_if(codegen_x86_64_t *codegen, ast_if_stmt_t if_stmt)
fprintf(codegen->out, ".L%ld:\n", end_else_label);
}
+static size_in_bytes_t
+codegen_x86_64_emit_unary_deref_address(codegen_x86_64_t *codegen,
+ ast_unary_op_t *unary_op)
+{
+ assert(unary_op->kind == AST_UNARY_DEREFERENCE);
+ // FIXME: support dereference of dereference (**)
+ assert(unary_op->expr->kind == AST_NODE_REF &&
+ "unsupported unary expression for dereference (*)");
+
+ return codegen_x86_64_emit_expression(codegen, unary_op->expr);
+}
+
+static size_in_bytes_t
+codegen_x86_64_emit_unary_deref_value(codegen_x86_64_t *codegen,
+ ast_unary_op_t *unary_op)
+{
+ codegen_x86_64_emit_unary_deref_address(codegen, unary_op);
+
+ ast_ref_t ref = unary_op->expr->as_ref;
+
+ symbol_t *symbol = scope_lookup(ref.scope, ref.id);
+
+ assert(symbol->type->kind == TYPE_PTR);
+
+ size_in_bytes_t deref_size = type_to_bytes(symbol->type->as_ptr.type);
+
+ fprintf(codegen->out,
+ " mov (%%rax), %s\n",
+ get_reg_for(REG_R10, deref_size));
+
+ fprintf(codegen->out,
+ " mov %s, %s\n",
+ get_reg_for(REG_R10, deref_size),
+ get_reg_for(REG_ACCUMULATOR, deref_size));
+
+ return deref_size;
+}
+
static size_t
type_to_bytes(type_t *type)
{
--
2.46.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang] fix: codegen: prevent stack overwrite
@ 2024-10-15 12:14 Carlos Maniero
2024-10-15 12:14 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-10-15 12:14 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
There was an issue in the stack allocation algorithm. Consider this
function:
fn a(): u32 {
var a: u32 = 0xAAAA
var b: u64 = 0xBBBBBBBB
ret
}
There are three information the stack is required to store:
- 8 bytes: rip (from call instruction)
- 4 bytes: a
- 8 bytes: b
The 0x7FFFFF07 memory address was used to represent the RIP value at
call instant.
Our codegen was assuming the stack works that way:
0 -8 -C
^-------^---^-------
7FFFFF07AAAABBBBBBBB
^-------^---^-------
rip a b
So the code gen was:
- Adding the value at the stack;
- Increasing the offset.
But actually the stack was behaving as following:
8 0 -8 -C
^-------^-------^---^
7FFFFF070000BBBBBBBB.
^---------------^---^
rip a b
Once the instruction *mov %rax, -0xC(%rbp)* writes from -0xC(%rbp)
(exclusive) to -0x4(%rbp) (inclusive).
So after this change, this is the actual stack template:
0 -4 -C
--------^---^-------^
7FFFFF07AAAABBBBBBBB.
--------^---^-------^
rip a b
Signed-off-by: Carlos Maniero <carlos@maniero.me>
---
src/codegen_linux_x86_64.c | 13 ++++++-------
tests/olc/0036_variable_overflow.ol | 30 +++++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 7 deletions(-)
create mode 100644 tests/olc/0036_variable_overflow.ol
diff --git a/src/codegen_linux_x86_64.c b/src/codegen_linux_x86_64.c
index fc8fcc4..83d1d2c 100644
--- a/src/codegen_linux_x86_64.c
+++ b/src/codegen_linux_x86_64.c
@@ -28,7 +28,6 @@
// The call instruction pushes EIP into stack so the first 8 bytes from stack
// must be preserved else the ret instruction will jump to nowere.
-#define X86_CALL_EIP_STACK_OFFSET (8)
#define X86_CALL_ARG_SIZE 6
#define bytes_max(a, b) ((a) > (b) ? (a) : (b))
@@ -795,6 +794,9 @@ codegen_linux_x86_64_emit_block(codegen_x86_64_t *codegen, ast_block_t *block)
symbol_t *symbol = scope_lookup(scope, var_def.id);
assert(symbol);
+ size_t type_size = type_to_bytes(symbol->type);
+ codegen->base_offset += type_size;
+
codegen_linux_x86_64_put_stack_offset(
codegen, symbol, codegen->base_offset);
@@ -803,13 +805,10 @@ codegen_linux_x86_64_emit_block(codegen_x86_64_t *codegen, ast_block_t *block)
var_def.value);
}
- size_t type_size = type_to_bytes(symbol->type);
-
fprintf(codegen->out,
" mov %s, -%ld(%%rbp)\n",
get_reg_for(REG_ACCUMULATOR, type_size),
codegen->base_offset);
- codegen->base_offset += type_size;
break;
}
@@ -957,7 +956,7 @@ static void
codegen_linux_x86_64_emit_function(codegen_x86_64_t *codegen,
ast_fn_definition_t *fn_def)
{
- codegen->base_offset = X86_CALL_EIP_STACK_OFFSET;
+ codegen->base_offset = 0;
ast_node_t *block_node = fn_def->block;
fprintf(codegen->out, "" SV_FMT ":\n", SV_ARG(fn_def->id));
@@ -975,6 +974,8 @@ codegen_linux_x86_64_emit_function(codegen_x86_64_t *codegen,
symbol_t *symbol = scope_lookup(fn_def->scope, param->id);
assert(symbol);
+ // FIXME: add offset according to the param size
+ codegen->base_offset += 8;
size_t offset = codegen->base_offset;
codegen_linux_x86_64_put_stack_offset(
@@ -986,8 +987,6 @@ codegen_linux_x86_64_emit_function(codegen_x86_64_t *codegen,
get_reg_for(x86_call_args[i], symbol->type->as_primitive.size),
offset);
- // FIXME: add offset according to the param size
- codegen->base_offset += 8;
++i;
}
diff --git a/tests/olc/0036_variable_overflow.ol b/tests/olc/0036_variable_overflow.ol
new file mode 100644
index 0000000..edb3c7e
--- /dev/null
+++ b/tests/olc/0036_variable_overflow.ol
@@ -0,0 +1,30 @@
+# Copyright (C) 2024 olang mantainers
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+fn main(): u32 {
+ var a: u32 = 0
+ var b: u64 = 0
+ var c: u32 = 0
+
+ # This operation will fill all bits in b location.
+ # If there is an overflow, both a or c would be impacted
+ b = ~b
+
+ return a + c
+}
+
+# TEST test_compile(exit_code=0)
+
+# TEST test_run_binary(exit_code=0)
base-commit: cf5e4abf07a38f0ddf3ac6979b01b942ab99a691
--
2.46.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v1] fix: build: add missing dependencies for check-spec
@ 2024-10-11 3:42 Johnny Richard
2024-10-11 1:43 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-10-11 3:42 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
.build.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.build.yml b/.build.yml
index 9c8eaa5..f09153c 100644
--- a/.build.yml
+++ b/.build.yml
@@ -6,6 +6,8 @@ packages:
- hut
- clang
- texinfo
+ - clojure
+ - rlwrap
environment:
site: o-lang.org
sources:
base-commit: 9d94b76123df435b52365fbd82babbb66ec20839
--
2.46.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang] parser: conform block line feeds with the spec
@ 2024-10-08 16:33 Carlos Maniero
2024-10-08 16:33 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-10-08 16:33 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
The line feeds has been handled by the statement parser functions when
the olang spec describe this grammar rule as a block responsibility.
There was also a FIXME related to allowing line feeds in between the
if/else statement and the block start that was also addressed.
Signed-off-by: Carlos Maniero <carlos@maniero.me>
---
src/parser.c | 47 ++++++++++++++-----------
| 30 ++++++++++++++++
2 files changed, 57 insertions(+), 20 deletions(-)
create mode 100644 tests/olc/0031_else_extra_line_feeds.ol
diff --git a/src/parser.c b/src/parser.c
index d16b79d..2096129 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -70,6 +70,9 @@ parser_parse_factor(parser_t *parser);
static void
skip_line_feeds(lexer_t *lexer);
+static void
+peek_next_non_lf_token(lexer_t *lexer, token_t *token);
+
void
parser_init(parser_t *parser, lexer_t *lexer, arena_t *arena)
{
@@ -493,6 +496,12 @@ StartLoop:
return NULL;
}
+ if (!skip_expected_token(parser, TOKEN_LF)) {
+ return NULL;
+ }
+
+ skip_line_feeds(parser->lexer);
+
list_append(node_block->as_block.nodes, node);
goto StartLoop;
@@ -522,11 +531,6 @@ parser_parse_return_stmt(parser_t *parser)
ast_node_t *node_return_stmt = ast_new_node_return_stmt(parser->arena, token_ret.loc, expr);
assert(node_return_stmt);
- if (!skip_expected_token(parser, TOKEN_LF)) {
- return NULL;
- }
- skip_line_feeds(parser->lexer);
-
return node_return_stmt;
}
@@ -544,6 +548,8 @@ parser_parse_if_stmt(parser_t *parser)
return NULL;
}
+ skip_line_feeds(parser->lexer);
+
ast_node_t *then = parser_parse_block(parser);
if (then == NULL) {
@@ -553,29 +559,25 @@ parser_parse_if_stmt(parser_t *parser)
ast_node_t *_else = NULL;
token_t next_token;
- lexer_next_token(parser->lexer, &next_token);
-
- // FIXME: We are not allowing line feed right after if block statement when
- // the else branch is present. We also noticed that if has the same
- // problem which will be addressed later.
+ peek_next_non_lf_token(parser->lexer, &next_token);
if (next_token.kind == TOKEN_ELSE) {
+ skip_line_feeds(parser->lexer);
+ lexer_next_token(parser->lexer, &next_token);
+ skip_line_feeds(parser->lexer);
+
_else = parser_parse_block(parser);
if (_else == NULL) {
return NULL;
}
- } else if (!expected_token(&next_token, TOKEN_LF)) {
- return NULL;
}
ast_node_t *node_if_stmt = ast_new_node_if_stmt(parser->arena, token_if.loc, cond, then, _else);
assert(node_if_stmt);
- skip_line_feeds(parser->lexer);
-
return node_if_stmt;
}
@@ -608,8 +610,6 @@ parser_parse_var_def(parser_t *parser)
ast_node_t *var_node = ast_new_node_var_def(parser->arena, token_id.loc, token_id.value, var_type, expr);
- skip_line_feeds(parser->lexer);
-
return var_node;
}
@@ -631,10 +631,6 @@ parser_parse_var_assign_stmt(parser_t *parser)
ast_node_t *ref = ast_new_node_ref(parser->arena, token_id.loc, token_id.value);
ast_node_t *expr = parser_parse_expr(parser);
- // FIXME: The expected line feed should be parsed from parent call
- // according to the grammar rules
- skip_line_feeds(parser->lexer);
-
return ast_new_node_var_assign_stmt(parser->arena, token_eq.loc, ref, expr);
}
@@ -683,3 +679,14 @@ skip_line_feeds(lexer_t *lexer)
lexer_peek_next(lexer, &token);
}
}
+
+static void
+peek_next_non_lf_token(lexer_t *lexer, token_t *token)
+{
+ lexer_cursor_t cur = lexer->cur;
+
+ skip_line_feeds(lexer);
+ lexer_peek_next(lexer, token);
+
+ lexer->cur = cur;
+}
--git a/tests/olc/0031_else_extra_line_feeds.ol b/tests/olc/0031_else_extra_line_feeds.ol
new file mode 100644
index 0000000..bc44ccd
--- /dev/null
+++ b/tests/olc/0031_else_extra_line_feeds.ol
@@ -0,0 +1,30 @@
+# Copyright (C) 2024 olang mantainers
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+fn main(): u32
+{
+ if 0 != 0
+ {
+ return 1
+ }
+ else
+ {
+ return 0
+ }
+}
+
+# TEST test_compile(exit_code=0)
+
+# TEST test_run_binary(exit_code=0)
base-commit: 3c8975ba27c87d084187eefe622cbd783e289c99
--
2.46.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v1 2/2] ast: inline ast_node_data_t union definition
@ 2024-08-13 18:16 Johnny Richard
2024-08-13 17:27 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-08-13 18:16 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
src/ast.c | 20 ++++++++++----------
src/ast.h | 20 +++++++++-----------
src/codegen_linux_aarch64.c | 10 +++++-----
src/codegen_linux_x86_64.c | 12 ++++++------
src/parser.c | 4 ++--
src/pretty_print_ast.c | 12 ++++++------
tests/unit/parser_test.c | 14 +++++++-------
7 files changed, 45 insertions(+), 47 deletions(-)
diff --git a/src/ast.c b/src/ast.c
index 1f7df9c..aa9e6db 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -29,7 +29,7 @@ ast_new_program(arena_t *arena, ast_node_t *fn_def)
assert(node);
node->kind = AST_NODE_PROGRAM;
- ast_program_t *program = &node->data.as_program;
+ ast_program_t *program = &node->as_program;
program->fn = fn_def;
@@ -43,7 +43,7 @@ ast_new_node_fn_def(arena_t *arena, string_view_t identifier, type_t return_type
assert(node_fn_def);
node_fn_def->kind = AST_NODE_FN_DEF;
- ast_fn_definition_t *fn_def = &node_fn_def->data.as_fn_def;
+ ast_fn_definition_t *fn_def = &node_fn_def->as_fn_def;
fn_def->identifier = identifier;
fn_def->return_type = return_type;
@@ -59,9 +59,9 @@ ast_new_node_bin_op(arena_t *arena, ast_binary_op_kind_t kind, ast_node_t *lhs,
assert(node_bin_op);
node_bin_op->kind = AST_NODE_BINARY_OP;
- node_bin_op->data.as_bin_op.kind = kind;
- node_bin_op->data.as_bin_op.lhs = lhs;
- node_bin_op->data.as_bin_op.rhs = rhs;
+ node_bin_op->as_bin_op.kind = kind;
+ node_bin_op->as_bin_op.lhs = lhs;
+ node_bin_op->as_bin_op.rhs = rhs;
return node_bin_op;
}
@@ -73,8 +73,8 @@ ast_new_node_literal_u32(arena_t *arena, uint32_t value)
assert(node_literal);
node_literal->kind = AST_NODE_LITERAL;
- node_literal->data.as_literal.kind = AST_LITERAL_U32;
- node_literal->data.as_literal.as_u32 = value;
+ node_literal->as_literal.kind = AST_LITERAL_U32;
+ node_literal->as_literal.as_u32 = value;
return node_literal;
}
@@ -98,10 +98,10 @@ ast_new_node_block(arena_t *arena)
node_block->kind = AST_NODE_BLOCK;
- node_block->data.as_block.nodes = (list_t *)arena_alloc(arena, sizeof(list_t));
- assert(node_block->data.as_block.nodes);
+ node_block->as_block.nodes = (list_t *)arena_alloc(arena, sizeof(list_t));
+ assert(node_block->as_block.nodes);
- list_init(node_block->data.as_block.nodes, arena);
+ list_init(node_block->as_block.nodes, arena);
return node_block;
}
diff --git a/src/ast.h b/src/ast.h
index a58a492..024f2cc 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -106,20 +106,18 @@ typedef struct ast_return_stmt
ast_node_t *data;
} ast_return_stmt_t;
-typedef union
-{
- ast_program_t as_program;
- ast_fn_definition_t as_fn_def;
- ast_binary_op_t as_bin_op;
- ast_literal_t as_literal;
- ast_block_t as_block;
- ast_return_stmt_t as_return_stmt;
-} ast_node_data_t;
-
typedef struct ast_node
{
ast_node_kind_t kind;
- ast_node_data_t data;
+ union
+ {
+ ast_program_t as_program;
+ ast_fn_definition_t as_fn_def;
+ ast_binary_op_t as_bin_op;
+ ast_literal_t as_literal;
+ ast_block_t as_block;
+ ast_return_stmt_t as_return_stmt;
+ };
} ast_node_t;
ast_node_t *
diff --git a/src/codegen_linux_aarch64.c b/src/codegen_linux_aarch64.c
index 73f4aab..dafdcc4 100644
--- a/src/codegen_linux_aarch64.c
+++ b/src/codegen_linux_aarch64.c
@@ -47,9 +47,9 @@ codegen_linux_aarch64_emit_program(FILE *out, ast_node_t *node)
codegen_linux_aarch64_emit_start_entrypoint(out);
assert(node->kind == AST_NODE_PROGRAM);
- ast_program_t program = node->data.as_program;
+ ast_program_t program = node->as_program;
- ast_fn_definition_t fn = program.fn->data.as_fn_def;
+ ast_fn_definition_t fn = program.fn->as_fn_def;
assert(string_view_eq_to_cstr(fn.identifier, "main"));
codegen_linux_aarch64_emit_function(out, &fn);
@@ -72,18 +72,18 @@ codegen_linux_aarch64_emit_function(FILE *out, ast_fn_definition_t *fn)
{
ast_node_t *block_node = fn->block;
assert(block_node->kind == AST_NODE_BLOCK);
- ast_block_t block = block_node->data.as_block;
+ ast_block_t block = block_node->as_block;
assert(list_size(block.nodes) == 1);
list_item_t *nodes_item = list_get(block.nodes, 0);
ast_node_t *return_node = nodes_item->value;
assert(return_node->kind == AST_NODE_RETURN_STMT);
- ast_return_stmt_t return_stmt = return_node->data.as_return_stmt;
+ ast_return_stmt_t return_stmt = return_node->as_return_stmt;
ast_node_t *literal_node = return_stmt.data;
assert(literal_node->kind == AST_NODE_LITERAL);
- ast_literal_t literal_u32 = literal_node->data.as_literal;
+ ast_literal_t literal_u32 = literal_node->as_literal;
assert(literal_u32.kind == AST_LITERAL_U32);
uint32_t exit_code = literal_u32.as_u32;
diff --git a/src/codegen_linux_x86_64.c b/src/codegen_linux_x86_64.c
index 28e7f8e..64ec0e0 100644
--- a/src/codegen_linux_x86_64.c
+++ b/src/codegen_linux_x86_64.c
@@ -38,9 +38,9 @@ codegen_linux_x86_64_emit_program(FILE *out, ast_node_t *node)
codegen_linux_x86_64_emit_start_entrypoint(out);
assert(node->kind == AST_NODE_PROGRAM);
- ast_program_t program = node->data.as_program;
+ ast_program_t program = node->as_program;
- ast_fn_definition_t fn = program.fn->data.as_fn_def;
+ ast_fn_definition_t fn = program.fn->as_fn_def;
assert(string_view_eq_to_cstr(fn.identifier, "main"));
codegen_linux_x86_64_emit_function(out, &fn);
@@ -70,7 +70,7 @@ codegen_linux_x86_64_emit_expression(FILE *out, ast_node_t *expr_node)
{
switch (expr_node->kind) {
case AST_NODE_LITERAL: {
- ast_literal_t literal_u32 = expr_node->data.as_literal;
+ ast_literal_t literal_u32 = expr_node->as_literal;
assert(literal_u32.kind == AST_LITERAL_U32);
uint32_t n = literal_u32.as_u32;
@@ -78,7 +78,7 @@ codegen_linux_x86_64_emit_expression(FILE *out, ast_node_t *expr_node)
return;
}
case AST_NODE_BINARY_OP: {
- ast_binary_op_t bin_op = expr_node->data.as_bin_op;
+ ast_binary_op_t bin_op = expr_node->as_bin_op;
switch (bin_op.kind) {
case AST_BINOP_ADDITION: {
codegen_linux_x86_64_emit_expression(out, bin_op.rhs);
@@ -305,14 +305,14 @@ codegen_linux_x86_64_emit_function(FILE *out, ast_fn_definition_t *fn)
{
ast_node_t *block_node = fn->block;
assert(block_node->kind == AST_NODE_BLOCK);
- ast_block_t block = block_node->data.as_block;
+ ast_block_t block = block_node->as_block;
assert(list_size(block.nodes) == 1);
list_item_t *nodes_item = list_get(block.nodes, 0);
ast_node_t *return_node = nodes_item->value;
assert(return_node->kind == AST_NODE_RETURN_STMT);
- ast_return_stmt_t return_stmt = return_node->data.as_return_stmt;
+ ast_return_stmt_t return_stmt = return_node->as_return_stmt;
ast_node_t *expr = return_stmt.data;
diff --git a/src/parser.c b/src/parser.c
index 5dd4ef1..24094b3 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -336,9 +336,9 @@ parser_parse_block(parser_t *parser)
return NULL;
}
- node_return_stmt->data.as_return_stmt.data = expr;
+ node_return_stmt->as_return_stmt.data = expr;
- list_append(node_block->data.as_block.nodes, node_return_stmt);
+ list_append(node_block->as_block.nodes, node_return_stmt);
if (!skip_expected_token(parser, TOKEN_LF)) {
return NULL;
}
diff --git a/src/pretty_print_ast.c b/src/pretty_print_ast.c
index 19ccafe..6ca172f 100644
--- a/src/pretty_print_ast.c
+++ b/src/pretty_print_ast.c
@@ -116,13 +116,13 @@ ast_node_to_pretty_print_node(ast_node_t *ast, arena_t *arena)
pretty_print_node_t *node = pretty_print_node_new(arena);
node->name = "Translation_Unit";
- pretty_print_node_t *fn_node = ast_node_to_pretty_print_node(ast->data.as_program.fn, arena);
+ pretty_print_node_t *fn_node = ast_node_to_pretty_print_node(ast->as_program.fn, arena);
list_append(node->children, fn_node);
return node;
}
case AST_NODE_FN_DEF: {
pretty_print_node_t *node = pretty_print_node_new(arena);
- ast_fn_definition_t fn_def = ast->data.as_fn_def;
+ ast_fn_definition_t fn_def = ast->as_fn_def;
char name[256];
sprintf(name,
@@ -138,7 +138,7 @@ ast_node_to_pretty_print_node(ast_node_t *ast, arena_t *arena)
}
case AST_NODE_BLOCK: {
pretty_print_node_t *node = pretty_print_node_new(arena);
- ast_block_t block = ast->data.as_block;
+ ast_block_t block = ast->as_block;
node->name = "Block";
@@ -152,7 +152,7 @@ ast_node_to_pretty_print_node(ast_node_t *ast, arena_t *arena)
}
case AST_NODE_RETURN_STMT: {
pretty_print_node_t *node = pretty_print_node_new(arena);
- ast_return_stmt_t return_stmt = ast->data.as_return_stmt;
+ ast_return_stmt_t return_stmt = ast->as_return_stmt;
node->name = "Return_Statement";
@@ -163,7 +163,7 @@ ast_node_to_pretty_print_node(ast_node_t *ast, arena_t *arena)
}
case AST_NODE_LITERAL: {
pretty_print_node_t *node = pretty_print_node_new(arena);
- ast_literal_t literal = ast->data.as_literal;
+ ast_literal_t literal = ast->as_literal;
char name[256];
switch (literal.kind) {
@@ -181,7 +181,7 @@ ast_node_to_pretty_print_node(ast_node_t *ast, arena_t *arena)
}
case AST_NODE_BINARY_OP: {
pretty_print_node_t *node = pretty_print_node_new(arena);
- ast_binary_op_t binop = ast->data.as_bin_op;
+ ast_binary_op_t binop = ast->as_bin_op;
switch (binop.kind) {
case AST_BINOP_ADDITION: {
diff --git a/tests/unit/parser_test.c b/tests/unit/parser_test.c
index 1925a95..3cdac41 100644
--- a/tests/unit/parser_test.c
+++ b/tests/unit/parser_test.c
@@ -45,11 +45,11 @@ parse_program_test(const MunitParameter params[], void *user_data_or_fixture)
assert_not_null(program_node);
assert_uint(program_node->kind, ==, AST_NODE_PROGRAM);
- ast_program_t program = program_node->data.as_program;
+ ast_program_t program = program_node->as_program;
assert_not_null(program.fn);
assert_uint(program.fn->kind, ==, AST_NODE_FN_DEF);
- ast_fn_definition_t fn = program.fn->data.as_fn_def;
+ ast_fn_definition_t fn = program.fn->as_fn_def;
assert_memory_equal(fn.identifier.size, fn.identifier.chars, "main");
assert_uint(fn.return_type, ==, TYPE_U32);
@@ -57,8 +57,8 @@ parse_program_test(const MunitParameter params[], void *user_data_or_fixture)
assert_not_null(block);
assert_uint(block->kind, ==, AST_NODE_BLOCK);
- assert_uint(list_size(block->data.as_block.nodes), ==, 1);
- list_item_t *block_item = list_get(block->data.as_block.nodes, 0);
+ assert_uint(list_size(block->as_block.nodes), ==, 1);
+ list_item_t *block_item = list_get(block->as_block.nodes, 0);
assert_not_null(block_item);
assert_not_null(block_item->value);
@@ -66,11 +66,11 @@ parse_program_test(const MunitParameter params[], void *user_data_or_fixture)
assert_not_null(node);
assert_uint(node->kind, ==, AST_NODE_RETURN_STMT);
- ast_node_t *number_node = node->data.as_return_stmt.data;
+ ast_node_t *number_node = node->as_return_stmt.data;
assert_not_null(number_node);
assert_uint(number_node->kind, ==, AST_NODE_LITERAL);
- assert_uint(number_node->data.as_literal.kind, ==, AST_LITERAL_U32);
- assert_uint(number_node->data.as_literal.as_u32, ==, 69);
+ assert_uint(number_node->as_literal.kind, ==, AST_LITERAL_U32);
+ assert_uint(number_node->as_literal.as_u32, ==, 69);
arena_free(&arena);
--
2.46.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v1] build: rename linter to format to avoid confusion
@ 2024-04-20 13:54 Johnny Richard
2024-04-20 12:57 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-04-20 13:54 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
We have been using linter as the target to check our code formatting.
Today we are using `clang-format` to verify the formatting. This tool
does only formatting, and miss other features that a linter have.
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
.build.yml | 4 ++--
Makefile | 16 ++++++++--------
docs/pages/contribute.md | 16 ++++++++--------
tests/integration/Makefile | 8 ++++----
tests/unit/Makefile | 8 ++++----
5 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/.build.yml b/.build.yml
index 72f0254..01f4aab 100644
--- a/.build.yml
+++ b/.build.yml
@@ -11,9 +11,9 @@ environment:
sources:
- https://git.sr.ht/~johnnyrichard/olang
tasks:
- - lint: |
+ - format: |
cd olang
- make linter
+ make format
- build: |
cd olang
make
diff --git a/Makefile b/Makefile
index cdfc8e1..27337d4 100644
--- a/Makefile
+++ b/Makefile
@@ -16,17 +16,17 @@ $(TARGET): $(BUILD_DIR) $(OBJS)
$(BUILD_DIR):
@mkdir -p $@
-.PHONY: linter
-linter: $(SRCS) $(HEADERS)
+.PHONY: format
+format: $(SRCS) $(HEADERS)
clang-format --dry-run --Werror $?
- $(MAKE) -C tests/integration/ linter
- $(MAKE) -C tests/unit/ linter
+ $(MAKE) -C tests/integration/ format
+ $(MAKE) -C tests/unit/ format
-.PHONY: linter-fix
-linter-fix: $(SRCS) $(HEADERS)
+.PHONY: format-fix
+format-fix: $(SRCS) $(HEADERS)
clang-format -i $?
- $(MAKE) -C tests/integration/ linter-fix
- $(MAKE) -C tests/unit/ linter-fix
+ $(MAKE) -C tests/integration/ format-fix
+ $(MAKE) -C tests/unit/ format-fix
.PHONY: integration-test
integration-test:
diff --git a/docs/pages/contribute.md b/docs/pages/contribute.md
index 884c4b4..a6dfd04 100644
--- a/docs/pages/contribute.md
+++ b/docs/pages/contribute.md
@@ -25,21 +25,21 @@ Code style
Instead of delineating every element of our coding style, we have
adopted the use of **clang-format** to enforce the olang code style.
-Please refer to the linter section below for guidance on its
+Please refer to the **Format** section below for guidance on its
application.
-### Linter
+### Format
-Checking for linter issues:
+Checking for format issues:
``` {.sh}
-make linter
+make format
```
Most of the common code style mistakes are fixed by:
``` {.sh}
-make linter-fix
+make format-fix
```
### .editorconfig
@@ -92,9 +92,9 @@ the email-driven workflow here, but you can check it out at
1. Write single-purpose commits.
2. Write a meaningful commit message.
3. Every commit must be production ready.
- - If the tests or the linter fail, you should not create a fix commit.
- Instead, you should amend the commit that caused the issue and then
- resend the patchset.
+ - If the tests or the format check fail, you should not create a fix
+ commit. Instead, you should amend the commit that caused the issue and
+ then resend the patchset.
### Step 2: Create your patch
diff --git a/tests/integration/Makefile b/tests/integration/Makefile
index db2b7d9..4625707 100644
--- a/tests/integration/Makefile
+++ b/tests/integration/Makefile
@@ -16,12 +16,12 @@ all: $(MUNIT) proc_exec.o cli_runner.o $(TESTS)
clean:
$(RM) *.o *_test
-.PHONY: linter
-linter: $(SRCS)
+.PHONY: format
+format: $(SRCS)
clang-format --dry-run --Werror $?
-.PHONY: linter-fix
-linter-fix: $(SRCS)
+.PHONY: format-fix
+format-fix: $(SRCS)
clang-format -i $?
cli_test: $(MUNIT) proc_exec.o cli_runner.o cli_test.o
diff --git a/tests/unit/Makefile b/tests/unit/Makefile
index 498bf98..783225c 100644
--- a/tests/unit/Makefile
+++ b/tests/unit/Makefile
@@ -18,12 +18,12 @@ clean:
$(RM) *.o *_test
$(RM) -rfv lib
-.PHONY: linter
-linter: $(SRCS)
+.PHONY: format
+format: $(SRCS)
clang-format --dry-run --Werror $?
-.PHONY: linter-fix
-linter-fix: $(SRCS)
+.PHONY: format-fix
+format-fix: $(SRCS)
clang-format -i $?
%_test: $(MUNIT) $(SUBJECT_OBJS) %_test.c
base-commit: 36b028f712ff2402761ea307467860c346d3c0a0
--
2.44.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v1] fe: lexer: add single line comments support
@ 2024-03-28 15:58 Johnny Richard
2024-03-28 14:59 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-03-28 15:58 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
examples/main_exit.ol | 3 +++
src/lexer.c | 7 +++++++
src/parser.c | 1 +
tests/integration/cli_test.c | 31 +++++++++++++++++--------------
4 files changed, 28 insertions(+), 14 deletions(-)
diff --git a/examples/main_exit.ol b/examples/main_exit.ol
index c86fc68..8952017 100644
--- a/examples/main_exit.ol
+++ b/examples/main_exit.ol
@@ -1,3 +1,6 @@
+# Expected:
+# - output: ""
+
fn main(): u32 {
return 0
}
diff --git a/src/lexer.c b/src/lexer.c
index 801e4d0..684cad1 100644
--- a/src/lexer.c
+++ b/src/lexer.c
@@ -76,6 +76,13 @@ lexer_next_token(lexer_t *lexer, token_t *token)
}
while (lexer_is_not_eof(lexer)) {
+ if (current_char == '#') {
+ while (current_char != '\n' && lexer_is_not_eof(lexer)) {
+ lexer_skip_char(lexer);
+ current_char = lexer_current_char(lexer);
+ }
+ }
+
if (isalpha(current_char)) {
size_t start_offset = lexer->offset;
while (isalnum(current_char) && lexer_is_not_eof(lexer)) {
diff --git a/src/parser.c b/src/parser.c
index 76ef91a..b800870 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -62,6 +62,7 @@ parser_init(parser_t *parser, lexer_t *lexer, arena_t *arena, char *file_path)
ast_node_t *
parser_parse_program(parser_t *parser)
{
+ skip_line_feeds(parser->lexer);
ast_node_t *fn = parser_parse_fn_definition(parser);
if (fn == NULL) {
return NULL;
diff --git a/tests/integration/cli_test.c b/tests/integration/cli_test.c
index d46471b..e7ae059 100644
--- a/tests/integration/cli_test.c
+++ b/tests/integration/cli_test.c
@@ -25,20 +25,23 @@ test_cli_dump_tokens_example_main_exit(const MunitParameter params[], void *user
cli_result_t compilation_result = cli_runner_compiler_dump_tokens("../../examples/main_exit.ol");
munit_assert_int(compilation_result.exec.exit_code, ==, 0);
munit_assert_string_equal(compilation_result.exec.stdout_buf,
- "../../examples/main_exit.ol:1:1: <fn>\n"
- "../../examples/main_exit.ol:1:4: <identifier>\n"
- "../../examples/main_exit.ol:1:8: <(>\n"
- "../../examples/main_exit.ol:1:9: <)>\n"
- "../../examples/main_exit.ol:1:10: <:>\n"
- "../../examples/main_exit.ol:1:12: <identifier>\n"
- "../../examples/main_exit.ol:1:16: <{>\n"
- "../../examples/main_exit.ol:1:17: <line_feed>\n"
- "../../examples/main_exit.ol:2:3: <return>\n"
- "../../examples/main_exit.ol:2:10: <number>\n"
- "../../examples/main_exit.ol:2:11: <line_feed>\n"
- "../../examples/main_exit.ol:3:1: <}>\n"
- "../../examples/main_exit.ol:3:2: <line_feed>\n"
- "../../examples/main_exit.ol:4:1: <EOF>\n");
+ "../../examples/main_exit.ol:1:12: <line_feed>\n"
+ "../../examples/main_exit.ol:2:16: <line_feed>\n"
+ "../../examples/main_exit.ol:3:1: <line_feed>\n"
+ "../../examples/main_exit.ol:4:1: <fn>\n"
+ "../../examples/main_exit.ol:4:4: <identifier>\n"
+ "../../examples/main_exit.ol:4:8: <(>\n"
+ "../../examples/main_exit.ol:4:9: <)>\n"
+ "../../examples/main_exit.ol:4:10: <:>\n"
+ "../../examples/main_exit.ol:4:12: <identifier>\n"
+ "../../examples/main_exit.ol:4:16: <{>\n"
+ "../../examples/main_exit.ol:4:17: <line_feed>\n"
+ "../../examples/main_exit.ol:5:3: <return>\n"
+ "../../examples/main_exit.ol:5:10: <number>\n"
+ "../../examples/main_exit.ol:5:11: <line_feed>\n"
+ "../../examples/main_exit.ol:6:1: <}>\n"
+ "../../examples/main_exit.ol:6:2: <line_feed>\n"
+ "../../examples/main_exit.ol:7:1: <EOF>\n");
return MUNIT_OK;
}
base-commit: 117a06874c48c64e8ad4befbab244670f4f9ca9c
--
2.44.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [olang/patches/.build.yml] build failed
2024-03-28 15:58 [PATCH olang v1] fe: lexer: add single line comments support Johnny Richard
@ 2024-03-28 14:59 ` builds.sr.ht
2024-03-28 16:46 ` Johnny Richard
0 siblings, 1 reply; 40+ messages in thread
From: builds.sr.ht @ 2024-03-28 14:59 UTC (permalink / raw)
To: Johnny Richard; +Cc: ~johnnyrichard/olang-devel
olang/patches/.build.yml: FAILED in 36s
[fe: lexer: add single line comments support][0] from [Johnny Richard][1]
[0]: https://lists.sr.ht/~johnnyrichard/olang-devel/patches/50503
[1]: mailto:johnny@johnnyrichard.com
✗ #1181030 FAILED olang/patches/.build.yml https://builds.sr.ht/~johnnyrichard/job/1181030
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [olang/patches/.build.yml] build failed
2024-03-28 14:59 ` [olang/patches/.build.yml] build failed builds.sr.ht
@ 2024-03-28 16:46 ` Johnny Richard
0 siblings, 0 replies; 40+ messages in thread
From: Johnny Richard @ 2024-03-28 16:46 UTC (permalink / raw)
To: builds.sr.ht; +Cc: ~johnnyrichard/olang-devel
On Thu, Mar 28, 2024 at 02:59:02PM +0000, builds.sr.ht wrote:
> olang/patches/.build.yml: FAILED in 36s
>
> [fe: lexer: add single line comments support][0] from [Johnny Richard][1]
>
> [0]: https://lists.sr.ht/~johnnyrichard/olang-devel/patches/50503
> [1]: mailto:johnny@johnnyrichard.com
>
> ✗ #1181030 FAILED olang/patches/.build.yml https://builds.sr.ht/~johnnyrichard/job/1181030
This build is failing due to a clang bumped version on Arch Linux
repositories. It was a major bump from 16 to 17 (it has breaking
changes).
We can solve this problem by fixing the version or by bumping our clang
locally. I will upgrade my clang meanwhile.
Please, consider the patch to fix it.
---->8----
Subject: [PATCH olang] fixup! fe: lexer: add single line comments support
diff --git a/src/pretty_print_ast.c b/src/pretty_print_ast.c
index e950796..129f090 100644
--- a/src/pretty_print_ast.c
+++ b/src/pretty_print_ast.c
@@ -26,7 +26,7 @@
#define ANSI_COLOR_MAGENTA "\x1b[35m"
#define ANSI_COLOR_RESET "\x1b[0m"
-#define PP_IS_BIT_SET(data, index) ((data)&1 << index)
+#define PP_IS_BIT_SET(data, index) ((data) & 1 << index)
typedef struct pretty_print_node
{
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v1 2/2] docs: spec: add variables and constants specification
@ 2024-03-27 3:21 Carlos Maniero
2024-03-27 3:22 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-03-27 3:21 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
This commit introduces the specification for variables and constants. A
valid program under this specification is as follows:
var x: u32 = 1
const y: u32 = 2
x = 2
fn main(): u32 {
var x: u32 = 1; const y: u32 = 2
return x
}
Variables and Linkage:
----------------------
Variables can be defined globally or within a function. Variables
defined globally are added to the *.data* section. On the other hand,
variables defined within a function utilize the stack. Currently, the
specification does not provide a keyword for external linkage yet, hence
variables cannot be accessed outside their translation unit.
Constants and Linkage:
----------------------
Constants behave similarly to variables. Constants defined at the file
level are added to the *.rodata* section. Constants within functions are
subject to semantic checks. Attempting to bypass these checks to
reassign a global constant will result in a segmentation fault
(SEGFAULT) and will be accepted at the function level.
Example of bypassing semantic checks in C:
int a = 1;
const int b = 2;
int *c = &a + 1;
*c = 3;
assert(b == 3); // its true
Static variables in function level
----------------------------------
In C, static variables can be defined within the scope of a function.
These variables are added to the `*.data*` segment and are only
accessible within the function where they are defined. This is a unique
behavior of C that some might find unusual.
However, olang does not support this feature. In olang, if you need a
variable that retains its value across function calls (like a static
variable in C), you must define it at the file level.
Signed-off-by: Carlos Maniero <carlos@maniero.me>
---
docs/pages/language-specification.md | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/docs/pages/language-specification.md b/docs/pages/language-specification.md
index 4541ba8..708b679 100644
--- a/docs/pages/language-specification.md
+++ b/docs/pages/language-specification.md
@@ -24,7 +24,15 @@ language.
(* Entry Point *)
<translation-unit> ::= <ows> (<global-statements> <end-of-statement>)*
-<global-statements> ::= <function-definition>
+<global-statements> ::= <function-definition> | <variable-definition> | <variable-reassign> | <const-definition>
+
+(* Variables *)
+<variable-definition> ::= 'var' <ws> <variable-name> <ows> ':' <ows> <type> <ows> <variable-assign>?
+<const-definition> ::= 'const' <ws> <variable-name> <ows> ':' <ows> <type> <ows> <variable-assign>
+
+<variable-name> ::= <identifier>
+<variable-assign> ::= '=' <ows> <expression>
+<variable-reassign> ::= <variable-name> <ows> <variable-assign> <end-of-statement>
(* Functions *)
<function-definition> ::= 'fn' <ws> <function-name> <ows>
@@ -38,11 +46,11 @@ language.
<block> ::= '{' <ows> <statement> <ows> (<end-of-statement>
<ows> <statement> <ows>)* <end-of-statement>? <ows> '}'
<end-of-statement> ::= ';' | <line-break> | <end-of-file>
-<statement> ::= <return-statement>
+<statement> ::= <return-statement> | <variable-definition> | <variable-reassign> | <const-definition>
<return-statement> ::= 'return' <ws> <expression>
(* Expressions *)
-<expression> ::= <integer>
+<expression> ::= <integer> | <identifier>
(* Identifiers *)
<type> ::= 'u32'
--
2.34.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v1 2/2] cli: remove code duplication
@ 2024-03-25 22:36 Johnny Richard
2024-03-25 21:37 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-03-25 22:36 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
Ops... LOL
src/main.c | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/main.c b/src/main.c
index 70e7d3f..e16695b 100644
--- a/src/main.c
+++ b/src/main.c
@@ -68,11 +68,6 @@ main(int argc, char **argv)
return EXIT_SUCCESS;
}
- if (opts.options & CLI_OPT_DUMP_TOKENS) {
- handle_dump_tokens(&opts);
- return EXIT_SUCCESS;
- }
-
return EXIT_FAILURE;
}
--
2.44.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v3] refactor: rename zero programming language to olang
@ 2024-03-13 12:32 Fabio Maciel
2024-03-13 12:33 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Fabio Maciel @ 2024-03-13 12:32 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard, Maria Ruy, Fabio Maciel
From: Johnny Richard <johnny@johnnyrichard.com>
After a long discussion we've decided to rename the project to olang.
This patch removes anything related to zero programming language and
also renames the compiler and extension.
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
Co-authored-by: Maria Ruy <maria.dev456@gmail.com>
Co-authored-by: Fabio Maciel <fabio@fabiomaciel.com>
---
V3 fix hacking.md
.gitignore | 2 +-
Makefile | 4 ++--
docs/Makefile | 2 +-
docs/index.md | 2 +-
docs/manpages/{0c.md => olang.md} | 8 +++----
docs/pages/hacking.md | 2 +-
docs/template.html | 2 +-
examples/{main_exit.0 => main_exit.ol} | 0
tests/integration/cli_runner.c | 6 ++---
tests/integration/cli_test.c | 32 +++++++++++++-------------
10 files changed, 30 insertions(+), 30 deletions(-)
rename docs/manpages/{0c.md => olang.md} (74%)
rename examples/{main_exit.0 => main_exit.ol} (100%)
diff --git a/.gitignore b/.gitignore
index fc7d161..a565aae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-0c
+olang
build
*.o
docs/site.tar.gz
diff --git a/Makefile b/Makefile
index 662d039..cdfc8e1 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-TARGET := 0c
+TARGET := olang
SRC_DIR := src
BUILD_DIR := build
CFLAGS := -Werror -Wall -Wextra -Wmissing-declarations -pedantic -std=c11 -ggdb
@@ -42,7 +42,7 @@ unit-test:
clean:
$(MAKE) -C tests/integration/ clean
$(MAKE) -C tests/unit/ clean
- @rm -rf build/ 0c
+ @rm -rf build/ $(TARGET)
.PHONY: check
check:
diff --git a/docs/Makefile b/docs/Makefile
index 54561a1..d34f9f5 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -21,7 +21,7 @@ clean:
dist: $(DIST_FILE)
.PHONY: manpages
-manpages: $(BUILD_DIR) $(MANPAGES)/0c.1
+manpages: $(BUILD_DIR) $(MANPAGES)/oc.1
$(MANPAGES)/%.1: manpages/%.md
$(PANDOC) -s -t man $< > $@
diff --git a/docs/index.md b/docs/index.md
index b6d5c1a..1a28069 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,6 +1,6 @@
% Welcome to olang documentation
-The zero programming language.
+The O Programming Language.
## olang manifest
diff --git a/docs/manpages/0c.md b/docs/manpages/olang.md
similarity index 74%
rename from docs/manpages/0c.md
rename to docs/manpages/olang.md
index e3d3cfc..c54c597 100644
--- a/docs/manpages/0c.md
+++ b/docs/manpages/olang.md
@@ -1,21 +1,21 @@
-% 0C(1)
+% OLANG(1)
% olang mantainers
% Feb 2024
# NAME
-0c - zero language compiler
+olang - O Programming Language compiler
# SYNOPSIS
-**0c**
+**olang**
source_file
[**----dump-tokens**]
[**--o** output_file [**----save-temps**]]
# DESCRIPTION
-**0c** is the offical compiler for zero language, it is also a tool that
+**olang** is the offical O programming language compiler, it is also a tool that
contains utilities to help the language development.
# GENERAL OPTIONS
diff --git a/docs/pages/hacking.md b/docs/pages/hacking.md
index ef88791..fe8f705 100644
--- a/docs/pages/hacking.md
+++ b/docs/pages/hacking.md
@@ -53,7 +53,7 @@ Testing
-------
There are two layers of tests **integration** and **unit**. The
-integration test is going to execute the **0c** compiler and check if
+integration test is going to execute the **olang** compiler and check if
the generated binary acts as expected. Unit tests will test C functions.
For both unit and integration we use **munit** framework:
diff --git a/docs/template.html b/docs/template.html
index ecc92a2..4e066d1 100644
--- a/docs/template.html
+++ b/docs/template.html
@@ -52,7 +52,7 @@
</head>
<body>
<header>
- <h1>∅lang | The zero programming language</h1>
+ <h1>olang | The O Programming Language</h1>
<nav>
<a href="/">Index</a> |
<a href="/pages/getting-started.html">Getting started (WIP)</a> |
diff --git a/examples/main_exit.0 b/examples/main_exit.ol
similarity index 100%
rename from examples/main_exit.0
rename to examples/main_exit.ol
diff --git a/tests/integration/cli_runner.c b/tests/integration/cli_runner.c
index fed12ab..636abfc 100644
--- a/tests/integration/cli_runner.c
+++ b/tests/integration/cli_runner.c
@@ -24,7 +24,7 @@
#include <sys/wait.h>
#include <unistd.h>
-#define OLANG_COMPILER_PATH "../../0c"
+#define OLANG_COMPILER_PATH "../../olang"
static int compiler_exists_already_checked = 0;
@@ -83,7 +83,7 @@ cli_runner_compiler_dump_tokens(char *src)
{
cli_result_t result = { 0 };
- char *program_args[] = { "0c", "--dump-tokens", src, NULL };
+ char *program_args[] = { "olang", "--dump-tokens", src, NULL };
cli_runner_compiler(&result, program_args);
return result;
}
@@ -94,7 +94,7 @@ cli_runner_compiler_compile(char *src)
cli_result_t result = { 0 };
create_tmp_file_name(result.binary_path);
- char *program_args[] = { "0c", src, "-o", result.binary_path, NULL };
+ char *program_args[] = { "olang", src, "-o", result.binary_path, NULL };
cli_runner_compiler(&result, program_args);
return result;
}
diff --git a/tests/integration/cli_test.c b/tests/integration/cli_test.c
index c5896df..8cc22f9 100644
--- a/tests/integration/cli_test.c
+++ b/tests/integration/cli_test.c
@@ -22,30 +22,30 @@
static MunitResult
test_cli_dump_tokens(const MunitParameter params[], void *user_data_or_fixture)
{
- cli_result_t compilation_result = cli_runner_compiler_dump_tokens("../../examples/main_exit.0");
+ cli_result_t compilation_result = cli_runner_compiler_dump_tokens("../../examples/main_exit.ol");
munit_assert_int(compilation_result.exec.exit_code, ==, 0);
munit_assert_string_equal(compilation_result.exec.stdout_buf,
- "../../examples/main_exit.0:1:1: <fn>\n"
- "../../examples/main_exit.0:1:4: <identifier>\n"
- "../../examples/main_exit.0:1:8: <(>\n"
- "../../examples/main_exit.0:1:9: <)>\n"
- "../../examples/main_exit.0:1:10: <:>\n"
- "../../examples/main_exit.0:1:12: <identifier>\n"
- "../../examples/main_exit.0:1:16: <{>\n"
- "../../examples/main_exit.0:1:17: <line_feed>\n"
- "../../examples/main_exit.0:2:3: <return>\n"
- "../../examples/main_exit.0:2:10: <number>\n"
- "../../examples/main_exit.0:2:11: <line_feed>\n"
- "../../examples/main_exit.0:3:1: <}>\n"
- "../../examples/main_exit.0:3:2: <line_feed>\n"
- "../../examples/main_exit.0:4:1: <EOF>\n");
+ "../../examples/main_exit.ol:1:1: <fn>\n"
+ "../../examples/main_exit.ol:1:4: <identifier>\n"
+ "../../examples/main_exit.ol:1:8: <(>\n"
+ "../../examples/main_exit.ol:1:9: <)>\n"
+ "../../examples/main_exit.ol:1:10: <:>\n"
+ "../../examples/main_exit.ol:1:12: <identifier>\n"
+ "../../examples/main_exit.ol:1:16: <{>\n"
+ "../../examples/main_exit.ol:1:17: <line_feed>\n"
+ "../../examples/main_exit.ol:2:3: <return>\n"
+ "../../examples/main_exit.ol:2:10: <number>\n"
+ "../../examples/main_exit.ol:2:11: <line_feed>\n"
+ "../../examples/main_exit.ol:3:1: <}>\n"
+ "../../examples/main_exit.ol:3:2: <line_feed>\n"
+ "../../examples/main_exit.ol:4:1: <EOF>\n");
return MUNIT_OK;
}
static MunitResult
test_cli_compile_minimal_program(const MunitParameter params[], void *user_data_or_fixture)
{
- cli_result_t compilation_result = cli_runner_compiler_compile("../../examples/main_exit.0");
+ cli_result_t compilation_result = cli_runner_compiler_compile("../../examples/main_exit.ol");
munit_assert_int(compilation_result.exec.exit_code, ==, 0);
char *command_args[] = { compilation_result.binary_path, NULL };
--
2.39.3 (Apple Git-145)
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang] parser: abort when parser identifies a syntax error
@ 2024-03-08 20:52 Johnny Richard
2024-03-08 19:54 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-03-08 20:52 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
The current implementation fails with segfault when the parser finds a
syntax error. We are not prepared to recover from errors at this
moment.
This patch aborts when found an expected token and it also improves the
error output by showing the wrong token string value.
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
Let's postpone syntax error recovery. I want to keep things simple
right now.
src/parser.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/parser.c b/src/parser.c
index a9699be..cfea9ef 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -173,18 +173,18 @@ expected_token(parser_t *parser, token_t *token, token_kind_t expected_kind)
if (token->kind != expected_kind) {
fprintf(stderr,
- "%s:%lu:%lu: error: got <%s> token but expect <%s>\n",
+ "%s:%lu:%lu: error: got '"SV_FMT"' token but expect <%s>\n",
parser->file_path,
token->location.row + 1,
(token->location.offset - token->location.bol) + 1,
- token_kind_to_cstr(token->kind),
+ SV_ARG(token->value),
token_kind_to_cstr(expected_kind));
string_view_t line = lexer_get_token_line(parser->lexer, token);
fprintf(stderr, "" SV_FMT "\n", SV_ARG(line));
fprintf(stderr, "%*s\n", (int)(token->location.offset - token->location.bol + 1), "^");
- return false;
+ exit(EXIT_FAILURE);
}
return true;
}
base-commit: 35f594370443a2b9f73d2d2ebe573b4cab472be6
--
2.44.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v2 3/3] cli: add compilation -o option with --save-temps
@ 2024-03-05 8:44 Johnny Richard
2024-03-05 7:51 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-03-05 8:44 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
In order to compile the program, we are introduction the option -o to
the compiler.
This implementation is very similar to the gcc one. Since the program
is made of a single file, no need to over complicate the compilation
with multiple files.
The option --save-temps is used to keep files used to create the binary
file (.a and .o). By default this option is disabled.
WARNING
-------
This implementation relies on "as" (GNU Assembler) and the "ld" (GNU
linker) command. Make sure you have it install before trying to compile
any program.
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
v1 -> v2: Change file_path type from string_view_t to cstr.
Use first bit for enum cli_opt_t.
Change cli_opts_t#prog -> cli_opts_t#compiler_path.
Fix assert on args.
Refactor creation of cstr asm_file.
docs/manpages/0c.md | 13 +++-
src/main.c | 164 ++++++++++++++++++++++++++++++++++++--------
2 files changed, 146 insertions(+), 31 deletions(-)
diff --git a/docs/manpages/0c.md b/docs/manpages/0c.md
index 87a56df..e3d3cfc 100644
--- a/docs/manpages/0c.md
+++ b/docs/manpages/0c.md
@@ -4,11 +4,14 @@
# NAME
-0c - zero langague compiler
+0c - zero language compiler
# SYNOPSIS
-**0c** **----dump-tokens** source.0
+**0c**
+ source_file
+ [**----dump-tokens**]
+ [**--o** output_file [**----save-temps**]]
# DESCRIPTION
@@ -19,3 +22,9 @@ contains utilities to help the language development.
**----dump-tokens**
: Display lexical tokens given a soruce.0 code.
+
+**--o <file>**
+: Compile program into a binary file
+
+**----save-temps**
+: Keep temp files used to compile program
diff --git a/src/main.c b/src/main.c
index 2b2f12a..e4be5f7 100644
--- a/src/main.c
+++ b/src/main.c
@@ -22,7 +22,9 @@
#include <string.h>
#include "arena.h"
+#include "codegen_linux_x86_64.h"
#include "lexer.h"
+#include "parser.h"
#include "string_view.h"
// TODO: find a better solution to define the arena capacity
@@ -37,14 +39,29 @@ typedef struct cli_args
char *
cli_args_shift(cli_args_t *args);
+typedef enum
+{
+ CLI_OPT_DUMP_TOKENS = 1 << 0,
+ CLI_OPT_OUTPUT = 1 << 1,
+ CLI_OPT_SAVE_TEMPS = 1 << 2
+} cli_opt_t;
+
typedef struct cli_opts
{
- bool dump_tokens;
+ uint32_t options;
+ char *compiler_path;
char *file_path;
+ string_view_t output_bin;
} cli_opts_t;
void
-print_usage(FILE *stream, char *prog);
+print_usage(FILE *stream, char *compiler_path);
+
+void
+handle_dump_tokens(cli_opts_t *opts);
+
+void
+handle_codegen_linux_x86_64(cli_opts_t *opts);
static void
print_token(char *file_path, token_t *token);
@@ -52,34 +69,64 @@ print_token(char *file_path, token_t *token);
string_view_t
read_entire_file(char *file_path, arena_t *arena);
+void
+cli_opts_parse_output(cli_opts_t *opts, cli_args_t *args);
+
int
main(int argc, char **argv)
{
cli_args_t args = { .argc = argc, .argv = argv };
cli_opts_t opts = { 0 };
- char *prog = cli_args_shift(&args);
+ opts.compiler_path = cli_args_shift(&args);
- if (argc != 3) {
- print_usage(stderr, prog);
- return EXIT_FAILURE;
- }
-
- for (char *arg = cli_args_shift(&args); arg != NULL; arg = cli_args_shift(&args)) {
+ char *arg = cli_args_shift(&args);
+ while (arg != NULL) {
if (strcmp(arg, "--dump-tokens") == 0) {
- opts.dump_tokens = true;
+ opts.options |= CLI_OPT_DUMP_TOKENS;
+ } else if (strcmp(arg, "--save-temps") == 0) {
+ opts.options |= CLI_OPT_SAVE_TEMPS;
+ } else if (strcmp(arg, "-o") == 0) {
+ cli_opts_parse_output(&opts, &args);
} else {
opts.file_path = arg;
}
+ arg = cli_args_shift(&args);
+ }
+
+ if (opts.options & CLI_OPT_OUTPUT) {
+ handle_codegen_linux_x86_64(&opts);
+ return EXIT_SUCCESS;
}
- if (!opts.dump_tokens) {
- print_usage(stderr, prog);
- return EXIT_FAILURE;
+ if (opts.options & CLI_OPT_DUMP_TOKENS) {
+ handle_dump_tokens(&opts);
+ return EXIT_SUCCESS;
+ }
+
+ print_usage(stderr, opts.compiler_path);
+ return EXIT_FAILURE;
+}
+
+char *
+cli_args_shift(cli_args_t *args)
+{
+ if (args->argc == 0)
+ return NULL;
+ --(args->argc);
+ return *(args->argv)++;
+}
+
+void
+handle_dump_tokens(cli_opts_t *opts)
+{
+ if (opts->file_path == NULL) {
+ print_usage(stderr, opts->compiler_path);
+ exit(EXIT_FAILURE);
}
arena_t arena = arena_new(ARENA_CAPACITY);
- string_view_t file_content = read_entire_file(opts.file_path, &arena);
+ string_view_t file_content = read_entire_file(opts->file_path, &arena);
lexer_t lexer = { 0 };
lexer_init(&lexer, file_content);
@@ -87,29 +134,70 @@ main(int argc, char **argv)
token_t token = { 0 };
lexer_next_token(&lexer, &token);
while (token.kind != TOKEN_EOF) {
- print_token(opts.file_path, &token);
+ print_token(opts->file_path, &token);
lexer_next_token(&lexer, &token);
}
- print_token(opts.file_path, &token);
+ print_token(opts->file_path, &token);
- free(file_content.chars);
-
- return EXIT_SUCCESS;
+ arena_free(&arena);
}
-char *
-cli_args_shift(cli_args_t *args)
+void
+handle_codegen_linux_x86_64(cli_opts_t *opts)
{
- if (args->argc == 0)
- return NULL;
- --(args->argc);
- return *(args->argv)++;
+ if (opts->file_path == NULL) {
+ print_usage(stderr, opts->compiler_path);
+ exit(EXIT_FAILURE);
+ }
+
+ arena_t arena = arena_new(ARENA_CAPACITY);
+ lexer_t lexer = { 0 };
+ parser_t parser = { 0 };
+
+ string_view_t file_content = read_entire_file(opts->file_path, &arena);
+ lexer_init(&lexer, file_content);
+ parser_init(&parser, &lexer, &arena, opts->file_path);
+
+ ast_node_t *ast = parser_parse_fn_definition(&parser);
+
+ char asm_file[opts->output_bin.size + 3];
+ sprintf(asm_file, "" SV_FMT ".s", SV_ARG(opts->output_bin));
+
+ FILE *out = fopen(asm_file, "w");
+ assert(out);
+ codegen_linux_x86_64_emit_program(out, ast);
+ fclose(out);
+
+ char command[512];
+ sprintf(command, "as %s -o " SV_FMT ".o", asm_file, SV_ARG(opts->output_bin));
+ system(command);
+
+ sprintf(command, "ld " SV_FMT ".o -o " SV_FMT "", SV_ARG(opts->output_bin), SV_ARG(opts->output_bin));
+ system(command);
+
+ if (!(opts->options & CLI_OPT_SAVE_TEMPS)) {
+ char output_file[256];
+
+ sprintf(output_file, "" SV_FMT ".s", SV_ARG(opts->output_bin));
+ remove(output_file);
+
+ sprintf(output_file, "" SV_FMT ".o", SV_ARG(opts->output_bin));
+ remove(output_file);
+ }
+
+ arena_free(&arena);
}
void
-print_usage(FILE *stream, char *prog)
+print_usage(FILE *stream, char *compiler_path)
{
- fprintf(stream, "usage: %s <source.0> --dump-tokens\n", prog);
+ fprintf(stream,
+ "Usage: %s [options] file...\n"
+ "Options:\n"
+ " --dump-tokens\t\tDisplay lexer token stream\n"
+ " -o <file>\t\tCompile program into a binary file\n"
+ " --save-temps\t\tKeep temp files used to compile program\n",
+ compiler_path);
}
string_view_t
@@ -118,7 +206,7 @@ read_entire_file(char *file_path, arena_t *arena)
FILE *stream = fopen(file_path, "rb");
if (stream == NULL) {
- fprintf(stderr, "Could not open file %s: %s\n", file_path, strerror(errno));
+ fprintf(stderr, "error: could not open file %s: %s\n", file_path, strerror(errno));
exit(EXIT_FAILURE);
}
@@ -133,7 +221,7 @@ read_entire_file(char *file_path, arena_t *arena)
file_content.chars = (char *)arena_alloc(arena, (size_t)file_content.size);
if (file_content.chars == NULL) {
- fprintf(stderr, "Could not read file %s: %s\n", file_path, strerror(errno));
+ fprintf(stderr, "error: could not read file %s: %s\n", file_path, strerror(errno));
exit(EXIT_FAILURE);
}
@@ -143,6 +231,24 @@ read_entire_file(char *file_path, arena_t *arena)
return file_content;
}
+void
+cli_opts_parse_output(cli_opts_t *opts, cli_args_t *args)
+{
+ assert(opts && "opts is required");
+ assert(args && "args is required");
+
+ char *output_bin = cli_args_shift(args);
+
+ if (output_bin == NULL) {
+ fprintf(stderr, "error: missing filename after '-o'\n");
+ print_usage(stderr, opts->compiler_path);
+ exit(EXIT_FAILURE);
+ }
+
+ opts->options |= CLI_OPT_OUTPUT;
+ opts->output_bin = string_view_from_cstr(output_bin);
+}
+
static void
print_token(char *file_path, token_t *token)
{
--
2.44.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v1 3/3] cli: add compilation -o option with --save-temps
@ 2024-03-04 19:23 Johnny Richard
2024-03-04 18:33 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-03-04 19:23 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
In order to compile the program, we are introduction the option -o to
the compiler.
This implementation is very similar to the gcc one. Since the program
is made of a single file, no need to over complicate the compilation
with multiple files.
The option --save-temps is used to keep files used to create the binary
file (.a and .o). By default this option is disabled.
WARNING
-------
This implementation relies on "as" (GNU Assembler) and the "ld" (GNU
linker) command. Make sure you have it install before trying to compile
any program.
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
docs/manpages/0c.md | 13 +++-
src/main.c | 180 +++++++++++++++++++++++++++++++++++---------
2 files changed, 157 insertions(+), 36 deletions(-)
diff --git a/docs/manpages/0c.md b/docs/manpages/0c.md
index 87a56df..e3d3cfc 100644
--- a/docs/manpages/0c.md
+++ b/docs/manpages/0c.md
@@ -4,11 +4,14 @@
# NAME
-0c - zero langague compiler
+0c - zero language compiler
# SYNOPSIS
-**0c** **----dump-tokens** source.0
+**0c**
+ source_file
+ [**----dump-tokens**]
+ [**--o** output_file [**----save-temps**]]
# DESCRIPTION
@@ -19,3 +22,9 @@ contains utilities to help the language development.
**----dump-tokens**
: Display lexical tokens given a soruce.0 code.
+
+**--o <file>**
+: Compile program into a binary file
+
+**----save-temps**
+: Keep temp files used to compile program
diff --git a/src/main.c b/src/main.c
index 2b2f12a..b27715a 100644
--- a/src/main.c
+++ b/src/main.c
@@ -22,7 +22,9 @@
#include <string.h>
#include "arena.h"
+#include "codegen_linux_x86_64.h"
#include "lexer.h"
+#include "parser.h"
#include "string_view.h"
// TODO: find a better solution to define the arena capacity
@@ -37,20 +39,38 @@ typedef struct cli_args
char *
cli_args_shift(cli_args_t *args);
+typedef enum
+{
+ CLI_OPT_DUMP_TOKENS = 1 << 1,
+ CLI_OPT_OUTPUT = 1 << 2,
+ CLI_OPT_SAVE_TEMPS = 1 << 3
+} cli_opt;
+
typedef struct cli_opts
{
- bool dump_tokens;
- char *file_path;
+ uint32_t options;
+ string_view_t prog;
+ string_view_t file_path;
+ string_view_t output_bin;
} cli_opts_t;
void
-print_usage(FILE *stream, char *prog);
+print_usage(FILE *stream, string_view_t prog);
+
+void
+handle_dump_tokens(cli_opts_t *opts);
+
+void
+handle_codegen_linux_x86_64(cli_opts_t *opts);
static void
print_token(char *file_path, token_t *token);
string_view_t
-read_entire_file(char *file_path, arena_t *arena);
+read_entire_file(string_view_t file_path, arena_t *arena);
+
+void
+cli_opts_parse_output(cli_opts_t *opts, cli_args_t *args);
int
main(int argc, char **argv)
@@ -58,28 +78,58 @@ main(int argc, char **argv)
cli_args_t args = { .argc = argc, .argv = argv };
cli_opts_t opts = { 0 };
- char *prog = cli_args_shift(&args);
+ opts.prog = string_view_from_cstr(cli_args_shift(&args));
- if (argc != 3) {
- print_usage(stderr, prog);
- return EXIT_FAILURE;
- }
-
- for (char *arg = cli_args_shift(&args); arg != NULL; arg = cli_args_shift(&args)) {
+ char *arg = cli_args_shift(&args);
+ while (arg != NULL) {
if (strcmp(arg, "--dump-tokens") == 0) {
- opts.dump_tokens = true;
+ opts.options |= CLI_OPT_DUMP_TOKENS;
+ arg = cli_args_shift(&args);
+ } else if (strcmp(arg, "--save-temps") == 0) {
+ opts.options |= CLI_OPT_SAVE_TEMPS;
+ arg = cli_args_shift(&args);
+ } else if (strcmp(arg, "-o") == 0) {
+ cli_opts_parse_output(&opts, &args);
+ arg = cli_args_shift(&args);
} else {
- opts.file_path = arg;
+ opts.file_path = string_view_from_cstr(arg);
+ arg = cli_args_shift(&args);
}
}
- if (!opts.dump_tokens) {
- print_usage(stderr, prog);
- return EXIT_FAILURE;
+ if (opts.options & CLI_OPT_OUTPUT) {
+ handle_codegen_linux_x86_64(&opts);
+ return EXIT_SUCCESS;
+ }
+
+ if (opts.options & CLI_OPT_DUMP_TOKENS) {
+ handle_dump_tokens(&opts);
+ return EXIT_SUCCESS;
+ }
+
+ print_usage(stderr, opts.prog);
+ return EXIT_FAILURE;
+}
+
+char *
+cli_args_shift(cli_args_t *args)
+{
+ if (args->argc == 0)
+ return NULL;
+ --(args->argc);
+ return *(args->argv)++;
+}
+
+void
+handle_dump_tokens(cli_opts_t *opts)
+{
+ if (opts->file_path.chars == NULL) {
+ print_usage(stderr, opts->prog);
+ exit(EXIT_FAILURE);
}
arena_t arena = arena_new(ARENA_CAPACITY);
- string_view_t file_content = read_entire_file(opts.file_path, &arena);
+ string_view_t file_content = read_entire_file(opts->file_path, &arena);
lexer_t lexer = { 0 };
lexer_init(&lexer, file_content);
@@ -87,38 +137,82 @@ main(int argc, char **argv)
token_t token = { 0 };
lexer_next_token(&lexer, &token);
while (token.kind != TOKEN_EOF) {
- print_token(opts.file_path, &token);
+ print_token(opts->file_path.chars, &token);
lexer_next_token(&lexer, &token);
}
- print_token(opts.file_path, &token);
+ print_token(opts->file_path.chars, &token);
- free(file_content.chars);
-
- return EXIT_SUCCESS;
+ arena_free(&arena);
}
-char *
-cli_args_shift(cli_args_t *args)
+void
+handle_codegen_linux_x86_64(cli_opts_t *opts)
{
- if (args->argc == 0)
- return NULL;
- --(args->argc);
- return *(args->argv)++;
+ if (opts->file_path.chars == NULL) {
+ print_usage(stderr, opts->prog);
+ exit(EXIT_FAILURE);
+ }
+
+ arena_t arena = arena_new(ARENA_CAPACITY);
+ lexer_t lexer = { 0 };
+ parser_t parser = { 0 };
+
+ string_view_t file_content = read_entire_file(opts->file_path, &arena);
+ lexer_init(&lexer, file_content);
+ parser_init(&parser, &lexer, &arena, opts->file_path.chars);
+
+ ast_node_t *ast = parser_parse_fn_definition(&parser);
+
+ string_view_t asm_ext = string_view_from_cstr(".s");
+ char asm_file[opts->output_bin.size + asm_ext.size + 1];
+ memcpy(asm_file, opts->output_bin.chars, opts->output_bin.size);
+ memcpy(asm_file + opts->output_bin.size, asm_ext.chars, asm_ext.size);
+ asm_file[opts->output_bin.size + asm_ext.size] = 0;
+
+ FILE *out = fopen(asm_file, "w");
+ assert(out);
+ codegen_linux_x86_64_emit_program(out, ast);
+ fclose(out);
+
+ char command[512];
+ sprintf(command, "as %s -o " SV_FMT ".o", asm_file, SV_ARG(opts->output_bin));
+ system(command);
+
+ sprintf(command, "ld " SV_FMT ".o -o " SV_FMT "", SV_ARG(opts->output_bin), SV_ARG(opts->output_bin));
+ system(command);
+
+ if (!(opts->options & CLI_OPT_SAVE_TEMPS)) {
+ char output_file[256];
+
+ sprintf(output_file, "" SV_FMT ".s", SV_ARG(opts->output_bin));
+ remove(output_file);
+
+ sprintf(output_file, "" SV_FMT ".o", SV_ARG(opts->output_bin));
+ remove(output_file);
+ }
+
+ arena_free(&arena);
}
void
-print_usage(FILE *stream, char *prog)
+print_usage(FILE *stream, string_view_t prog)
{
- fprintf(stream, "usage: %s <source.0> --dump-tokens\n", prog);
+ fprintf(stream,
+ "Usage: " SV_FMT " [options] file...\n"
+ "Options:\n"
+ " --dump-tokens\t\tDisplay lexer token stream\n"
+ " -o <file>\t\tCompile program into a binary file\n"
+ " --save-temps\t\tKeep temp files used to compile program\n",
+ SV_ARG(prog));
}
string_view_t
-read_entire_file(char *file_path, arena_t *arena)
+read_entire_file(string_view_t file_path, arena_t *arena)
{
- FILE *stream = fopen(file_path, "rb");
+ FILE *stream = fopen(file_path.chars, "rb");
if (stream == NULL) {
- fprintf(stderr, "Could not open file %s: %s\n", file_path, strerror(errno));
+ fprintf(stderr, "error: could not open file " SV_FMT ": %s\n", SV_ARG(file_path), strerror(errno));
exit(EXIT_FAILURE);
}
@@ -133,7 +227,7 @@ read_entire_file(char *file_path, arena_t *arena)
file_content.chars = (char *)arena_alloc(arena, (size_t)file_content.size);
if (file_content.chars == NULL) {
- fprintf(stderr, "Could not read file %s: %s\n", file_path, strerror(errno));
+ fprintf(stderr, "Could not read file " SV_FMT ": %s\n", SV_ARG(file_path), strerror(errno));
exit(EXIT_FAILURE);
}
@@ -143,6 +237,24 @@ read_entire_file(char *file_path, arena_t *arena)
return file_content;
}
+void
+cli_opts_parse_output(cli_opts_t *opts, cli_args_t *args)
+{
+ assert(opts && "opts is required");
+ assert(opts && "args is required");
+
+ char *output_bin = cli_args_shift(args);
+
+ if (output_bin == NULL) {
+ fprintf(stderr, "error: missing filename after '-o'\n");
+ print_usage(stderr, opts->prog);
+ exit(EXIT_FAILURE);
+ }
+
+ opts->options |= CLI_OPT_OUTPUT;
+ opts->output_bin = string_view_from_cstr(output_bin);
+}
+
static void
print_token(char *file_path, token_t *token)
{
--
2.44.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [olang/patches/.build.yml] build failed
2024-03-04 19:23 [PATCH olang v1 3/3] cli: add compilation -o option with --save-temps Johnny Richard
@ 2024-03-04 18:33 ` builds.sr.ht
2024-03-04 19:39 ` Johnny Richard
0 siblings, 1 reply; 40+ messages in thread
From: builds.sr.ht @ 2024-03-04 18:33 UTC (permalink / raw)
To: Johnny Richard; +Cc: ~johnnyrichard/olang-devel
olang/patches/.build.yml: FAILED in 12s
[implement assembly linux x86_64 compiler][0] from [Johnny Richard][1]
[0]: https://lists.sr.ht/~johnnyrichard/olang-devel/patches/49981
[1]: mailto:johnny@johnnyrichard.com
✗ #1161742 FAILED olang/patches/.build.yml https://builds.sr.ht/~johnnyrichard/job/1161742
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [olang/patches/.build.yml] build failed
2024-03-04 18:33 ` [olang/patches/.build.yml] build failed builds.sr.ht
@ 2024-03-04 19:39 ` Johnny Richard
2024-03-05 2:05 ` Carlos Maniero
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-03-04 19:39 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel
On Mon, Mar 04, 2024 at 06:33:31PM +0000, builds.sr.ht wrote:
> olang/patches/.build.yml: FAILED in 12s
>
> [implement assembly linux x86_64 compiler][0] from [Johnny Richard][1]
>
> [0]: https://lists.sr.ht/~johnnyrichard/olang-devel/patches/49981
> [1]: mailto:johnny@johnnyrichard.com
>
> ✗ #1161742 FAILED olang/patches/.build.yml https://builds.sr.ht/~johnnyrichard/job/1161742
I not sure what is happening, the build works find on my machine. Looks
like the CI machines are failing to setup the environment, nothing to do
with my changes I believe:
[#1161745] 2024/03/04 18:36:41 Booting image archlinux (default) on port 22563
[#1161745] 2024/03/04 18:36:42 Waiting for guest to settle
[#1161745] 2024/03/04 18:36:49 Sending tasks
[#1161745] 2024/03/04 18:36:52 Sending build environment
[#1161745] 2024/03/04 18:36:53 Installing packages
Warning: Permanently added '[localhost]:22563' (ED25519) to the list of known hosts.
:: Synchronizing package databases...
core downloading...
extra downloading...
multilib downloading...
warning: archlinux-keyring-20240208-1 is up to date -- skipping
there is nothing to do
Warning: Permanently added '[localhost]:22563' (ED25519) to the list of known hosts.
error: missing dependency 'initramfs' for package 'linux'
linux: ignoring package upgrade (6.7.6.arch1-1 => 6.7.8.arch1-1)
mkinitcpio: ignoring package upgrade (37.3-1 => 38-3)
Resolving dependencies...
Checking package conflicts...
:: uninstalling package 'mkinitcpio-37.3-1' due to conflict with 'cryptsetup-2.7.0-3'
[#1161745] 2024/03/04 18:36:54 Processing post-failed triggers...
[#1161745] 2024/03/04 18:36:54 Sending webhook...
[#1161745] 2024/03/04 18:36:54 Webhook response: 200
[#1161745] 2024/03/04 18:36:54 Thanks!
[#1161745] 2024/03/04 18:36:54 Build failed.
[#1161745] 2024/03/04 18:36:54 The build environment will be kept alive for 10 minutes.
[#1161745] 2024/03/04 18:36:54 To log in with SSH and examine it, use the following command:
[#1161745] 2024/03/04 18:36:54
[#1161745] 2024/03/04 18:36:54 ssh -t builds@fra02.builds.sr.ht connect 1161745
[#1161745] 2024/03/04 18:36:54
[#1161745] 2024/03/04 18:36:54 After logging in, the deadline is increased to your remaining build time.
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang] string_view: fix stack buffer overflow on to_u32
@ 2024-03-02 20:01 Johnny Richard
2024-03-02 19:02 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-03-02 20:01 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
src/string_view.c | 2 +-
tests/unit/string_view_test.c | 4 ++++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/string_view.c b/src/string_view.c
index 084f417..646dabd 100644
--- a/src/string_view.c
+++ b/src/string_view.c
@@ -40,7 +40,7 @@ uint32_t
string_view_to_u32(string_view_t str)
{
char ret[str.size + 1];
- ret[str.size + 1] = 0;
+ ret[str.size] = 0;
memcpy(ret, str.chars, str.size);
return atoi(ret);
}
diff --git a/tests/unit/string_view_test.c b/tests/unit/string_view_test.c
index 1d8627f..fe3dacb 100644
--- a/tests/unit/string_view_test.c
+++ b/tests/unit/string_view_test.c
@@ -48,6 +48,10 @@ string_view_to_u32_test(const MunitParameter params[], void *user_data_or_fixtur
assert_uint32(string_view_to_u32(str), ==, 69);
+ str = (string_view_t) { .chars = "39;", .size = 2 };
+
+ assert_uint32(string_view_to_u32(str), ==, 39);
+
return MUNIT_OK;
}
--
2.44.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang] string_view: add n + 1 test to string_view_to_u32 function
@ 2024-03-02 19:02 Johnny Richard
2024-03-02 18:03 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-03-02 19:02 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
tests/unit/string_view_test.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/tests/unit/string_view_test.c b/tests/unit/string_view_test.c
index 1d8627f..fe3dacb 100644
--- a/tests/unit/string_view_test.c
+++ b/tests/unit/string_view_test.c
@@ -48,6 +48,10 @@ string_view_to_u32_test(const MunitParameter params[], void *user_data_or_fixtur
assert_uint32(string_view_to_u32(str), ==, 69);
+ str = (string_view_t) { .chars = "39;", .size = 2 };
+
+ assert_uint32(string_view_to_u32(str), ==, 39);
+
return MUNIT_OK;
}
--
2.44.0
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v2] utils: add arena
@ 2024-02-20 16:39 Carlos Maniero
2024-02-20 16:45 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-02-20 16:39 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
This is a simple implementation of arena. At this point arena is not
expandable. Since the size of arena is predicable and basically used
used to store pointers, but we can make it more robust in the future.
Signed-off-by: Carlos Maniero <carlos@maniero.me>
---
v2:
- fix lint
- improve commit message
.gitignore | 2 +-
Makefile | 8 ++++--
src/arena.c | 53 +++++++++++++++++++++++++++++++++++
src/arena.h | 41 +++++++++++++++++++++++++++
tests/unit/Makefile | 19 ++++++++-----
tests/unit/arena_test.c | 62 +++++++++++++++++++++++++++++++++++++++++
6 files changed, 175 insertions(+), 10 deletions(-)
create mode 100644 src/arena.c
create mode 100644 src/arena.h
create mode 100644 tests/unit/arena_test.c
diff --git a/.gitignore b/.gitignore
index 92496d7..fc7d161 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,4 @@
build
*.o
docs/site.tar.gz
-tests/integration/*_test
+tests/*/*_test
diff --git a/Makefile b/Makefile
index 555029f..ea6ba53 100644
--- a/Makefile
+++ b/Makefile
@@ -20,11 +20,13 @@ $(BUILD_DIR):
linter: $(SRCS) $(HEADERS)
clang-format --dry-run --Werror $?
$(MAKE) -C tests/integration/ linter
+ $(MAKE) -C tests/unit/ linter
.PHONY: linter-fix
linter-fix: $(SRCS) $(HEADERS)
clang-format -i $?
$(MAKE) -C tests/integration/ linter-fix
+ $(MAKE) -C tests/unit/ linter-fix
.PHONY: integration-test
integration-test:
@@ -33,12 +35,14 @@ integration-test:
.PHONY: unit-test
unit-test:
+ $(MAKE)
$(MAKE) -C tests/unit/
.PHONY: check
check:
- $(MAKE) integration-test
- $(MAKE) unit-test
+ $(MAKE)
+ $(MAKE) -C tests/integration/
+ $(MAKE) -C tests/unit/
.PHONY: docs
docs:
diff --git a/src/arena.c b/src/arena.c
new file mode 100644
index 0000000..3d084ab
--- /dev/null
+++ b/src/arena.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#include "arena.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+arena_t
+arena_new(size_t size)
+{
+ arena_t arena;
+ arena.offset = 0;
+ arena.region = malloc(sizeof(uint8_t) * size);
+ arena.size = size;
+ return arena;
+}
+
+void *
+arena_alloc(arena_t *arena, size_t size)
+{
+ if ((arena->offset + size) > arena->size) {
+ return NULL;
+ }
+ void *pointer = arena->region + arena->offset;
+ arena->offset += size;
+ return pointer;
+}
+
+void
+arena_release(arena_t *arena)
+{
+ arena->offset = 0;
+}
+
+void
+arena_free(arena_t *arena)
+{
+ arena->size = 0;
+ free(arena->region);
+}
diff --git a/src/arena.h b/src/arena.h
new file mode 100644
index 0000000..a3e97aa
--- /dev/null
+++ b/src/arena.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#ifndef ARENA_H
+#define ARENA_H
+#include <stdlib.h>
+#include <stdint.h>
+
+typedef struct arena
+{
+ size_t offset;
+ size_t size;
+ uint8_t *region;
+} arena_t;
+
+arena_t
+arena_new(size_t size);
+
+void *
+arena_alloc(arena_t *arena, size_t size);
+
+void
+arena_release(arena_t *arena);
+
+void
+arena_free(arena_t *arena);
+
+#endif
diff --git a/tests/unit/Makefile b/tests/unit/Makefile
index ab250cf..7c6a8b3 100644
--- a/tests/unit/Makefile
+++ b/tests/unit/Makefile
@@ -1,10 +1,11 @@
-SRCS := $(wildcard *_test.c)
-OBJS := $(patsubst %_test.c, %_test.o, $(SRCS))
-CFLAGS := -I../../src -I../shared
-TESTS := $(patsubst %_test.c, %_test, $(SRCS))
-EXEC_TESTS := $(patsubst %_test, ./%_test, $(TESTS))
-MUNIT_SRC := ../shared/munit.c
-MUNIT := ./munit.o
+SRCS := $(wildcard *_test.c)
+OBJS := $(patsubst %_test.c, %_test.o, $(SRCS))
+SUBJECT_OBJS := $(filter-out ../../build/0c.o, $(wildcard ../../build/*.o))
+CFLAGS := -I../../src -I../shared
+TESTS := $(patsubst %_test.c, %_test, $(SRCS))
+EXEC_TESTS := $(patsubst %_test, ./%_test, $(TESTS))
+MUNIT_SRC := ../shared/munit.c
+MUNIT := ./munit.o
.PHONY: all
all: $(MUNIT) $(TESTS)
@@ -15,6 +16,7 @@ all: $(MUNIT) $(TESTS)
.PHONY: clean
clean:
$(RM) *.o *_test
+ $(RM) -rfv lib
.PHONY: linter
linter: $(SRCS)
@@ -24,5 +26,8 @@ linter: $(SRCS)
linter-fix: $(SRCS)
clang-format -i $?
+%_test: $(MUNIT) $(SUBJECT_OBJS) %_test.c
+ $(CC) $? $(CFLAGS) -o $@
+
$(MUNIT):
$(CC) -c $(MUNIT_SRC) $(CFLAGS) -o $(MUNIT)
diff --git a/tests/unit/arena_test.c b/tests/unit/arena_test.c
new file mode 100644
index 0000000..6310795
--- /dev/null
+++ b/tests/unit/arena_test.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#define MUNIT_ENABLE_ASSERT_ALIASES
+#include "arena.h"
+#include "munit.h"
+
+static MunitResult
+arena_test(const MunitParameter params[], void *user_data_or_fixture)
+{
+ arena_t arena = arena_new(sizeof(int) * 2);
+
+ int *a = arena_alloc(&arena, sizeof(int));
+ *a = 1;
+
+ int *b = arena_alloc(&arena, sizeof(int));
+ *b = 2;
+
+ munit_assert_int(*a, ==, 1);
+ munit_assert_int(*b, ==, 2);
+
+ arena_release(&arena);
+
+ int *c = arena_alloc(&arena, sizeof(int));
+ *c = 3;
+
+ munit_assert_int(*c, ==, 3);
+ munit_assert_int(*a, ==, 3);
+ munit_assert_int(*b, ==, 2);
+
+ munit_assert_ptr_not_null(arena_alloc(&arena, sizeof(int)));
+ munit_assert_ptr_null(arena_alloc(&arena, 1));
+
+ arena_free(&arena);
+
+ return MUNIT_OK;
+}
+
+static MunitTest tests[] = { { "/arena_test", arena_test, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
+ { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } };
+
+static const MunitSuite suite = { "/cli_test", tests, NULL, 1, MUNIT_SUITE_OPTION_NONE };
+
+int
+main(int argc, char *argv[])
+{
+ return munit_suite_main(&suite, NULL, argc, argv);
+ return EXIT_SUCCESS;
+}
--
2.34.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang] utils: add arena
@ 2024-02-20 16:10 Carlos Maniero
2024-02-20 16:15 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-02-20 16:10 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
This is a simple implementation of arena. At this point arena is not
expandable. We can add support to it if neded after we implement linked
lists.
Since arena was the first code unit-tested, I've completed the unit
tests setup at this commit.
Signed-off-by: Carlos Maniero <carlos@maniero.me>
---
.gitignore | 2 +-
Makefile | 8 ++++--
src/arena.c | 53 +++++++++++++++++++++++++++++++++++
src/arena.h | 41 +++++++++++++++++++++++++++
tests/unit/Makefile | 19 ++++++++-----
tests/unit/arena_test.c | 62 +++++++++++++++++++++++++++++++++++++++++
6 files changed, 175 insertions(+), 10 deletions(-)
create mode 100644 src/arena.c
create mode 100644 src/arena.h
create mode 100644 tests/unit/arena_test.c
diff --git a/.gitignore b/.gitignore
index 92496d7..fc7d161 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,4 @@
build
*.o
docs/site.tar.gz
-tests/integration/*_test
+tests/*/*_test
diff --git a/Makefile b/Makefile
index 555029f..ea6ba53 100644
--- a/Makefile
+++ b/Makefile
@@ -20,11 +20,13 @@ $(BUILD_DIR):
linter: $(SRCS) $(HEADERS)
clang-format --dry-run --Werror $?
$(MAKE) -C tests/integration/ linter
+ $(MAKE) -C tests/unit/ linter
.PHONY: linter-fix
linter-fix: $(SRCS) $(HEADERS)
clang-format -i $?
$(MAKE) -C tests/integration/ linter-fix
+ $(MAKE) -C tests/unit/ linter-fix
.PHONY: integration-test
integration-test:
@@ -33,12 +35,14 @@ integration-test:
.PHONY: unit-test
unit-test:
+ $(MAKE)
$(MAKE) -C tests/unit/
.PHONY: check
check:
- $(MAKE) integration-test
- $(MAKE) unit-test
+ $(MAKE)
+ $(MAKE) -C tests/integration/
+ $(MAKE) -C tests/unit/
.PHONY: docs
docs:
diff --git a/src/arena.c b/src/arena.c
new file mode 100644
index 0000000..3d084ab
--- /dev/null
+++ b/src/arena.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#include "arena.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+arena_t
+arena_new(size_t size)
+{
+ arena_t arena;
+ arena.offset = 0;
+ arena.region = malloc(sizeof(uint8_t) * size);
+ arena.size = size;
+ return arena;
+}
+
+void *
+arena_alloc(arena_t *arena, size_t size)
+{
+ if ((arena->offset + size) > arena->size) {
+ return NULL;
+ }
+ void *pointer = arena->region + arena->offset;
+ arena->offset += size;
+ return pointer;
+}
+
+void
+arena_release(arena_t *arena)
+{
+ arena->offset = 0;
+}
+
+void
+arena_free(arena_t *arena)
+{
+ arena->size = 0;
+ free(arena->region);
+}
diff --git a/src/arena.h b/src/arena.h
new file mode 100644
index 0000000..a3e97aa
--- /dev/null
+++ b/src/arena.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#ifndef ARENA_H
+#define ARENA_H
+#include <stdlib.h>
+#include <stdint.h>
+
+typedef struct arena
+{
+ size_t offset;
+ size_t size;
+ uint8_t *region;
+} arena_t;
+
+arena_t
+arena_new(size_t size);
+
+void *
+arena_alloc(arena_t *arena, size_t size);
+
+void
+arena_release(arena_t *arena);
+
+void
+arena_free(arena_t *arena);
+
+#endif
diff --git a/tests/unit/Makefile b/tests/unit/Makefile
index ab250cf..7c6a8b3 100644
--- a/tests/unit/Makefile
+++ b/tests/unit/Makefile
@@ -1,10 +1,11 @@
-SRCS := $(wildcard *_test.c)
-OBJS := $(patsubst %_test.c, %_test.o, $(SRCS))
-CFLAGS := -I../../src -I../shared
-TESTS := $(patsubst %_test.c, %_test, $(SRCS))
-EXEC_TESTS := $(patsubst %_test, ./%_test, $(TESTS))
-MUNIT_SRC := ../shared/munit.c
-MUNIT := ./munit.o
+SRCS := $(wildcard *_test.c)
+OBJS := $(patsubst %_test.c, %_test.o, $(SRCS))
+SUBJECT_OBJS := $(filter-out ../../build/0c.o, $(wildcard ../../build/*.o))
+CFLAGS := -I../../src -I../shared
+TESTS := $(patsubst %_test.c, %_test, $(SRCS))
+EXEC_TESTS := $(patsubst %_test, ./%_test, $(TESTS))
+MUNIT_SRC := ../shared/munit.c
+MUNIT := ./munit.o
.PHONY: all
all: $(MUNIT) $(TESTS)
@@ -15,6 +16,7 @@ all: $(MUNIT) $(TESTS)
.PHONY: clean
clean:
$(RM) *.o *_test
+ $(RM) -rfv lib
.PHONY: linter
linter: $(SRCS)
@@ -24,5 +26,8 @@ linter: $(SRCS)
linter-fix: $(SRCS)
clang-format -i $?
+%_test: $(MUNIT) $(SUBJECT_OBJS) %_test.c
+ $(CC) $? $(CFLAGS) -o $@
+
$(MUNIT):
$(CC) -c $(MUNIT_SRC) $(CFLAGS) -o $(MUNIT)
diff --git a/tests/unit/arena_test.c b/tests/unit/arena_test.c
new file mode 100644
index 0000000..6310795
--- /dev/null
+++ b/tests/unit/arena_test.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#define MUNIT_ENABLE_ASSERT_ALIASES
+#include "arena.h"
+#include "munit.h"
+
+static MunitResult
+arena_test(const MunitParameter params[], void *user_data_or_fixture)
+{
+ arena_t arena = arena_new(sizeof(int) * 2);
+
+ int *a = arena_alloc(&arena, sizeof(int));
+ *a = 1;
+
+ int *b = arena_alloc(&arena, sizeof(int));
+ *b = 2;
+
+ munit_assert_int(*a, ==, 1);
+ munit_assert_int(*b, ==, 2);
+
+ arena_release(&arena);
+
+ int *c = arena_alloc(&arena, sizeof(int));
+ *c = 3;
+
+ munit_assert_int(*c, ==, 3);
+ munit_assert_int(*a, ==, 3);
+ munit_assert_int(*b, ==, 2);
+
+ munit_assert_ptr_not_null(arena_alloc(&arena, sizeof(int)));
+ munit_assert_ptr_null(arena_alloc(&arena, 1));
+
+ arena_free(&arena);
+
+ return MUNIT_OK;
+}
+
+static MunitTest tests[] = { { "/arena_test", arena_test, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
+ { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } };
+
+static const MunitSuite suite = { "/cli_test", tests, NULL, 1, MUNIT_SUITE_OPTION_NONE };
+
+int
+main(int argc, char *argv[])
+{
+ return munit_suite_main(&suite, NULL, argc, argv);
+ return EXIT_SUCCESS;
+}
--
2.34.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v4 4/4] lexer: test: add integration tests for --dump-tokens
@ 2024-02-19 21:04 Johnny Richard
2024-02-19 20:07 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-02-19 21:04 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero, Johnny Richard
From: Carlos Maniero <carlos@maniero.me>
We want to test the 0c compiler in a black box way. This test explores
`pipe` in order to handle input/output data, and it tests the
--dump-tokens against the examples/main_exit.0.
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
src/0c.c | 1 -
tests/integration/cli_runner.c | 47 ++++++++++++++++++++++++++++++----
tests/integration/cli_runner.h | 1 +
tests/integration/cli_test.c | 14 ++++++++++
4 files changed, 57 insertions(+), 6 deletions(-)
diff --git a/src/0c.c b/src/0c.c
index e84559d..978b770 100644
--- a/src/0c.c
+++ b/src/0c.c
@@ -75,7 +75,6 @@ main(int argc, char **argv)
string_view_t file_content = read_entire_file(opts.file_path);
- // TODO: missing integration test for lexer tokenizing
lexer_t lexer = { 0 };
lexer_init(&lexer, file_content);
diff --git a/tests/integration/cli_runner.c b/tests/integration/cli_runner.c
index 0531bcc..7e4fe9a 100644
--- a/tests/integration/cli_runner.c
+++ b/tests/integration/cli_runner.c
@@ -20,6 +20,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/wait.h>
#include <unistd.h>
#define OLANG_COMPILER_PATH "../../0c"
@@ -62,16 +63,52 @@ create_tmp_file_name(char *file_name)
}
cli_result_t
-cli_runner_compiler_dump_tokens(char *src)
+cli_runner_compiler(char *src, char *args[])
{
assert_compiler_exists();
- cli_result_t result;
+ cli_result_t result = { 0 };
create_tmp_file_name(result.program_path);
- char command[1024];
- sprintf(command, "%s %s --dump-tokens", OLANG_COMPILER_PATH, src);
+ int fd_link[2];
+
+ if (pipe(fd_link) == -1) {
+ perror("pipe error.");
+ exit(1);
+ }
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ perror("fork error.");
+ exit(1);
+ }
+
+ if (pid == 0) {
+ dup2(fd_link[1], STDOUT_FILENO);
+ close(fd_link[0]);
+ close(fd_link[1]);
+
+ execv(OLANG_COMPILER_PATH, args);
+ perror("execl error.");
+ exit(127);
+ } else {
+ close(fd_link[1]);
+ if (read(fd_link[0], result.compiler_output, sizeof(result.compiler_output)) == -1) {
+ perror("read error.");
+ exit(1);
+ }
+ int status;
+ waitpid(pid, &status, 0);
+ result.exit_code = WEXITSTATUS(status);
+ }
- result.exit_code = system(command);
return result;
}
+
+cli_result_t
+cli_runner_compiler_dump_tokens(char *src)
+{
+ char *program_args[] = { "0c", "--dump-tokens", src, NULL };
+ return cli_runner_compiler(src, program_args);
+}
diff --git a/tests/integration/cli_runner.h b/tests/integration/cli_runner.h
index 8f4d69a..7ce4e7b 100644
--- a/tests/integration/cli_runner.h
+++ b/tests/integration/cli_runner.h
@@ -20,6 +20,7 @@ typedef struct cli_result_t
{
int exit_code;
char program_path[255];
+ char compiler_output[1024];
} cli_result_t;
cli_result_t
diff --git a/tests/integration/cli_test.c b/tests/integration/cli_test.c
index ce2ed91..1fd70c7 100644
--- a/tests/integration/cli_test.c
+++ b/tests/integration/cli_test.c
@@ -23,6 +23,20 @@ test_cli_hello_file(const MunitParameter params[], void *user_data_or_fixture)
{
cli_result_t compilation_result = cli_runner_compiler_dump_tokens("../../examples/main_exit.0");
munit_assert_int(compilation_result.exit_code, ==, 0);
+ munit_assert_string_equal(compilation_result.compiler_output,
+ "../../examples/main_exit.0:1:1: <fn>\n"
+ "../../examples/main_exit.0:1:4: <identifier>\n"
+ "../../examples/main_exit.0:1:8: <(>\n"
+ "../../examples/main_exit.0:1:9: <)>\n"
+ "../../examples/main_exit.0:1:10: <:>\n"
+ "../../examples/main_exit.0:1:12: <identifier>\n"
+ "../../examples/main_exit.0:1:16: <{>\n"
+ "../../examples/main_exit.0:1:17: <line_feed>\n"
+ "../../examples/main_exit.0:2:3: <return>\n"
+ "../../examples/main_exit.0:2:10: <number>\n"
+ "../../examples/main_exit.0:2:11: <line_feed>\n"
+ "../../examples/main_exit.0:3:1: <}>\n"
+ "../../examples/main_exit.0:3:2: <line_feed>\n");
return MUNIT_OK;
}
--
2.43.2
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang v2 2/2] lexer: create --dump-tokens cli command
@ 2024-02-19 1:23 Johnny Richard
2024-02-19 0:27 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-02-19 1:23 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
This patch introduces the dump tokens interface and create the initial
setup for lexical analysis.
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
Changes:
- v2 make linter-fix due to linter build failure
examples/main_exit.0 | 3 +
src/0c.c | 121 ++++++++++++++++++++++-
src/lexer.c | 224 +++++++++++++++++++++++++++++++++++++++++++
src/lexer.h | 74 ++++++++++++++
4 files changed, 420 insertions(+), 2 deletions(-)
create mode 100644 examples/main_exit.0
create mode 100644 src/lexer.c
create mode 100644 src/lexer.h
diff --git a/examples/main_exit.0 b/examples/main_exit.0
new file mode 100644
index 0000000..c86fc68
--- /dev/null
+++ b/examples/main_exit.0
@@ -0,0 +1,3 @@
+fn main(): u32 {
+ return 0
+}
diff --git a/src/0c.c b/src/0c.c
index 33ac945..e5199a7 100644
--- a/src/0c.c
+++ b/src/0c.c
@@ -14,8 +14,125 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lexer.h"
+#include "string_view.h"
+
+typedef struct cli_args
+{
+ int argc;
+ char **argv;
+} cli_args_t;
+
+char *
+cli_args_shift(cli_args_t *args);
+
+typedef struct cli_opts
+{
+ // TODO: create man page instruction for --dump-tokens option
+ bool dump_tokens;
+ char *file_path;
+} cli_opts_t;
+
+void
+print_usage(FILE *stream, char *prog);
+
+string_view_t
+read_entire_file(char *file_path);
+
int
-main(void)
+main(int argc, char **argv)
+{
+ cli_args_t args = { .argc = argc, .argv = argv };
+ cli_opts_t opts = { 0 };
+
+ char *prog = cli_args_shift(&args);
+
+ if (argc != 3) {
+ print_usage(stderr, prog);
+ return EXIT_FAILURE;
+ }
+
+ for (char *arg = cli_args_shift(&args); arg != NULL; arg = cli_args_shift(&args)) {
+ if (strcmp(arg, "--dump-tokens") == 0) {
+ opts.dump_tokens = true;
+ } else {
+ opts.file_path = arg;
+ }
+ }
+
+ if (!opts.dump_tokens) {
+ print_usage(stderr, prog);
+ return EXIT_FAILURE;
+ }
+
+ string_view_t file_content = read_entire_file(opts.file_path);
+
+ // TODO: missing integration test for lexer tokenizing
+ lexer_t lexer = { 0 };
+ lexer_init(&lexer, file_content);
+
+ token_t token = { 0 };
+ lexer_next_token(&lexer, &token);
+ while (token.kind != TOKEN_EOF) {
+ printf("%s:%lu:%lu: <%s>\n",
+ opts.file_path,
+ token.location.row + 1,
+ (token.location.offset - token.location.bol) + 1,
+ token_kind_to_cstr(token.kind));
+ lexer_next_token(&lexer, &token);
+ }
+
+ free(file_content.chars);
+
+ return EXIT_SUCCESS;
+}
+
+char *
+cli_args_shift(cli_args_t *args)
+{
+ if (args->argc == 0)
+ return NULL;
+ --(args->argc);
+ return *(args->argv)++;
+}
+
+void
+print_usage(FILE *stream, char *prog)
+{
+ fprintf(stream, "usage: %s <source.0> --dump-tokens\n", prog);
+}
+
+string_view_t
+read_entire_file(char *file_path)
{
- return 0;
+ FILE *stream = fopen(file_path, "rb");
+
+ if (stream == NULL) {
+ fprintf(stderr, "Could not open file %s: %s\n", file_path, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ string_view_t file_content = { 0 };
+
+ fseek(stream, 0, SEEK_END);
+ file_content.size = ftell(stream);
+ fseek(stream, 0, SEEK_SET);
+
+ file_content.chars = (char *)malloc(file_content.size);
+
+ if (file_content.chars == NULL) {
+ fprintf(stderr, "Could not read file %s: %s\n", file_path, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ fread(file_content.chars, 1, file_content.size, stream);
+ fclose(stream);
+
+ return file_content;
}
diff --git a/src/lexer.c b/src/lexer.c
new file mode 100644
index 0000000..544a54d
--- /dev/null
+++ b/src/lexer.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#include "lexer.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdbool.h>
+
+void
+lexer_init(lexer_t *lexer, string_view_t source)
+{
+ assert(lexer);
+ lexer->source = source;
+ lexer->offset = 0;
+ lexer->row = 0;
+ lexer->bol = 0;
+}
+
+static char
+lexer_next_char(lexer_t *lexer);
+
+static void
+lexer_skip_char(lexer_t *lexer);
+
+static bool
+lexer_is_eof(lexer_t *lexer);
+
+static bool
+lexer_is_not_eof(lexer_t *lexer);
+
+static bool
+_isspace(char c);
+
+static void
+lexer_init_char_token(lexer_t *lexer, token_t *token, token_kind_t kind);
+
+static void
+lexer_init_str_token(lexer_t *lexer, token_t *token, token_kind_t kind, size_t start_offset);
+
+static token_kind_t
+lexer_str_to_token_kind(string_view_t text);
+
+void
+lexer_next_token(lexer_t *lexer, token_t *token)
+{
+ if (lexer_is_eof(lexer)) {
+ *token = (token_t){ .kind = TOKEN_EOF };
+ return;
+ }
+
+ char current_char = lexer_next_char(lexer);
+
+ if (_isspace(current_char)) {
+ while (_isspace(current_char) && lexer_is_not_eof(lexer)) {
+ lexer_skip_char(lexer);
+ current_char = lexer_next_char(lexer);
+ }
+ }
+
+ while (lexer_is_not_eof(lexer)) {
+ if (isalpha(current_char)) {
+ size_t start_offset = lexer->offset;
+ while (isalnum(current_char) && lexer_is_not_eof(lexer)) {
+ lexer_skip_char(lexer);
+ current_char = lexer_next_char(lexer);
+ }
+
+ string_view_t text = { .chars = lexer->source.chars + start_offset, .size = lexer->offset - start_offset };
+
+ lexer_init_str_token(lexer, token, lexer_str_to_token_kind(text), start_offset);
+ return;
+ }
+
+ if (isdigit(current_char)) {
+ size_t start_offset = lexer->offset;
+ while (isdigit(current_char) && lexer_is_not_eof(lexer)) {
+ lexer_skip_char(lexer);
+ current_char = lexer_next_char(lexer);
+ }
+
+ lexer_init_str_token(lexer, token, TOKEN_NUMBER, start_offset);
+ return;
+ }
+
+ switch (current_char) {
+ case '(': {
+ lexer_init_char_token(lexer, token, TOKEN_OPAREN);
+ lexer_skip_char(lexer);
+ return;
+ }
+ case ')': {
+ lexer_init_char_token(lexer, token, TOKEN_CPAREN);
+ lexer_skip_char(lexer);
+ return;
+ }
+ case ':': {
+ lexer_init_char_token(lexer, token, TOKEN_COLON);
+ lexer_skip_char(lexer);
+ return;
+ }
+ case '{': {
+ lexer_init_char_token(lexer, token, TOKEN_OCURLY);
+ lexer_skip_char(lexer);
+ return;
+ }
+ case '}': {
+ lexer_init_char_token(lexer, token, TOKEN_CCURLY);
+ lexer_skip_char(lexer);
+ return;
+ }
+ case '\n': {
+ lexer_init_char_token(lexer, token, TOKEN_LF);
+ lexer_skip_char(lexer);
+ return;
+ }
+ default: {
+ lexer_init_char_token(lexer, token, TOKEN_UNKNOWN);
+ lexer_skip_char(lexer);
+ return;
+ }
+ }
+ }
+
+ if (lexer_is_eof(lexer)) {
+ *token = (token_t){ .kind = TOKEN_EOF };
+ return;
+ }
+}
+
+static char *token_kind_str_table[] = {
+ [TOKEN_UNKNOWN] = "unknown", [TOKEN_IDENTIFIER] = "identifier",
+ [TOKEN_NUMBER] = "number", [TOKEN_FN] = "fn",
+ [TOKEN_RETURN] = "return", [TOKEN_LF] = "line_feed",
+ [TOKEN_OPAREN] = "(", [TOKEN_CPAREN] = ")",
+ [TOKEN_COLON] = ":", [TOKEN_OCURLY] = "{",
+ [TOKEN_CCURLY] = "}", [TOKEN_EOF] = "EOF",
+};
+
+char *
+token_kind_to_cstr(token_kind_t kind)
+{
+ assert(kind < sizeof(token_kind_str_table));
+ return token_kind_str_table[kind];
+}
+
+static char
+lexer_next_char(lexer_t *lexer)
+{
+ return lexer->source.chars[lexer->offset];
+}
+
+static void
+lexer_skip_char(lexer_t *lexer)
+{
+ assert(lexer->offset < lexer->source.size);
+ if (lexer->source.chars[lexer->offset] == '\n') {
+ lexer->row++;
+ lexer->bol = ++lexer->offset;
+ } else {
+ lexer->offset++;
+ }
+}
+
+static bool
+lexer_is_eof(lexer_t *lexer)
+{
+ return lexer->offset >= lexer->source.size;
+}
+
+static bool
+lexer_is_not_eof(lexer_t *lexer)
+{
+ return !lexer_is_eof(lexer);
+}
+
+static bool
+_isspace(char c)
+{
+ return c == ' ' || c == '\f' || c == '\r' || c == '\t' || c == '\v';
+}
+
+static void
+lexer_init_char_token(lexer_t *lexer, token_t *token, token_kind_t kind)
+{
+ string_view_t str = { .chars = lexer->source.chars + lexer->offset, .size = 1 };
+ token_loc_t location = { .offset = lexer->offset, .row = lexer->row, .bol = lexer->bol };
+ *token = (token_t){ .kind = kind, .value = str, .location = location };
+}
+
+static void
+lexer_init_str_token(lexer_t *lexer, token_t *token, token_kind_t kind, size_t start_offset)
+{
+ string_view_t str = { .chars = lexer->source.chars + start_offset, .size = lexer->offset - start_offset };
+ token_loc_t location = { .offset = start_offset, .row = lexer->row, .bol = lexer->bol };
+ *token = (token_t){ .kind = kind, .value = str, .location = location };
+}
+
+static token_kind_t
+lexer_str_to_token_kind(string_view_t text)
+{
+ if (string_view_eq_to_cstr(text, "return")) {
+ return TOKEN_RETURN;
+ }
+
+ if (string_view_eq_to_cstr(text, "fn")) {
+ return TOKEN_FN;
+ }
+
+ return TOKEN_IDENTIFIER;
+}
diff --git a/src/lexer.h b/src/lexer.h
new file mode 100644
index 0000000..8c09e02
--- /dev/null
+++ b/src/lexer.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#ifndef LEXER_H
+#define LEXER_H
+
+#include "string_view.h"
+#include <stdint.h>
+
+typedef struct lexer
+{
+ string_view_t source;
+ size_t offset;
+ size_t row;
+ size_t bol;
+} lexer_t;
+
+typedef enum token_kind
+{
+ TOKEN_UNKNOWN,
+ TOKEN_IDENTIFIER,
+ TOKEN_NUMBER,
+
+ // Keywords
+ TOKEN_FN,
+ TOKEN_RETURN,
+
+ // Single char
+ TOKEN_LF,
+ TOKEN_OPAREN,
+ TOKEN_CPAREN,
+ TOKEN_COLON,
+ TOKEN_OCURLY,
+ TOKEN_CCURLY,
+ TOKEN_EOF
+} token_kind_t;
+
+typedef struct token_loc
+{
+ size_t offset;
+ size_t row;
+ size_t bol;
+} token_loc_t;
+
+typedef struct token
+{
+ token_kind_t kind;
+ string_view_t value;
+ token_loc_t location;
+} token_t;
+
+void
+lexer_init(lexer_t *lexer, string_view_t source);
+
+void
+lexer_next_token(lexer_t *lexer, token_t *token);
+
+char *
+token_kind_to_cstr(token_kind_t kind);
+
+#endif /* LEXER_H */
--
2.43.2
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang 2/2] lexer: create --dump-tokens cli command
@ 2024-02-19 1:15 Johnny Richard
2024-02-19 0:20 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-02-19 1:15 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
This patch introduces the dump tokens interface and create the initial
setup for lexical analysis.
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
examples/main_exit.0 | 3 +
src/0c.c | 121 ++++++++++++++++++++++-
src/lexer.c | 224 +++++++++++++++++++++++++++++++++++++++++++
src/lexer.h | 74 ++++++++++++++
4 files changed, 420 insertions(+), 2 deletions(-)
create mode 100644 examples/main_exit.0
create mode 100644 src/lexer.c
create mode 100644 src/lexer.h
diff --git a/examples/main_exit.0 b/examples/main_exit.0
new file mode 100644
index 0000000..c86fc68
--- /dev/null
+++ b/examples/main_exit.0
@@ -0,0 +1,3 @@
+fn main(): u32 {
+ return 0
+}
diff --git a/src/0c.c b/src/0c.c
index 33ac945..e5199a7 100644
--- a/src/0c.c
+++ b/src/0c.c
@@ -14,8 +14,125 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lexer.h"
+#include "string_view.h"
+
+typedef struct cli_args
+{
+ int argc;
+ char **argv;
+} cli_args_t;
+
+char *
+cli_args_shift(cli_args_t *args);
+
+typedef struct cli_opts
+{
+ // TODO: create man page instruction for --dump-tokens option
+ bool dump_tokens;
+ char *file_path;
+} cli_opts_t;
+
+void
+print_usage(FILE *stream, char *prog);
+
+string_view_t
+read_entire_file(char *file_path);
+
int
-main(void)
+main(int argc, char **argv)
+{
+ cli_args_t args = { .argc = argc, .argv = argv };
+ cli_opts_t opts = { 0 };
+
+ char *prog = cli_args_shift(&args);
+
+ if (argc != 3) {
+ print_usage(stderr, prog);
+ return EXIT_FAILURE;
+ }
+
+ for (char *arg = cli_args_shift(&args); arg != NULL; arg = cli_args_shift(&args)) {
+ if (strcmp(arg, "--dump-tokens") == 0) {
+ opts.dump_tokens = true;
+ } else {
+ opts.file_path = arg;
+ }
+ }
+
+ if (!opts.dump_tokens) {
+ print_usage(stderr, prog);
+ return EXIT_FAILURE;
+ }
+
+ string_view_t file_content = read_entire_file(opts.file_path);
+
+ // TODO: missing integration test for lexer tokenizing
+ lexer_t lexer = { 0 };
+ lexer_init(&lexer, file_content);
+
+ token_t token = { 0 };
+ lexer_next_token(&lexer, &token);
+ while (token.kind != TOKEN_EOF) {
+ printf("%s:%lu:%lu: <%s>\n",
+ opts.file_path,
+ token.location.row + 1,
+ (token.location.offset - token.location.bol) + 1,
+ token_kind_to_cstr(token.kind));
+ lexer_next_token(&lexer, &token);
+ }
+
+ free(file_content.chars);
+
+ return EXIT_SUCCESS;
+}
+
+char *
+cli_args_shift(cli_args_t *args)
+{
+ if (args->argc == 0)
+ return NULL;
+ --(args->argc);
+ return *(args->argv)++;
+}
+
+void
+print_usage(FILE *stream, char *prog)
+{
+ fprintf(stream, "usage: %s <source.0> --dump-tokens\n", prog);
+}
+
+string_view_t
+read_entire_file(char *file_path)
{
- return 0;
+ FILE *stream = fopen(file_path, "rb");
+
+ if (stream == NULL) {
+ fprintf(stderr, "Could not open file %s: %s\n", file_path, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ string_view_t file_content = { 0 };
+
+ fseek(stream, 0, SEEK_END);
+ file_content.size = ftell(stream);
+ fseek(stream, 0, SEEK_SET);
+
+ file_content.chars = (char *)malloc(file_content.size);
+
+ if (file_content.chars == NULL) {
+ fprintf(stderr, "Could not read file %s: %s\n", file_path, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ fread(file_content.chars, 1, file_content.size, stream);
+ fclose(stream);
+
+ return file_content;
}
diff --git a/src/lexer.c b/src/lexer.c
new file mode 100644
index 0000000..7866a9a
--- /dev/null
+++ b/src/lexer.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#include "lexer.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdbool.h>
+
+void
+lexer_init(lexer_t *lexer, string_view_t source)
+{
+ assert(lexer);
+ lexer->source = source;
+ lexer->offset = 0;
+ lexer->row = 0;
+ lexer->bol = 0;
+}
+
+static char
+lexer_next_char(lexer_t *lexer);
+
+static void
+lexer_skip_char(lexer_t *lexer);
+
+static bool
+lexer_is_eof(lexer_t *lexer);
+
+static bool
+lexer_is_not_eof(lexer_t *lexer);
+
+static bool
+_isspace(char c);
+
+static void
+lexer_init_char_token(lexer_t *lexer, token_t *token, token_kind_t kind);
+
+static void
+lexer_init_str_token(lexer_t *lexer, token_t *token, token_kind_t kind, size_t start_offset);
+
+static token_kind_t
+lexer_str_to_token_kind(string_view_t text);
+
+void
+lexer_next_token(lexer_t *lexer, token_t *token)
+{
+ if (lexer_is_eof(lexer)) {
+ *token = (token_t){ .kind = TOKEN_EOF };
+ return;
+ }
+
+ char current_char = lexer_next_char(lexer);
+
+ if (_isspace(current_char)) {
+ while (_isspace(current_char) && lexer_is_not_eof(lexer)) {
+ lexer_skip_char(lexer);
+ current_char = lexer_next_char(lexer);
+ }
+ }
+
+ while (lexer_is_not_eof(lexer)) {
+ if (isalpha(current_char)) {
+ size_t start_offset = lexer->offset;
+ while (isalnum(current_char) && lexer_is_not_eof(lexer)) {
+ lexer_skip_char(lexer);
+ current_char = lexer_next_char(lexer);
+ }
+
+ string_view_t text = { .chars = lexer->source.chars + start_offset, .size = lexer->offset - start_offset };
+
+ lexer_init_str_token(lexer, token, lexer_str_to_token_kind(text), start_offset);
+ return;
+ }
+
+ if (isdigit(current_char)) {
+ size_t start_offset = lexer->offset;
+ while (isdigit(current_char) && lexer_is_not_eof(lexer)) {
+ lexer_skip_char(lexer);
+ current_char = lexer_next_char(lexer);
+ }
+
+ lexer_init_str_token(lexer, token, TOKEN_NUMBER, start_offset);
+ return;
+ }
+
+ switch (current_char) {
+ case '(': {
+ lexer_init_char_token(lexer, token, TOKEN_OPAREN);
+ lexer_skip_char(lexer);
+ return;
+ }
+ case ')': {
+ lexer_init_char_token(lexer, token, TOKEN_CPAREN);
+ lexer_skip_char(lexer);
+ return;
+ }
+ case ':': {
+ lexer_init_char_token(lexer, token, TOKEN_COLON);
+ lexer_skip_char(lexer);
+ return;
+ }
+ case '{': {
+ lexer_init_char_token(lexer, token, TOKEN_OCURLY);
+ lexer_skip_char(lexer);
+ return;
+ }
+ case '}': {
+ lexer_init_char_token(lexer, token, TOKEN_CCURLY);
+ lexer_skip_char(lexer);
+ return;
+ }
+ case '\n': {
+ lexer_init_char_token(lexer, token, TOKEN_LF);
+ lexer_skip_char(lexer);
+ return;
+ }
+ default: {
+ lexer_init_char_token(lexer, token, TOKEN_UNKNOWN);
+ lexer_skip_char(lexer);
+ return;
+ }
+ }
+ }
+
+ if (lexer_is_eof(lexer)) {
+ *token = (token_t){ .kind = TOKEN_EOF };
+ return;
+ }
+}
+
+static char *token_kind_str_table[] = {
+ [TOKEN_UNKNOWN] = "unknown", [TOKEN_IDENTIFIER] = "identifier",
+ [TOKEN_NUMBER] = "number", [TOKEN_FN] = "fn",
+ [TOKEN_RETURN] = "return", [TOKEN_LF] = "line_feed",
+ [TOKEN_OPAREN] = "(", [TOKEN_CPAREN] = ")",
+ [TOKEN_COLON] = ":", [TOKEN_OCURLY] = "{",
+ [TOKEN_CCURLY] = "}", [TOKEN_EOF] = "EOF",
+};
+
+char *
+token_kind_to_cstr(token_kind_t kind)
+{
+ assert(kind < sizeof(token_kind_str_table));
+ return token_kind_str_table[kind];
+}
+
+static char
+lexer_next_char(lexer_t *lexer)
+{
+ return lexer->source.chars[lexer->offset];
+}
+
+static void
+lexer_skip_char(lexer_t *lexer)
+{
+ assert(lexer->offset < lexer->source.size);
+ if (lexer->source.chars[lexer->offset] == '\n') {
+ lexer->row++;
+ lexer->bol = ++lexer->offset;
+ } else {
+ lexer->offset++;
+ }
+}
+
+static bool
+lexer_is_eof(lexer_t *lexer)
+{
+ return lexer->offset >= lexer->source.size;
+}
+
+static bool
+lexer_is_not_eof(lexer_t *lexer)
+{
+ return !lexer_is_eof(lexer);
+}
+
+static bool
+_isspace(char c)
+{
+ return c == ' ' || c == '\f' || c == '\r' || c == '\t' || c == '\v';
+}
+
+static void
+lexer_init_char_token(lexer_t *lexer, token_t *token, token_kind_t kind)
+{
+ string_view_t str = { .chars = lexer->source.chars + lexer->offset, .size = 1 };
+ token_loc_t location = { .offset = lexer->offset, .row = lexer->row, .bol = lexer->bol };
+ *token = (token_t){ .kind = kind, .value = str, .location = location };
+}
+
+static void
+lexer_init_str_token(lexer_t *lexer, token_t *token, token_kind_t kind, size_t start_offset)
+{
+ string_view_t str = { .chars = lexer->source.chars + start_offset, .size = lexer->offset - start_offset };
+ token_loc_t location = { .offset = start_offset, .row = lexer->row, .bol = lexer->bol };
+ *token = (token_t){ .kind = kind, .value = str, .location = location };
+}
+
+static token_kind_t
+lexer_str_to_token_kind(string_view_t text)
+{
+ if (string_view_eq_to_cstr(text, "return")) {
+ return TOKEN_RETURN;
+ }
+
+ if (string_view_eq_to_cstr(text, "fn")) {
+ return TOKEN_FN;
+ }
+
+ return TOKEN_IDENTIFIER;
+}
diff --git a/src/lexer.h b/src/lexer.h
new file mode 100644
index 0000000..8c09e02
--- /dev/null
+++ b/src/lexer.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 olang maintainers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#ifndef LEXER_H
+#define LEXER_H
+
+#include "string_view.h"
+#include <stdint.h>
+
+typedef struct lexer
+{
+ string_view_t source;
+ size_t offset;
+ size_t row;
+ size_t bol;
+} lexer_t;
+
+typedef enum token_kind
+{
+ TOKEN_UNKNOWN,
+ TOKEN_IDENTIFIER,
+ TOKEN_NUMBER,
+
+ // Keywords
+ TOKEN_FN,
+ TOKEN_RETURN,
+
+ // Single char
+ TOKEN_LF,
+ TOKEN_OPAREN,
+ TOKEN_CPAREN,
+ TOKEN_COLON,
+ TOKEN_OCURLY,
+ TOKEN_CCURLY,
+ TOKEN_EOF
+} token_kind_t;
+
+typedef struct token_loc
+{
+ size_t offset;
+ size_t row;
+ size_t bol;
+} token_loc_t;
+
+typedef struct token
+{
+ token_kind_t kind;
+ string_view_t value;
+ token_loc_t location;
+} token_t;
+
+void
+lexer_init(lexer_t *lexer, string_view_t source);
+
+void
+lexer_next_token(lexer_t *lexer, token_t *token);
+
+char *
+token_kind_to_cstr(token_kind_t kind);
+
+#endif /* LEXER_H */
--
2.43.2
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang 2/2] Revert "docs: add sphinx documentation support"
@ 2024-02-17 13:46 Carlos Maniero
2024-02-17 13:51 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-02-17 13:46 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
This reverts commit 0a48a94d9ade6ac92bbc42c3e7681f326d4f8d70.
---
.gitignore | 1 -
docs/Makefile | 20 --------------------
docs/conf.py | 28 ----------------------------
docs/index.rst | 13 -------------
4 files changed, 62 deletions(-)
delete mode 100644 docs/Makefile
delete mode 100644 docs/conf.py
delete mode 100644 docs/index.rst
diff --git a/.gitignore b/.gitignore
index 77f3000..b140d08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
0c
build
*.o
-docs/_build
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index d4bb2cb..0000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,20 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line, and also
-# from the environment for the first two.
-SPHINXOPTS ?=
-SPHINXBUILD ?= sphinx-build
-SOURCEDIR = .
-BUILDDIR = _build
-
-# Put it first so that "make" without argument is like "make help".
-help:
- @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
- @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/conf.py b/docs/conf.py
deleted file mode 100644
index ee7421a..0000000
--- a/docs/conf.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Configuration file for the Sphinx documentation builder.
-#
-# For the full list of built-in configuration values, see the documentation:
-# https://www.sphinx-doc.org/en/master/usage/configuration.html
-
-# -- Project information -----------------------------------------------------
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
-
-project = 'olang'
-copyright = '2024, olang maintainers'
-author = 'olang maintainers'
-release = '0.0.1'
-
-# -- General configuration ---------------------------------------------------
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
-
-extensions = []
-
-templates_path = ['_templates']
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
-
-
-
-# -- Options for HTML output -------------------------------------------------
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
-
-html_theme = 'alabaster'
-html_static_path = ['_static']
diff --git a/docs/index.rst b/docs/index.rst
deleted file mode 100644
index 4ad0971..0000000
--- a/docs/index.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-Welcome to olang's documentation!
-=================================
-
-.. toctree::
- :caption: Contents:
-
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`search`
--
2.34.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang] docs: add HACKING documentation
@ 2024-02-17 4:23 Carlos Maniero
2024-02-17 4:23 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-02-17 4:23 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
The purpose of this docs is to teach newcomers developers to start
contributing with the project.
Signed-off-by: Carlos Maniero <carlos@maniero.me>
---
docs/index.rst | 8 ++-
docs/pages/hacking.rst | 158 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 164 insertions(+), 2 deletions(-)
create mode 100644 docs/pages/hacking.rst
diff --git a/docs/index.rst b/docs/index.rst
index 4ad0971..f09a97d 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,9 +1,13 @@
-Welcome to olang's documentation!
-=================================
+Welcome to olang's docs!
+========================
+The zero programming language.
+
.. toctree::
:caption: Contents:
+ pages/hacking
+
Indices and tables
diff --git a/docs/pages/hacking.rst b/docs/pages/hacking.rst
new file mode 100644
index 0000000..5fd7a39
--- /dev/null
+++ b/docs/pages/hacking.rst
@@ -0,0 +1,158 @@
+=======
+Hacking
+=======
+
+We’re thrilled to have you here! Your interest in making olang the most
+exceptional and straightforward language ever is greatly appreciated.
+
+In this document, we’ll outline how you can begin contributing to olang.
+
+First and foremost, clone the project if you haven’t done so already.
+
+.. code-block:: sh
+
+ git clone https://git.sr.ht/~johnnyrichard/olang
+
+Dependencies
+============
+
+The olang compiler is crafted in C. To build the project, you’ll require
+merely three dependencies: **make**, **gcc** (version 11 or higher), and
+**clang-format** (version 14 or higher).
+
+As an optional step, you can install **sphinx** to refresh this documentation.
+
+Code style
+==========
+
+Instead of delineating every element of our coding style, we have adopted the
+use of **clang-format** to enforce the olang code style. Please refer to the
+linter section below for guidance on its application.
+
+Linter
+------
+
+Checking for linter issues:
+
+.. code-block:: sh
+
+ make linter
+
+Most of the common code style mistakes are fixed by:
+
+.. code-block:: sh
+
+ make linter-fix
+
+.editorconfig
+-------------
+
+We also provide a **.editorconfig** file at project's root. Follow
+https://editorconfig.org/ to learn how to make it work with your favorite
+editor.
+
+Testing
+=======
+
+There are two layers of tests **integration** and **unit**. The integration
+test is going to execute the **0c** compiler and check if the generated binary
+acts as expected. Unit tests will test C functions.
+
+For both unit and integration we use **munit** framework: https://nemequ.github.io/munit/.
+
+To execute tests you can execute:
+
+.. code-block:: sh
+
+ make check
+
+
+Submitting a patch
+==================
+
+Before submit a patch, ensure your code follows our coding style and is
+well-tested. After that, you're good to follow the steps bellow.
+
+Step 1: Commit your changes
+---------------------------
+
+Begin by committing your modifications with a detailed and significant commit
+message. We take great pleasure in maintaining a record of our changes over
+time. Skeptical? Execute a **git log** command and admire the well-documented
+history we’ve created so far.
+
+But it isn't all about personal preference. We use a email-driven workflow
+to propose our changes, meaning the commit message you write is the email the
+olang maintainers will get. I won’t go into the perks of the email-driven
+workflow here, but you can check it out at
+https://drewdevault.com/2018/07/02/Email-driven-git.html.
+
+Best practices
+^^^^^^^^^^^^^^
+
+#. Write single-purpose commits.
+#. Write a meaningful commit message.
+#. Every commit must be production ready.
+ - If the tests or the linter fail, you should not create a fix commit.
+ Instead, you should amend the commit that caused the issue and then resend
+ the patchset.
+
+Step 2: Create your patch
+-------------------------
+
+You can create a patch using the command:
+
+.. code-block:: sh
+
+ git format-patch --cover-letter -M origin/main -o outgoing/
+
+Step 3: Write a cover letter:
+-----------------------------
+
+The command above generates a **outgoing/0000-cover-letter.patch** file.
+
+ The cover letter is like a pull request description on Github. Replace the
+ \*\*\* SUBJECT HERE \*\*\* with the patchset title and the
+ \*\*\* BLURB HERE \*\*\* with a synopsis of your changes.
+
+If you are sending a single-commit patch you can remove the **--cover-lette**
+argument from **git format-patch** and skip this step.
+
+Step 4: Submit your patch
+-------------------------
+
+Make sure you have configured your **git send-email**. You can learn how to
+configure it here:
+https://git-scm.com/docs/git-send-email#_examples
+
+Once you have everything set you just need to send the patch over our
+mailing list.
+
+.. code-block:: sh
+
+ git send-email outgoing/* --to=~johnnyrichard/olang-devel@lists.sr.ht
+
+The review process
+------------------
+
+Upon submission, you’ll receive an automated email from our pipeline. If the
+check is successful, the olang maintainers will review your patch.
+Subsequently, you’ll receive an email indicating whether your patch has been
+approved, requires changes, or has been rejected.
+
+Submitting changes in a patchset
+--------------------------------
+
+If your patchset requires any modifications, you’ll have to submit a new
+version of your patch. The submission process remains unchanged, except for the
+addition of the version argument to the **git format-patch** command.
+
+.. code-block:: sh
+
+ git format-patch --cover-letter -M origin/main -o outgoing/ -v2
+
+After send a new email with **git send-email**.
+
+----
+
+Thanks again and happy hacking!
--
2.34.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang] style: fix clang-format format indentation
@ 2024-02-14 23:36 Carlos Maniero
2024-02-14 23:41 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Carlos Maniero @ 2024-02-14 23:36 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Carlos Maniero
editorconfig was set to 4 spaces clang-format was expecting 2.
Signed-off-by: Carlos Maniero <carlos@maniero.me>
---
.clang-format | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.clang-format b/.clang-format
index 2850fff..b76afee 100644
--- a/.clang-format
+++ b/.clang-format
@@ -81,8 +81,8 @@ ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
QualifierAlignment: Leave
CompactNamespaces: false
-ConstructorInitializerIndentWidth: 2
-ContinuationIndentWidth: 2
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DeriveLineEnding: true
DerivePointerAlignment: false
@@ -124,7 +124,7 @@ IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequiresClause: true
-IndentWidth: 2
+IndentWidth: 4
IndentWrappedFunctionNames: false
InsertBraces: false
InsertTrailingCommas: None
--
2.34.1
^ permalink raw reply [flat|nested] 40+ messages in thread
* [PATCH olang] build: enable continuous integration through .build.yml
@ 2024-02-13 21:36 Johnny Richard
2024-02-13 20:34 ` [olang/patches/.build.yml] build failed builds.sr.ht
0 siblings, 1 reply; 40+ messages in thread
From: Johnny Richard @ 2024-02-13 21:36 UTC (permalink / raw)
To: ~johnnyrichard/olang-devel; +Cc: Johnny Richard
A small build pipeline with linter and compilation settings.
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
---
.build.yml | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 .build.yml
diff --git a/.build.yml b/.build.yml
new file mode 100644
index 0000000..35a5da8
--- /dev/null
+++ b/.build.yml
@@ -0,0 +1,12 @@
+image: archlinux
+packages:
+ - gcc
+ - make
+ - clang
+tasks:
+ - lint: |
+ cd olang
+ make linter
+ - build: |
+ cd olang
+ make
--
2.42.0
^ permalink raw reply [flat|nested] 40+ messages in thread
end of thread, other threads:[~2024-10-17 2:53 UTC | newest]
Thread overview: 40+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-02-16 2:58 [PATCH olang v2 0/2] Add integration tests Carlos Maniero
2024-02-16 2:58 ` [PATCH olang v2 1/2] tests: add munit testing framework file Carlos Maniero
2024-02-16 2:58 ` [PATCH olang v2 2/2] tests: add integration test setup Carlos Maniero
2024-02-16 3:03 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-16 3:15 ` [PATCH olang v2 0/2] Add integration tests Carlos Maniero
2024-02-16 8:02 ` Johnny Richard
2024-02-16 13:43 ` Carlos Maniero
2024-02-16 16:06 ` Johnny Richard
-- strict thread matches above, loose matches on Subject: below --
2024-10-17 2:48 [PATCH olang v1 1/1] codegen: x64: deref returns pointer value Carlos Maniero
2024-10-17 2:49 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-10-17 2:52 ` Carlos Maniero
2024-10-15 12:14 [PATCH olang] fix: codegen: prevent stack overwrite Carlos Maniero
2024-10-15 12:14 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-10-15 23:03 ` Carlos Maniero
2024-10-11 3:42 [PATCH olang v1] fix: build: add missing dependencies for check-spec Johnny Richard
2024-10-11 1:43 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-10-08 16:33 [PATCH olang] parser: conform block line feeds with the spec Carlos Maniero
2024-10-08 16:33 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-08-13 18:16 [PATCH olang v1 2/2] ast: inline ast_node_data_t union definition Johnny Richard
2024-08-13 17:27 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-08-13 19:03 ` Johnny Richard
2024-04-20 13:54 [PATCH olang v1] build: rename linter to format to avoid confusion Johnny Richard
2024-04-20 12:57 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-03-28 15:58 [PATCH olang v1] fe: lexer: add single line comments support Johnny Richard
2024-03-28 14:59 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-03-28 16:46 ` Johnny Richard
2024-03-27 3:21 [PATCH olang v1 2/2] docs: spec: add variables and constants specification Carlos Maniero
2024-03-27 3:22 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-03-25 22:36 [PATCH olang v1 2/2] cli: remove code duplication Johnny Richard
2024-03-25 21:37 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-03-26 2:32 ` Carlos Maniero
2024-03-26 2:35 ` Carlos Maniero
2024-03-13 12:32 [PATCH olang v3] refactor: rename zero programming language to olang Fabio Maciel
2024-03-13 12:33 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-03-08 20:52 [PATCH olang] parser: abort when parser identifies a syntax error Johnny Richard
2024-03-08 19:54 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-03-05 8:44 [PATCH olang v2 3/3] cli: add compilation -o option with --save-temps Johnny Richard
2024-03-05 7:51 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-03-04 19:23 [PATCH olang v1 3/3] cli: add compilation -o option with --save-temps Johnny Richard
2024-03-04 18:33 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-03-04 19:39 ` Johnny Richard
2024-03-05 2:05 ` Carlos Maniero
2024-03-02 20:01 [PATCH olang] string_view: fix stack buffer overflow on to_u32 Johnny Richard
2024-03-02 19:02 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-03-02 19:02 [PATCH olang] string_view: add n + 1 test to string_view_to_u32 function Johnny Richard
2024-03-02 18:03 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-20 16:39 [PATCH olang v2] utils: add arena Carlos Maniero
2024-02-20 16:45 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-20 16:10 [PATCH olang] utils: add arena Carlos Maniero
2024-02-20 16:15 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-19 21:04 [PATCH olang v4 4/4] lexer: test: add integration tests for --dump-tokens Johnny Richard
2024-02-19 20:07 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-19 1:23 [PATCH olang v2 2/2] lexer: create --dump-tokens cli command Johnny Richard
2024-02-19 0:27 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-19 1:15 [PATCH olang 2/2] lexer: create --dump-tokens cli command Johnny Richard
2024-02-19 0:20 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-17 13:46 [PATCH olang 2/2] Revert "docs: add sphinx documentation support" Carlos Maniero
2024-02-17 13:51 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-17 4:23 [PATCH olang] docs: add HACKING documentation Carlos Maniero
2024-02-17 4:23 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-14 23:36 [PATCH olang] style: fix clang-format format indentation Carlos Maniero
2024-02-14 23:41 ` [olang/patches/.build.yml] build failed builds.sr.ht
2024-02-13 21:36 [PATCH olang] build: enable continuous integration through .build.yml Johnny Richard
2024-02-13 20:34 ` [olang/patches/.build.yml] build failed builds.sr.ht
Code repositories for project(s) associated with this public inbox
https://git.johnnyrichard.com/olang.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox