Ver código fonte

Ready. Set. Go!

rexim 4 anos atrás
commit
16b293cc4c
8 arquivos alterados com 796 adições e 0 exclusões
  1. 1 0
      .gitignore
  2. 20 0
      LICENSE
  3. 4 0
      Makefile
  4. 20 0
      README.md
  5. 84 0
      bubble.tex
  6. 281 0
      src/flag.h
  7. 132 0
      src/main.c
  8. 254 0
      src/sv.h

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+lit

+ 20 - 0
LICENSE

@@ -0,0 +1,20 @@
+Copyright 2021 Alexey Kutepov <[email protected]>
+
+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.

+ 4 - 0
Makefile

@@ -0,0 +1,4 @@
+CFLAGS=-Wall -Wextra -std=c11 -pedantic -ggdb
+
+lit: src/main.c
+	$(CC) $(CFLAGS) -o lit src/main.c

+ 20 - 0
README.md

@@ -0,0 +1,20 @@
+# Simple Literate Programming System
+
+Inspired by the [Literate Haskell](https://wiki.haskell.org/Literate_programming).
+
+## Quick Start
+
+The idea is to generate the executable program and the documentation from the same file:
+
+```console
+$ make
+$ ./lit -input bubble.tex > bubble.c
+$ cc -c bubble.c
+$ pdflatex bubble.tex
+```
+
+## How It Works?
+
+The program just iterates through the entire file line by line and comments out everything that is not inside of the "code block" so the final result can be passed to a compiler or an interpreter.
+
+The "code block" markers and the comment style are customizable. See `./lit -help` for more information.

+ 84 - 0
bubble.tex

@@ -0,0 +1,84 @@
+\documentclass{article}
+
+\usepackage{verbatim}
+\newenvironment{code}{\footnotesize\verbatim}{\endverbatim\normalsize}
+
+\begin{document}
+
+\section{Bubble Sort in C}
+
+Bubble Sort is the probably the first sorting algorithm everyone is introduced to. Let's first include the necessary header files:
+
+\begin{code}
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+\end{code}
+
+The main operation of Bubble Sort is swapping two values:
+
+\begin{code}
+void swap(int *x, int *y)
+{
+    int t = *x;
+    *x = *y;
+    *y = t;
+}   
+\end{code}
+
+The easiest way to remember how to implement it is when you assign something to the variable \verb|t| like \verb|int t = *x;| you are saving that thing so on the next line you can override it \verb|*x = *y;|. And after that finish the process by assigning the second variable \verb|*y = t;|.
+
+
+Alright, now let's implement the bubble sort itself:
+
+\begin{code}
+void bubble_sort(int *xs, int xs_size)
+{
+   for (int i = xs_size - 1; i > 1; --i) {
+       for (int j = 0; j < i; ++j) {
+           if (xs[j] > xs[j + 1]) {
+               swap(&xs[j], &xs[j + 1]);
+           }
+       }
+   }
+}
+\end{code}
+
+Let's check if this bubble sort algorithm works correctly. Let's implement a function that can generate \verb|n| random numbers for testing.
+
+\begin{code}
+#define MAX_X_SIZE 100
+
+void generate_n_numbers(int *xs, int n)
+{
+    for (int i = 0; i < n; ++i) {
+        xs[i] = rand() % MAX_X_SIZE;
+    }
+}
+\end{code}
+
+We also need to be able to check that the array is sorted. We can do that by iterating the array with a ``window'' of size 2 and checking if the pairs are ascending
+
+\pagebreak
+
+\begin{code}
+// 1 2 3 5 4 6
+// ^ ^ ascending
+// 1 2 3 5 4 6
+//   ^ ^ ascending
+// 1 2 3 5 4 6
+//     ^ ^ ascending
+// 1 2 3 5 4 6
+//       ^ ^ DESCENDNIG!!!
+bool is_sorted(int *xs, int n)
+{
+    for (int i = 0; i < n - 1; ++i) {
+        if (xs[i] > xs[i + 1]) {
+            return false;
+        }
+    }
+    return true;
+}
+\end{code}
+
+\end{document}

+ 281 - 0
src/flag.h

@@ -0,0 +1,281 @@
+// flag.h -- command-line flag parsing
+//
+//   Inspired by Go's flag module: https://pkg.go.dev/flag
+//
+#ifndef FLAG_H_
+#define FLAG_H_
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+
+// TODO: add support for -flag=x syntax
+// TODO: stop parsing after the first non-flag argument or -- terminator
+// Add ability to get the rest unparsed arguments
+// TODO: *_var function variants
+// void flag_bool_var(bool *var, const char *name, bool def, const char *desc);
+// void flag_bool_uint64(uint64_t *var, const char *name, bool def, const char *desc);
+// etc.
+// WARNING! *_var functions may break the flag_name() functionality
+
+char *flag_name(void *val);
+bool *flag_bool(const char *name, bool def, const char *desc);
+uint64_t *flag_uint64(const char *name, uint64_t def, const char *desc);
+char **flag_str(const char *name, const char *def, const char *desc);
+bool flag_parse(int argc, char **argv);
+void flag_print_error(FILE *stream);
+void flag_print_options(FILE *stream);
+
+#endif // FLAG_H_
+
+//////////////////////////////
+
+#ifdef FLAG_IMPLEMENTATION
+
+typedef enum {
+    FLAG_BOOL,
+    FLAG_UINT64,
+    FLAG_STR,
+} Flag_Type;
+
+typedef union {
+    char *as_str;
+    uint64_t as_uint64;
+    bool as_bool;
+} Flag_Value;
+
+typedef enum {
+    FLAG_NO_ERROR = 0,
+    FLAG_ERROR_UNKNOWN,
+    FLAG_ERROR_NO_VALUE,
+    FLAG_ERROR_INVALID_NUMBER,
+    FLAG_ERROR_INTEGER_OVERFLOW,
+} Flag_Error;
+
+typedef struct {
+    Flag_Type type;
+    char *name;
+    char *desc;
+    Flag_Value val;
+    Flag_Value def;
+} Flag;
+
+#ifndef FLAGS_CAP
+#define FLAGS_CAP 256
+#endif
+
+static Flag flags[FLAGS_CAP];
+static size_t flags_count = 0;
+
+static Flag_Error flag_error = FLAG_NO_ERROR;
+static char *flag_error_name = NULL;
+
+Flag *flag_new(Flag_Type type, const char *name, const char *desc)
+{
+    assert(flags_count < FLAGS_CAP);
+    Flag *flag = &flags[flags_count++];
+    memset(flag, 0, sizeof(*flag));
+    flag->type = type;
+    // NOTE: I won't touch them I promise Kappa
+    flag->name = (char*) name;
+    flag->desc = (char*) desc;
+    return flag;
+}
+
+char *flag_name(void *val)
+{
+    Flag *flag = (Flag*) ((char*) val - offsetof(Flag, val));
+    return flag->name;
+}
+
+bool *flag_bool(const char *name, bool def, const char *desc)
+{
+    Flag *flag = flag_new(FLAG_BOOL, name, desc);
+    flag->def.as_bool = def;
+    flag->val.as_bool = def;
+    return &flag->val.as_bool;
+}
+
+uint64_t *flag_uint64(const char *name, uint64_t def, const char *desc)
+{
+    Flag *flag = flag_new(FLAG_UINT64, name, desc);
+    flag->val.as_uint64 = def;
+    flag->def.as_uint64 = def;
+    return &flag->val.as_uint64;
+}
+
+char **flag_str(const char *name, const char *def, const char *desc)
+{
+    Flag *flag = flag_new(FLAG_STR, name, desc);
+    flag->val.as_str = (char*) def;
+    flag->def.as_str = (char*) def;
+    return &flag->val.as_str;
+}
+
+static char *flag_shift_args(int *argc, char ***argv)
+{
+    assert(*argc > 0);
+    char *result = **argv;
+    *argv += 1;
+    *argc -= 1;
+    return result;
+}
+
+bool flag_parse(int argc, char **argv)
+{
+    flag_shift_args(&argc, &argv);
+
+    while (argc > 0) {
+        char *flag = flag_shift_args(&argc, &argv);
+
+        if (*flag != '-') {
+            flag_error = FLAG_ERROR_UNKNOWN;
+            flag_error_name = flag;
+            return false;
+        }
+
+        flag += 1;
+
+        bool found = false;
+        for (size_t i = 0; i < flags_count; ++i) {
+            if (strcmp(flags[i].name, flag) == 0) {
+                switch (flags[i].type) {
+                case FLAG_BOOL: {
+                    flags[i].val.as_bool = true;
+                }
+                break;
+
+                case FLAG_STR: {
+                    if (argc == 0) {
+                        flag_error = FLAG_ERROR_NO_VALUE;
+                        flag_error_name = flag;
+                        return false;
+                    }
+                    char *arg = flag_shift_args(&argc, &argv);
+                    flags[i].val.as_str = arg;
+                }
+                break;
+
+                case FLAG_UINT64: {
+                    if (argc == 0) {
+                        flag_error = FLAG_ERROR_NO_VALUE;
+                        flag_error_name = flag;
+                        return false;
+                    }
+                    char *arg = flag_shift_args(&argc, &argv);
+
+                    static_assert(sizeof(unsigned long long int) == sizeof(uint64_t), "The original author designed this for x86_64 machine with the compiler that expects unsigned long long int and uint64_t to be the same thing, so they could use strtoull() function to parse it. Please adjust this code for your case and maybe even send the patch to upstream to make it work on a wider range of environments.");
+                    char *endptr;
+                    // TODO: replace strtoull with a custom solution
+                    // That way we can get rid of the dependency on errno and static_assert
+                    unsigned long long int result = strtoull(arg, &endptr, 10);
+
+                    if (arg == endptr || *endptr != '\0') {
+                        flag_error = FLAG_ERROR_INVALID_NUMBER;
+                        flag_error_name = flag;
+                        return false;
+                    }
+                    if (result == ULLONG_MAX && errno == ERANGE) {
+                        flag_error = FLAG_ERROR_INTEGER_OVERFLOW;
+                        flag_error_name = flag;
+                        return false;
+                    }
+
+                    flags[i].val.as_uint64 = result;
+                }
+                break;
+
+                default: {
+                    assert(0 && "unreachable");
+                    exit(69);
+                }
+                }
+
+                found = true;
+            }
+        }
+
+        if (!found) {
+            flag_error = FLAG_ERROR_UNKNOWN;
+            flag_error_name = flag;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+void flag_print_options(FILE *stream)
+{
+    for (size_t i = 0; i < flags_count; ++i) {
+        fprintf(stream, "    -%s\n", flags[i].name);
+        fprintf(stream, "        %s.\n", flags[i].desc);
+        switch (flags[i].type) {
+        case FLAG_BOOL:
+            fprintf(stream, "        Default: %s\n", flags[i].val.as_bool ? "true" : "false");
+            break;
+        case FLAG_UINT64:
+            fprintf(stream, "        Default: %" PRIu64 "\n", flags[i].val.as_uint64);
+            break;
+        case FLAG_STR:
+            fprintf(stream, "        Default: %s\n", flags[i].val.as_str);
+            break;
+        default:
+            assert(0 && "unreachable");
+            exit(69);
+        }
+    }
+}
+
+void flag_print_error(FILE *stream)
+{
+    switch (flag_error) {
+    case FLAG_NO_ERROR:
+        // NOTE: don't call flag_print_error() if flag_parse() didn't return false, okay? ._.
+        fprintf(stream, "Operation Failed Successfully! Please tell the developer of this software that they don't know what they are doing! :)");
+        break;
+    case FLAG_ERROR_UNKNOWN:
+        fprintf(stream, "ERROR: -%s: unknown flag\n", flag_error_name);
+        break;
+    case FLAG_ERROR_NO_VALUE:
+        fprintf(stream, "ERROR: -%s: no value provided\n", flag_error_name);
+        break;
+    case FLAG_ERROR_INVALID_NUMBER:
+        fprintf(stream, "ERROR: -%s: invalid number\n", flag_error_name);
+        break;
+    case FLAG_ERROR_INTEGER_OVERFLOW:
+        fprintf(stream, "ERROR: -%s: integer overflow\n", flag_error_name);
+        break;
+    default:
+        assert(0 && "unreachable");
+        exit(69);
+    }
+}
+
+#endif
+// Copyright 2021 Alexey Kutepov <[email protected]>
+//
+// 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.

+ 132 - 0
src/main.c

@@ -0,0 +1,132 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#define SV_IMPLEMENTATION
+#include "./sv.h"
+
+#define FLAG_IMPLEMENTATION
+#include "./flag.h"
+
+typedef struct {
+    void *content_data;
+    size_t content_size;
+
+    int fd;
+    bool fd_open;
+} Mapped_File;
+
+void unmap_file(Mapped_File *mf)
+{
+    if (mf->content_data != NULL) {
+        munmap(mf->content_data, mf->content_size);
+    }
+
+    if (mf->fd_open) {
+        close(mf->fd);
+    }
+
+    memset(mf, 0, sizeof(*mf));
+}
+
+bool map_file(Mapped_File *mf, const char *file_path)
+{
+    unmap_file(mf);
+
+    mf->fd = open(file_path, O_RDONLY);
+    if (mf->fd < 0) {
+        goto error;
+    }
+    mf->fd_open = true;
+
+    struct stat statbuf = {0};
+    if (fstat(mf->fd, &statbuf) < 0) {
+        goto error;
+    }
+
+    mf->content_size = statbuf.st_size;
+    mf->content_data = mmap(NULL, mf->content_size, PROT_READ, MAP_PRIVATE, mf->fd, 0);
+    if (mf->content_data == NULL) {
+        goto error;
+    }
+
+    return true;
+error:
+    unmap_file(mf);
+    return false;
+}
+
+void usage(FILE *stream)
+{
+    fprintf(stream, "Usage: lit [OPTIONS]\n");
+    flag_print_options(stream);
+}
+
+int main(int argc, char **argv)
+{
+    bool *help = flag_bool("help", false, "Print this help to stdout and exit with 0");
+    char **input = flag_str("input", NULL, "Path to the input file");
+    char **begin = flag_str("begin", "\\begin{code}", "Line that denotes the beginning of the code block in the markup language");
+    char **end = flag_str("end", "\\end{code}", "Line that denotes the end of the code block in the markup language");
+    char **comment = flag_str("comment", "//", "The inline comment of the programming language");
+
+    if (!flag_parse(argc, argv)) {
+        usage(stderr);
+        flag_print_error(stderr);
+        exit(1);
+    }
+
+    if (*help) {
+        usage(stdout);
+        exit(0);
+    }
+
+    if (*input == NULL) {
+        usage(stderr);
+        fprintf(stderr, "ERROR: No input file is provided\n");
+        exit(1);
+    }
+
+    const char *input_file_path = *input;
+
+    Mapped_File mf = {0};
+
+    if (!map_file(&mf, input_file_path)) {
+        fprintf(stderr, "ERROR: could not read file %s: %s\n",
+                input_file_path, strerror(errno));
+        exit(1);
+    }
+
+    bool code_mode = false;
+
+    String_View content = sv_from_parts(mf.content_data, mf.content_size);
+    while (content.count > 0) {
+        String_View line = sv_chop_by_delim(&content, '\n');
+
+        if (code_mode) {
+            if (sv_eq(sv_trim(line), sv_from_cstr(*end))) {
+                printf("%s " SV_Fmt"\n", *comment, SV_Arg(line));
+                code_mode = false;
+            } else {
+                printf(SV_Fmt"\n", SV_Arg(line));
+            }
+        } else {
+            printf("%s " SV_Fmt"\n", *comment, SV_Arg(line));
+            if (sv_eq(sv_trim(line), sv_from_cstr(*begin))) {
+                code_mode = true;
+            }
+        }
+    }
+
+    unmap_file(&mf);
+
+    return 0;
+}

+ 254 - 0
src/sv.h

@@ -0,0 +1,254 @@
+// Copyright 2021 Alexey Kutepov <[email protected]>
+
+// 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.
+
+#ifndef SV_H_
+#define SV_H_
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+
+typedef struct {
+    size_t count;
+    const char *data;
+} String_View;
+
+#define SV(cstr_lit) sv_from_parts(cstr_lit, sizeof(cstr_lit) - 1)
+#define SV_STATIC(cstr_lit)   \
+    {                         \
+        sizeof(cstr_lit) - 1, \
+        (cstr_lit)            \
+    }
+
+#define SV_NULL sv_from_parts(NULL, 0)
+
+// printf macros for String_View
+#define SV_Fmt "%.*s"
+#define SV_Arg(sv) (int) (sv).count, (sv).data
+// USAGE:
+//   String_View name = ...;
+//   printf("Name: "SV_Fmt"\n", SV_Arg(name));
+
+String_View sv_from_parts(const char *data, size_t count);
+String_View sv_from_cstr(const char *cstr);
+String_View sv_trim_left(String_View sv);
+String_View sv_trim_right(String_View sv);
+String_View sv_trim(String_View sv);
+String_View sv_take_left_while(String_View sv, bool (*predicate)(char x));
+String_View sv_chop_by_delim(String_View *sv, char delim);
+bool sv_try_chop_by_delim(String_View *sv, char delim, String_View *chunk);
+String_View sv_chop_left(String_View *sv, size_t n);
+String_View sv_chop_right(String_View *sv, size_t n);
+String_View sv_chop_left_while(String_View *sv, bool (*predicate)(char x));
+bool sv_index_of(String_View sv, char c, size_t *index);
+bool sv_eq(String_View a, String_View b);
+bool sv_starts_with(String_View sv, String_View prefix);
+bool sv_ends_with(String_View sv, String_View suffix);
+uint64_t sv_to_u64(String_View sv);
+
+#endif  // SV_H_
+
+#ifdef SV_IMPLEMENTATION
+
+String_View sv_from_parts(const char *data, size_t count)
+{
+    String_View sv;
+    sv.count = count;
+    sv.data = data;
+    return sv;
+}
+
+String_View sv_from_cstr(const char *cstr)
+{
+    return sv_from_parts(cstr, strlen(cstr));
+}
+
+String_View sv_trim_left(String_View sv)
+{
+    size_t i = 0;
+    while (i < sv.count && isspace(sv.data[i])) {
+        i += 1;
+    }
+
+    return sv_from_parts(sv.data + i, sv.count - i);
+}
+
+String_View sv_trim_right(String_View sv)
+{
+    size_t i = 0;
+    while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) {
+        i += 1;
+    }
+
+    return sv_from_parts(sv.data, sv.count - i);
+}
+
+String_View sv_trim(String_View sv)
+{
+    return sv_trim_right(sv_trim_left(sv));
+}
+
+String_View sv_chop_left(String_View *sv, size_t n)
+{
+    if (n > sv->count) {
+        n = sv->count;
+    }
+
+    String_View result = sv_from_parts(sv->data, n);
+
+    sv->data  += n;
+    sv->count -= n;
+
+    return result;
+}
+
+String_View sv_chop_right(String_View *sv, size_t n)
+{
+    if (n > sv->count) {
+        n = sv->count;
+    }
+
+    String_View result = sv_from_parts(sv->data + sv->count - n, n);
+
+    sv->count -= n;
+
+    return result;
+}
+
+bool sv_index_of(String_View sv, char c, size_t *index)
+{
+    size_t i = 0;
+    while (i < sv.count && sv.data[i] != c) {
+        i += 1;
+    }
+
+    if (i < sv.count) {
+        if (index) {
+            *index = i;
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool sv_try_chop_by_delim(String_View *sv, char delim, String_View *chunk)
+{
+    size_t i = 0;
+    while (i < sv->count && sv->data[i] != delim) {
+        i += 1;
+    }
+
+    String_View result = sv_from_parts(sv->data, i);
+
+    if (i < sv->count) {
+        sv->count -= i + 1;
+        sv->data  += i + 1;
+        if (chunk) {
+            *chunk = result;
+        }
+        return true;
+    }
+
+    return false;
+}
+
+String_View sv_chop_by_delim(String_View *sv, char delim)
+{
+    size_t i = 0;
+    while (i < sv->count && sv->data[i] != delim) {
+        i += 1;
+    }
+
+    String_View result = sv_from_parts(sv->data, i);
+
+    if (i < sv->count) {
+        sv->count -= i + 1;
+        sv->data  += i + 1;
+    } else {
+        sv->count -= i;
+        sv->data  += i;
+    }
+
+    return result;
+}
+
+bool sv_starts_with(String_View sv, String_View expected_prefix)
+{
+    if (expected_prefix.count <= sv.count) {
+        String_View actual_prefix = sv_from_parts(sv.data, expected_prefix.count);
+        return sv_eq(expected_prefix, actual_prefix);
+    }
+
+    return false;
+}
+
+bool sv_ends_with(String_View sv, String_View expected_suffix)
+{
+    if (expected_suffix.count <= sv.count) {
+        String_View actual_suffix = sv_from_parts(sv.data + sv.count - expected_suffix.count, expected_suffix.count);
+        return sv_eq(expected_suffix, actual_suffix);
+    }
+
+    return false;
+}
+
+bool sv_eq(String_View a, String_View b)
+{
+    if (a.count != b.count) {
+        return false;
+    } else {
+        return memcmp(a.data, b.data, a.count) == 0;
+    }
+}
+
+uint64_t sv_to_u64(String_View sv)
+{
+    uint64_t result = 0;
+
+    for (size_t i = 0; i < sv.count && isdigit(sv.data[i]); ++i) {
+        result = result * 10 + (uint64_t) sv.data[i] - '0';
+    }
+
+    return result;
+}
+
+String_View sv_chop_left_while(String_View *sv, bool (*predicate)(char x))
+{
+    size_t i = 0;
+    while (i < sv->count && predicate(sv->data[i])) {
+        i += 1;
+    }
+    return sv_chop_left(sv, i);
+}
+
+String_View sv_take_left_while(String_View sv, bool (*predicate)(char x))
+{
+    size_t i = 0;
+    while (i < sv.count && predicate(sv.data[i])) {
+        i += 1;
+    }
+    return sv_from_parts(sv.data, i);
+}
+
+#endif // SV_IMPLEMENTATION