From fe8f76dd44885dc948641af33a6505024032b088 Mon Sep 17 00:00:00 2001 From: tocariimaa Date: Thu, 26 Dec 2024 14:02:40 -0300 Subject: [PATCH] initial commit --- allocators/buddyalloc.c | 269 ++++++++++++++++ audiovisual/karplus_strong.c | 86 +++++ audiovisual/sound_synthesis.c | 122 +++++++ misc/abacaba.lua | 14 + misc/collatz.lua | 15 + misc/gregorian.c | 25 ++ misc/huffman.cpp | 87 +++++ misc/luhn-lua/luhn.lua | 40 +++ misc/luhn-lua/main.lua | 23 ++ misc/mmap-file.c | 55 ++++ misc/open_tray_linux.c | 38 +++ misc/proc_vm.c | 47 +++ misc/procread.c | 203 ++++++++++++ parsers/bencode.nim | 116 +++++++ proglangs/nsforth/Makefile | 19 ++ proglangs/nsforth/defs.h | 66 ++++ proglangs/nsforth/dict.c | 170 ++++++++++ proglangs/nsforth/dict.h | 17 + proglangs/nsforth/natives.c | 0 proglangs/nsforth/nsforth.c | 580 ++++++++++++++++++++++++++++++++++ proglangs/nsforth/test.fs | 38 +++ proglangs/nsforth/util.c | 20 ++ proglangs/rpn-go/go.mod | 8 + proglangs/rpn-go/go.sum | 4 + proglangs/rpn-go/prog1 | 3 + proglangs/rpn-go/rpn.go | 130 ++++++++ proglangs/rpn-go/stack.go | 31 ++ proglangs/rpn-go/words.go | 98 ++++++ protocols/ntp.c | 153 +++++++++ 29 files changed, 2477 insertions(+) create mode 100644 allocators/buddyalloc.c create mode 100644 audiovisual/karplus_strong.c create mode 100644 audiovisual/sound_synthesis.c create mode 100644 misc/abacaba.lua create mode 100644 misc/collatz.lua create mode 100644 misc/gregorian.c create mode 100644 misc/huffman.cpp create mode 100644 misc/luhn-lua/luhn.lua create mode 100644 misc/luhn-lua/main.lua create mode 100644 misc/mmap-file.c create mode 100644 misc/open_tray_linux.c create mode 100644 misc/proc_vm.c create mode 100644 misc/procread.c create mode 100644 parsers/bencode.nim create mode 100644 proglangs/nsforth/Makefile create mode 100644 proglangs/nsforth/defs.h create mode 100644 proglangs/nsforth/dict.c create mode 100644 proglangs/nsforth/dict.h create mode 100644 proglangs/nsforth/natives.c create mode 100644 proglangs/nsforth/nsforth.c create mode 100644 proglangs/nsforth/test.fs create mode 100644 proglangs/nsforth/util.c create mode 100644 proglangs/rpn-go/go.mod create mode 100644 proglangs/rpn-go/go.sum create mode 100644 proglangs/rpn-go/prog1 create mode 100644 proglangs/rpn-go/rpn.go create mode 100644 proglangs/rpn-go/stack.go create mode 100644 proglangs/rpn-go/words.go create mode 100644 protocols/ntp.c diff --git a/allocators/buddyalloc.c b/allocators/buddyalloc.c new file mode 100644 index 0000000..985d26e --- /dev/null +++ b/allocators/buddyalloc.c @@ -0,0 +1,269 @@ +#define _BSD_SOURCE +#include +#include +#include +#include +#include + +#include + +#define u16set(hi, lo) ((hi << 8) | (lo & 0xff)) +#define align_up(n, align) (((n) + (align - 1)) & (-align)) +#define header_set_magic(block, mag, poi) ((block)->magic = u16set(poi, mag)) +#define Assert(pred) if (!(pred)) {__builtin_trap();} + +#define BUDDY_BLOCK_MAGIC 0xb7 +#define BUDDY_BLOCK_FREED 0xaa +#define BUDDY_MIN_ALIGNMENT sizeof(BuddyBlockHeader) + +#define false ((bool)0) +#define true ((bool)1) +#define nil ((void *)0) + +typedef uint8_t u8; +typedef _Bool bool; +typedef ptrdiff_t isize; + +/* Header of a buddy block. This gets baked into memory. */ +typedef struct { + isize size; + bool used; + uint16_t magic; +} BuddyBlockHeader; +/* Being 16 bytes the minimum alignment, at least on a 64-bit machine... */ +_Static_assert(sizeof(BuddyBlockHeader) == 16, "BuddyBlockHeader size is not 16 bytes"); + +typedef struct { + BuddyBlockHeader *head, *tail; + isize backing_len; +} BuddyAllocator; + +static void +abort_msg(const char *s) +{ + fputs(s, stderr); + fflush(stderr); + abort(); +} + +static BuddyBlockHeader * +next_buddy_block(BuddyBlockHeader *cur) +{ + /* Calculate the address for the next buddy block header start. + * This has to be padded to a boundary of at least + * `sizeof(BuddyBlockHeader)` bytes. */ + return (BuddyBlockHeader *)align_up( + (uintptr_t)((u8 *)cur + cur->size), BUDDY_MIN_ALIGNMENT); +} + +/* Split a block until it has a size equal or greater than `size`. + * This function also "builds" the buddy headers. */ +static BuddyBlockHeader * +split_block(BuddyBlockHeader *block, isize size) +{ + BuddyBlockHeader *cur = block; + while (cur->size > size) { + /* XXX: Splitting by half seems quite wasteful... (see `buddy_alloc_aligned`) */ + const isize half = cur->size / 2; + cur->size = half; + cur = next_buddy_block(cur); + cur->size = half; + cur->used = false; + cur->magic = u16set(0, BUDDY_BLOCK_MAGIC); + } + Assert(cur->size >= size); + return cur; +} + +static BuddyBlockHeader * +find_block(BuddyAllocator *ba, isize size) +{ + BuddyBlockHeader *cur = ba->head; + BuddyBlockHeader *next = next_buddy_block(cur); + /* Case for an initial state, where there aren't any split buddies */ + if (!cur->used && next == ba->tail) + return split_block(cur, size); + + /* Traverse the block list for a suitable block */ + BuddyBlockHeader *block = nil; + while (cur < ba->tail) { + Assert((cur->magic & 0xff) == BUDDY_BLOCK_MAGIC); + Assert(cur->size <= ba->backing_len); + /* Coalesce two adjacent buddies if possible */ + if (next < ba->tail + && !next->used + && next->size == cur->size + && next->size + cur->size >= size) { + Assert((next->magic & 0xff) == BUDDY_BLOCK_MAGIC); + fprintf(stderr, "candidate for coalescing (%p U %p)\n", (void *)cur, (void *)next); + cur->size += next->size; + /* wipe header of the buddy just in case */ + memset(next, 0, sizeof(*next)); + } + if (!cur->used && cur->size >= size) { + fprintf(stderr, "candidate at %p (size: %td)\n", (void *)cur, cur->size); + /* Either if no candidate has been picked yet or the current candidate + * is smaller (but `size` can still fit) than the current buddy. */ + if (block == nil || block->size > cur->size) + block = cur; + } + cur = next_buddy_block(cur); + next = cur < ba->tail ? next_buddy_block(cur) : nil; + } + if (block != nil) + return split_block(block, size); + return nil; +} + +static void +coalesce_buddies(BuddyAllocator *ba) +{ + BuddyBlockHeader *cur = ba->head; + BuddyBlockHeader *next = next_buddy_block(cur); + + while (cur < ba->tail) { + Assert((cur->magic & 0xff) == BUDDY_BLOCK_MAGIC); + Assert(cur->size <= ba->backing_len); + if (next < ba->tail + && !next->used + && next->size == cur->size) { + Assert((next->magic & 0xff) == BUDDY_BLOCK_MAGIC); + cur->size += next->size; + memset(next, 0, sizeof(*next)); + } + cur = next_buddy_block(cur); + next = cur < ba->tail ? next_buddy_block(cur) : nil; + } +} + +static void +buddy_alloc_init(BuddyAllocator *balloc, u8 *buf, isize buflen) +{ + balloc->head = (BuddyBlockHeader *)buf; + balloc->tail = (BuddyBlockHeader *)(buf + buflen); + balloc->backing_len = buflen; + *balloc->head = (BuddyBlockHeader) { + .size = buflen, + .used = false, + .magic = u16set(0, BUDDY_BLOCK_MAGIC), + }; +} + +void * +buddy_alloc_aligned(BuddyAllocator *ba, isize size, isize alignment) +{ + if (size == 0) + return nil; + + Assert(size > 0); + + if (alignment < BUDDY_MIN_ALIGNMENT) + alignment = BUDDY_MIN_ALIGNMENT; + isize aligned_size = align_up(size + sizeof(BuddyBlockHeader), alignment); + isize ss = alignment; /* squared up size (because we split the buddies in half) */ + while (ss <= aligned_size) + ss *= 2; + Assert(ss <= ba->backing_len); + + BuddyBlockHeader *block; + if ((block = find_block(ba, ss)) != nil) + goto happy; + /* First attempt failed, maybe due to fragmentation. Do a run of coalescing to + * solve fragmentation if any and try allocating again. */ + coalesce_buddies(ba); + if ((block = find_block(ba, ss)) != nil) + goto happy; + /* Too fragmented or ran out of memory. */ + return nil; +happy: + block->used = true; + block->magic &= 0xff; /* clear free'd flag */ + return (u8 *)block + sizeof(BuddyBlockHeader); +} + +void * +buddy_alloc(BuddyAllocator *ba, isize size) +{ + return buddy_alloc_aligned(ba, size, BUDDY_MIN_ALIGNMENT); +} + +void +buddy_free(BuddyAllocator *ba, void *ptr) +{ + if (ptr == nil) + return; + + BuddyBlockHeader *header = (BuddyBlockHeader *)((u8 *)ptr - sizeof(*header)); + Assert((header->magic & 0xff) == BUDDY_BLOCK_MAGIC); + /* double free detection */ + Assert((header->magic >> 8) != BUDDY_BLOCK_FREED); + header->used = false; + header->magic = u16set(BUDDY_BLOCK_FREED, BUDDY_BLOCK_MAGIC); +} + +static void +iter_blocks(BuddyBlockHeader *head, BuddyBlockHeader *tail) +{ + int cc = fprintf(stderr, "------ list start (head: %p; tail: %p; size: %td) ------\n", + (void *)head, (void *)tail, tail - head); + + BuddyBlockHeader *cur = head; + while (cur < tail) { + Assert((cur->magic & 0xff) == BUDDY_BLOCK_MAGIC); + fprintf(stderr, "block@%p(size: %8td; used: %3s; magic: %04x)\n", + (void *)cur, cur->size, cur->used? "yes":"no", cur->magic); + cur = next_buddy_block(cur); + } + + while (cc--) + fputc('-', stderr); + fputc('\n', stderr); +} + +#include + +static void +dirt_block(u8 *buf, isize size) +{ + u8 *bufend = buf + size; + while (buf < bufend) + *buf++ = rand() & 0xff; +} + +int +main(void) +{ + const isize backing_len = align_up(1 << 20, 16); + u8 *backing = mmap( + nil, backing_len, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0 + ); + if (backing == MAP_FAILED) { + perror("mmap"); + return 1; + } + *(volatile u8 *)backing = 0xaa; /* page fault it */ + + BuddyAllocator ba = {0}; + buddy_alloc_init(&ba, backing, backing_len); + + srand(time(nil)); + + iter_blocks(ba.head, ba.tail); + u8 *buf1 = buddy_alloc(&ba, 255); + dirt_block(buf1, 255); + u8 *buf2 = buddy_alloc(&ba, 1024); + dirt_block(buf2, 1024); + buddy_free(&ba, buf1); + iter_blocks(ba.head, ba.tail); + u8 *buf3 = buddy_alloc(&ba, 512); + dirt_block(buf3, 512); + iter_blocks(ba.head, ba.tail); + buddy_free(&ba, buf2); + buddy_free(&ba, buf3); + munmap(backing, backing_len); + + return 0; +} diff --git a/audiovisual/karplus_strong.c b/audiovisual/karplus_strong.c new file mode 100644 index 0000000..5be7889 --- /dev/null +++ b/audiovisual/karplus_strong.c @@ -0,0 +1,86 @@ +// Karplus-Strong string synthesis algorithm implementation, spits out the file to stdout as a WAV file +// if you use mpv, you can redirect it to mpv directly: `./karplus_strong | mpv -` +#include +#include +#include +#include // arc4random (link with -lbsd) + +struct wav_header // not the most portable way really +{ + char chunk_id[4]; + uint32_t chunk_size; + char format[4]; + char subchunk_id[4]; + uint32_t subchunk1_size; + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; + char subchunk2_id[4]; + uint32_t subchunk2_size; +}; + +void wav_populate_header(struct wav_header* wavh, size_t total_samples, size_t channels, size_t bits_per_sample, size_t sample_rate) +{ + memcpy(wavh->chunk_id, "RIFF", 4); + memcpy(wavh->format, "WAVE", 4); + memcpy(wavh->subchunk_id, "fmt ", 4); + + wavh->chunk_size = (sizeof(struct wav_header) + total_samples) - 8; + wavh->subchunk1_size = 16; + wavh->audio_format = 1; + wavh->num_channels = 1; + wavh->sample_rate = sample_rate; + wavh->byte_rate = (sample_rate * channels * bits_per_sample) / 8; + wavh->block_align = (channels * bits_per_sample) / 8; + wavh->bits_per_sample = bits_per_sample; + + memcpy(wavh->subchunk2_id, "data", 4); + wavh->subchunk2_size = (total_samples * channels * bits_per_sample) / 8; +} + +void wav_create_file(struct wav_header* wavh, FILE* wav_fp, int16_t* data, size_t total_samples) +{ + fwrite(wavh, sizeof(struct wav_header), 1, wav_fp); // write WAV header to file + fwrite(data, sizeof(int16_t), total_samples, wav_fp); // ...and finally the audio data +} + +int main(int argc, char** argv) +{ + if (argc < 2) { + fprintf(stderr, "frequency argument expected\n"); + return 1; + } + + const size_t SAMPLE_RATE = 44100; + const size_t freq = strtoull(argv[1], NULL, 0); // you should check for errors + const size_t seconds = 3; + const size_t total_samples = seconds * SAMPLE_RATE; + const size_t period = SAMPLE_RATE / freq; + + int16_t* noise_table = malloc(period * sizeof(int16_t)); // wavetable + int16_t* buffer = malloc(total_samples * sizeof(int16_t)); // PCM buffer + // fill wavetable with noise, it doesn't have to be this function, rand() over all elements would suffice + arc4random_buf(noise_table, period); + + // algorithm + size_t prev_val = 0; + for (size_t n = 0; n < total_samples; ++n) { + size_t cs = n % period; + noise_table[cs] = (noise_table[cs] + prev_val) * 0.5; + buffer[n] = noise_table[cs]; + prev_val = noise_table[cs]; + } + + // finally write the WAV file + struct wav_header wavh = {}; + wav_populate_header(&wavh, total_samples, 1, 16, SAMPLE_RATE); + wav_create_file(&wavh, stdout, buffer, total_samples); + + free(noise_table); + free(buffer); + + return 0; +} diff --git a/audiovisual/sound_synthesis.c b/audiovisual/sound_synthesis.c new file mode 100644 index 0000000..fd03684 --- /dev/null +++ b/audiovisual/sound_synthesis.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + +const double PI = 3.141592653589793; +const double SECONDS = 0.5; +const size_t CHANNELS = 1; +const size_t SAMPLE_RATE = 44100; +const size_t BITS_PER_SAMPLE = 16; +const size_t TOTAL_SAMPLES = (SECONDS * CHANNELS * SAMPLE_RATE); + +// http://soundfile.sapp.org/doc/WaveFormat/ (https://archive.is/069fj) +struct wav_header +{ + char chunk_id[4]; + uint32_t chunk_size; + char format[4]; + + char subchunk_id[4]; + uint32_t subchunk1_size; + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; + + char subchunk2_id[4]; + uint32_t subchunk2_size; +}; + +static void *xcalloc(const size_t num, const size_t size) +{ + void *ptr = calloc(num, size); + if (ptr == NULL) { + perror("calloc"); + exit(1); + } + return ptr; +} + +void wav_populate_header(struct wav_header *const wavh, const size_t total_samples, const size_t channels) +{ + memcpy(wavh->chunk_id, "RIFF", 4); + memcpy(wavh->format, "WAVE", 4); + memcpy(wavh->subchunk_id, "fmt ", 4); + + wavh->chunk_size = (sizeof(struct wav_header) + total_samples) - 8; + wavh->subchunk1_size = 16; + wavh->audio_format = 1; + wavh->num_channels = 1; + wavh->sample_rate = SAMPLE_RATE; + wavh->byte_rate = (SAMPLE_RATE * channels * BITS_PER_SAMPLE) / 8; + wavh->block_align = (channels * BITS_PER_SAMPLE) / 8; + wavh->bits_per_sample = BITS_PER_SAMPLE; + + memcpy(wavh->subchunk2_id, "data", 4); + wavh->subchunk2_size = (total_samples * channels * BITS_PER_SAMPLE) / 8; +} + +void wav_create_file(const struct wav_header *wavh, FILE *wav_fp, const int16_t *data, const size_t total_samples) +{ + fwrite(wavh, sizeof(struct wav_header), 1, wav_fp); // write WAV header to file + fwrite(data, sizeof(int16_t), total_samples, wav_fp); // ...and finally the audio data +} + +int16_t sinewave(const size_t time, const size_t sample_rate, const double freq) +{ + const double rad = (2 * PI) * (time / (sample_rate / freq)); // get radian frequency + const double t_amplitude = (32767 * 0.9); + return t_amplitude * sin(rad); +} + +int16_t squarewave(const size_t time, const size_t sample_rate, const double freq) +{ + const double t_amplitude = (32767 * 0.9); + const size_t period = (sample_rate / freq); + if ((time % period) < (period / 2)) + return t_amplitude; + return 0; + //const double rad = (2 * PI) * (time / (sample_rate / freq)); // get radian frequency + //return ((sin(rad) >= 0) ? t_amplitude : 0); // -1 * t_amplitude for peak to peak amp. + //32767 * (2 * fabs(2 * (((time / period) - floor((time / period) + (1 / 2)))) ) * -1); +} + +int16_t trianglewave(const size_t time, const size_t sample_rate, const double freq) +{ + // a triangle wave can be expressed in terms of sine and arcsine + const double t_amplitude = (32767 * 0.9); + const double period = sin((2 * PI) * (time / (sample_rate / freq))); + return (2 * t_amplitude / PI) * asin(period); +} + +int main(int argc, char **argv) +{ + FILE *fp = stdout; + if (argc > 1) { + if ((fp = fopen(argv[1], "wb")) == NULL) { + perror("fopen"); + return 1; + } + } + + int16_t buffer[TOTAL_SAMPLES]; + const double freq = 220; + + for (int i = 0; i < TOTAL_SAMPLES; ++i) { + buffer[i] = sinewave(i, SAMPLE_RATE, freq); + //buffer[i] = squarewave(i, SAMPLE_RATE, freq); + //buffer[i] = trianglewave(i, SAMPLE_RATE, freq); + } + + struct wav_header *wavh = xcalloc(1, sizeof(struct wav_header)); + wav_populate_header(wavh, TOTAL_SAMPLES, CHANNELS); + wav_create_file(wavh, fp, buffer, TOTAL_SAMPLES); + + free(wavh); + fclose(fp); + return 0; +} diff --git a/misc/abacaba.lua b/misc/abacaba.lua new file mode 100644 index 0000000..b44743b --- /dev/null +++ b/misc/abacaba.lua @@ -0,0 +1,14 @@ +if #arg < 1 then + print("iteration argument required") + return +end + +alpha = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "q", "p", "q", "r", "s", "t", "u", "v", "x", "y", "z", } +prev_pttr = "" +curr_pttr = "" +iterations = arg[1] % #alpha +for i = 1, iterations do + curr_pttr = prev_pttr .. alpha[i] .. prev_pttr + prev_pttr = curr_pttr + print(curr_pttr) +end diff --git a/misc/collatz.lua b/misc/collatz.lua new file mode 100644 index 0000000..1f232c0 --- /dev/null +++ b/misc/collatz.lua @@ -0,0 +1,15 @@ +function collatz(n) + io.write(n) + while n > 1 do + if (n % 2) == 0 then + n = n // 2 + else + n = 3 * n + 1 + end + io.write(' ' .. n) + end + io.write('\n') +end + +assert(#arg ~= 0, 'expected initial number') +collatz(tonumber(arg[1])) diff --git a/misc/gregorian.c b/misc/gregorian.c new file mode 100644 index 0000000..93d6291 --- /dev/null +++ b/misc/gregorian.c @@ -0,0 +1,25 @@ +#include +#include + +char *dow_n[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + +// The day of week returned by this algorithm starts from zero, and assumes that Sunday is the first day of the week. +int sakamoto_dow(unsigned int year, unsigned int month, unsigned int day) +{ + static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; + year -= (month < 3); + return (year + year / 4 - year / 100 + year / 400 + t[month - 1] + day) % 7; +} + +int main(int argc, char **argv) +{ + if (argc < 4) return 1; + int c_year = strtol(argv[1], &argv[1], 10); + int c_month = strtol(argv[2], &argv[2], 10); + int c_day = strtol(argv[3], &argv[3], 10); + + int day = sakamoto_dow(c_year, c_month, c_day); + printf("Day of week for (%i/%i/%i): %i (%s)\n", c_year, c_month, c_day, day, dow_n[day]); + + return 0; +} diff --git a/misc/huffman.cpp b/misc/huffman.cpp new file mode 100644 index 0000000..ef4b5ed --- /dev/null +++ b/misc/huffman.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include + +#define HT_TREE_MARK '@' + +struct ht { + char chr; + size_t weight; + + struct ht *left{}; + struct ht *right{}; + + ht(char _chr, size_t _w, struct ht *l = nullptr, struct ht *r = nullptr) : + chr(_chr), weight(_w), left(l), right(r) {} +}; + +void ht_build_codes(struct ht *tree, std::unordered_map &ht_codes, std::string path="") +{ + if (tree == nullptr) + return; + if (tree->chr != HT_TREE_MARK) { + std::clog << tree->chr << ": " << path << std::endl; + ht_codes[tree->chr] = path; + } + ht_build_codes(tree->left, ht_codes, path + '0'); + ht_build_codes(tree->right, ht_codes, path + '1'); +} + +bool ht_comp_fn(const struct ht *n1, const struct ht *n2) +{ + // enforce stable sorting + if (n1->weight == n2->weight) + return false; + return n1->weight > n2->weight; +}; + +int main() +{ + std::string s = "abracadabraacacacabbaadadeea"; + + // count occurences (frequency, weight) of each character in the message + std::unordered_map chr_occu; + for (const auto& c : s) + ++chr_occu[c]; + + std::priority_queue, decltype(&ht_comp_fn)> nodes(ht_comp_fn); + // create initial nodes, from here we start building the tree + for (const auto& v : chr_occu) { + auto *node = new struct ht(v.first, v.second); + nodes.push(node); + } + + auto printpq = [](decltype(nodes) n) { + while (!n.empty()) { + auto *cn = n.top(); + std::clog << cn->chr << ":" << cn->weight << " "; + n.pop(); + } + std::clog << std::endl; + }; + + while (nodes.size() > 1) { // when the nodes queue element count is 1, we already have our tree + // remove the two first (the one with the lowest weights) + auto *n1 = nodes.top(); + nodes.pop(); + auto *n2 = nodes.top(); + nodes.pop(); + + // create a new tree with a weight equal to the sum of the two popped nodes + auto *node = new struct ht(HT_TREE_MARK, n1->weight + n2->weight, n1, n2); + nodes.push(node); + } + + std::unordered_map ht_codes; + std::string enc_msg; + enc_msg.reserve(255); + ht_build_codes(nodes.top(), ht_codes); + + for (const auto& c : s) + enc_msg += ht_codes[c]; + std::clog << s << " -> " << enc_msg << std::endl; + + return 0; +} diff --git a/misc/luhn-lua/luhn.lua b/misc/luhn-lua/luhn.lua new file mode 100644 index 0000000..416150e --- /dev/null +++ b/misc/luhn-lua/luhn.lua @@ -0,0 +1,40 @@ +-- implementation of Luhn's checksum algorithm, computing and checking of checksum digit +local luhn = {} + +-- main part of the algorithm, double each 2 digits, clamp them and return the sum. +function luhn:_sum_digits(num) + assert(num >= 0, "number must be positive") + local sum = 0 + local digits_n = 0 + + repeat -- iterate over digits from right to left + local tmp = num % 10 -- get current unit + -- each 2 digits, double the current digit + if (digits_n % 2) == 0 then tmp = tmp * 2 end + if tmp > 9 then tmp = tmp - 9 end + + digits_n = digits_n + 1 + sum = sum + tmp + -- remove unit from number + num = math.floor(num / 10) + until num == 0 + return sum +end + +-- returns the computed check digit +function luhn:compute_check_digit(num) + return (self:_sum_digits(num) * 9) % 10 +end + +-- returns true if its valid, false otherwise +function luhn:validate_check_digit(num) + local check_digit = num % 10 -- get check digit + return self:compute_check_digit(math.floor(num / 10)) == check_digit +end + +-- returns the original number with the check digit concatenated at the end +function luhn:make_number(num) + return ((num * 10) + self:compute_check_digit(num)) +end + +return luhn diff --git a/misc/luhn-lua/main.lua b/misc/luhn-lua/main.lua new file mode 100644 index 0000000..4ec4c1f --- /dev/null +++ b/misc/luhn-lua/main.lua @@ -0,0 +1,23 @@ +#!/usr/bin/lua +luhn = require "luhn" + +if #arg ~= 0 then + local a = tonumber(arg[1]) -- 7992739871 + print("check digit: " .. luhn:compute_check_digit(a)) +end + +local function bool_str(bool) + return bool and "yes" or "no" +end + +print("49927398716: " .. bool_str(luhn:validate_check_digit(49927398716))) +print("49927398717: " .. bool_str(luhn:validate_check_digit(49927398717))) +print("1234567812345678: " .. bool_str(luhn:validate_check_digit(1234567812345678))) +print("1234567812345670: " .. bool_str(luhn:validate_check_digit(1234567812345670))) + +print("\ncheck digits:") +print("4992739871: " .. luhn:compute_check_digit(4992739871)) +print("4992739871: " .. luhn:compute_check_digit(4992739871)) +print("123456781234567: " .. luhn:compute_check_digit(123456781234567)) +print("123456781234567: " .. luhn:compute_check_digit(123456781234567)) +print(luhn:make_number(4992739871)) diff --git a/misc/mmap-file.c b/misc/mmap-file.c new file mode 100644 index 0000000..3831772 --- /dev/null +++ b/misc/mmap-file.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + if (argc < 2) + errx(EXIT_FAILURE, "expected file name"); + + const char *filename = argv[1]; + int fd = open(filename, O_RDONLY); + if (fd < 0) + err(EXIT_FAILURE, "open"); + + struct stat fst = {0}; + if (fstat(fd, &fst) < 0) + err(EXIT_FAILURE, "fstat"); + + const size_t filesize = fst.st_size; + uint8_t *filem = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0); + if (filem == MAP_FAILED) + err(EXIT_FAILURE, "mmap failed"); + /* we can close the descriptor now */ + close(fd); + + unsigned int seed = 0; + memcpy(&seed, (void *)getauxval(AT_RANDOM), sizeof(seed)); + srand(seed); + + const size_t off = abs(rand()) % filesize; + uint8_t pb = 0, cb; + size_t cbo = 0; + for (size_t i = off; i < off + 0xff && i < filesize; ++i) { + cb = filem[i]; + if (cb == pb) + ++cbo; + if (cbo == 16) { + printf("* "); + cbo = 0; + } else + printf("%x ", cb); + pb = cb; + } + putchar('\n'); + + munmap(filem, filesize); + return 0; +} diff --git a/misc/open_tray_linux.c b/misc/open_tray_linux.c new file mode 100644 index 0000000..7c597d0 --- /dev/null +++ b/misc/open_tray_linux.c @@ -0,0 +1,38 @@ +#include +#include +#include + +#include +#include +#include + +int main(void) +{ + // Open cdrom device, it must be opened in non-blocking mode + int cdfd = open("/dev/cdrom", O_RDONLY | O_NONBLOCK); + if (cdfd < 0) { + perror("open"); + return 1; + } + + int cdrom_stat = ioctl(cdfd, CDROM_DRIVE_STATUS, 0); + + if ((cdrom_stat > -1 && cdrom_stat != CDS_NO_INFO) && + cdrom_stat == CDS_TRAY_OPEN) { + printf("tray is already open\n"); + return 0; + } + + // Issue eject command + if (ioctl(cdfd, CDROMEJECT, 0) < 0) { + if (errno == EBUSY) { + fputs("either another process is using the drive or the door is locked.\n", stderr); + return 1; + } + perror("ioctl"); + return 1; + } + + close(cdfd); + return 0; +} diff --git a/misc/proc_vm.c b/misc/proc_vm.c new file mode 100644 index 0000000..eb599da --- /dev/null +++ b/misc/proc_vm.c @@ -0,0 +1,47 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + if (argc < 3) { + fprintf(stderr, "%s [pid] [address]\n", argv[0]); + return 0; + } + pid_t proc_pid = strtol(argv[1], NULL, 0); + off_t vm_addr = strtol(argv[2], NULL, 0); + int proc_mem_fd = 0; + char proc_mem_path[255]; + + snprintf(proc_mem_path, sizeof(proc_mem_path) - 1, "/proc/%i/mem", proc_pid); + if ((proc_mem_fd = open(proc_mem_path, O_RDWR)) < 0) { // open process memory + perror("open"); + return 1; + } + if (ptrace(PTRACE_ATTACH, proc_pid, NULL, NULL) < 0) { // attach to process + perror("ptrace"); + return 1; + } + waitpid(proc_pid, NULL, 0); // wait for the tracee to stop (SIGSTOP) completly + + uint64_t mem_val = 0; + // a read unsigned 64 bit integer from the specified address + if (pread(proc_mem_fd, &mem_val, sizeof(uint64_t), vm_addr) < 0) { + perror("pread"); + return 1; + } + printf("value at 0x%lx:%lx\n", vm_addr, mem_val); + + if (ptrace(PTRACE_DETACH, proc_pid, NULL, NULL) < 0) { // detach from process + perror("ptrace"); + return 1; + } + close(proc_mem_fd); + return 0; +} diff --git a/misc/procread.c b/misc/procread.c new file mode 100644 index 0000000..dec8d74 --- /dev/null +++ b/misc/procread.c @@ -0,0 +1,203 @@ +// see proc(5) manpage for more information +#include +#include +#include +#include +#include +#include +#include +#include // pid_t +#include // MAJOR() and MINOR() tty macros + +#define __STR(n) #n +#define STR(n) __STR(n) +// Filename length of the process executable, truncated to 16 + nil terminator +// However, kernel processes have longer names than that. +#define _TASK_COMM_LEN 255 +#define PROC_FOLDER "/proc" +#define INIT_PID 1 +#define IS_KERNEL_PROC(p) (((p)->pid != INIT_PID && (p)->session_id == 0)) + +struct proc_t +{ + pid_t pid; + pid_t parent_pid; + gid_t group_id; + id_t session_id; + id_t tty; + char *exe_name; + char state; + unsigned int flags; +}; + +void *xcalloc(size_t num, size_t size) +{ + void *ptr = calloc(num, size); + if (ptr == NULL) { + perror("xcalloc"); + //fcloseall(); + abort(); + } + return ptr; +} + +char *build_proc_path(char *buffer, const char *proc_pid, const char *dir) +{ + char *path = buffer; //xcalloc(256, sizeof(char)); + strcpy(path, PROC_FOLDER"/"); + strncat(path, proc_pid, 255 - 5); // always leave space for /stat + char proc_dir[255] = {0}; + snprintf(proc_dir, sizeof(proc_dir), "/%s", dir); + strcat(path, proc_dir); + return path; +} + +FILE *process_open(char *path) +{ + FILE *proc_fp = fopen(path, "rb"); + if (proc_fp == NULL) { + perror("fopen"); + exit(EXIT_FAILURE); + } + return proc_fp; +} + +int parse_proc(FILE *proc_fp, struct proc_t *p) +{ +#define NMATCHES 7 // <- increment each time a format directive (that is bound to a reference) is added + p->exe_name = malloc(_TASK_COMM_LEN * sizeof(char)); + int ret = fscanf( + proc_fp, + "%d %*[(]%"STR(_TASK_COMM_LEN)"[^)]%*[)] %c %d %d %d %u", + &p->pid, p->exe_name, &p->state, &p->parent_pid, &p->session_id, &p->tty, + &p->flags + ); + if (ret != NMATCHES || (ret == EOF && ferror(proc_fp))) { + fprintf(stderr, "couldn't parse stat info\n"); + return 1; + } + return 0; +} + +int is_numeric_str(char *str) +{ + while (*str) + if (!isdigit(*str++)) + return 0; + return 1; +} + +void list_procs_init(struct proc_t **proc_list, int proc_list_cnt, int offset) +{ + for (int i = offset; i < proc_list_cnt; ++i) { + proc_list[i] = xcalloc(1, sizeof(struct proc_t)); + } +} + +struct proc_t **list_procs() +{ + int proc_list_it = 0; + int proc_list_cnt = 255; + struct proc_t **proc_list = xcalloc(proc_list_cnt + 1, sizeof(**proc_list)); + list_procs_init(proc_list, proc_list_cnt, 0); + + char path_buf[255] = {0}; + DIR *proc_dir = opendir(PROC_FOLDER); + if (proc_dir == NULL) { + perror("opendir"); + exit(EXIT_FAILURE); + } + struct dirent *dir; + while ((dir = readdir(proc_dir)) != NULL) { + if (dir->d_type == DT_DIR && is_numeric_str(dir->d_name)) { + if ((proc_list_it + 1) > proc_list_cnt) { + proc_list_cnt += proc_list_cnt; + proc_list = realloc(proc_list, (proc_list_cnt + 1) * sizeof(**proc_list)); + list_procs_init(proc_list, proc_list_cnt, proc_list_it); + } + FILE *pf = process_open(build_proc_path(path_buf, dir->d_name, "stat")); + parse_proc(pf, proc_list[proc_list_it++]); + fclose(pf); + } + } + closedir(proc_dir); + return proc_list; +} + +void print_proc_info(struct proc_t *proc) +{ + char *is_kernel_proc = IS_KERNEL_PROC(proc) ? "yes" : "no"; + if (IS_KERNEL_PROC(proc)) // not init neither a child of it + printf("process %s (PID: %d) is a kernel process\n", proc->exe_name, proc->pid); + printf("PID: %d (parent: %d, session id: %d) Name: %s Status: %c TTY: %d" + " flags: 0x%08x\n", + proc->pid, proc->parent_pid, proc->session_id, proc->exe_name, proc->state, MAJOR(proc->tty), proc->flags); +} + +void backtrace_to_init(struct proc_t *proc) +{ + if (proc->parent_pid == 0) + return; + // pid == group pid for parent processes + while (proc->pid != INIT_PID && proc->parent_pid != 0) { + char pid_str[32]; + char proc_path[255] = {0}; + snprintf(pid_str, sizeof(pid_str), "%d", proc->parent_pid); + FILE *pf = process_open(build_proc_path(proc_path, pid_str, "stat")); + if (parse_proc(pf, proc)) + break; + print_proc_info(proc); + fclose(pf); + free(proc->exe_name); + proc->exe_name = NULL; + } +} + +int main(int argc, char **argv) +{ + int c; + int list_procs_flag = 0, kernel_procs_flag = 0; + while ((c = getopt(argc, argv, "lkp:")) != -1) { + switch (c) { + case 'l': list_procs_flag = 1; + break; + case 'k': kernel_procs_flag = 1; + break; + case '?': + fprintf(stderr, "usage: ./procread [-lk][-p PID]\n"); + return EXIT_FAILURE; + } + } + + if (list_procs_flag) { + struct proc_t **proc, **proc_o; + proc = proc_o = list_procs(); + while (*proc != NULL) { + if ((*proc)->pid && (!IS_KERNEL_PROC(*proc) || kernel_procs_flag)) { + print_proc_info(*proc); + } + free((*proc)->exe_name); + free(*proc++); + } + free(proc_o); + } else { + FILE *proc_fp; + char pid_str[24]; + char proc_path[255] = {0}; + if (optind >= argc) { + snprintf(pid_str, sizeof(pid_str), "%d", getpid()); + proc_fp = process_open(build_proc_path(proc_path, pid_str, "stat")); + } else { + proc_fp = process_open(build_proc_path(proc_path, argv[optind], "stat")); + } + struct proc_t p = {}; + if (parse_proc(proc_fp, &p)) + return 1; + print_proc_info(&p); + free(p.exe_name); + fclose(proc_fp); + backtrace_to_init(&p); + } + + return EXIT_SUCCESS; +} diff --git a/parsers/bencode.nim b/parsers/bencode.nim new file mode 100644 index 0000000..b98ff10 --- /dev/null +++ b/parsers/bencode.nim @@ -0,0 +1,116 @@ +## Bencode decoder +## Resources: +## https://en.m.wikipedia.org/wiki/Bencode +## https://archive.is/yCwj4 +import std/[streams, strutils, tables] +from strformat import fmt + +type + BencodeSyntaxError = object of ValueError + BencodeInt = int64 + BencodeByteStr = string + BencodeDict = Table[string, BencodeObject] + + BencodeObjectKind = enum + bokNum + bokBseq + bokList + bokDict + BencodeObject = object + case kind: BencodeObjectKind + of bokNum: num: BencodeInt + of bokBseq: bseq: BencodeByteStr + of bokList: list: seq[BencodeObject] + of bokDict: dict: BencodeDict + + +proc decodeBencode*(bs: Stream): BencodeObject + +var putbackdChar: char +proc expectToken(bs: Stream, expected: char) = + var c: char + if putbackdChar != '\0': + c = putbackdChar + putbackdChar = '\0' + else: + c = bs.readChar() + if c != expected: + let msg = + if c != '\0': fmt"expected `{expected}`, got `{c}`" + else: "unexpected EOB" + raise newException(BencodeSyntaxError, msg) + +# converts a base-10 number to its integer representation +proc readNumber(bs: Stream): BencodeInt = + const digits = "0123456789" + var + c = bs.readChar() + sign: int + + if c == '-': + c = bs.readChar() + sign = 2 + + while isDigit(c): + let charVal = digits.find(c) + assert charVal != -1 + result = (result * 10) + charVal + c = bs.readChar() + putbackdChar = c + result -= result * sign + +# i1234e i-727e +proc parseBencodeInteger(bs: Stream): BencodeInt = + expectToken(bs, 'i') + result = readNumber(bs) + expectToken(bs, 'e') + +# 4:spam +proc parseByteSequence(bs: Stream): BencodeByteStr = + var byteLen = readNumber(bs) + expectToken(bs, ':') + + while byteLen > 0: + result.add(char(bs.readInt8())) + dec byteLen + +proc parseList(bs: Stream): seq[BencodeObject] = + expectToken(bs, 'l') + while bs.peekChar() != 'e': + result.add(decodeBencode(bs)) + expectToken(bs, 'e') + +proc parseDict(bs: Stream): BencodeDict = + expectToken(bs, 'd') + while bs.peekChar() != 'e': + let (key, val) = (parseByteSequence(bs), decodeBencode(bs)) + result[key] = val + expectToken(bs, 'e') + +proc decodeBencode*(bs: Stream): BencodeObject = + let c = bs.peekChar() + if isDigit(c): + if c == '0': + raise newException(BencodeSyntaxError, "Byte string with zero length") + let bseq = parseByteSequence(bs) + return BencodeObject(kind: bokBseq, bseq: bseq) + + case c + of 'i': + let num = parseBencodeInteger(bs) + result = BencodeObject(kind: bokNum, num: num) + of 'l': + let ben = parseList(bs) + result = BencodeObject(kind: bokList, list: ben) + of 'd': + let dict = parseDict(bs) + result = BencodeObject(kind: bokDict, dict: dict) + of '\0': + raise newException(BencodeSyntaxError, fmt"Unexpected EOB") + else: + raise newException(BencodeSyntaxError, fmt"Unknown token `{c}`") + +#var bs = newStringStream("li1234e4:spam6:i1234ed3:foo3:bar4:fuzzli1337e4:abcdeee") +var bs = newStringStream("li1234el4:spamli1337e4:eggseee") +echo decodeBencode(bs).repr() +bs.close() diff --git a/proglangs/nsforth/Makefile b/proglangs/nsforth/Makefile new file mode 100644 index 0000000..361ddd6 --- /dev/null +++ b/proglangs/nsforth/Makefile @@ -0,0 +1,19 @@ +SRC = $(wildcard *.c) +OBJS = $(SRC:.c=.o) +RELEASE_TARBALL = nsforth.tar.gz +CFLAGS = -Wall -Wextra -ggdb3 -O0 + +nsforth: $(OBJS) + $(CC) $(OBJS) -o $@ + +%.o: %.c + $(CC) $(CFLAGS) -c $< + +release: + touch $(RELEASE_TARBALL) + tar -cvzf $(RELEASE_TARBALL) ../nsforth + +clean: + rm -f *.o *.tar.gz nsforth + +.PHONY: all clean release diff --git a/proglangs/nsforth/defs.h b/proglangs/nsforth/defs.h new file mode 100644 index 0000000..86f4f5e --- /dev/null +++ b/proglangs/nsforth/defs.h @@ -0,0 +1,66 @@ +#ifndef _DEFS_H_ +#define _DEFS_H_ + +#define INT_TYPE intmax_t +#define FLAG_NATIVE 0x1u +#define FLAG_IMMEDIATE 0x2u +#define FLAG_PARAM_WORD 0x4u + +#define SIZE_ARR(a) (sizeof(a) / sizeof(*(a))) +#define STRIP_FINAL_NL(s) s[strcspn(s, "\r\n")] = '\0' + +#define unbox_exsp(ctype, type, err) (*(ctype *)unbox_ex(pop_stack(), type, err)) +#define unbox_exb(box, ctype, type, err) (*(ctype *)unbox_ex(box, type, err)) +#define strnil(s) ((s) == NULL || *(s) == '\0') + +#define UNREACHABLE() fatal_error("%s:%d: unreachable clause\n", __func__, __LINE__) +#define fatal_error(fmt, ...) \ + do { \ + error(fmt, ##__VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } while (0); + +typedef void (*dict_fn)(void); + +struct dict_ent +{ + uint8_t flags; + char *word_name; + dict_fn nat_fn; + struct comp_word *fn_def; +}; + +struct dict_table +{ + struct dict_table *prev; // previous environment + struct dict_ent *entries; // symbols in this environment + size_t level; + size_t capacity; + size_t used; +}; + +struct comp_word +{ + size_t len; + size_t capacity; + struct value_box *ents; +}; + +enum comp_ent_type { + CET_WORD, CET_NUMBER, CET_ADDR, +}; + +struct value_box +{ + enum comp_ent_type type; + union { + struct dict_ent *word; // pointer to a word entry in the dictionary + void *addr; + INT_TYPE number; + }; +}; + +void *xcalloc(size_t nmemb, size_t size); +void *reallocarray(void *ptr, size_t nmemb, size_t size); + +#endif diff --git a/proglangs/nsforth/dict.c b/proglangs/nsforth/dict.c new file mode 100644 index 0000000..ad4e214 --- /dev/null +++ b/proglangs/nsforth/dict.c @@ -0,0 +1,170 @@ +#include +#include +#include +#include "dict.h" +#include + +#define FREE_DICT_NAMES(env) \ + while ((env)->capacity > 0) { \ + char *word_name = (env)->entries[--(env)->capacity].word_name; \ + if (word_name != NULL && word_name != DELETED_ENTRY_MARK) \ + free(word_name); \ + } + +const size_t DEFAULT_HASHMAP_SIZE = 255; +const size_t HASHMAP_REHASH_MARK = 70; +struct dict_ent *dict_last; + +static size_t dict_table_fnv1a(const char *key, const size_t key_len) +{ + const size_t fnv_offset = 0xcbf29ce484222325; + const size_t fnv_prime = 0x100000001b3; + size_t hash = fnv_offset; + + for (size_t ki = 0; ki < key_len; ++ki) { + hash ^= (uint8_t)key[ki]; + hash *= fnv_prime; + } + return hash; +} + +static void dict_destroy_enviroment(struct dict_table *env) +{ + FREE_DICT_NAMES(env); + free(env->entries); + free(env); +} + +struct dict_table *dict_new_env(struct dict_table *prev_env) +{ + struct dict_table *new_env = xcalloc(1, sizeof(*new_env)); + new_env->entries = xcalloc(DEFAULT_HASHMAP_SIZE, sizeof(struct dict_ent)); + new_env->prev = prev_env; + new_env->capacity = DEFAULT_HASHMAP_SIZE; + + if (prev_env != NULL) + new_env->level = prev_env->level + 1; + return new_env; +} + +void dict_destroy_env(struct dict_table *env) +{ + if (env->prev == NULL) { + dict_destroy_enviroment(env); + return; + } + + while (env != NULL) { + struct dict_table *tmp = env; + env = env->prev; + dict_destroy_enviroment(tmp); + } +} + +struct dict_ent *_dict_insert( + struct dict_table *env, const char *word_name, uint8_t flags, dict_fn nat_fn, + struct comp_word *fn_def +); +// Resizes the bucket size if needed (shrink or expand), also removes deleted entries +void dict_table_rehash(struct dict_table *env) +{ + const size_t new_capacity = env->capacity * 2; + + struct dict_table new_dict_table = {0}; + new_dict_table.entries = xcalloc(new_capacity, sizeof(struct dict_ent)); + new_dict_table.capacity = new_capacity; + + // copy elements to the new dict_table, removing deleted entries in the process + for (size_t i = 0; i < env->capacity; ++i) { + struct dict_ent *oldent = &env->entries[i]; + //if (oldent->word_name != NULL && oldent->word_name != DELETED_ENTRY_MARK) { + if (IS_ENTRY(oldent)) { + dict_insert(&new_dict_table, oldent->word_name, oldent->flags, oldent->nat_fn, oldent->fn_def); + } + } + + FREE_DICT_NAMES(env); + free(env->entries); // free old buckets + env->capacity = new_capacity; + env->entries = new_dict_table.entries; +} + +struct dict_ent *_dict_insert( + struct dict_table *env, const char *word_name, uint8_t flags, dict_fn nat_fn, + struct comp_word *fn_def +) +{ + if ((env->used * 100) / env->capacity >= HASHMAP_REHASH_MARK) + dict_table_rehash(env); + + size_t hash_index = dict_table_fnv1a(word_name, strlen(word_name)) % env->capacity; + + while (env->entries[hash_index].word_name != NULL) { + ++hash_index; + if (hash_index >= env->capacity) + hash_index = 0; + } + + if (env->entries[hash_index].word_name != DELETED_ENTRY_MARK) + ++env->used; + + struct dict_ent *dslot = &env->entries[hash_index]; + dslot->word_name = (char *)word_name; + dslot->flags = flags; + dslot->nat_fn = nat_fn; + dslot->fn_def = fn_def; + + dict_last = dslot; + return dslot; +} + +struct dict_ent *dict_insert( + struct dict_table *env, const char *word_name, uint8_t flags, dict_fn nat_fn, + struct comp_word *fn_def +) +{ + return _dict_insert(env, strdup(word_name), flags, nat_fn, fn_def); +} + +// like dict_lookup it searches a symbol, but limited to the current environment (scope) +struct dict_ent *dict_lookup(struct dict_table *env, const char *word_name) +{ + if (env->entries == NULL) + return NULL; + size_t hash_index = dict_table_fnv1a(word_name, strlen(word_name)) % env->capacity; + + while (env->entries[hash_index].word_name != NULL) { + struct dict_ent *ent = &env->entries[hash_index]; + if (ent->word_name != DELETED_ENTRY_MARK && !strcmp(ent->word_name, word_name)) + return ent; + + ++hash_index; + if (hash_index >= env->capacity) + hash_index = 0; + } + + // entry not found + return NULL; +} + +// searches for a symbol in the current table and in all linked environments (scopes) +//struct dict_ent *dict_lookup(struct dict_table *env, char *word_name) +//{ +// if (env->entries == NULL) +// return NULL; +// size_t hash_index = dict_table_fnv1a(word_name, strlen(word_name)) % env->capacity; +// +// for (struct dict_table *s = env; s != NULL; s = s->prev) { +// while (s->entries[hash_index].word_name != NULL) { +// if (s->entries[hash_index].word_name != DELETED_ENTRY_MARK && !strcmp(s->entries[hash_index].word_name, word_name)) +// return &s->entries[hash_index]; +// +// ++hash_index; +// if (hash_index >= s->capacity) +// hash_index = 0; +// } +// } +// +// // entry not found +// return NULL; +//} diff --git a/proglangs/nsforth/dict.h b/proglangs/nsforth/dict.h new file mode 100644 index 0000000..651a42d --- /dev/null +++ b/proglangs/nsforth/dict.h @@ -0,0 +1,17 @@ +#ifndef _DICT_H_ +#define _DICT_H_ +#include "defs.h" + +#define DELETED_ENTRY_MARK ((void *)-1) +#define IS_ENTRY(ent) ((ent)->word_name != NULL && (ent)->word_name != DELETED_ENTRY_MARK) +#define dict_delete(ent) ((ent)->word_name = DELETED_ENTRY_MARK) +//#define dict_enter_scope(sym_t) ((sym_t = dict_new_enviroment(sym_t))) +//#define dict_leave_scope(sym_t) ((sym_t = sym_t->prev)) + +struct dict_table *dict_new_env(struct dict_table *prev_env); +void dict_destroy_env(struct dict_table *env); +struct dict_ent *dict_insert(struct dict_table *env, const char *word_name, uint8_t flags, dict_fn nat_fn, struct comp_word *fn_def); +struct dict_ent *dict_lookup(struct dict_table *env, const char *word_name); +//struct dict_ent *dict_lookup_scoped(struct dict_table *env, char *word_name); + +#endif diff --git a/proglangs/nsforth/natives.c b/proglangs/nsforth/natives.c new file mode 100644 index 0000000..e69de29 diff --git a/proglangs/nsforth/nsforth.c b/proglangs/nsforth/nsforth.c new file mode 100644 index 0000000..e9ecd29 --- /dev/null +++ b/proglangs/nsforth/nsforth.c @@ -0,0 +1,580 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "defs.h" +#include "dict.h" + +#define STACK_SIZE 4096u + +#define DEF_NATIVE(name) void name##_fn(void) +#define DEF_ARITH_FN(op, name, ctype) \ + DEF_NATIVE(name) { \ + int err = 0; \ + struct value_box *n2 = pop_stack(); \ + struct value_box *n1 = pop_stack(); \ + if (n1 == NULL || n2 == NULL) return; \ + ctype v2 = unbox_exb(n2, ctype, CET_NUMBER, &err); \ + ctype v1 = unbox_exb(n1, ctype, CET_NUMBER, &err); \ + if (err) return; \ + push_stack(box_number(v1 op v2)); \ + } + +#define DEF_BOX(name, val_type, box_member, type_tag) \ + struct value_box *box_##name(val_type val) { \ + struct value_box *tmp = xcalloc(1, sizeof(*tmp)); \ + tmp->type = type_tag; \ + tmp->box_member = val; \ + return tmp; \ + } + +struct { + struct value_box *stack[STACK_SIZE]; + size_t sp; +} data_stack; + +struct dict_table *dict; +extern struct dict_ent *dict_last; // pointer to the last added dictionary entry + +size_t pc; +char *next_word; +char **program; +size_t program_len; + +int compile_mode; +int error_flag; + +void error(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + error_flag = 1; +} + +intmax_t xstrtoimax(const char *restrict nptr, int base, int *restrict eflag) +{ + errno = 0; + char *endptr; + intmax_t val = strtoimax(nptr, &endptr, base); + if (((val == INTMAX_MAX || val == INTMAX_MIN) && errno == ERANGE) || (!val && errno == EINVAL) || *endptr != '\0') { + if (eflag != NULL) + *eflag = 1; + else + fatal_error("invalid number on stream\n"); + } + return val; +} + +// unboxes a value, only if it has the same type as specified. +// must be cast to the appropiate C type, since it returns a void * +void *unbox_ex(struct value_box *val, enum comp_ent_type type, int *err) +{ +#define SETNNIL(v) if ((v) != NULL) {*(v) = 1;} + if (val == NULL) { + SETNNIL(err); + return NULL; + } + if (val->type != type) { + error("invalid type\n"); + SETNNIL(err); + return NULL; + } + switch (val->type) { + case CET_NUMBER: return &val->number; + case CET_WORD: return &val->word; + case CET_ADDR: return &val->addr; + default: UNREACHABLE(); + } + return NULL; +#undef SETNNIL +} + +void push_stack(struct value_box *val) +{ + size_t *sp = &data_stack.sp; + if ((*sp + 1) > SIZE_ARR(data_stack.stack)) { + error("stack overflow\n"); + return; + } + data_stack.stack[(*sp)++] = val; +} + +struct value_box *pop_stack() +{ + size_t *sp = &data_stack.sp; + if ((*sp - 1) == SIZE_MAX) { + error("stack underflow\n"); + return NULL; + } + return data_stack.stack[--*sp]; +} + +static inline void print_box(const struct value_box *obj) +{ + switch (obj->type) { + case CET_NUMBER: + printf("%jd", obj->number); + break; + case CET_WORD: + printf("", (void *)obj->word, obj->word->word_name); + break; + case CET_ADDR: puts("address"); break; + default: UNREACHABLE(); + } +} + +// non-recursive word dump) +void word_dump(const char *word) +{ + struct dict_ent *ent; + if ((ent = dict_lookup(dict, word)) != NULL) { + if (ent->flags & FLAG_NATIVE) { + printf("\n", (void *)ent, ent->word_name); + return; + } + printf(": %s ", word); + for (size_t i = 0; i < ent->fn_def->len; ++i) { + struct value_box *cent = &(ent->fn_def->ents[i]); + switch (cent->type) { + case CET_ADDR: + printf("addr\n"); + break; + case CET_WORD: + printf("%s ", cent->word->word_name); + break; + case CET_NUMBER: + printf("%jd ", cent->number); + break; + default: UNREACHABLE(); + } + } + puts(";"); + } else { + error("no such word `%s`\n", word); + } +} + +DEF_BOX(number, INT_TYPE, number, CET_NUMBER) +DEF_BOX(word, struct dict_ent *, word, CET_WORD) + +// --------------- native (primitives) --------------- // +DEF_ARITH_FN(+, add, INT_TYPE) +DEF_ARITH_FN(-, sub, INT_TYPE) +DEF_ARITH_FN(*, mul, INT_TYPE) +DEF_ARITH_FN(/, div, INT_TYPE) +DEF_ARITH_FN(==, equal, INT_TYPE) +DEF_ARITH_FN(<, less, INT_TYPE) + +DEF_NATIVE(clearstack) { + while (data_stack.sp > 0) + free(data_stack.stack[--data_stack.sp]); +} + +// ' returns the address of a dictionary entry, aborting if it does not exist +DEF_NATIVE(quote) { + ++pc; + if (strnil(next_word)) + error("' requires a name\n"); + + struct dict_ent *ent = dict_lookup(dict, next_word); + if (ent == NULL) { + error("no such word `%s'\n", next_word); + return; + } + push_stack(box_word(ent)); +} + +// allot (n -- ) adds n bytes to the parameter field of the most recently defined word +DEF_NATIVE(allot) { + int err = 0; + const size_t nbytes = unbox_exsp(size_t, CET_NUMBER, &err); + if (err) + return; + const size_t org_len = dict_last->fn_def->len; + const size_t len = org_len + nbytes; + struct value_box **ents = &dict_last->fn_def->ents; + + dict_last->fn_def->capacity = len; + *ents = reallocarray(*ents, len, sizeof(struct value_box)); + // printf("allot: %d\n", len - dict_last->fn_def->len); + // clear the newly allocated part + //memset(*ents + org_len, 0, len - org_len); +} + +DEF_NATIVE(comma) { + struct comp_word *pf = dict_last->fn_def; + + if (pf->ents == NULL) { + pf->capacity = 4; + pf->ents = xcalloc(pf->capacity, sizeof(struct value_box)); + } else if (pf->len + 1 > pf->capacity) { + pf->capacity <<= 1; + pf->ents = reallocarray(pf->ents, pf->capacity, sizeof(struct value_box)); + } + struct value_box *val = pop_stack(); + if (val == NULL) + return; + pf->ents[pf->len++] = *val; +} + +DEF_NATIVE(create) { + ++pc; + if (strnil(next_word)) + error("create requires a name\n"); + + struct comp_word *param = xcalloc(1, sizeof(*param)); + dict_insert(dict, next_word, 0, NULL, param); +} + +DEF_NATIVE(forget) { + ++pc; + if (strnil(next_word)) + error("forget requires a name\n"); + struct dict_ent *ent; + if ((ent = dict_lookup(dict, next_word)) == NULL) { + error("no such word `%s'\n", next_word); + return; + } + + if (!(ent->flags & FLAG_NATIVE)) + free(ent->fn_def->ents); + dict_delete(ent); +} + +DEF_NATIVE(see) { + ++pc; + if (strnil(next_word)) { + error("see requires a name\n"); + return; + } + word_dump(next_word); +} + +DEF_NATIVE(words) { + for (size_t i = 0; i < dict->capacity; ++i) + if (IS_ENTRY(&dict->entries[i])) + printf("%s ", dict->entries[i].word_name); + putchar('\n'); +} + +DEF_NATIVE(compile) { + compile_mode = 1; +} + +DEF_NATIVE(endcompile) { + compile_mode = 0; +} + +DEF_NATIVE(dup) { + push_stack(data_stack.stack[data_stack.sp - 1]); +} + +DEF_NATIVE(drop) { + free(pop_stack()); +} + +DEF_NATIVE(over) { + push_stack(data_stack.stack[data_stack.sp - 2]); +} + +DEF_NATIVE(swap) { + struct value_box *e2 = pop_stack(); + struct value_box *e1 = pop_stack(); + if (e1 == NULL || e2 == NULL) + return; + push_stack(e2); + push_stack(e1); +} + +DEF_NATIVE(zero) { + int err = 0; + struct value_box *tmp = box_number(!unbox_exsp(INT_TYPE, CET_NUMBER, &err)); + if (err) + return; + push_stack(tmp); +} + +DEF_NATIVE(emit) { + int err = 0; + int chr = unbox_exsp(int, CET_NUMBER, &err); + if (!err) + putchar(chr); +} + +DEF_NATIVE(print) { + struct value_box *val = pop_stack(); + if (val == NULL) + return; + print_box(val); + putchar('\n'); + free(val); +} + +DEF_NATIVE(prints) { + if (data_stack.sp) { + size_t c = 0; + printf("stack dump: <%zu> ", data_stack.sp); + while (c < data_stack.sp) { + print_box(data_stack.stack[c++]); + putchar(' '); + } + putchar('\n'); + } +} + +static inline void append_to_code(struct value_box *slot, const char *current_word) +{ + int errn = 0; + intmax_t val = xstrtoimax(current_word, 0, &errn); + + if (!errn) { + slot->type = CET_NUMBER; + slot->number = val; + } else { + struct dict_ent *ent = dict_lookup(dict, current_word); + if (ent == NULL) + fatal_error("no such word `%s`\n", current_word); + slot->type = CET_WORD; + slot->word = ent; + } +} + +void eval_def(struct comp_word *def); +void if_fn() +{ + size_t start_pc = ++pc; + size_t end_pc; + while (pc < program_len && strcmp(program[pc], "end") != 0) + ++pc; + if (pc >= program_len) + fatal_error("expected `end`, reached EOP\n"); + end_pc = pc; + + int err = 0; + int cond = unbox_exsp(int, CET_NUMBER, &err); + if (err) + fatal_error("if requires a condition\n"); + + if (cond) { + char **program_slice = program + start_pc; + size_t len = end_pc - start_pc; + + struct value_box *code = xcalloc(len, sizeof(*code)); + struct comp_word c = {.len = len, .capacity = len, .ents = code}; + + for (size_t i = 0; i < len; ++i) + append_to_code(&code[i], program_slice[i]); + eval_def(&c); + free(code); + } +} + +// --------------- interpretation --------------- // +void eval_def(struct comp_word *def) +{ + for (size_t i = 0; i < def->len; ++i) { + const struct value_box *cent = &(def->ents[i]); + struct dict_ent *ent; + + switch (cent->type) { + case CET_WORD: + ent = cent->word; + if (ent->flags & FLAG_NATIVE) + ent->nat_fn(); + else + eval_def(ent->fn_def); + break; + case CET_NUMBER: + push_stack(box_number(cent->number)); + break; + default: UNREACHABLE(); + } + } +} + +void interpret(char **prog, size_t prog_len) +{ + struct comp_word *c_word = NULL; + char *fn_name = NULL; + + for (; pc < prog_len; ++pc) { + if (error_flag) { + pc = prog_len; + break; + } + char *word = prog[pc]; + next_word = (pc + 1) < prog_len ? prog[pc + 1] : NULL; + + if (compile_mode) { + if (*word == ';') { + dict_insert(dict, fn_name, 0, NULL, c_word); + c_word = NULL; + fn_name = NULL; + compile_mode = 0; + continue; + } + if (c_word == NULL) { + c_word = xcalloc(1, sizeof(*c_word)); + c_word->ents = xcalloc(256, sizeof(*c_word->ents)); + } + if (fn_name == NULL) { + fn_name = word; + continue; + } + append_to_code(&(c_word->ents[c_word->len++]), word); + } else { + int nef = 0; + INT_TYPE num = xstrtoimax(word, 0, &nef); + if (!nef) { + push_stack(box_number(num)); + continue; + } + + struct dict_ent *ent; + if ((ent = dict_lookup(dict, word)) != NULL) { + if (ent->flags & FLAG_NATIVE) + ent->nat_fn(); + else + eval_def(ent->fn_def); + } else { + fatal_error("interpretation: no such word `%s`\n", word); + } + } + } +} + +// --------------- Parsing --------------- // +void tokenize(char *s, char **buf, size_t buflen, size_t *i) +{ + const char delim = ' '; + char *p = s, *wp = s; + + while (*p != '\0') { + if (*p == '(') { // eat block comment + while (*p != ')' && *p != '\0') + ++p; + p += 2; // jump past the ) and space + wp = p; + continue; + } else if (*p == '\\') { + while (*p++ != '\0'); + wp = p; + continue; + } + if (*p == delim) { + if (*i + 1 > buflen) + return; + *p = '\0'; + buf[(*i)++] = strdup(wp); + wp = ++p; + } else { + ++p; + } + } + if (wp < p && *i < buflen) // handle last element + buf[(*i)++] = strdup(wp); +} + +size_t parse_program(FILE *fp, char **buf, size_t buflen) +{ + size_t i = 0; + char line_buf[1024] = {0}; + while (fgets(line_buf, sizeof(line_buf), fp) != NULL) { + STRIP_FINAL_NL(line_buf); + tokenize(line_buf, buf, buflen, &i); + } + return i; +} + +void repl(void) +{ + char *prog[1024] = {0}; + size_t prog_len = 0; + + char *line_buf = NULL; + size_t line_buf_len = 0; + ssize_t rb = 0; + int prompt_comp_mode = 0; + + errno = 0; + for (;;) { + printf("[%s]%s ", error_flag ? "err" : "ok", !prompt_comp_mode ? ">" : ">>"); + fflush(stdout); + error_flag = 0; + + if ((rb = getline(&line_buf, &line_buf_len, stdin)) < 0) + break; + + STRIP_FINAL_NL(line_buf); + tokenize(line_buf, prog, SIZE_ARR(prog), &prog_len); + if (strchr(line_buf, ':') != NULL) + prompt_comp_mode = 1; + else if (strchr(line_buf, ';') != NULL) + prompt_comp_mode = 0; + + if (!prompt_comp_mode) + interpret(prog, prog_len); + } + if (rb < 0 && errno) + fatal_error("getline err\n"); + free(line_buf); +} + +void init() +{ + dict = dict_new_env(NULL); + dict_insert(dict, "+", FLAG_NATIVE, add_fn, NULL); + dict_insert(dict, "-", FLAG_NATIVE, sub_fn, NULL); + dict_insert(dict, "*", FLAG_NATIVE, mul_fn, NULL); + dict_insert(dict, "=", FLAG_NATIVE, equal_fn, NULL); + dict_insert(dict, "<", FLAG_NATIVE, less_fn, NULL); + dict_insert(dict, ".", FLAG_NATIVE, print_fn, NULL); + dict_insert(dict, ".s", FLAG_NATIVE, prints_fn, NULL); + dict_insert(dict, "zero?", FLAG_NATIVE, zero_fn, NULL); + dict_insert(dict, ":", FLAG_NATIVE, compile_fn, NULL); + dict_insert(dict, ";", FLAG_NATIVE | FLAG_IMMEDIATE, endcompile_fn, NULL); + dict_insert(dict, "'", FLAG_NATIVE, quote_fn, NULL); + dict_insert(dict, ",", FLAG_NATIVE, comma_fn, NULL); + dict_insert(dict, "print", FLAG_NATIVE, print_fn, NULL); + dict_insert(dict, "see", FLAG_NATIVE, see_fn, NULL); + dict_insert(dict, "dup", FLAG_NATIVE, dup_fn, NULL); + dict_insert(dict, "drop", FLAG_NATIVE, drop_fn, NULL); + dict_insert(dict, "over", FLAG_NATIVE, over_fn, NULL); + dict_insert(dict, "swap", FLAG_NATIVE, swap_fn, NULL); + dict_insert(dict, "create", FLAG_NATIVE, create_fn, NULL); + dict_insert(dict, "forget", FLAG_NATIVE, forget_fn, NULL); + dict_insert(dict, "allot", FLAG_NATIVE, allot_fn, NULL); + dict_insert(dict, "cst", FLAG_NATIVE, clearstack_fn, NULL); + dict_insert(dict, "words", FLAG_NATIVE, words_fn, NULL); + dict_insert(dict, "emit", FLAG_NATIVE, emit_fn, NULL); + dict_insert(dict, "if", FLAG_NATIVE, if_fn, NULL); +} + +int main(int argc, char **argv) +{ + init(); + if (argc < 2) { + printf("nsforth interactive repl (build: %s, %s)\n", __DATE__, __TIME__); + repl(); + } else { + FILE *fp = fopen(*++argv, "r"); + if (fp == NULL) + fatal_error("error opening '%s': %s\n", *argv, strerror(errno)); + + char *prog[1024] = {0}; + size_t prog_len = parse_program(fp, prog, SIZE_ARR(prog)); + fclose(fp); + program = prog; + program_len = prog_len; + interpret(prog, prog_len); + } + + dict_destroy_env(dict); + return EXIT_SUCCESS; +} diff --git a/proglangs/nsforth/test.fs b/proglangs/nsforth/test.fs new file mode 100644 index 0000000..e5ebe2f --- /dev/null +++ b/proglangs/nsforth/test.fs @@ -0,0 +1,38 @@ +\ nsforth a small and slow implementation of a subset of Forth. + +: sq ( n -- n ) dup * ; +: foo dup * 10 + ; + +5 foo . 42 .s 3 zero? . +see foo \ see foo definition +' foo . +' foo 32 .s swap .s +create array1 4 allot \ 4 elements +create array? 1 , 2 , + +: inc ( n1 -- n1 ) 1 + ; +: dec ( n1 -- n1 ) 1 - ; +4 inc . +4 dec . + +\ Forth is a concatenative language, that is: +: fn1 2 + ; +: fn2 3 * ; +: fn3 4 * ; + +3 fn1 fn2 fn3 . +forget fn1 forget fn2 forget fn3 +\ 4 fn1 <- error +: let create ; + +cst +-4 3 - . +\ if end +2 3 + 10 < if + 3 sq . +end + +let var1 2 , +\ while end + +\ import file.fs diff --git a/proglangs/nsforth/util.c b/proglangs/nsforth/util.c new file mode 100644 index 0000000..18ed7a0 --- /dev/null +++ b/proglangs/nsforth/util.c @@ -0,0 +1,20 @@ +#include +#include +#include + +void *xcalloc(size_t nmemb, size_t size) +{ + void *ptr = calloc(nmemb, size); + if (ptr == NULL && (nmemb || size)) + abort(); + return ptr; +} + +void *reallocarray(void *ptr, size_t nmemb, size_t size) +{ + if (nmemb && size > (SIZE_MAX / nmemb)) { + errno = ENOMEM; + return NULL; + } + return realloc(ptr, nmemb * size); +} diff --git a/proglangs/rpn-go/go.mod b/proglangs/rpn-go/go.mod new file mode 100644 index 0000000..34b450e --- /dev/null +++ b/proglangs/rpn-go/go.mod @@ -0,0 +1,8 @@ +module rpn + +go 1.20 + +require ( + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect +) diff --git a/proglangs/rpn-go/go.sum b/proglangs/rpn-go/go.sum new file mode 100644 index 0000000..dafcc5e --- /dev/null +++ b/proglangs/rpn-go/go.sum @@ -0,0 +1,4 @@ +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= diff --git a/proglangs/rpn-go/prog1 b/proglangs/rpn-go/prog1 new file mode 100644 index 0000000..ef5dc42 --- /dev/null +++ b/proglangs/rpn-go/prog1 @@ -0,0 +1,3 @@ +77 50 + . +77 50 + 2 * . +bye diff --git a/proglangs/rpn-go/rpn.go b/proglangs/rpn-go/rpn.go new file mode 100644 index 0000000..1b1b9e8 --- /dev/null +++ b/proglangs/rpn-go/rpn.go @@ -0,0 +1,130 @@ +package main + +import ( + "bufio" + "fmt" + "golang.org/x/term" + "os" + "strings" +) + +type Word struct { + name string + impl func(stack *Stack) error +} + +var Dictionary = map[string]Word{ + "+": Word{impl: natAdd}, + "*": Word{impl: natMul}, + "-": Word{impl: natSub}, + "/": Word{impl: natDiv}, + "mod": Word{impl: natMod}, + ".": Word{impl: natPrint}, + "s?": Word{impl: natPrintStack}, + "dup": Word{impl: natDup}, + "drop": Word{impl: natDrop}, + "rnd": Word{impl: natRnd}, + "bye": Word{impl: natBye}, +} + +func isNumberAscii(chr byte) bool { + return chr >= '0' && chr <= '9' +} + +func isIdent(chr byte) bool { + return (chr >= 'A' && chr <= 'Z') || + (chr >= 'a' && chr <= 'z') || + (chr >= '!' && chr <= '/') || + (chr >= ':' && chr <= '`') +} + +func parseNumber(s string, cur *int) StackType { + var num int + const digits = "0123456789abcdf" + for _, c := range s { + bc := byte(c) + if !isNumberAscii(bc) { + *cur-- + break + } + idx := strings.IndexByte(digits, bc) + if idx < 0 { // we should't reach this place + panic("unexpected non-digit character") + } + num = num*10 + idx + *cur++ + } + return StackType(num) +} + +func parseWord(s string, cur *int) string { + for i, c := range s { + if !isIdent(byte(c)) { + return s[:i] + } + *cur++ + } + return s +} + +func lookupWord(wordName string) (*Word, error) { + word, ok := Dictionary[wordName] + if !ok { + return nil, fmt.Errorf("no such word: `%s`", wordName) + } + return &word, nil +} + +func eval(stack *Stack, prog string) { + for i := 0; i < len(prog); i++ { + bc := byte(prog[i]) + if bc == ' ' || bc == '\t' || bc == '\n' || bc == '\r' { + continue + } + if isNumberAscii(bc) { + num := parseNumber(prog[i:], &i) + stack.push(num) + } else { + wordImpl, err := lookupWord(parseWord(prog[i:], &i)) + if err != nil { + fmt.Printf("Error: %s\n", err) + break + } + if err := wordImpl.impl(stack); err != nil { + fmt.Printf("Error: %s\n", err) + } + } + } +} + +func main() { + stack := new(Stack) + stack.data = make([]StackType, 0, StackSize) + + Dictionary["words"] = Word{impl: natHelp} + Dictionary["s!"] = Word{ + impl: func(stack *Stack) error { + stack.wipe() + return nil + }, + } + + is_tty := term.IsTerminal(int(os.Stdin.Fd())) + scanner := bufio.NewScanner(os.Stdin) + if is_tty { + fmt.Println("RPN calculator - Type Ctrl-D or \"bye\" to exit") + } + + for { + if is_tty { + fmt.Print("> ") + } + if !scanner.Scan() { + if err := scanner.Err(); err != nil { + panic(err) + } + break + } + eval(stack, scanner.Text()) + } +} diff --git a/proglangs/rpn-go/stack.go b/proglangs/rpn-go/stack.go new file mode 100644 index 0000000..7323ab8 --- /dev/null +++ b/proglangs/rpn-go/stack.go @@ -0,0 +1,31 @@ +package main + +import "errors" + +const StackSize = 64 + +type StackType int +type Stack struct { + data []StackType + pos int +} + +func (s *Stack) push(val StackType) { + s.data = append(s.data, val) + s.pos++ +} + +func (s *Stack) pop() (StackType, error) { + if s.pos-1 < 0 { + return 0, errors.New("stack is empty") + } + s.pos-- + res := s.data[s.pos] + s.data = s.data[:s.pos] + return res, nil +} + +func (s *Stack) wipe() { + s.pos = 0 + s.data = s.data[:0] +} diff --git a/proglangs/rpn-go/words.go b/proglangs/rpn-go/words.go new file mode 100644 index 0000000..59426e8 --- /dev/null +++ b/proglangs/rpn-go/words.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + "os" +) + +func binOp(stack *Stack, op func(StackType, StackType) StackType) error { + v1, err := stack.pop() + v2, err := stack.pop() + if err != nil { + return err + } + stack.push(op(v1, v2)) + return nil +} + +func natAdd(stack *Stack) error { + return binOp(stack, func(y StackType, x StackType) StackType { + return x + y + }) +} + +func natSub(stack *Stack) error { + return binOp(stack, func(y StackType, x StackType) StackType { + return x - y + }) +} + +func natMul(stack *Stack) error { + return binOp(stack, func(y StackType, x StackType) StackType { + return x * y + }) +} + +func natDiv(stack *Stack) error { + return binOp(stack, func(y StackType, x StackType) StackType { + return x / y + }) +} + +func natMod(stack *Stack) error { + return binOp(stack, func(y StackType, x StackType) StackType { + return x % y + }) +} + +func natPrint(stack *Stack) error { + val, err := stack.pop() + if err != nil { + return err + } + fmt.Printf("%d\n", val) + return nil +} + +func natPrintStack(stack *Stack) error { + if stack.pos == 0 { + return nil + } + for _, v := range stack.data { + fmt.Printf("%d ", v) + } + fmt.Println("") + return nil +} + +func natDup(stack *Stack) error { + val := stack.data[stack.pos-1] + stack.push(val) + return nil +} + +func natDrop(stack *Stack) error { + if _, err := stack.pop(); err != nil { + return err + } + return nil +} + +func natRnd(stack *Stack) error { + stack.push(42) + return nil +} + +func natBye(stack *Stack) error { + os.Exit(0) + return nil +} + +func natHelp(stack *Stack) error { + fmt.Println("Defined words:") + for k := range Dictionary { + fmt.Print(k, " ") + } + fmt.Print("\n") + return nil +} diff --git a/protocols/ntp.c b/protocols/ntp.c new file mode 100644 index 0000000..cf55bc2 --- /dev/null +++ b/protocols/ntp.c @@ -0,0 +1,153 @@ +// NTP client simple enough to print the current date and time in ctime() and ISO 8601 format +// https://datatracker.ietf.org/doc/html/rfc5905 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define NTP_PORT 123 +#define _INT2STR(x) #x +#define MACRO_INT2STR(x) _INT2STR(x) +#define NTP_PORT_STR MACRO_INT2STR(NTP_PORT) +//const uint8_t NTP_PORT = 123; +const uint64_t NTP_UNIX_DELTA = 2208988800ull; // January 1, 1970 in NTP timestamp format +const uint8_t NTP_CURRENT_VER = 4; + +struct ntp_packet +{ + uint8_t leap_ver_mode; + uint8_t stratum; + uint8_t poll; + uint8_t precision; + + uint32_t root_delay; + uint32_t root_dispersion; + uint32_t ref_id; + + uint32_t ref_tstamp_sec; + uint32_t ref_tstamp_frac; + + uint32_t origin_tstamp_sec; + uint32_t origin_tstamp_frac; + + uint32_t rx_tstamp_sec; + uint32_t rx_tstamp_frac; + + uint32_t tx_tstamp_sec; + uint32_t tx_tstamp_frac; +}; + +//#include +//int set_system_date(time_t date) +//{ +// if (settimeofday(&(struct timeval){.tv_sec = date, .tv_usec = 0}, NULL) < 0) { +// perror("settimeofday"); +// return 1; +// } +// return 0; +//} + +int main(const int argc, const char **argv) +{ + if (argc < 2) { + fprintf(stderr, "usage: ./ntp [ntp server hostname]\n"); + return 1; + } + int ntp_server_fd; + + // create UDP connection + if ((ntp_server_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { + perror("socket"); + return 1; + } + + const struct timeval time_val = {.tv_sec = 4, .tv_usec = 0}; + if (setsockopt(ntp_server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &(int){1}, sizeof(int)) < 0 + || setsockopt(ntp_server_fd, SOL_SOCKET, SO_RCVTIMEO, &time_val, sizeof(struct timeval)) < 0 + || setsockopt(ntp_server_fd, SOL_SOCKET, SO_SNDTIMEO, &time_val, sizeof(struct timeval)) < 0 + ) { + perror("setsockopt"); + return 1; + } + + struct addrinfo *res; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP, + .ai_flags = 0, + }; + int ret_val = 0; + if ((ret_val = getaddrinfo(argv[1], MACRO_INT2STR(NTP_PORT), &hints, &res)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret_val)); + return 1; + } + + for (; res != NULL; res = res->ai_next) { + //char h[NI_MAXHOST] = {0}; + //getnameinfo(res->ai_addr, res->ai_addrlen, h, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + //printf("getaddrinfo ip: %s\n", h); + if (connect(ntp_server_fd, res->ai_addr, res->ai_addrlen) < 0) { + perror("connect"); + freeaddrinfo(res); + return 1; + } else { // if zero, we already connected + break; + } + } + freeaddrinfo(res); + + struct ntp_packet ntp_pk = { + .leap_ver_mode = 0x23, // leap = 0, vn = 4, mode = 3 (client) + }; + + // send packet + if (send(ntp_server_fd, &ntp_pk, sizeof(struct ntp_packet), 0) < 0) { + perror("send"); + return 1; + } + // receive answer packet from the server + if (recv(ntp_server_fd, &ntp_pk, sizeof(struct ntp_packet), 0) < 0) { + perror("recv"); + return 1; + } + + // close connection with the server + shutdown(ntp_server_fd, SHUT_RDWR); + close(ntp_server_fd); + + ntp_pk.leap_ver_mode = ntohs(ntp_pk.leap_ver_mode); + ntp_pk.stratum = ntohs(ntp_pk.stratum); + //ntp_pk.ref_id = ntohl(ntp_pk.ref_id); + printf("NTP version: %u\n", ntp_pk.leap_ver_mode); + printf("stratum: %u\n", ntp_pk.stratum); + + if ((ntp_pk.leap_ver_mode & 0x38) >= NTP_CURRENT_VER) { + fprintf(stderr, "invalid version number, got %u", ntp_pk.leap_ver_mode & 0x38); + return 1; + } + // check if it is a KoD packet + if (ntp_pk.stratum == 0) { + char kod_msg[5] = {0}; + memcpy(&kod_msg, &ntp_pk.ref_id, 4); + printf("KoD msg: %s\n", kod_msg); + } + ntp_pk.tx_tstamp_sec = ntohl(ntp_pk.tx_tstamp_sec); + ntp_pk.tx_tstamp_frac = ntohl(ntp_pk.tx_tstamp_frac); + + time_t ntp_time = (time_t)(ntp_pk.tx_tstamp_sec - NTP_UNIX_DELTA); + char fmt_time[64] = {0}; + + strftime(fmt_time, sizeof(fmt_time), "%Y-%m-%d %H:%M:%S%z", localtime(&ntp_time)); + printf("Server NTP timestamp: %u (frac): %u\n", ntp_pk.tx_tstamp_sec, ntp_pk.tx_tstamp_frac); + printf("Server NTP time (ctime fmt'd): %s", ctime(&ntp_time)); + printf("Server NTP time (ISO 8601): %s\n", fmt_time); + + return 0; +}