feat: c-md transpiler v0.0.1

This commit is contained in:
lohhiiccc 2026-01-11 23:31:39 +01:00
parent f4e51df5e8
commit 8c0ed3462f
28 changed files with 791 additions and 10 deletions

View file

@ -33,7 +33,7 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
test: $(TEST_BIN) test: $(TEST_BIN)
./$(TEST_BIN) ./$(TEST_BIN)
$(TEST_BIN): $(TESTS) $(filter %.o,$(OBJS)) $(TEST_BIN): $(TESTS) $(filter-out $(OBJ_DIR)/main.o,$(OBJS))
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $^ -o $@

View file

21
includes/cli.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef CLI_H
# define CLI_H
# include <stdint.h>
typedef struct s_args
{
const char *ext;
const char *input;
const char *output;
const char *map_path;
uint8_t show_help;
} t_args;
int8_t
cli_parse(t_args *args, int32_t argc, char **argv);
void
cli_print_help(const char *progname);
#endif

View file

@ -0,0 +1,19 @@
#ifndef MAP_INTERNAL_H
# define MAP_INTERNAL_H
# include <stdio.h>
# include <stdint.h>
# include "map.h"
# define MAP_INIT_CAP 16
int8_t
map_grow(t_map *map);
int8_t
write_header(FILE *f, const char *source, const char *target);
int8_t
write_ranges(FILE *f, t_map *map);
#endif

View file

@ -0,0 +1,37 @@
#ifndef TRANSPILE_INTERNAL_H
# define TRANSPILE_INTERNAL_H
# include <stdio.h>
# include <stdint.h>
# include "map.h"
typedef struct s_state
{
FILE *in;
FILE *out;
const char *ext;
t_map *map;
uint32_t src_line;
uint32_t dst_line;
uint32_t block_src_start;
uint32_t block_dst_start;
uint8_t in_block;
uint8_t first_block;
} t_state;
void
state_init(t_state *s, FILE *in, FILE *out, const char *ext, t_map *map);
int8_t
process_line(t_state *s, char *line);
int8_t
handle_fence_open(t_state *s, char *line);
int8_t
handle_fence_close(t_state *s);
int8_t
handle_code_line(t_state *s, char *line);
#endif

19
includes/io.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef IO_H
# define IO_H
# include <stdio.h>
# include <stdint.h>
typedef struct s_io
{
FILE *in;
FILE *out;
} t_io;
int8_t
io_open(t_io *io, const char *input_path, const char *output_path);
void
io_close(t_io *io);
#endif

35
includes/map.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef MAP_H
# define MAP_H
# include <stddef.h>
# include <stdint.h>
typedef struct s_range
{
uint32_t src_start;
uint32_t src_end;
uint32_t dst_start;
uint32_t dst_end;
} t_range;
typedef struct s_map
{
t_range *ranges;
size_t count;
size_t capacity;
} t_map;
void
map_init(t_map *map);
void
map_add(t_map *map, uint32_t src_start, uint32_t src_end,
uint32_t dst_start, uint32_t dst_end);
int8_t
map_write(t_map *map, const char *path, const char *source, const char *target);
void
map_free(t_map *map);
#endif

11
includes/transpile.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef TRANSPILE_H
# define TRANSPILE_H
# include <stdint.h>
# include <stdio.h>
# include "map.h"
int8_t
transpile(FILE *in, FILE *out, const char *ext, t_map *map);
#endif

22
includes/utils.h Normal file
View file

@ -0,0 +1,22 @@
#ifndef UTILS_H
# define UTILS_H
# include <stdint.h>
# include <stdio.h>
char *
read_line(FILE *f);
int8_t
starts_with(const char *str, const char *prefix);
char *
extract_fence_ext(const char *fence);
const char *
extract_file_ext(const char *path);
const char *
infer_ext_from_filename(const char *path);
#endif

