initial commit
This commit is contained in:
commit
fe8f76dd44
29 changed files with 2477 additions and 0 deletions
269
allocators/buddyalloc.c
Normal file
269
allocators/buddyalloc.c
Normal file
|
@ -0,0 +1,269 @@
|
|||
#define _BSD_SOURCE
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#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 <time.h>
|
||||
|
||||
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;
|
||||
}
|
86
audiovisual/karplus_strong.c
Normal file
86
audiovisual/karplus_strong.c
Normal file
|
@ -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 <freq> | mpv -`
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <bsd/stdlib.h> // 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;
|
||||
}
|
122
audiovisual/sound_synthesis.c
Normal file
122
audiovisual/sound_synthesis.c
Normal file
|
@ -0,0 +1,122 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
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;
|
||||
}
|
14
misc/abacaba.lua
Normal file
14
misc/abacaba.lua
Normal file
|
@ -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
|
15
misc/collatz.lua
Normal file
15
misc/collatz.lua
Normal file
|
@ -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]))
|
25
misc/gregorian.c
Normal file
25
misc/gregorian.c
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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;
|
||||
}
|
87
misc/huffman.cpp
Normal file
87
misc/huffman.cpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#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<char, std::string> &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<char, size_t> chr_occu;
|
||||
for (const auto& c : s)
|
||||
++chr_occu[c];
|
||||
|
||||
std::priority_queue<struct ht *, std::vector<struct ht *>, 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<char, std::string> 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;
|
||||
}
|
40
misc/luhn-lua/luhn.lua
Normal file
40
misc/luhn-lua/luhn.lua
Normal file
|
@ -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
|
23
misc/luhn-lua/main.lua
Normal file
23
misc/luhn-lua/main.lua
Normal file
|
@ -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))
|
55
misc/mmap-file.c
Normal file
55
misc/mmap-file.c
Normal file
|
@ -0,0 +1,55 @@
|
|||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/auxv.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <err.h>
|
||||
|
||||
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;
|
||||
}
|
38
misc/open_tray_linux.c
Normal file
38
misc/open_tray_linux.c
Normal file
|
@ -0,0 +1,38 @@
|
|||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <linux/cdrom.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
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;
|
||||
}
|
47
misc/proc_vm.c
Normal file
47
misc/proc_vm.c
Normal file
|
@ -0,0 +1,47 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
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;
|
||||
}
|
203
misc/procread.c
Normal file
203
misc/procread.c
Normal file
|
@ -0,0 +1,203 @@
|
|||
// see proc(5) manpage for more information
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h> // pid_t
|
||||
#include <linux/kdev_t.h> // 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;
|
||||
}
|
116
parsers/bencode.nim
Normal file
116
parsers/bencode.nim
Normal file
|
@ -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()
|
19
proglangs/nsforth/Makefile
Normal file
19
proglangs/nsforth/Makefile
Normal file
|
@ -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
|
66
proglangs/nsforth/defs.h
Normal file
66
proglangs/nsforth/defs.h
Normal file
|
@ -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
|
170
proglangs/nsforth/dict.c
Normal file
170
proglangs/nsforth/dict.c
Normal file
|
@ -0,0 +1,170 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "dict.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
//}
|
17
proglangs/nsforth/dict.h
Normal file
17
proglangs/nsforth/dict.h
Normal file
|
@ -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
|
0
proglangs/nsforth/natives.c
Normal file
0
proglangs/nsforth/natives.c
Normal file
580
proglangs/nsforth/nsforth.c
Normal file
580
proglangs/nsforth/nsforth.c
Normal file
|
@ -0,0 +1,580 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <errno.h>
|
||||
#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("<word at %p (%s)>", (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("<native word at %p (%s)>\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]);
|
||||
}
|
||||
|
||||
// ' <word> 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;
|
||||
}
|
38
proglangs/nsforth/test.fs
Normal file
38
proglangs/nsforth/test.fs
Normal file
|
@ -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 - .
|
||||
\ <cond> if <body> end
|
||||
2 3 + 10 < if
|
||||
3 sq .
|
||||
end
|
||||
|
||||
let var1 2 ,
|
||||
\ <cond> while <body> end
|
||||
|
||||
\ import file.fs
|
20
proglangs/nsforth/util.c
Normal file
20
proglangs/nsforth/util.c
Normal file
|
@ -0,0 +1,20 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
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);
|
||||
}
|
8
proglangs/rpn-go/go.mod
Normal file
8
proglangs/rpn-go/go.mod
Normal file
|
@ -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
|
||||
)
|
4
proglangs/rpn-go/go.sum
Normal file
4
proglangs/rpn-go/go.sum
Normal file
|
@ -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=
|
3
proglangs/rpn-go/prog1
Normal file
3
proglangs/rpn-go/prog1
Normal file
|
@ -0,0 +1,3 @@
|
|||
77 50 + .
|
||||
77 50 + 2 * .
|
||||
bye
|
130
proglangs/rpn-go/rpn.go
Normal file
130
proglangs/rpn-go/rpn.go
Normal file
|
@ -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())
|
||||
}
|
||||
}
|
31
proglangs/rpn-go/stack.go
Normal file
31
proglangs/rpn-go/stack.go
Normal file
|
@ -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]
|
||||
}
|
98
proglangs/rpn-go/words.go
Normal file
98
proglangs/rpn-go/words.go
Normal file
|
@ -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
|
||||
}
|
153
protocols/ntp.c
Normal file
153
protocols/ntp.c
Normal file
|
@ -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 <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#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 <sys/time.h>
|
||||
//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;
|
||||
}
|
Loading…
Add table
Reference in a new issue