feat: hello, world!
This commit is contained in:
parent
c1da9bc218
commit
14ab06110b
|
@ -2,21 +2,24 @@
|
|||
-Wall
|
||||
-Wextra
|
||||
-Werror
|
||||
-D__ck_loader_limine__
|
||||
-Isrc/libs
|
||||
-Isrc/kernel/klibs
|
||||
-Isrc/kernel/archs
|
||||
-D__ck_toolchain_value=clang
|
||||
-D__ck_encoding_utf8__
|
||||
-D__ck_sys_kernel__
|
||||
-D__ck_freestanding__
|
||||
-D__ck_loader_value=limine
|
||||
-D__ck_encoding_value=utf8
|
||||
-D__ck_arch_x86_64__
|
||||
-D__ck_bits_64__
|
||||
-D__ck_toolchain_clang__
|
||||
-D__ck_bits_value=64
|
||||
-D__ck_abi_sysv__
|
||||
-D__ck_abi_value=sysv
|
||||
-D__ck_sys_value=kernel
|
||||
-D__ck_loader_limine__
|
||||
-D__ck_sys_kernel__
|
||||
-D__ck_encoding_utf8__
|
||||
-D__ck_abi_sysv__
|
||||
-D__ck_freestanding__
|
||||
-D__ck_arch_value=x86_64
|
||||
-D__ck_bits_64__
|
||||
-D__ck_bits_value=64
|
||||
-D__ck_arch_x86_64__
|
||||
-ffreestanding
|
||||
-fno-stack-protector
|
||||
-mno-80387
|
||||
|
@ -26,3 +29,4 @@
|
|||
-mno-sse2
|
||||
-mno-red-zone
|
||||
-Dauto=__auto_type
|
||||
-Isrc/kernel/klibs
|
||||
|
|
19
src/kernel/archs/hal.h
Normal file
19
src/kernel/archs/hal.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <io/stream.h>
|
||||
|
||||
void hal_setup(void);
|
||||
|
||||
/* --- Assembly function --------------------------------------------------- */
|
||||
|
||||
void hal_disable_interrupts(void);
|
||||
|
||||
void hal_enable_interrupts(void);
|
||||
|
||||
void hal_pause(void);
|
||||
|
||||
void hal_panic(void);
|
||||
|
||||
/* --- I/O ---------------------------------------------------------------- */
|
||||
|
||||
Stream hal_dbg_stream(void);
|
19
src/kernel/archs/x86_64/asm.c
Normal file
19
src/kernel/archs/x86_64/asm.c
Normal file
|
@ -0,0 +1,19 @@
|
|||
void hal_disable_interrupts(void)
|
||||
{
|
||||
asm volatile("cli");
|
||||
}
|
||||
|
||||
void hal_enable_interrupts(void)
|
||||
{
|
||||
asm volatile("sti");
|
||||
}
|
||||
|
||||
void hal_pause(void)
|
||||
{
|
||||
asm volatile("pause");
|
||||
}
|
||||
|
||||
void hal_panic(void)
|
||||
{
|
||||
asm volatile("int $1");
|
||||
}
|
19
src/kernel/archs/x86_64/e9.c
Normal file
19
src/kernel/archs/x86_64/e9.c
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include <res.h>
|
||||
|
||||
#include "e9.h"
|
||||
|
||||
Res e9_putc(char c)
|
||||
{
|
||||
asm volatile("outb %0, $0xe9" : : "a"(c) : "memory");
|
||||
return ok$();
|
||||
}
|
||||
|
||||
Res e9_puts(size_t n, char const *s)
|
||||
{
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
e9_putc(s[i]);
|
||||
}
|
||||
|
||||
return ok$();
|
||||
}
|
7
src/kernel/archs/x86_64/e9.h
Normal file
7
src/kernel/archs/x86_64/e9.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <res.h>
|
||||
|
||||
Res e9_putc(char c);
|
||||
|
||||
Res e9_puts(size_t n, char const *s);
|
16
src/kernel/archs/x86_64/manifest.json
Normal file
16
src/kernel/archs/x86_64/manifest.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1",
|
||||
"type": "lib",
|
||||
"id": "arch (x86_64)",
|
||||
"enableIf": {
|
||||
"arch": [
|
||||
"x86_64"
|
||||
]
|
||||
},
|
||||
"provides": [
|
||||
"arch"
|
||||
],
|
||||
"requires": [
|
||||
"dbg"
|
||||
]
|
||||
}
|
14
src/kernel/archs/x86_64/mod.c
Normal file
14
src/kernel/archs/x86_64/mod.c
Normal file
|
@ -0,0 +1,14 @@
|
|||
#include <io/stream.h>
|
||||
|
||||
#include "e9.h"
|
||||
|
||||
Stream hal_dbg_stream(void)
|
||||
{
|
||||
return (Stream){
|
||||
.write = e9_puts,
|
||||
};
|
||||
}
|
||||
|
||||
void hal_setup(void)
|
||||
{
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
#include <dbg/log.h>
|
||||
|
||||
_Noreturn int _start()
|
||||
{
|
||||
log$("Hello, world!");
|
||||
for (;;)
|
||||
;
|
||||
}
|
|
@ -1,5 +1,17 @@
|
|||
{
|
||||
"$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1",
|
||||
"type": "exe",
|
||||
"id": "core"
|
||||
"id": "core",
|
||||
"requires": [
|
||||
"arch",
|
||||
"dbg",
|
||||
"stdc-shim"
|
||||
],
|
||||
"tools": {
|
||||
"cc": {
|
||||
"args": [
|
||||
"-Isrc/kernel/klibs"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
46
src/kernel/klibs/dbg/log.c
Normal file
46
src/kernel/klibs/dbg/log.c
Normal file
|
@ -0,0 +1,46 @@
|
|||
#include <fmt/fmt.h>
|
||||
#include <hal.h>
|
||||
#include <stdarg.h>
|
||||
#include <sync/spinlock.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
static Spinlock _lock = SPINLOCK_INIT;
|
||||
|
||||
static char const *level_names[LOG_EVENT_LENGTH] = {
|
||||
[LOG_NONE] = "",
|
||||
[LOG_INFO] = "INFO",
|
||||
[LOG_WARN] = "WARN",
|
||||
[LOG_ERROR] = "ERROR",
|
||||
[LOG_CRIT] = "CRITIC",
|
||||
};
|
||||
|
||||
static char const *level_colors[LOG_EVENT_LENGTH] = {
|
||||
[LOG_NONE] = "",
|
||||
[LOG_INFO] = "\e[1;34m",
|
||||
[LOG_WARN] = "\e[1;33m",
|
||||
[LOG_ERROR] = "\e[1;31m",
|
||||
[LOG_CRIT] = "\e[1;35m",
|
||||
};
|
||||
|
||||
void _log(LogEvent event, Loc loc, char const *format, ...)
|
||||
{
|
||||
spinlock_acquire(&_lock);
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
if (event != LOG_NONE)
|
||||
{
|
||||
fmt(hal_dbg_stream(), "%s%s\e[0m %s:%d ", level_colors[event], level_names[event], loc.file, loc.line);
|
||||
}
|
||||
|
||||
vfmt(hal_dbg_stream(), format, args);
|
||||
|
||||
if (event != LOG_NONE)
|
||||
{
|
||||
hal_dbg_stream().write(1, "\n");
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
spinlock_release(&_lock);
|
||||
}
|
24
src/kernel/klibs/dbg/log.h
Normal file
24
src/kernel/klibs/dbg/log.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <io/stream.h>
|
||||
|
||||
#include "loc.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
LOG_NONE,
|
||||
LOG_INFO,
|
||||
LOG_WARN,
|
||||
LOG_ERROR,
|
||||
LOG_CRIT,
|
||||
|
||||
LOG_EVENT_LENGTH
|
||||
} LogEvent;
|
||||
|
||||
#define log$(...) _log(LOG_INFO, loc$(), __VA_ARGS__)
|
||||
#define warn$(...) _log(LOG_WARN, loc$(), __VA_ARGS__)
|
||||
#define error$(...) _log(LOG_ERROR, loc$(), __VA_ARGS__)
|
||||
#define critical$(...) _log(LOG_CRIT, loc$(), __VA_ARGS__)
|
||||
#define print$(...) _log(LOG_NONE, (Loc){}, __VA_ARGS__)
|
||||
|
||||
void _log(LogEvent event, Loc loc, char const *format, ...);
|
14
src/kernel/klibs/dbg/manifest.json
Normal file
14
src/kernel/klibs/dbg/manifest.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1",
|
||||
"type": "lib",
|
||||
"id": "dbg",
|
||||
"enableIf": {
|
||||
"freestanding": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"requires": [
|
||||
"sync",
|
||||
"fmt"
|
||||
]
|
||||
}
|
13
src/kernel/klibs/stdc-shim/manifest.json
Normal file
13
src/kernel/klibs/stdc-shim/manifest.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1",
|
||||
"type": "lib",
|
||||
"id": "stdc-shim",
|
||||
"props": {
|
||||
"cpp-root-include": true
|
||||
},
|
||||
"enableIf": {
|
||||
"freestanding": [
|
||||
true
|
||||
]
|
||||
}
|
||||
}
|
51
src/kernel/klibs/stdc-shim/string.c
Normal file
51
src/kernel/klibs/stdc-shim/string.c
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include "string.h"
|
||||
|
||||
size_t strlen(char const str[static 1])
|
||||
{
|
||||
size_t len = 0;
|
||||
|
||||
while (*str++)
|
||||
{
|
||||
len++;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void *memcpy(void *restrict dst, void const *restrict src, size_t n)
|
||||
{
|
||||
char *restrict d = dst;
|
||||
char const *restrict s = src;
|
||||
|
||||
while (n--)
|
||||
{
|
||||
*d++ = *s++;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
void *memset(void *dest, int c, size_t n)
|
||||
{
|
||||
char *d = dest;
|
||||
|
||||
while (n--)
|
||||
{
|
||||
*d++ = c;
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
int memcmp(const void *s1, const void *s2, size_t n)
|
||||
{
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
if (((const char *)s1)[i] != ((const char *)s2)[i])
|
||||
{
|
||||
return ((const char *)s1)[i] - ((const char *)s2)[i];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
11
src/kernel/klibs/stdc-shim/string.h
Normal file
11
src/kernel/klibs/stdc-shim/string.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
size_t strlen(char const s[static 1]);
|
||||
|
||||
void *memcpy(void *restrict dest, void const *restrict src, size_t n);
|
||||
|
||||
void *memset(void *dest, int c, size_t n);
|
||||
|
||||
int memcmp(void const *s1, void const *s2, size_t n);
|
10
src/kernel/klibs/sync/manifest.json
Normal file
10
src/kernel/klibs/sync/manifest.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1",
|
||||
"type": "lib",
|
||||
"id": "sync",
|
||||
"enableIf": {
|
||||
"freestanding": [
|
||||
true
|
||||
]
|
||||
}
|
||||
}
|
30
src/kernel/klibs/sync/spinlock.c
Normal file
30
src/kernel/klibs/sync/spinlock.c
Normal file
|
@ -0,0 +1,30 @@
|
|||
#include <hal.h>
|
||||
|
||||
#include "spinlock.h"
|
||||
|
||||
static _Atomic int retain_count = 0;
|
||||
|
||||
static void retain_interrupts(void)
|
||||
{
|
||||
retain_count++;
|
||||
}
|
||||
|
||||
static void release_interrupt(void)
|
||||
{
|
||||
retain_count--;
|
||||
}
|
||||
|
||||
void spinlock_acquire(Spinlock spinlock[static 1])
|
||||
{
|
||||
retain_interrupts();
|
||||
while (atomic_flag_test_and_set(&spinlock->lock))
|
||||
{
|
||||
hal_pause();
|
||||
}
|
||||
}
|
||||
|
||||
void spinlock_release(Spinlock spinlock[static 1])
|
||||
{
|
||||
atomic_flag_clear(&spinlock->lock);
|
||||
release_interrupt();
|
||||
}
|
17
src/kernel/klibs/sync/spinlock.h
Normal file
17
src/kernel/klibs/sync/spinlock.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
volatile atomic_flag lock;
|
||||
} Spinlock;
|
||||
|
||||
#define SPINLOCK_INIT \
|
||||
(Spinlock) { .lock = ATOMIC_FLAG_INIT }
|
||||
|
||||
Spinlock spinlock_new(void);
|
||||
|
||||
void spinlock_acquire(Spinlock spinlock[static 1]);
|
||||
|
||||
void spinlock_release(Spinlock spinlock[static 1]);
|
191
src/libs/fmt/fmt.c
Normal file
191
src/libs/fmt/fmt.c
Normal file
|
@ -0,0 +1,191 @@
|
|||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fmt.h"
|
||||
|
||||
static char *strrev(char *str)
|
||||
{
|
||||
int start;
|
||||
int end;
|
||||
char tmp;
|
||||
|
||||
end = strlen(str) - 1;
|
||||
start = 0;
|
||||
|
||||
while (start < end)
|
||||
{
|
||||
tmp = str[start];
|
||||
str[start] = str[end];
|
||||
str[end] = tmp;
|
||||
start++;
|
||||
end--;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *itoa(int64_t value, char *str, int base)
|
||||
{
|
||||
int i = 0;
|
||||
bool isNegative = false;
|
||||
|
||||
if (value == 0)
|
||||
{
|
||||
str[i++] = '0';
|
||||
str[i] = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
if (value < 0 && base == 10)
|
||||
{
|
||||
isNegative = true;
|
||||
value = -value;
|
||||
}
|
||||
|
||||
while (value != 0)
|
||||
{
|
||||
int rem = value % base;
|
||||
str[i++] = (rem > 9) ? (rem - 10) + 'a' : rem + '0';
|
||||
value = value / base;
|
||||
}
|
||||
|
||||
if (isNegative)
|
||||
{
|
||||
str[i++] = '-';
|
||||
}
|
||||
|
||||
str[i] = '\0';
|
||||
|
||||
return strrev(str);
|
||||
}
|
||||
|
||||
char *utoa(uint64_t value, char *str, int base)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (value == 0)
|
||||
{
|
||||
str[i++] = '0';
|
||||
str[i] = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
while (value != 0)
|
||||
{
|
||||
int rem = value % base;
|
||||
str[i++] = (rem > 9) ? (rem - 10) + 'a' : rem + '0';
|
||||
value = value / base;
|
||||
}
|
||||
|
||||
str[i] = '\0';
|
||||
|
||||
return strrev(str);
|
||||
}
|
||||
|
||||
Res fmt(Stream stream, char const fmt[static 1], ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
Res res = vfmt(stream, fmt, args);
|
||||
va_end(args);
|
||||
return res;
|
||||
}
|
||||
|
||||
Res vfmt(Stream stream, char const fmt[static 1], va_list args)
|
||||
{
|
||||
const char *s = fmt;
|
||||
|
||||
while (*s)
|
||||
{
|
||||
if (*s == '%')
|
||||
{
|
||||
switch (*++s)
|
||||
{
|
||||
case 'd':
|
||||
{
|
||||
s++;
|
||||
char buf[100];
|
||||
int64_t value = va_arg(args, int64_t);
|
||||
|
||||
itoa(value, buf, 10);
|
||||
stream.write(strlen(buf), buf);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'u':
|
||||
{
|
||||
s++;
|
||||
char buf[100];
|
||||
uint64_t value = va_arg(args, int64_t);
|
||||
|
||||
utoa(value, buf, 10);
|
||||
stream.write(strlen(buf), buf);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
{
|
||||
s++;
|
||||
char buf[100];
|
||||
uint64_t value = va_arg(args, uint64_t);
|
||||
|
||||
utoa(value, buf, 16);
|
||||
stream.write(2, "0x");
|
||||
|
||||
for (size_t i = 0; i < 16 - strlen(buf); i++)
|
||||
{
|
||||
stream.write(1, "0");
|
||||
}
|
||||
|
||||
stream.write(strlen(buf), buf);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'x':
|
||||
{
|
||||
s++;
|
||||
char buf[100];
|
||||
uint64_t value = va_arg(args, uint64_t);
|
||||
|
||||
utoa(value, buf, 16);
|
||||
stream.write(strlen(buf), buf);
|
||||
break;
|
||||
}
|
||||
|
||||
case 's':
|
||||
{
|
||||
s++;
|
||||
char *value = va_arg(args, char *);
|
||||
stream.write(strlen(value), value);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'c':
|
||||
{
|
||||
s++;
|
||||
char value = va_arg(args, int);
|
||||
stream.write(1, &value);
|
||||
break;
|
||||
}
|
||||
|
||||
case '%':
|
||||
{
|
||||
s++;
|
||||
stream.write(1, "%");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return err$(RES_INVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char sub[2] = {*s++, '\0'};
|
||||
stream.write(1, sub);
|
||||
}
|
||||
}
|
||||
|
||||
return uok$(0);
|
||||
}
|
8
src/libs/fmt/fmt.h
Normal file
8
src/libs/fmt/fmt.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <io/stream.h>
|
||||
#include <res.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
Res fmt(Stream stream, char const fmt[static 1], ...);
|
||||
Res vfmt(Stream stream, char const fmt[static 1], va_list args);
|
5
src/libs/fmt/manifest.json
Normal file
5
src/libs/fmt/manifest.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1",
|
||||
"type": "lib",
|
||||
"id": "fmt"
|
||||
}
|
5
src/libs/io/manifest.json
Normal file
5
src/libs/io/manifest.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1",
|
||||
"type": "lib",
|
||||
"id": "io"
|
||||
}
|
9
src/libs/io/stream.h
Normal file
9
src/libs/io/stream.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "res.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Res (*write)(size_t n, char const buf[static n]);
|
||||
Res (*read)(size_t n, char buf[static n]);
|
||||
} Stream;
|
18
src/libs/loc.h
Normal file
18
src/libs/loc.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char const *file;
|
||||
const char *full;
|
||||
const char *func;
|
||||
size_t line;
|
||||
} Loc;
|
||||
|
||||
#define loc$() ((Loc){ \
|
||||
.file = __FILE_NAME__, \
|
||||
.full = __FILE__, \
|
||||
.func = __func__, \
|
||||
.line = __LINE__, \
|
||||
})
|
56
src/libs/res.h
Normal file
56
src/libs/res.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "loc.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define RES_TYPE(F) \
|
||||
F(RES_OK) \
|
||||
F(RES_INVAL) \
|
||||
F(RES_NOMEM) \
|
||||
F(RES_BADALIGN) \
|
||||
F(RES_NOENT)
|
||||
|
||||
enum res_type
|
||||
{
|
||||
RES_TYPE(make_enum$)
|
||||
};
|
||||
|
||||
static char const *res_type_str[] = {RES_TYPE(make_str$)};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
enum res_type type;
|
||||
Loc loc;
|
||||
union
|
||||
{
|
||||
size_t uvalue;
|
||||
ptrdiff_t ivalue;
|
||||
};
|
||||
} Res;
|
||||
|
||||
#define ok$() \
|
||||
(Res) { .type = RES_OK, .uvalue = 0, .loc = loc$() }
|
||||
|
||||
#define uok$(u) \
|
||||
(Res) { .type = RES_OK, .uvalue = (u), .loc = loc$() }
|
||||
|
||||
#define iok$(i) \
|
||||
(Res) { .type = RES_OK, .ivalue = (i), .loc = loc$() }
|
||||
|
||||
#define err$(t) \
|
||||
(Res) { .type = (t), .uvalue = 0, .loc = loc$() }
|
||||
|
||||
#define try$(EXPR) \
|
||||
({ \
|
||||
Res __result = (Res)(EXPR); \
|
||||
if (__result.type != RES_OK) \
|
||||
return __result; \
|
||||
__result.ivalue; \
|
||||
})
|
||||
|
||||
static inline char const *res_to_str(Res res)
|
||||
{
|
||||
return res_type_str[res.type];
|
||||
}
|
9
src/libs/utils.h
Normal file
9
src/libs/utils.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#define max$(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
#define min$(a, b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
#define make_enum$(enum) enum,
|
||||
|
||||
#define make_str$(str) #str,
|
Loading…
Reference in a new issue