10
includes/validator.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef VALIDATOR_H
# define VALIDATOR_H
# include <stdint.h>
# include "cli.h"
int8_t
validator_validate_args(t_args *args);
#endif

View file

@ -1,6 +1,25 @@
SRC_DIR = srcs SRC_DIR = srcs
SRCS = $(SRC_DIR)/main.c SRCS = $(SRC_DIR)/main.c \
$(SRC_DIR)/io/streams.c \
$(SRC_DIR)/utils/io/read_line.c \
$(SRC_DIR)/utils/string/starts_with.c \
$(SRC_DIR)/utils/string/extract_fence_ext.c \
$(SRC_DIR)/utils/string/extract_file_ext.c \
$(SRC_DIR)/utils/string/infer_ext_from_filename.c \
$(SRC_DIR)/transpile/state.c \
$(SRC_DIR)/transpile/fence.c \
$(SRC_DIR)/transpile/code.c \
$(SRC_DIR)/transpile/core.c \
$(SRC_DIR)/map/core.c \
$(SRC_DIR)/map/io.c \
$(SRC_DIR)/cli/cli.c \
$(SRC_DIR)/cli/help.c \
$(SRC_DIR)/validator/validator.c
TESTS_DIR = tests TESTS_DIR = tests
TESTS= $(TESTS_DIR)/test_main.c TESTS = $(TESTS_DIR)/test_utils.c \
$(TESTS_DIR)/test_map.c \
$(TESTS_DIR)/test_transpile.c \
$(TESTS_DIR)/test_cli.c \
$(TESTS_DIR)/test_integration.c

111
srcs/cli/cli.c Normal file
View file

@ -0,0 +1,111 @@
#include <stddef.h>
#include <getopt.h>
#include <string.h>
#include "cli.h"
typedef void (*t_handler)(t_args *, const char *);
typedef struct s_opt
{
int8_t short_opt;
t_handler handler;
} t_opt;
static void handle_help(t_args *args, const char *value);
static void handle_ext(t_args *args, const char *value);
static void handle_input(t_args *args, const char *value);
static void handle_output(t_args *args, const char *value);
static void handle_map(t_args *args, const char *value);
static t_handler find_handler(int8_t opt);
static void init_args(t_args *args);
static const t_opt g_opts[] = {
{'h', handle_help},
{'e', handle_ext},
{'i', handle_input},
{'o', handle_output},
{'m', handle_map},
{0, NULL}
};
static struct option g_long_opts[] = {
{"help", no_argument, NULL, 'h'},
{"ext", required_argument, NULL, 'e'},
{"input", required_argument, NULL, 'i'},
{"map", required_argument, NULL, 'm'},
{NULL, 0, NULL, 0}
};
int8_t
cli_parse(t_args *args, int32_t argc, char **argv)
{
int32_t opt;
t_handler handler;
init_args(args);
while (-1 != (opt = getopt_long(argc, argv, "he:i:o:m:", g_long_opts, NULL)))
{
handler = find_handler((int8_t)opt);
if (NULL == handler)
return (-1);
handler(args, optarg);
}
return (0);
}
static void
init_args(t_args *args)
{
args->ext = NULL;
args->input = NULL;
args->output = NULL;
args->map_path = NULL;
args->show_help = 0;
}
static t_handler
find_handler(int8_t opt)
{
size_t i;
i = 0;
while (0 != g_opts[i].short_opt)
{
if (opt == g_opts[i].short_opt)
return (g_opts[i].handler);
i++;
}
return (NULL);
}
static void
handle_help(t_args *args, const char *value)
{
(void)value;
args->show_help = 1;
}
static void
handle_ext(t_args *args, const char *value)
{
args->ext = value;
}
static void
handle_input(t_args *args, const char *value)
{
args->input = value;
}
static void
handle_output(t_args *args, const char *value)
{
args->output = value;
}
static void
handle_map(t_args *args, const char *value)
{
args->map_path = value;
}

21
srcs/cli/help.c Normal file
View file

@ -0,0 +1,21 @@
#include <stdio.h>
#include "cli.h"
void
cli_print_help(const char *progname)
{
fprintf(stderr, "Usage: %s [OPTIONS]\n\n", progname);
fprintf(stderr, "Extract code blocks from literate markdown files.\n\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " -h, --help Show this help\n");
fprintf(stderr, " -e, --ext EXT Extension to extract (c, h, py, etc.)\n");
fprintf(stderr, " Required for stdin, optional for files\n");
fprintf(stderr, " -i, --input FILE Input file (default: stdin)\n");
fprintf(stderr, " -o, --output FILE Output file (default: stdout)\n");
fprintf(stderr, " -m, --map FILE Generate line mapping file\n\n");
fprintf(stderr, "Examples:\n");
fprintf(stderr, " %s -e c < input.md > output.c\n", progname);
fprintf(stderr, " %s -i main.c.md -o main.c\n", progname);
fprintf(stderr, " %s -e h -i file.md -o file.h -m file.map\n", progname);
}

32
srcs/io/streams.c Normal file
View file

@ -0,0 +1,32 @@
#include <stdio.h>
#include "io.h"
int8_t
io_open(t_io *io, const char *input_path, const char *output_path)
{
io->in = (NULL != input_path) ? fopen(input_path, "r") : stdin;
if (NULL == io->in)
{
perror(input_path);
return (1);
}
io->out = (NULL != output_path) ? fopen(output_path, "w") : stdout;
if (NULL == io->out)
{
perror(output_path);
if (io->in != stdin)
fclose(io->in);
return (1);
}
return (0);
}
void
io_close(t_io *io)
{
if (NULL != io->in && io->in != stdin)
fclose(io->in);
if (NULL != io->out && io->out != stdout)
fclose(io->out);
}

View file

@ -1,6 +1,67 @@
#include <stdio.h>
#include "cli.h"
#include "validator.h"
#include "transpile.h"
#include "map.h"
#include "io.h"
static int8_t run(t_args *args);
static void print_error(int8_t code, const char *progname);
int int
main() main(int argc, char **argv)
{ {
return 0; t_args args;
int8_t validation_code;
if (0 != cli_parse(&args, (int32_t)argc, argv))
{
cli_print_help(argv[0]);
return (1);
}
if (args.show_help)
{
cli_print_help(argv[0]);
return (0);
}
validation_code = validator_validate_args(&args);
if (0 != validation_code)
{
print_error(validation_code, argv[0]);
return (1);
}
return (run(&args));
}
static void
print_error(int8_t code, const char *progname)
{
if (1 == code)
fprintf(stderr, "Error: -e required when reading from stdin\n");
else if (2 == code)
fprintf(stderr, "Error: -e required (cannot infer from filename)\n");
cli_print_help(progname);
}
static int8_t
run(t_args *args)
{
t_io io;
t_map map;
int8_t ret;
if (0 != io_open(&io, args->input, args->output))
return (1);
map_init(&map);
ret = transpile(io.in, io.out, args->ext, &map);
if (0 == ret && NULL != args->map_path)
{
ret = map_write(&map, args->map_path,
(NULL != args->input) ? args->input : "stdin",
(NULL != args->output) ? args->output : "stdout");
}
map_free(&map);
io_close(&io);
return (ret);
} }

55
srcs/map/core.c Normal file
View file

@ -0,0 +1,55 @@
#include <stdlib.h>
#include "map.h"
#include "internal/map_internal.h"
void
map_init(t_map *map)
{
map->ranges = NULL;
map->count = 0;
map->capacity = 0;
}
void
map_add(t_map *map, uint32_t src_start, uint32_t src_end,
uint32_t dst_start, uint32_t dst_end)
{
t_range *range;
if (map->count >= map->capacity)
{
if (0 != map_grow(map))
return ;
}
range = &map->ranges[map->count];
range->src_start = src_start;
range->src_end = src_end;
range->dst_start = dst_start;
range->dst_end = dst_end;
map->count++;
}
void
map_free(t_map *map)
{
free(map->ranges);
map->ranges = NULL;
map->count = 0;
map->capacity = 0;
}
int8_t
map_grow(t_map *map)
{
size_t new_cap;
t_range *new_ranges;
new_cap = (0 == map->capacity) ? MAP_INIT_CAP : map->capacity * 2;
new_ranges = realloc(map->ranges, new_cap * sizeof(t_range));
if (NULL == new_ranges)
return (1);
map->ranges = new_ranges;
map->capacity = new_cap;
return (0);
}

55
srcs/map/io.c Normal file
View file

@ -0,0 +1,55 @@
#include <stdio.h>
#include "map.h"
#include "internal/map_internal.h"
int8_t
write_header(FILE *f, const char *source, const char *target)
{
if (0 > fprintf(f, "C-MD MAP v1\n"))
return (1);
if (0 > fprintf(f, "source: %s\n", source))
return (1);
if (0 > fprintf(f, "target: %s\n", target))
return (1);
if (0 > fprintf(f, "---\n"))
return (1);
return (0);
}
int8_t
write_ranges(FILE *f, t_map *map)
{
size_t i;
t_range *r;
i = 0;
while (i < map->count)
{
r = &map->ranges[i];
if (0 > fprintf(f, "%u-%u:%u-%u\n",
r->src_start, r->src_end, r->dst_start, r->dst_end))
return (1);
i++;
}
return (0);
}
int8_t
map_write(t_map *map, const char *path, const char *source, const char *target)
{
FILE *f;
int8_t ret;
f = fopen(path, "w");
if (NULL == f)
{
perror(path);
return (1);
}
ret = write_header(f, source, target);
if (0 == ret)
ret = write_ranges(f, map);
fclose(f);
return (ret);
}

12
srcs/transpile/code.c Normal file
View file

@ -0,0 +1,12 @@
#include <stdio.h>
#include "internal/transpile_internal.h"
int8_t
handle_code_line(t_state *s, char *line)
{
if (0 > fputs(line, s->out))
return (1);
s->dst_line++;
return (0);
}

38
srcs/transpile/core.c Normal file
View file

@ -0,0 +1,38 @@
#include <stdlib.h>
#include "transpile.h"
#include "internal/transpile_internal.h"
#include "utils.h"
int8_t
process_line(t_state *s, char *line)
{
s->src_line++;
if (0 == s->in_block && starts_with(line, "```"))
return (handle_fence_open(s, line));
if (1 == s->in_block && starts_with(line, "```"))
return (handle_fence_close(s));
if (1 == s->in_block)
return (handle_code_line(s, line));
return (0);
}
int8_t
transpile(FILE *in, FILE *out, const char *ext, t_map *map)
{
t_state s;
char *line;
int8_t ret;
state_init(&s, in, out, ext, map);
ret = 0;
line = read_line(in);
while (NULL != line && 0 == ret)
{
ret = process_line(&s, line);
free(line);
line = read_line(in);
}
free(line);
return (ret);
}

50
srcs/transpile/fence.c Normal file
View file

@ -0,0 +1,50 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "internal/transpile_internal.h"
#include "utils.h"
int8_t
handle_fence_open(t_state *s, char *line)
{
char *block_ext;
block_ext = extract_fence_ext(line);
if (NULL == block_ext)
return (0);
if (0 != strcmp(block_ext, s->ext))
{
free(block_ext);
return (0);
}
free(block_ext);
if (0 == s->first_block)
{
if (0 > fprintf(s->out, "\n"))
return (1);
s->dst_line++;
}
s->first_block = 0;
s->in_block = 1;
s->block_src_start = s->src_line + 1;
s->block_dst_start = s->dst_line + 1;
return (0);
}
int8_t
handle_fence_close(t_state *s)
{
uint32_t src_end;
uint32_t dst_end;
s->in_block = 0;
src_end = s->src_line - 1;
dst_end = s->dst_line;
if (s->block_src_start <= src_end)
{
map_add(s->map, s->block_src_start, src_end,
s->block_dst_start, dst_end);
}
return (0);
}

16
srcs/transpile/state.c Normal file
View file

@ -0,0 +1,16 @@
#include "internal/transpile_internal.h"
void
state_init(t_state *s, FILE *in, FILE *out, const char *ext, t_map *map)
{
s->in = in;
s->out = out;
s->ext = ext;
s->map = map;
s->src_line = 0;
s->dst_line = 0;
s->block_src_start = 0;
s->block_dst_start = 0;
s->in_block = 0;
s->first_block = 1;
}

24
srcs/utils/io/read_line.c Normal file
View file

@ -0,0 +1,24 @@
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <sys/types.h>
#include "utils.h"
char *
read_line(FILE *f)
{
char *line;
size_t len;
ssize_t read;
line = NULL;
len = 0;
read = getline(&line, &len, f);
if (-1 == read)
{
free(line);
return (NULL);
}
return (line);
}

View file

@ -0,0 +1,32 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "utils.h"
char *
extract_fence_ext(const char *fence)
{
const char *start;
const char *end;
size_t len;
char *ext;
if (0 != strncmp(fence, "```", 3))
return (NULL);
start = fence + 3;
while (' ' == *start || '\t' == *start)
start++;
if ('\n' == *start || '\0' == *start)
return (NULL);
end = start;
while ('\n' != *end && '\0' != *end && ' ' != *end && '\t' != *end)
end++;
len = (size_t)(end - start);
ext = malloc(len + 1);
if (NULL == ext)
return (NULL);
memcpy(ext, start, len);
ext[len] = '\0';
return (ext);
}

View file

@ -0,0 +1,14 @@
#include <string.h>
#include "utils.h"
const char *
extract_file_ext(const char *path)
{
const char *dot;
dot = strrchr(path, '.');
if (NULL == dot || dot == path)
return (NULL);
return (dot + 1);
}

View file

@ -0,0 +1,37 @@
#include <string.h>
#include "utils.h"
const char *
infer_ext_from_filename(const char *path)
{
static char ext_buffer[32];
const char *md_ext;
const char *before_md;
const char *dot;
size_t len;
size_t ext_len;
if (NULL == path)
return (NULL);
len = strlen(path);
if (len < 6)
return (NULL);
md_ext = path + len - 3;
if (0 != strcmp(md_ext, ".md"))
return (NULL);
before_md = md_ext - 1;
while (before_md > path && '.' != *before_md)
before_md--;
if (before_md == path || '.' != *before_md)
return (NULL);
dot = before_md;
if (dot + 1 >= md_ext)
return (NULL);
ext_len = md_ext - (dot + 1);
if (ext_len >= sizeof(ext_buffer))
return (NULL);
memcpy(ext_buffer, dot + 1, ext_len);
ext_buffer[ext_len] = '\0';
return (ext_buffer);
}

View file

@ -0,0 +1,13 @@
#include <string.h>
#include <stdint.h>
#include "utils.h"
int8_t
starts_with(const char *str, const char *prefix)
{
size_t prefix_len;
prefix_len = strlen(prefix);
return (0 == strncmp(str, prefix, prefix_len));
}

View file

@ -0,0 +1,22 @@
#include <stddef.h>
#include "validator.h"
#include "utils.h"
int8_t
validator_validate_args(t_args *args)
{
if (NULL == args->input)
{
if (NULL == args->ext)
return (1);
return (0);
}
if (NULL == args->ext)
{
args->ext = infer_ext_from_filename(args->input);
if (NULL == args->ext)
return (2);
}
return (0);
}

View file

@ -1,5 +0,0 @@
#include <criterion/criterion.h>
Test(dummy, always_pass) {
cr_assert(0, "Hello, world!");
}